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

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.
Files changed (38) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +40 -19
  3. package/dist/css/color-picker.css +481 -337
  4. package/dist/css/color-picker.min.css +2 -0
  5. package/dist/css/color-picker.rtl.css +506 -0
  6. package/dist/css/color-picker.rtl.min.css +2 -0
  7. package/dist/js/color-picker-element-esm.js +3810 -2
  8. package/dist/js/color-picker-element-esm.min.js +2 -0
  9. package/dist/js/color-picker-element.js +2009 -1242
  10. package/dist/js/color-picker-element.min.js +2 -2
  11. package/dist/js/color-picker-esm.js +3704 -0
  12. package/dist/js/color-picker-esm.min.js +2 -0
  13. package/dist/js/color-picker.js +1962 -1256
  14. package/dist/js/color-picker.min.js +2 -2
  15. package/package.json +18 -9
  16. package/src/js/color-palette.js +62 -0
  17. package/src/js/color-picker-element.js +55 -13
  18. package/src/js/color-picker.js +686 -595
  19. package/src/js/color.js +615 -349
  20. package/src/js/index.js +0 -9
  21. package/src/js/util/colorNames.js +2 -152
  22. package/src/js/util/colorPickerLabels.js +22 -0
  23. package/src/js/util/getColorControls.js +103 -0
  24. package/src/js/util/getColorForm.js +27 -19
  25. package/src/js/util/getColorMenu.js +95 -0
  26. package/src/js/util/isValidJSON.js +13 -0
  27. package/src/js/util/nonColors.js +5 -0
  28. package/src/js/util/templates.js +1 -0
  29. package/src/scss/color-picker.rtl.scss +23 -0
  30. package/src/scss/color-picker.scss +430 -0
  31. package/types/cp.d.ts +263 -160
  32. package/types/index.d.ts +9 -2
  33. package/types/source/source.ts +2 -1
  34. package/types/source/types.d.ts +28 -5
  35. package/dist/js/color-picker.esm.js +0 -2998
  36. package/dist/js/color-picker.esm.min.js +0 -2
  37. package/src/js/util/getColorControl.js +0 -49
  38. package/src/js/util/init.js +0 -14
@@ -0,0 +1,3704 @@
1
+ /*!
2
+ * ColorPicker v0.0.1alpha2 (http://thednp.github.io/color-picker)
3
+ * Copyright 2022 © thednp
4
+ * Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
5
+ */
6
+ /** @type {Record<string, any>} */
7
+ const EventRegistry = {};
8
+
9
+ /**
10
+ * The global event listener.
11
+ *
12
+ * @this {Element | HTMLElement | Window | Document}
13
+ * @param {Event} e
14
+ * @returns {void}
15
+ */
16
+ function globalListener(e) {
17
+ const that = this;
18
+ const { type } = e;
19
+ const oneEvMap = EventRegistry[type] ? [...EventRegistry[type]] : [];
20
+
21
+ oneEvMap.forEach((elementsMap) => {
22
+ const [element, listenersMap] = elementsMap;
23
+ [...listenersMap].forEach((listenerMap) => {
24
+ if (element === that) {
25
+ const [listener, options] = listenerMap;
26
+ listener.apply(element, [e]);
27
+
28
+ if (options && options.once) {
29
+ removeListener(element, type, listener, options);
30
+ }
31
+ }
32
+ });
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Register a new listener with its options and attach the `globalListener`
38
+ * to the target if this is the first listener.
39
+ *
40
+ * @param {Element | HTMLElement | Window | Document} element
41
+ * @param {string} eventType
42
+ * @param {EventListenerObject['handleEvent']} listener
43
+ * @param {AddEventListenerOptions=} options
44
+ */
45
+ const addListener = (element, eventType, listener, options) => {
46
+ // get element listeners first
47
+ if (!EventRegistry[eventType]) {
48
+ EventRegistry[eventType] = new Map();
49
+ }
50
+ const oneEventMap = EventRegistry[eventType];
51
+
52
+ if (!oneEventMap.has(element)) {
53
+ oneEventMap.set(element, new Map());
54
+ }
55
+ const oneElementMap = oneEventMap.get(element);
56
+
57
+ // get listeners size
58
+ const { size } = oneElementMap;
59
+
60
+ // register listener with its options
61
+ if (oneElementMap) {
62
+ oneElementMap.set(listener, options);
63
+ }
64
+
65
+ // add listener last
66
+ if (!size) {
67
+ element.addEventListener(eventType, globalListener, options);
68
+ }
69
+ };
70
+
71
+ /**
72
+ * Remove a listener from registry and detach the `globalListener`
73
+ * if no listeners are found in the registry.
74
+ *
75
+ * @param {Element | HTMLElement | Window | Document} element
76
+ * @param {string} eventType
77
+ * @param {EventListenerObject['handleEvent']} listener
78
+ * @param {AddEventListenerOptions=} options
79
+ */
80
+ const removeListener = (element, eventType, listener, options) => {
81
+ // get listener first
82
+ const oneEventMap = EventRegistry[eventType];
83
+ const oneElementMap = oneEventMap && oneEventMap.get(element);
84
+ const savedOptions = oneElementMap && oneElementMap.get(listener);
85
+
86
+ // also recover initial options
87
+ const { options: eventOptions } = savedOptions !== undefined
88
+ ? savedOptions
89
+ : { options };
90
+
91
+ // unsubscribe second, remove from registry
92
+ if (oneElementMap && oneElementMap.has(listener)) oneElementMap.delete(listener);
93
+ if (oneEventMap && (!oneElementMap || !oneElementMap.size)) oneEventMap.delete(element);
94
+ if (!oneEventMap || !oneEventMap.size) delete EventRegistry[eventType];
95
+
96
+ // remove listener last
97
+ if (!oneElementMap || !oneElementMap.size) {
98
+ element.removeEventListener(eventType, globalListener, eventOptions);
99
+ }
100
+ };
101
+
102
+ /**
103
+ * A global namespace for aria-description.
104
+ * @type {string}
105
+ */
106
+ const ariaDescription = 'aria-description';
107
+
108
+ /**
109
+ * A global namespace for aria-selected.
110
+ * @type {string}
111
+ */
112
+ const ariaSelected = 'aria-selected';
113
+
114
+ /**
115
+ * A global namespace for aria-expanded.
116
+ * @type {string}
117
+ */
118
+ const ariaExpanded = 'aria-expanded';
119
+
120
+ /**
121
+ * A global namespace for aria-valuetext.
122
+ * @type {string}
123
+ */
124
+ const ariaValueText = 'aria-valuetext';
125
+
126
+ /**
127
+ * A global namespace for aria-valuenow.
128
+ * @type {string}
129
+ */
130
+ const ariaValueNow = 'aria-valuenow';
131
+
132
+ /**
133
+ * A global namespace for aria-haspopup.
134
+ * @type {string}
135
+ */
136
+ const ariaHasPopup = 'aria-haspopup';
137
+
138
+ /**
139
+ * A global namespace for aria-hidden.
140
+ * @type {string}
141
+ */
142
+ const ariaHidden = 'aria-hidden';
143
+
144
+ /**
145
+ * A global namespace for aria-labelledby.
146
+ * @type {string}
147
+ */
148
+ const ariaLabelledBy = 'aria-labelledby';
149
+
150
+ /**
151
+ * A global namespace for `ArrowDown` key.
152
+ * @type {string} e.which = 40 equivalent
153
+ */
154
+ const keyArrowDown = 'ArrowDown';
155
+
156
+ /**
157
+ * A global namespace for `ArrowUp` key.
158
+ * @type {string} e.which = 38 equivalent
159
+ */
160
+ const keyArrowUp = 'ArrowUp';
161
+
162
+ /**
163
+ * A global namespace for `ArrowLeft` key.
164
+ * @type {string} e.which = 37 equivalent
165
+ */
166
+ const keyArrowLeft = 'ArrowLeft';
167
+
168
+ /**
169
+ * A global namespace for `ArrowRight` key.
170
+ * @type {string} e.which = 39 equivalent
171
+ */
172
+ const keyArrowRight = 'ArrowRight';
173
+
174
+ /**
175
+ * A global namespace for `Enter` key.
176
+ * @type {string} e.which = 13 equivalent
177
+ */
178
+ const keyEnter = 'Enter';
179
+
180
+ /**
181
+ * A global namespace for `Space` key.
182
+ * @type {string} e.which = 32 equivalent
183
+ */
184
+ const keySpace = 'Space';
185
+
186
+ /**
187
+ * A global namespace for `Escape` key.
188
+ * @type {string} e.which = 27 equivalent
189
+ */
190
+ const keyEscape = 'Escape';
191
+
192
+ /**
193
+ * A global namespace for `focusin` event.
194
+ * @type {string}
195
+ */
196
+ const focusinEvent = 'focusin';
197
+
198
+ /**
199
+ * A global namespace for `click` event.
200
+ * @type {string}
201
+ */
202
+ const mouseclickEvent = 'click';
203
+
204
+ /**
205
+ * A global namespace for `keydown` event.
206
+ * @type {string}
207
+ */
208
+ const keydownEvent = 'keydown';
209
+
210
+ /**
211
+ * A global namespace for `change` event.
212
+ * @type {string}
213
+ */
214
+ const changeEvent = 'change';
215
+
216
+ /**
217
+ * A global namespace for `touchstart` event.
218
+ * @type {string}
219
+ */
220
+ const touchstartEvent = 'touchstart';
221
+
222
+ /**
223
+ * A global namespace for `touchmove` event.
224
+ * @type {string}
225
+ */
226
+ const touchmoveEvent = 'touchmove';
227
+
228
+ /**
229
+ * A global namespace for `touchend` event.
230
+ * @type {string}
231
+ */
232
+ const touchendEvent = 'touchend';
233
+
234
+ /**
235
+ * A global namespace for `mousedown` event.
236
+ * @type {string}
237
+ */
238
+ const mousedownEvent = 'mousedown';
239
+
240
+ /**
241
+ * A global namespace for `mousemove` event.
242
+ * @type {string}
243
+ */
244
+ const mousemoveEvent = 'mousemove';
245
+
246
+ /**
247
+ * A global namespace for `mouseup` event.
248
+ * @type {string}
249
+ */
250
+ const mouseupEvent = 'mouseup';
251
+
252
+ /**
253
+ * A global namespace for `scroll` event.
254
+ * @type {string}
255
+ */
256
+ const scrollEvent = 'scroll';
257
+
258
+ /**
259
+ * A global namespace for `keyup` event.
260
+ * @type {string}
261
+ */
262
+ const keyupEvent = 'keyup';
263
+
264
+ /**
265
+ * A global namespace for `resize` event.
266
+ * @type {string}
267
+ */
268
+ const resizeEvent = 'resize';
269
+
270
+ /**
271
+ * A global namespace for `focusout` event.
272
+ * @type {string}
273
+ */
274
+ const focusoutEvent = 'focusout';
275
+
276
+ // @ts-ignore
277
+ const { userAgentData: uaDATA } = navigator;
278
+
279
+ /**
280
+ * A global namespace for `userAgentData` object.
281
+ */
282
+ const userAgentData = uaDATA;
283
+
284
+ const { userAgent: userAgentString } = navigator;
285
+
286
+ /**
287
+ * A global namespace for `navigator.userAgent` string.
288
+ */
289
+ const userAgent = userAgentString;
290
+
291
+ const mobileBrands = /iPhone|iPad|iPod|Android/i;
292
+ let isMobileCheck = false;
293
+
294
+ if (userAgentData) {
295
+ isMobileCheck = userAgentData.brands
296
+ .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
297
+ } else {
298
+ isMobileCheck = mobileBrands.test(userAgent);
299
+ }
300
+
301
+ /**
302
+ * A global `boolean` for mobile detection.
303
+ * @type {boolean}
304
+ */
305
+ const isMobile = isMobileCheck;
306
+
307
+ /**
308
+ * Returns the `document` or the `#document` element.
309
+ * @see https://github.com/floating-ui/floating-ui
310
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
311
+ * @returns {Document}
312
+ */
313
+ function getDocument(node) {
314
+ if (node instanceof HTMLElement) return node.ownerDocument;
315
+ if (node instanceof Window) return node.document;
316
+ return window.document;
317
+ }
318
+
319
+ /**
320
+ * Returns the `document.documentElement` or the `<html>` element.
321
+ *
322
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
323
+ * @returns {HTMLElement | HTMLHtmlElement}
324
+ */
325
+ function getDocumentElement(node) {
326
+ return getDocument(node).documentElement;
327
+ }
328
+
329
+ /**
330
+ * Returns the `Window` object of a target node.
331
+ * @see https://github.com/floating-ui/floating-ui
332
+ *
333
+ * @param {(Node | HTMLElement | Element | Window)=} node target node
334
+ * @returns {globalThis}
335
+ */
336
+ function getWindow(node) {
337
+ if (node == null) {
338
+ return window;
339
+ }
340
+
341
+ if (!(node instanceof Window)) {
342
+ const { ownerDocument } = node;
343
+ return ownerDocument ? ownerDocument.defaultView || window : window;
344
+ }
345
+
346
+ // @ts-ignore
347
+ return node;
348
+ }
349
+
350
+ /**
351
+ * Shortcut for `window.getComputedStyle(element).propertyName`
352
+ * static method.
353
+ *
354
+ * * If `element` parameter is not an `HTMLElement`, `getComputedStyle`
355
+ * throws a `ReferenceError`.
356
+ *
357
+ * @param {HTMLElement | Element} element target
358
+ * @param {string} property the css property
359
+ * @return {string} the css property value
360
+ */
361
+ function getElementStyle(element, property) {
362
+ const computedStyle = getComputedStyle(element);
363
+
364
+ // @ts-ignore -- must use camelcase strings,
365
+ // or non-camelcase strings with `getPropertyValue`
366
+ return property in computedStyle ? computedStyle[property] : '';
367
+ }
368
+
369
+ let elementUID = 0;
370
+ let elementMapUID = 0;
371
+ const elementIDMap = new Map();
372
+
373
+ /**
374
+ * Returns a unique identifier for popover, tooltip, scrollspy.
375
+ *
376
+ * @param {HTMLElement | Element} element target element
377
+ * @param {string=} key predefined key
378
+ * @returns {number} an existing or new unique ID
379
+ */
380
+ function getUID(element, key) {
381
+ let result = key ? elementUID : elementMapUID;
382
+
383
+ if (key) {
384
+ const elID = getUID(element);
385
+ const elMap = elementIDMap.get(elID) || new Map();
386
+ if (!elementIDMap.has(elID)) {
387
+ elementIDMap.set(elID, elMap);
388
+ }
389
+ if (!elMap.has(key)) {
390
+ elMap.set(key, result);
391
+ elementUID += 1;
392
+ } else result = elMap.get(key);
393
+ } else {
394
+ const elkey = element.id || element;
395
+
396
+ if (!elementIDMap.has(elkey)) {
397
+ elementIDMap.set(elkey, result);
398
+ elementMapUID += 1;
399
+ } else result = elementIDMap.get(elkey);
400
+ }
401
+ return result;
402
+ }
403
+
404
+ /**
405
+ * Returns the bounding client rect of a target `HTMLElement`.
406
+ *
407
+ * @see https://github.com/floating-ui/floating-ui
408
+ *
409
+ * @param {HTMLElement | Element} element event.target
410
+ * @param {boolean=} includeScale when *true*, the target scale is also computed
411
+ * @returns {SHORTER.BoundingClientRect} the bounding client rect object
412
+ */
413
+ function getBoundingClientRect(element, includeScale) {
414
+ const {
415
+ width, height, top, right, bottom, left,
416
+ } = element.getBoundingClientRect();
417
+ let scaleX = 1;
418
+ let scaleY = 1;
419
+
420
+ if (includeScale && element instanceof HTMLElement) {
421
+ const { offsetWidth, offsetHeight } = element;
422
+ scaleX = offsetWidth > 0 ? Math.round(width) / offsetWidth || 1 : 1;
423
+ scaleY = offsetHeight > 0 ? Math.round(height) / offsetHeight || 1 : 1;
424
+ }
425
+
426
+ return {
427
+ width: width / scaleX,
428
+ height: height / scaleY,
429
+ top: top / scaleY,
430
+ right: right / scaleX,
431
+ bottom: bottom / scaleY,
432
+ left: left / scaleX,
433
+ x: left / scaleX,
434
+ y: top / scaleY,
435
+ };
436
+ }
437
+
438
+ /**
439
+ * A global namespace for 'transitionDuration' string.
440
+ * @type {string}
441
+ */
442
+ const transitionDuration = 'transitionDuration';
443
+
444
+ /**
445
+ * A global namespace for `transitionProperty` string for modern browsers.
446
+ *
447
+ * @type {string}
448
+ */
449
+ const transitionProperty = 'transitionProperty';
450
+
451
+ /**
452
+ * Utility to get the computed `transitionDuration`
453
+ * from Element in miliseconds.
454
+ *
455
+ * @param {HTMLElement | Element} element target
456
+ * @return {number} the value in miliseconds
457
+ */
458
+ function getElementTransitionDuration(element) {
459
+ const propertyValue = getElementStyle(element, transitionProperty);
460
+ const durationValue = getElementStyle(element, transitionDuration);
461
+ const durationScale = durationValue.includes('ms') ? 1 : 1000;
462
+ const duration = propertyValue && propertyValue !== 'none'
463
+ ? parseFloat(durationValue) * durationScale : 0;
464
+
465
+ return !Number.isNaN(duration) ? duration : 0;
466
+ }
467
+
468
+ /**
469
+ * A global array of possible `ParentNode`.
470
+ */
471
+ const parentNodes = [Document, Element, HTMLElement];
472
+
473
+ /**
474
+ * A global array with `Element` | `HTMLElement`.
475
+ */
476
+ const elementNodes = [Element, HTMLElement];
477
+
478
+ /**
479
+ * Utility to check if target is typeof `HTMLElement`, `Element`, `Node`
480
+ * or find one that matches a selector.
481
+ *
482
+ * @param {HTMLElement | Element | string} selector the input selector or target element
483
+ * @param {(HTMLElement | Element | Document)=} parent optional node to look into
484
+ * @return {(HTMLElement | Element)?} the `HTMLElement` or `querySelector` result
485
+ */
486
+ function querySelector(selector, parent) {
487
+ const lookUp = parentNodes.some((x) => parent instanceof x)
488
+ ? parent : getDocument();
489
+
490
+ // @ts-ignore
491
+ return elementNodes.some((x) => selector instanceof x)
492
+ // @ts-ignore
493
+ ? selector : lookUp.querySelector(selector);
494
+ }
495
+
496
+ /**
497
+ * Shortcut for `HTMLElement.closest` method which also works
498
+ * with children of `ShadowRoot`. The order of the parameters
499
+ * is intentional since they're both required.
500
+ *
501
+ * @see https://stackoverflow.com/q/54520554/803358
502
+ *
503
+ * @param {HTMLElement | Element} element Element to look into
504
+ * @param {string} selector the selector name
505
+ * @return {(HTMLElement | Element)?} the query result
506
+ */
507
+ function closest(element, selector) {
508
+ return element ? (element.closest(selector)
509
+ // @ts-ignore -- break out of `ShadowRoot`
510
+ || closest(element.getRootNode().host, selector)) : null;
511
+ }
512
+
513
+ /**
514
+ * Shortcut for `HTMLElement.getElementsByClassName` method. Some `Node` elements
515
+ * like `ShadowRoot` do not support `getElementsByClassName`.
516
+ *
517
+ * @param {string} selector the class name
518
+ * @param {(HTMLElement | Element | Document)=} parent optional Element to look into
519
+ * @return {HTMLCollectionOf<HTMLElement | Element>} the 'HTMLCollection'
520
+ */
521
+ function getElementsByClassName(selector, parent) {
522
+ const lookUp = parent && parentNodes.some((x) => parent instanceof x)
523
+ ? parent : getDocument();
524
+ return lookUp.getElementsByClassName(selector);
525
+ }
526
+
527
+ /**
528
+ * Shortcut for `Object.assign()` static method.
529
+ * @param {Record<string, any>} obj a target object
530
+ * @param {Record<string, any>} source a source object
531
+ */
532
+ const ObjectAssign = (obj, source) => Object.assign(obj, source);
533
+
534
+ /**
535
+ * This is a shortie for `document.createElement` method
536
+ * which allows you to create a new `HTMLElement` for a given `tagName`
537
+ * or based on an object with specific non-readonly attributes:
538
+ * `id`, `className`, `textContent`, `style`, etc.
539
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
540
+ *
541
+ * @param {Record<string, string> | string} param `tagName` or object
542
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
543
+ */
544
+ function createElement(param) {
545
+ if (typeof param === 'string') {
546
+ return getDocument().createElement(param);
547
+ }
548
+
549
+ const { tagName } = param;
550
+ const attr = { ...param };
551
+ const newElement = createElement(tagName);
552
+ delete attr.tagName;
553
+ ObjectAssign(newElement, attr);
554
+ return newElement;
555
+ }
556
+
557
+ /**
558
+ * This is a shortie for `document.createElementNS` method
559
+ * which allows you to create a new `HTMLElement` for a given `tagName`
560
+ * or based on an object with specific non-readonly attributes:
561
+ * `id`, `className`, `textContent`, `style`, etc.
562
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
563
+ *
564
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
565
+ * @param {Record<string, string> | string} param `tagName` or object
566
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
567
+ */
568
+ function createElementNS(namespace, param) {
569
+ if (typeof param === 'string') {
570
+ return getDocument().createElementNS(namespace, param);
571
+ }
572
+
573
+ const { tagName } = param;
574
+ const attr = { ...param };
575
+ const newElement = createElementNS(namespace, tagName);
576
+ delete attr.tagName;
577
+ ObjectAssign(newElement, attr);
578
+ return newElement;
579
+ }
580
+
581
+ /**
582
+ * Shortcut for the `Element.dispatchEvent(Event)` method.
583
+ *
584
+ * @param {HTMLElement | Element} element is the target
585
+ * @param {Event} event is the `Event` object
586
+ */
587
+ const dispatchEvent = (element, event) => element.dispatchEvent(event);
588
+
589
+ /** @type {Map<string, Map<HTMLElement | Element, Record<string, any>>>} */
590
+ const componentData = new Map();
591
+ /**
592
+ * An interface for web components background data.
593
+ * @see https://github.com/thednp/bootstrap.native/blob/master/src/components/base-component.js
594
+ */
595
+ const Data = {
596
+ /**
597
+ * Sets web components data.
598
+ * @param {HTMLElement | Element | string} target target element
599
+ * @param {string} component the component's name or a unique key
600
+ * @param {Record<string, any>} instance the component instance
601
+ */
602
+ set: (target, component, instance) => {
603
+ const element = querySelector(target);
604
+ if (!element) return;
605
+
606
+ if (!componentData.has(component)) {
607
+ componentData.set(component, new Map());
608
+ }
609
+
610
+ const instanceMap = componentData.get(component);
611
+ // @ts-ignore - not undefined, but defined right above
612
+ instanceMap.set(element, instance);
613
+ },
614
+
615
+ /**
616
+ * Returns all instances for specified component.
617
+ * @param {string} component the component's name or a unique key
618
+ * @returns {Map<HTMLElement | Element, Record<string, any>>?} all the component instances
619
+ */
620
+ getAllFor: (component) => {
621
+ const instanceMap = componentData.get(component);
622
+
623
+ return instanceMap || null;
624
+ },
625
+
626
+ /**
627
+ * Returns the instance associated with the target.
628
+ * @param {HTMLElement | Element | string} target target element
629
+ * @param {string} component the component's name or a unique key
630
+ * @returns {Record<string, any>?} the instance
631
+ */
632
+ get: (target, component) => {
633
+ const element = querySelector(target);
634
+ const allForC = Data.getAllFor(component);
635
+ const instance = element && allForC && allForC.get(element);
636
+
637
+ return instance || null;
638
+ },
639
+
640
+ /**
641
+ * Removes web components data.
642
+ * @param {HTMLElement | Element | string} target target element
643
+ * @param {string} component the component's name or a unique key
644
+ */
645
+ remove: (target, component) => {
646
+ const element = querySelector(target);
647
+ const instanceMap = componentData.get(component);
648
+ if (!instanceMap || !element) return;
649
+
650
+ instanceMap.delete(element);
651
+
652
+ if (instanceMap.size === 0) {
653
+ componentData.delete(component);
654
+ }
655
+ },
656
+ };
657
+
658
+ /**
659
+ * An alias for `Data.get()`.
660
+ * @type {SHORTER.getInstance<any>}
661
+ */
662
+ const getInstance = (target, component) => Data.get(target, component);
663
+
664
+ /**
665
+ * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
666
+ * @param {HTMLElement | Element} element target element
667
+ * @param {Partial<CSSStyleDeclaration>} styles attribute value
668
+ */
669
+ // @ts-ignore
670
+ const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
671
+
672
+ /**
673
+ * Shortcut for `HTMLElement.getAttribute()` method.
674
+ * @param {HTMLElement | Element} element target element
675
+ * @param {string} attribute attribute name
676
+ * @returns {string?} attribute value
677
+ */
678
+ const getAttribute = (element, attribute) => element.getAttribute(attribute);
679
+
680
+ /**
681
+ * The raw value or a given component option.
682
+ *
683
+ * @typedef {string | HTMLElement | Function | number | boolean | null} niceValue
684
+ */
685
+
686
+ /**
687
+ * Utility to normalize component options
688
+ *
689
+ * @param {any} value the input value
690
+ * @return {niceValue} the normalized value
691
+ */
692
+ function normalizeValue(value) {
693
+ if (value === 'true') { // boolean
694
+ return true;
695
+ }
696
+
697
+ if (value === 'false') { // boolean
698
+ return false;
699
+ }
700
+
701
+ if (!Number.isNaN(+value)) { // number
702
+ return +value;
703
+ }
704
+
705
+ if (value === '' || value === 'null') { // null
706
+ return null;
707
+ }
708
+
709
+ // string / function / HTMLElement / object
710
+ return value;
711
+ }
712
+
713
+ /**
714
+ * Shortcut for `Object.keys()` static method.
715
+ * @param {Record<string, any>} obj a target object
716
+ * @returns {string[]}
717
+ */
718
+ const ObjectKeys = (obj) => Object.keys(obj);
719
+
720
+ /**
721
+ * Shortcut for `String.toLowerCase()`.
722
+ *
723
+ * @param {string} source input string
724
+ * @returns {string} lowercase output string
725
+ */
726
+ const toLowerCase = (source) => source.toLowerCase();
727
+
728
+ /**
729
+ * Utility to normalize component options.
730
+ *
731
+ * @param {HTMLElement | Element} element target
732
+ * @param {Record<string, any>} defaultOps component default options
733
+ * @param {Record<string, any>} inputOps component instance options
734
+ * @param {string=} ns component namespace
735
+ * @return {Record<string, any>} normalized component options object
736
+ */
737
+ function normalizeOptions(element, defaultOps, inputOps, ns) {
738
+ // @ts-ignore -- our targets are always `HTMLElement`
739
+ const data = { ...element.dataset };
740
+ /** @type {Record<string, any>} */
741
+ const normalOps = {};
742
+ /** @type {Record<string, any>} */
743
+ const dataOps = {};
744
+ const title = 'title';
745
+
746
+ ObjectKeys(data).forEach((k) => {
747
+ const key = ns && k.includes(ns)
748
+ ? k.replace(ns, '').replace(/[A-Z]/, (match) => toLowerCase(match))
749
+ : k;
750
+
751
+ dataOps[key] = normalizeValue(data[k]);
752
+ });
753
+
754
+ ObjectKeys(inputOps).forEach((k) => {
755
+ inputOps[k] = normalizeValue(inputOps[k]);
756
+ });
757
+
758
+ ObjectKeys(defaultOps).forEach((k) => {
759
+ if (k in inputOps) {
760
+ normalOps[k] = inputOps[k];
761
+ } else if (k in dataOps) {
762
+ normalOps[k] = dataOps[k];
763
+ } else {
764
+ normalOps[k] = k === title
765
+ ? getAttribute(element, title)
766
+ : defaultOps[k];
767
+ }
768
+ });
769
+
770
+ return normalOps;
771
+ }
772
+
773
+ /**
774
+ * Utility to force re-paint of an `HTMLElement` target.
775
+ *
776
+ * @param {HTMLElement | Element} element is the target
777
+ * @return {number} the `Element.offsetHeight` value
778
+ */
779
+ // @ts-ignore
780
+ const reflow = (element) => element.offsetHeight;
781
+
782
+ /**
783
+ * Utility to focus an `HTMLElement` target.
784
+ *
785
+ * @param {HTMLElement | Element} element is the target
786
+ */
787
+ // @ts-ignore -- `Element`s resulted from querySelector can focus too
788
+ const focus = (element) => element.focus();
789
+
790
+ /**
791
+ * Check class in `HTMLElement.classList`.
792
+ *
793
+ * @param {HTMLElement | Element} element target
794
+ * @param {string} classNAME to check
795
+ * @returns {boolean}
796
+ */
797
+ function hasClass(element, classNAME) {
798
+ return element.classList.contains(classNAME);
799
+ }
800
+
801
+ /**
802
+ * Add class to `HTMLElement.classList`.
803
+ *
804
+ * @param {HTMLElement | Element} element target
805
+ * @param {string} classNAME to add
806
+ * @returns {void}
807
+ */
808
+ function addClass(element, classNAME) {
809
+ element.classList.add(classNAME);
810
+ }
811
+
812
+ /**
813
+ * Remove class from `HTMLElement.classList`.
814
+ *
815
+ * @param {HTMLElement | Element} element target
816
+ * @param {string} classNAME to remove
817
+ * @returns {void}
818
+ */
819
+ function removeClass(element, classNAME) {
820
+ element.classList.remove(classNAME);
821
+ }
822
+
823
+ /**
824
+ * Shortcut for `HTMLElement.setAttribute()` method.
825
+ * @param {HTMLElement | Element} element target element
826
+ * @param {string} attribute attribute name
827
+ * @param {string} value attribute value
828
+ * @returns {void}
829
+ */
830
+ const setAttribute = (element, attribute, value) => element.setAttribute(attribute, value);
831
+
832
+ /**
833
+ * Shortcut for `HTMLElement.removeAttribute()` method.
834
+ * @param {HTMLElement | Element} element target element
835
+ * @param {string} attribute attribute name
836
+ * @returns {void}
837
+ */
838
+ const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
839
+
840
+ /** @type {Record<string, string>} */
841
+ const colorPickerLabels = {
842
+ pickerLabel: 'Colour Picker',
843
+ appearanceLabel: 'Colour Appearance',
844
+ valueLabel: 'Colour Value',
845
+ toggleLabel: 'Select Colour',
846
+ presetsLabel: 'Colour Presets',
847
+ defaultsLabel: 'Colour Defaults',
848
+ formatLabel: 'Format',
849
+ alphaLabel: 'Alpha',
850
+ hexLabel: 'Hexadecimal',
851
+ hueLabel: 'Hue',
852
+ whitenessLabel: 'Whiteness',
853
+ blacknessLabel: 'Blackness',
854
+ saturationLabel: 'Saturation',
855
+ lightnessLabel: 'Lightness',
856
+ redLabel: 'Red',
857
+ greenLabel: 'Green',
858
+ blueLabel: 'Blue',
859
+ };
860
+
861
+ /**
862
+ * A list of 17 color names used for WAI-ARIA compliance.
863
+ * @type {string[]}
864
+ */
865
+ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
866
+
867
+ /**
868
+ * A list of explicit default non-color values.
869
+ */
870
+ const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
871
+
872
+ /**
873
+ * Shortcut for `String.toUpperCase()`.
874
+ *
875
+ * @param {string} source input string
876
+ * @returns {string} uppercase output string
877
+ */
878
+ const toUpperCase = (source) => source.toUpperCase();
879
+
880
+ const vHidden = 'v-hidden';
881
+
882
+ /**
883
+ * Returns the color form for `ColorPicker`.
884
+ *
885
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
886
+ * @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
887
+ */
888
+ function getColorForm(self) {
889
+ const { format, id, componentLabels } = self;
890
+ const colorForm = createElement({
891
+ tagName: 'div',
892
+ className: `color-form ${format}`,
893
+ });
894
+
895
+ let components = ['hex'];
896
+ if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
897
+ else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
898
+ else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
899
+
900
+ components.forEach((c) => {
901
+ const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
902
+ const cID = `color_${format}_${c}_${id}`;
903
+ const formatLabel = componentLabels[`${c}Label`];
904
+ const cInputLabel = createElement({ tagName: 'label' });
905
+ setAttribute(cInputLabel, 'for', cID);
906
+ cInputLabel.append(
907
+ createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
908
+ createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
909
+ );
910
+ const cInput = createElement({
911
+ tagName: 'input',
912
+ id: cID,
913
+ // name: cID, - prevent saving the value to a form
914
+ type: format === 'hex' ? 'text' : 'number',
915
+ value: c === 'alpha' ? '100' : '0',
916
+ className: `color-input ${c}`,
917
+ });
918
+ setAttribute(cInput, 'autocomplete', 'off');
919
+ setAttribute(cInput, 'spellcheck', 'false');
920
+
921
+ // alpha
922
+ let max = '100';
923
+ let step = '1';
924
+ if (c !== 'alpha') {
925
+ if (format === 'rgb') {
926
+ max = '255'; step = '1';
927
+ } else if (c === 'hue') {
928
+ max = '360'; step = '1';
929
+ }
930
+ }
931
+ ObjectAssign(cInput, {
932
+ min: '0',
933
+ max,
934
+ step,
935
+ });
936
+ // }
937
+ colorForm.append(cInputLabel, cInput);
938
+ });
939
+ return colorForm;
940
+ }
941
+
942
+ /**
943
+ * A global namespace for aria-label.
944
+ * @type {string}
945
+ */
946
+ const ariaLabel = 'aria-label';
947
+
948
+ /**
949
+ * A global namespace for aria-valuemin.
950
+ * @type {string}
951
+ */
952
+ const ariaValueMin = 'aria-valuemin';
953
+
954
+ /**
955
+ * A global namespace for aria-valuemax.
956
+ * @type {string}
957
+ */
958
+ const ariaValueMax = 'aria-valuemax';
959
+
960
+ /**
961
+ * Returns all color controls for `ColorPicker`.
962
+ *
963
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
964
+ * @returns {HTMLElement | Element} color controls
965
+ */
966
+ function getColorControls(self) {
967
+ const { format, componentLabels } = self;
968
+ const {
969
+ hueLabel, alphaLabel, lightnessLabel, saturationLabel,
970
+ whitenessLabel, blacknessLabel,
971
+ } = componentLabels;
972
+
973
+ const max1 = format === 'hsl' ? 360 : 100;
974
+ const max2 = format === 'hsl' ? 100 : 360;
975
+ const max3 = 100;
976
+
977
+ let ctrl1Label = format === 'hsl'
978
+ ? `${hueLabel} & ${lightnessLabel}`
979
+ : `${lightnessLabel} & ${saturationLabel}`;
980
+
981
+ ctrl1Label = format === 'hwb'
982
+ ? `${whitenessLabel} & ${blacknessLabel}`
983
+ : ctrl1Label;
984
+
985
+ const ctrl2Label = format === 'hsl'
986
+ ? `${saturationLabel}`
987
+ : `${hueLabel}`;
988
+
989
+ const colorControls = createElement({
990
+ tagName: 'div',
991
+ className: `color-controls ${format}`,
992
+ });
993
+
994
+ const colorPointer = 'color-pointer';
995
+ const colorSlider = 'color-slider';
996
+
997
+ const controls = [
998
+ {
999
+ i: 1,
1000
+ c: colorPointer,
1001
+ l: ctrl1Label,
1002
+ min: 0,
1003
+ max: max1,
1004
+ },
1005
+ {
1006
+ i: 2,
1007
+ c: colorSlider,
1008
+ l: ctrl2Label,
1009
+ min: 0,
1010
+ max: max2,
1011
+ },
1012
+ {
1013
+ i: 3,
1014
+ c: colorSlider,
1015
+ l: alphaLabel,
1016
+ min: 0,
1017
+ max: max3,
1018
+ },
1019
+ ];
1020
+
1021
+ controls.forEach((template) => {
1022
+ const {
1023
+ i, c, l, min, max,
1024
+ } = template;
1025
+ // const hidden = i === 2 && format === 'hwb' ? ' v-hidden' : '';
1026
+ const control = createElement({
1027
+ tagName: 'div',
1028
+ // className: `color-control${hidden}`,
1029
+ className: 'color-control',
1030
+ });
1031
+ setAttribute(control, 'role', 'presentation');
1032
+
1033
+ control.append(
1034
+ createElement({
1035
+ tagName: 'div',
1036
+ className: `visual-control visual-control${i}`,
1037
+ }),
1038
+ );
1039
+
1040
+ const knob = createElement({
1041
+ tagName: 'div',
1042
+ className: `${c} knob`,
1043
+ ariaLive: 'polite',
1044
+ });
1045
+
1046
+ setAttribute(knob, ariaLabel, l);
1047
+ setAttribute(knob, 'role', 'slider');
1048
+ setAttribute(knob, 'tabindex', '0');
1049
+ setAttribute(knob, ariaValueMin, `${min}`);
1050
+ setAttribute(knob, ariaValueMax, `${max}`);
1051
+ control.append(knob);
1052
+ colorControls.append(control);
1053
+ });
1054
+
1055
+ return colorControls;
1056
+ }
1057
+
1058
+ /**
1059
+ * Returns the `document.head` or the `<head>` element.
1060
+ *
1061
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
1062
+ * @returns {HTMLElement | HTMLHeadElement}
1063
+ */
1064
+ function getDocumentHead(node) {
1065
+ return getDocument(node).head;
1066
+ }
1067
+
1068
+ // Color supported formats
1069
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
1070
+
1071
+ // Hue angles
1072
+ const ANGLES = 'deg|rad|grad|turn';
1073
+
1074
+ // <http://www.w3.org/TR/css3-values/#integers>
1075
+ const CSS_INTEGER = '[-\\+]?\\d+%?';
1076
+
1077
+ // Include CSS3 Module
1078
+ // <http://www.w3.org/TR/css3-values/#number-value>
1079
+ const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
1080
+
1081
+ // Include CSS4 Module Hue degrees unit
1082
+ // <https://www.w3.org/TR/css3-values/#angle-value>
1083
+ const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
1084
+
1085
+ // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
1086
+ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
1087
+
1088
+ // Add angles to the mix
1089
+ const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
1090
+
1091
+ // Actual matching.
1092
+ // Parentheses and commas are optional, but not required.
1093
+ // Whitespace can take the place of commas or opening paren
1094
+ const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
1095
+
1096
+ const matchers = {
1097
+ CSS_UNIT: new RegExp(CSS_UNIT2),
1098
+ hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
1099
+ rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
1100
+ hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
1101
+ hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
1102
+ hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
1103
+ hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
1104
+ hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
1105
+ hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
1106
+ };
1107
+
1108
+ /**
1109
+ * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
1110
+ * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
1111
+ * @param {string} n testing number
1112
+ * @returns {boolean} the query result
1113
+ */
1114
+ function isOnePointZero(n) {
1115
+ return `${n}`.includes('.') && parseFloat(n) === 1;
1116
+ }
1117
+
1118
+ /**
1119
+ * Check to see if string passed in is a percentage
1120
+ * @param {string} n testing number
1121
+ * @returns {boolean} the query result
1122
+ */
1123
+ function isPercentage(n) {
1124
+ return `${n}`.includes('%');
1125
+ }
1126
+
1127
+ /**
1128
+ * Check to see if string passed in is an angle
1129
+ * @param {string} n testing string
1130
+ * @returns {boolean} the query result
1131
+ */
1132
+ function isAngle(n) {
1133
+ return ANGLES.split('|').some((a) => `${n}`.includes(a));
1134
+ }
1135
+
1136
+ /**
1137
+ * Check to see if string passed is a web safe colour.
1138
+ * @param {string} color a colour name, EG: *red*
1139
+ * @returns {boolean} the query result
1140
+ */
1141
+ function isColorName(color) {
1142
+ return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
1143
+ && !/[0-9]/.test(color);
1144
+ }
1145
+
1146
+ /**
1147
+ * Check to see if it looks like a CSS unit
1148
+ * (see `matchers` above for definition).
1149
+ * @param {string | number} color testing value
1150
+ * @returns {boolean} the query result
1151
+ */
1152
+ function isValidCSSUnit(color) {
1153
+ return Boolean(matchers.CSS_UNIT.exec(String(color)));
1154
+ }
1155
+
1156
+ /**
1157
+ * Take input from [0, n] and return it as [0, 1]
1158
+ * @param {*} N the input number
1159
+ * @param {number} max the number maximum value
1160
+ * @returns {number} the number in [0, 1] value range
1161
+ */
1162
+ function bound01(N, max) {
1163
+ let n = N;
1164
+ if (isOnePointZero(n)) n = '100%';
1165
+
1166
+ n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
1167
+
1168
+ // Handle hue angles
1169
+ if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
1170
+
1171
+ // Automatically convert percentage into number
1172
+ if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
1173
+
1174
+ // Handle floating point rounding errors
1175
+ if (Math.abs(n - max) < 0.000001) {
1176
+ return 1;
1177
+ }
1178
+ // Convert into [0, 1] range if it isn't already
1179
+ if (max === 360) {
1180
+ // If n is a hue given in degrees,
1181
+ // wrap around out-of-range values into [0, 360] range
1182
+ // then convert into [0, 1].
1183
+ n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
1184
+ } else {
1185
+ // If n not a hue given in degrees
1186
+ // Convert into [0, 1] range if it isn't already.
1187
+ n = (n % max) / parseFloat(String(max));
1188
+ }
1189
+ return n;
1190
+ }
1191
+
1192
+ /**
1193
+ * Return a valid alpha value [0,1] with all invalid values being set to 1.
1194
+ * @param {string | number} a transparency value
1195
+ * @returns {number} a transparency value in the [0, 1] range
1196
+ */
1197
+ function boundAlpha(a) {
1198
+ let na = parseFloat(`${a}`);
1199
+
1200
+ if (Number.isNaN(na) || na < 0 || na > 1) {
1201
+ na = 1;
1202
+ }
1203
+
1204
+ return na;
1205
+ }
1206
+
1207
+ /**
1208
+ * Force a number between 0 and 1.
1209
+ * @param {number} v the float number
1210
+ * @returns {number} - the resulting number
1211
+ */
1212
+ function clamp01(v) {
1213
+ return Math.min(1, Math.max(0, v));
1214
+ }
1215
+
1216
+ /**
1217
+ * Returns the hexadecimal value of a web safe colour.
1218
+ * @param {string} name
1219
+ * @returns {string}
1220
+ */
1221
+ function getRGBFromName(name) {
1222
+ const documentHead = getDocumentHead();
1223
+ setElementStyle(documentHead, { color: name });
1224
+ const colorName = getElementStyle(documentHead, 'color');
1225
+ setElementStyle(documentHead, { color: '' });
1226
+ return colorName;
1227
+ }
1228
+
1229
+ /**
1230
+ * Converts a decimal value to hexadecimal.
1231
+ * @param {number} d the input number
1232
+ * @returns {string} - the hexadecimal value
1233
+ */
1234
+ function convertDecimalToHex(d) {
1235
+ return Math.round(d * 255).toString(16);
1236
+ }
1237
+
1238
+ /**
1239
+ * Converts a hexadecimal value to decimal.
1240
+ * @param {string} h hexadecimal value
1241
+ * @returns {number} number in decimal format
1242
+ */
1243
+ function convertHexToDecimal(h) {
1244
+ return parseIntFromHex(h) / 255;
1245
+ }
1246
+
1247
+ /**
1248
+ * Converts a base-16 hexadecimal value into a base-10 integer.
1249
+ * @param {string} val
1250
+ * @returns {number}
1251
+ */
1252
+ function parseIntFromHex(val) {
1253
+ return parseInt(val, 16);
1254
+ }
1255
+
1256
+ /**
1257
+ * Force a hexadecimal value to have 2 characters.
1258
+ * @param {string} c string with [0-9A-F] ranged values
1259
+ * @returns {string} 0 => 00, a => 0a
1260
+ */
1261
+ function pad2(c) {
1262
+ return c.length === 1 ? `0${c}` : String(c);
1263
+ }
1264
+
1265
+ /**
1266
+ * Converts an RGB colour value to HSL.
1267
+ *
1268
+ * @param {number} R Red component [0, 255]
1269
+ * @param {number} G Green component [0, 255]
1270
+ * @param {number} B Blue component [0, 255]
1271
+ * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
1272
+ */
1273
+ function rgbToHsl(R, G, B) {
1274
+ const r = R / 255;
1275
+ const g = G / 255;
1276
+ const b = B / 255;
1277
+ const max = Math.max(r, g, b);
1278
+ const min = Math.min(r, g, b);
1279
+ let h = 0;
1280
+ let s = 0;
1281
+ const l = (max + min) / 2;
1282
+ if (max === min) {
1283
+ s = 0;
1284
+ h = 0; // achromatic
1285
+ } else {
1286
+ const d = max - min;
1287
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
1288
+ switch (max) {
1289
+ case r:
1290
+ h = (g - b) / d + (g < b ? 6 : 0);
1291
+ break;
1292
+ case g:
1293
+ h = (b - r) / d + 2;
1294
+ break;
1295
+ case b:
1296
+ h = (r - g) / d + 4;
1297
+ break;
1298
+ }
1299
+ h /= 6;
1300
+ }
1301
+ return { h, s, l };
1302
+ }
1303
+
1304
+ /**
1305
+ * Returns a normalized RGB component value.
1306
+ * @param {number} p
1307
+ * @param {number} q
1308
+ * @param {number} t
1309
+ * @returns {number}
1310
+ */
1311
+ function hueToRgb(p, q, t) {
1312
+ let T = t;
1313
+ if (T < 0) T += 1;
1314
+ if (T > 1) T -= 1;
1315
+ if (T < 1 / 6) return p + (q - p) * (6 * T);
1316
+ if (T < 1 / 2) return q;
1317
+ if (T < 2 / 3) return p + (q - p) * (2 / 3 - T) * 6;
1318
+ return p;
1319
+ }
1320
+
1321
+ /**
1322
+ * Returns an HWB colour object from an RGB colour object.
1323
+ * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
1324
+ * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
1325
+ *
1326
+ * @param {number} R Red component [0, 255]
1327
+ * @param {number} G Green [0, 255]
1328
+ * @param {number} B Blue [0, 255]
1329
+ * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
1330
+ */
1331
+ function rgbToHwb(R, G, B) {
1332
+ const r = R / 255;
1333
+ const g = G / 255;
1334
+ const b = B / 255;
1335
+
1336
+ let f = 0;
1337
+ let i = 0;
1338
+ const whiteness = Math.min(r, g, b);
1339
+ const max = Math.max(r, g, b);
1340
+ const black = 1 - max;
1341
+
1342
+ if (max === whiteness) return { h: 0, w: whiteness, b: black };
1343
+ if (r === whiteness) {
1344
+ f = g - b;
1345
+ i = 3;
1346
+ } else {
1347
+ f = g === whiteness ? b - r : r - g;
1348
+ i = g === whiteness ? 5 : 1;
1349
+ }
1350
+
1351
+ const h = (i - f / (max - whiteness)) / 6;
1352
+ return {
1353
+ h: h === 1 ? 0 : h,
1354
+ w: whiteness,
1355
+ b: black,
1356
+ };
1357
+ }
1358
+
1359
+ /**
1360
+ * Returns an RGB colour object from an HWB colour.
1361
+ *
1362
+ * @param {number} H Hue Angle [0, 1]
1363
+ * @param {number} W Whiteness [0, 1]
1364
+ * @param {number} B Blackness [0, 1]
1365
+ * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
1366
+ *
1367
+ * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
1368
+ * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
1369
+ */
1370
+ function hwbToRgb(H, W, B) {
1371
+ if (W + B >= 1) {
1372
+ const gray = (W / (W + B)) * 255;
1373
+ return { r: gray, g: gray, b: gray };
1374
+ }
1375
+ let { r, g, b } = hslToRgb(H, 1, 0.5);
1376
+ [r, g, b] = [r, g, b]
1377
+ .map((v) => (v / 255) * (1 - W - B) + W)
1378
+ .map((v) => v * 255);
1379
+
1380
+ return { r, g, b };
1381
+ }
1382
+
1383
+ /**
1384
+ * Converts an HSL colour value to RGB.
1385
+ *
1386
+ * @param {number} h Hue Angle [0, 1]
1387
+ * @param {number} s Saturation [0, 1]
1388
+ * @param {number} l Lightness Angle [0, 1]
1389
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
1390
+ */
1391
+ function hslToRgb(h, s, l) {
1392
+ let r = 0;
1393
+ let g = 0;
1394
+ let b = 0;
1395
+
1396
+ if (s === 0) {
1397
+ // achromatic
1398
+ g = l;
1399
+ b = l;
1400
+ r = l;
1401
+ } else {
1402
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1403
+ const p = 2 * l - q;
1404
+ r = hueToRgb(p, q, h + 1 / 3);
1405
+ g = hueToRgb(p, q, h);
1406
+ b = hueToRgb(p, q, h - 1 / 3);
1407
+ }
1408
+ [r, g, b] = [r, g, b].map((x) => x * 255);
1409
+
1410
+ return { r, g, b };
1411
+ }
1412
+
1413
+ /**
1414
+ * Converts an RGB colour value to HSV.
1415
+ *
1416
+ * @param {number} R Red component [0, 255]
1417
+ * @param {number} G Green [0, 255]
1418
+ * @param {number} B Blue [0, 255]
1419
+ * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
1420
+ */
1421
+ function rgbToHsv(R, G, B) {
1422
+ const r = R / 255;
1423
+ const g = G / 255;
1424
+ const b = B / 255;
1425
+ const max = Math.max(r, g, b);
1426
+ const min = Math.min(r, g, b);
1427
+ let h = 0;
1428
+ const v = max;
1429
+ const d = max - min;
1430
+ const s = max === 0 ? 0 : d / max;
1431
+ if (max === min) {
1432
+ h = 0; // achromatic
1433
+ } else {
1434
+ switch (max) {
1435
+ case r:
1436
+ h = (g - b) / d + (g < b ? 6 : 0);
1437
+ break;
1438
+ case g:
1439
+ h = (b - r) / d + 2;
1440
+ break;
1441
+ case b:
1442
+ h = (r - g) / d + 4;
1443
+ break;
1444
+ }
1445
+ h /= 6;
1446
+ }
1447
+ return { h, s, v };
1448
+ }
1449
+
1450
+ /**
1451
+ * Converts an HSV colour value to RGB.
1452
+ *
1453
+ * @param {number} H Hue Angle [0, 1]
1454
+ * @param {number} S Saturation [0, 1]
1455
+ * @param {number} V Brightness Angle [0, 1]
1456
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
1457
+ */
1458
+ function hsvToRgb(H, S, V) {
1459
+ const h = H * 6;
1460
+ const s = S;
1461
+ const v = V;
1462
+ const i = Math.floor(h);
1463
+ const f = h - i;
1464
+ const p = v * (1 - s);
1465
+ const q = v * (1 - f * s);
1466
+ const t = v * (1 - (1 - f) * s);
1467
+ const mod = i % 6;
1468
+ const r = [v, q, p, p, t, v][mod];
1469
+ const g = [t, v, v, q, p, p][mod];
1470
+ const b = [p, p, t, v, v, q][mod];
1471
+ return { r: r * 255, g: g * 255, b: b * 255 };
1472
+ }
1473
+
1474
+ /**
1475
+ * Converts an RGB colour to hex
1476
+ *
1477
+ * Assumes r, g, and b are contained in the set [0, 255]
1478
+ * Returns a 3 or 6 character hex
1479
+ * @param {number} r Red component [0, 255]
1480
+ * @param {number} g Green [0, 255]
1481
+ * @param {number} b Blue [0, 255]
1482
+ * @param {boolean=} allow3Char
1483
+ * @returns {string}
1484
+ */
1485
+ function rgbToHex(r, g, b, allow3Char) {
1486
+ const hex = [
1487
+ pad2(Math.round(r).toString(16)),
1488
+ pad2(Math.round(g).toString(16)),
1489
+ pad2(Math.round(b).toString(16)),
1490
+ ];
1491
+
1492
+ // Return a 3 character hex if possible
1493
+ if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
1494
+ && hex[1].charAt(0) === hex[1].charAt(1)
1495
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
1496
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
1497
+ }
1498
+
1499
+ return hex.join('');
1500
+ }
1501
+
1502
+ /**
1503
+ * Converts an RGBA color plus alpha transparency to hex8.
1504
+ *
1505
+ * @param {number} r Red component [0, 255]
1506
+ * @param {number} g Green [0, 255]
1507
+ * @param {number} b Blue [0, 255]
1508
+ * @param {number} a Alpha transparency [0, 1]
1509
+ * @param {boolean=} allow4Char when *true* it will also find hex shorthand
1510
+ * @returns {string} a hexadecimal value with alpha transparency
1511
+ */
1512
+ function rgbaToHex(r, g, b, a, allow4Char) {
1513
+ const hex = [
1514
+ pad2(Math.round(r).toString(16)),
1515
+ pad2(Math.round(g).toString(16)),
1516
+ pad2(Math.round(b).toString(16)),
1517
+ pad2(convertDecimalToHex(a)),
1518
+ ];
1519
+
1520
+ // Return a 4 character hex if possible
1521
+ if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
1522
+ && hex[1].charAt(0) === hex[1].charAt(1)
1523
+ && hex[2].charAt(0) === hex[2].charAt(1)
1524
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
1525
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
1526
+ }
1527
+ return hex.join('');
1528
+ }
1529
+
1530
+ /**
1531
+ * Returns a colour object corresponding to a given number.
1532
+ * @param {number} color input number
1533
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
1534
+ */
1535
+ function numberInputToObject(color) {
1536
+ /* eslint-disable no-bitwise */
1537
+ return {
1538
+ r: color >> 16,
1539
+ g: (color & 0xff00) >> 8,
1540
+ b: color & 0xff,
1541
+ };
1542
+ /* eslint-enable no-bitwise */
1543
+ }
1544
+
1545
+ /**
1546
+ * Permissive string parsing. Take in a number of formats, and output an object
1547
+ * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
1548
+ * @param {string} input colour value in any format
1549
+ * @returns {Record<string, (number | string)> | false} an object matching the RegExp
1550
+ */
1551
+ function stringInputToObject(input) {
1552
+ let color = input.trim().toLowerCase();
1553
+ if (color.length === 0) {
1554
+ return {
1555
+ r: 0, g: 0, b: 0, a: 0,
1556
+ };
1557
+ }
1558
+ let named = false;
1559
+ if (isColorName(color)) {
1560
+ color = getRGBFromName(color);
1561
+ named = true;
1562
+ } else if (nonColors.includes(color)) {
1563
+ const isTransparent = color === 'transparent';
1564
+ const rgb = isTransparent ? 0 : 255;
1565
+ const a = isTransparent ? 0 : 1;
1566
+ return {
1567
+ r: rgb, g: rgb, b: rgb, a, format: 'rgb',
1568
+ };
1569
+ }
1570
+
1571
+ // Try to match string input using regular expressions.
1572
+ // Keep most of the number bounding out of this function,
1573
+ // don't worry about [0,1] or [0,100] or [0,360]
1574
+ // Just return an object and let the conversion functions handle that.
1575
+ // This way the result will be the same whether Color is initialized with string or object.
1576
+ let [, m1, m2, m3, m4] = matchers.rgb.exec(color) || [];
1577
+ if (m1 && m2 && m3/* && m4 */) {
1578
+ return {
1579
+ r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
1580
+ };
1581
+ }
1582
+ [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
1583
+ if (m1 && m2 && m3/* && m4 */) {
1584
+ return {
1585
+ h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
1586
+ };
1587
+ }
1588
+ [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
1589
+ if (m1 && m2 && m3/* && m4 */) {
1590
+ return {
1591
+ h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
1592
+ };
1593
+ }
1594
+ [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
1595
+ if (m1 && m2 && m3) {
1596
+ return {
1597
+ h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
1598
+ };
1599
+ }
1600
+ [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
1601
+ if (m1 && m2 && m3 && m4) {
1602
+ return {
1603
+ r: parseIntFromHex(m1),
1604
+ g: parseIntFromHex(m2),
1605
+ b: parseIntFromHex(m3),
1606
+ a: convertHexToDecimal(m4),
1607
+ // format: named ? 'rgb' : 'hex8',
1608
+ format: named ? 'rgb' : 'hex',
1609
+ };
1610
+ }
1611
+ [, m1, m2, m3] = matchers.hex6.exec(color) || [];
1612
+ if (m1 && m2 && m3) {
1613
+ return {
1614
+ r: parseIntFromHex(m1),
1615
+ g: parseIntFromHex(m2),
1616
+ b: parseIntFromHex(m3),
1617
+ format: named ? 'rgb' : 'hex',
1618
+ };
1619
+ }
1620
+ [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
1621
+ if (m1 && m2 && m3 && m4) {
1622
+ return {
1623
+ r: parseIntFromHex(m1 + m1),
1624
+ g: parseIntFromHex(m2 + m2),
1625
+ b: parseIntFromHex(m3 + m3),
1626
+ a: convertHexToDecimal(m4 + m4),
1627
+ // format: named ? 'rgb' : 'hex8',
1628
+ format: named ? 'rgb' : 'hex',
1629
+ };
1630
+ }
1631
+ [, m1, m2, m3] = matchers.hex3.exec(color) || [];
1632
+ if (m1 && m2 && m3) {
1633
+ return {
1634
+ r: parseIntFromHex(m1 + m1),
1635
+ g: parseIntFromHex(m2 + m2),
1636
+ b: parseIntFromHex(m3 + m3),
1637
+ format: named ? 'rgb' : 'hex',
1638
+ };
1639
+ }
1640
+ return false;
1641
+ }
1642
+
1643
+ /**
1644
+ * Given a string or object, convert that input to RGB
1645
+ *
1646
+ * Possible string inputs:
1647
+ * ```
1648
+ * "red"
1649
+ * "#f00" or "f00"
1650
+ * "#ff0000" or "ff0000"
1651
+ * "#ff000000" or "ff000000" // CSS4 Module
1652
+ * "rgb 255 0 0" or "rgb (255, 0, 0)"
1653
+ * "rgb 1.0 0 0" or "rgb (1, 0, 0)"
1654
+ * "rgba(255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
1655
+ * "rgba(1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
1656
+ * "rgb(255 0 0 / 10%)" or "rgb 255 0 0 0.1" // CSS4 Module
1657
+ * "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
1658
+ * "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
1659
+ * "hsl(0deg 100% 50% / 50%)" or "hsl 0 100 50 50" // CSS4 Module
1660
+ * "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
1661
+ * "hsva(0, 100%, 100%, 0.1)" or "hsva 0 100% 100% 0.1"
1662
+ * "hsv(0deg 100% 100% / 10%)" or "hsv 0 100 100 0.1" // CSS4 Module
1663
+ * "hwb(0deg, 100%, 100%, 100%)" or "hwb 0 100% 100% 0.1" // CSS4 Module
1664
+ * ```
1665
+ * @param {string | Record<string, any>} input
1666
+ * @returns {CP.ColorObject}
1667
+ */
1668
+ function inputToRGB(input) {
1669
+ let rgb = { r: 0, g: 0, b: 0 };
1670
+ let color = input;
1671
+ let a = 1;
1672
+ let s = null;
1673
+ let v = null;
1674
+ let l = null;
1675
+ let w = null;
1676
+ let b = null;
1677
+ let h = null;
1678
+ let ok = false;
1679
+ let format = 'hex';
1680
+
1681
+ if (typeof input === 'string') {
1682
+ // @ts-ignore -- this now is converted to object
1683
+ color = stringInputToObject(input);
1684
+ if (color) ok = true;
1685
+ }
1686
+ if (typeof color === 'object') {
1687
+ if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
1688
+ rgb = { r: color.r, g: color.g, b: color.b }; // RGB values in [0, 255] range
1689
+ ok = true;
1690
+ format = 'rgb';
1691
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
1692
+ ({ h, s, v } = color);
1693
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1694
+ s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1695
+ v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
1696
+ rgb = hsvToRgb(h, s, v);
1697
+ ok = true;
1698
+ format = 'hsv';
1699
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
1700
+ ({ h, s, l } = color);
1701
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1702
+ s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1703
+ l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
1704
+ rgb = hslToRgb(h, s, l);
1705
+ ok = true;
1706
+ format = 'hsl';
1707
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
1708
+ ({ h, w, b } = color);
1709
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1710
+ w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
1711
+ b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
1712
+ rgb = hwbToRgb(h, w, b);
1713
+ ok = true;
1714
+ format = 'hwb';
1715
+ }
1716
+ if (isValidCSSUnit(color.a)) {
1717
+ a = color.a;
1718
+ a = isPercentage(`${a}`) ? bound01(a, 100) : a;
1719
+ }
1720
+ }
1721
+
1722
+ return {
1723
+ ok, // @ts-ignore
1724
+ format: color.format || format,
1725
+ r: Math.min(255, Math.max(rgb.r, 0)),
1726
+ g: Math.min(255, Math.max(rgb.g, 0)),
1727
+ b: Math.min(255, Math.max(rgb.b, 0)),
1728
+ a: boundAlpha(a),
1729
+ };
1730
+ }
1731
+
1732
+ /**
1733
+ * @class
1734
+ * Returns a new `Color` instance.
1735
+ * @see https://github.com/bgrins/TinyColor
1736
+ */
1737
+ class Color {
1738
+ /**
1739
+ * @constructor
1740
+ * @param {CP.ColorInput} input the given colour value
1741
+ * @param {CP.ColorFormats=} config the given format
1742
+ */
1743
+ constructor(input, config) {
1744
+ let color = input;
1745
+ const configFormat = config && COLOR_FORMAT.includes(config)
1746
+ ? config : 'rgb';
1747
+
1748
+ // If input is already a `Color`, return itself
1749
+ if (color instanceof Color) {
1750
+ color = inputToRGB(color);
1751
+ }
1752
+ if (typeof color === 'number') {
1753
+ color = numberInputToObject(color);
1754
+ }
1755
+ const {
1756
+ r, g, b, a, ok, format,
1757
+ } = inputToRGB(color);
1758
+
1759
+ // bind
1760
+ const self = this;
1761
+
1762
+ /** @type {CP.ColorInput} */
1763
+ self.originalInput = color;
1764
+ /** @type {number} */
1765
+ self.r = r;
1766
+ /** @type {number} */
1767
+ self.g = g;
1768
+ /** @type {number} */
1769
+ self.b = b;
1770
+ /** @type {number} */
1771
+ self.a = a;
1772
+ /** @type {boolean} */
1773
+ self.ok = ok;
1774
+ /** @type {CP.ColorFormats} */
1775
+ self.format = configFormat || format;
1776
+
1777
+ // Don't let the range of [0,255] come back in [0,1].
1778
+ // Potentially lose a little bit of precision here, but will fix issues where
1779
+ // .5 gets interpreted as half of the total, instead of half of 1
1780
+ // If it was supposed to be 128, this was already taken care of by `inputToRgb`
1781
+ if (r < 1) self.r = Math.round(r);
1782
+ if (g < 1) self.g = Math.round(g);
1783
+ if (b < 1) self.b = Math.round(b);
1784
+ }
1785
+
1786
+ /**
1787
+ * Checks if the current input value is a valid colour.
1788
+ * @returns {boolean} the query result
1789
+ */
1790
+ get isValid() {
1791
+ return this.ok;
1792
+ }
1793
+
1794
+ /**
1795
+ * Checks if the current colour requires a light text colour.
1796
+ * @returns {boolean} the query result
1797
+ */
1798
+ get isDark() {
1799
+ return this.brightness < 128;
1800
+ }
1801
+
1802
+ /**
1803
+ * Returns the perceived luminance of a colour.
1804
+ * @see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
1805
+ * @returns {number} a number in the [0, 1] range
1806
+ */
1807
+ get luminance() {
1808
+ const { r, g, b } = this;
1809
+ let R = 0;
1810
+ let G = 0;
1811
+ let B = 0;
1812
+ const rp = r / 255;
1813
+ const rg = g / 255;
1814
+ const rb = b / 255;
1815
+
1816
+ if (rp <= 0.03928) {
1817
+ R = rp / 12.92;
1818
+ } else {
1819
+ R = ((rp + 0.055) / 1.055) ** 2.4;
1820
+ }
1821
+ if (rg <= 0.03928) {
1822
+ G = rg / 12.92;
1823
+ } else {
1824
+ G = ((rg + 0.055) / 1.055) ** 2.4;
1825
+ }
1826
+ if (rb <= 0.03928) {
1827
+ B = rb / 12.92;
1828
+ } else {
1829
+ B = ((rb + 0.055) / 1.055) ** 2.4;
1830
+ }
1831
+ return 0.2126 * R + 0.7152 * G + 0.0722 * B;
1832
+ }
1833
+
1834
+ /**
1835
+ * Returns the perceived brightness of the colour.
1836
+ * @returns {number} a number in the [0, 255] range
1837
+ */
1838
+ get brightness() {
1839
+ const { r, g, b } = this;
1840
+ return (r * 299 + g * 587 + b * 114) / 1000;
1841
+ }
1842
+
1843
+ /**
1844
+ * Returns the colour as an RGBA object.
1845
+ * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
1846
+ */
1847
+ toRgb() {
1848
+ const {
1849
+ r, g, b, a,
1850
+ } = this;
1851
+ const [R, G, B] = [r, g, b].map((x) => Math.round(x));
1852
+
1853
+ return {
1854
+ r: R,
1855
+ g: G,
1856
+ b: B,
1857
+ a: Math.round(a * 100) / 100,
1858
+ };
1859
+ }
1860
+
1861
+ /**
1862
+ * Returns the RGBA values concatenated into a CSS3 Module string format.
1863
+ * * rgb(255,255,255)
1864
+ * * rgba(255,255,255,0.5)
1865
+ * @returns {string} the CSS valid colour in RGB/RGBA format
1866
+ */
1867
+ toRgbString() {
1868
+ const {
1869
+ r, g, b, a,
1870
+ } = this.toRgb();
1871
+
1872
+ return a === 1
1873
+ ? `rgb(${r}, ${g}, ${b})`
1874
+ : `rgba(${r}, ${g}, ${b}, ${a})`;
1875
+ }
1876
+
1877
+ /**
1878
+ * Returns the RGBA values concatenated into a CSS4 Module string format.
1879
+ * * rgb(255 255 255)
1880
+ * * rgb(255 255 255 / 50%)
1881
+ * @returns {string} the CSS valid colour in CSS4 RGB format
1882
+ */
1883
+ toRgbCSS4String() {
1884
+ const {
1885
+ r, g, b, a,
1886
+ } = this.toRgb();
1887
+ const A = a === 1 ? '' : ` / ${Math.round(a * 100)}%`;
1888
+
1889
+ return `rgb(${r} ${g} ${b}${A})`;
1890
+ }
1891
+
1892
+ /**
1893
+ * Returns the hexadecimal value of the colour. When the parameter is *true*
1894
+ * it will find a 3 characters shorthand of the decimal value.
1895
+ *
1896
+ * @param {boolean=} allow3Char when `true` returns shorthand HEX
1897
+ * @returns {string} the hexadecimal colour format
1898
+ */
1899
+ toHex(allow3Char) {
1900
+ const {
1901
+ r, g, b, a,
1902
+ } = this.toRgb();
1903
+
1904
+ return a === 1
1905
+ ? rgbToHex(r, g, b, allow3Char)
1906
+ : rgbaToHex(r, g, b, a, allow3Char);
1907
+ }
1908
+
1909
+ /**
1910
+ * Returns the CSS valid hexadecimal vaue of the colour. When the parameter is *true*
1911
+ * it will find a 3 characters shorthand of the value.
1912
+ *
1913
+ * @param {boolean=} allow3Char when `true` returns shorthand HEX
1914
+ * @returns {string} the CSS valid colour in hexadecimal format
1915
+ */
1916
+ toHexString(allow3Char) {
1917
+ return `#${this.toHex(allow3Char)}`;
1918
+ }
1919
+
1920
+ /**
1921
+ * Returns the HEX8 value of the colour.
1922
+ * @param {boolean=} allow4Char when `true` returns shorthand HEX
1923
+ * @returns {string} the CSS valid colour in hexadecimal format
1924
+ */
1925
+ toHex8(allow4Char) {
1926
+ const {
1927
+ r, g, b, a,
1928
+ } = this.toRgb();
1929
+
1930
+ return rgbaToHex(r, g, b, a, allow4Char);
1931
+ }
1932
+
1933
+ /**
1934
+ * Returns the HEX8 value of the colour.
1935
+ * @param {boolean=} allow4Char when `true` returns shorthand HEX
1936
+ * @returns {string} the CSS valid colour in hexadecimal format
1937
+ */
1938
+ toHex8String(allow4Char) {
1939
+ return `#${this.toHex8(allow4Char)}`;
1940
+ }
1941
+
1942
+ /**
1943
+ * Returns the colour as a HSVA object.
1944
+ * @returns {CP.HSVA} the `{h,s,v,a}` object with [0, 1] ranged values
1945
+ */
1946
+ toHsv() {
1947
+ const {
1948
+ r, g, b, a,
1949
+ } = this.toRgb();
1950
+ const { h, s, v } = rgbToHsv(r, g, b);
1951
+
1952
+ return {
1953
+ h, s, v, a,
1954
+ };
1955
+ }
1956
+
1957
+ /**
1958
+ * Returns the colour as an HSLA object.
1959
+ * @returns {CP.HSLA} the `{h,s,l,a}` object with [0, 1] ranged values
1960
+ */
1961
+ toHsl() {
1962
+ const {
1963
+ r, g, b, a,
1964
+ } = this.toRgb();
1965
+ const { h, s, l } = rgbToHsl(r, g, b);
1966
+
1967
+ return {
1968
+ h, s, l, a,
1969
+ };
1970
+ }
1971
+
1972
+ /**
1973
+ * Returns the HSLA values concatenated into a CSS3 Module format string.
1974
+ * * `hsl(150, 100%, 50%)`
1975
+ * * `hsla(150, 100%, 50%, 0.5)`
1976
+ * @returns {string} the CSS valid colour in HSL/HSLA format
1977
+ */
1978
+ toHslString() {
1979
+ let {
1980
+ h, s, l, a,
1981
+ } = this.toHsl();
1982
+ h = Math.round(h * 360);
1983
+ s = Math.round(s * 100);
1984
+ l = Math.round(l * 100);
1985
+ a = Math.round(a * 100) / 100;
1986
+
1987
+ return a === 1
1988
+ ? `hsl(${h}, ${s}%, ${l}%)`
1989
+ : `hsla(${h}, ${s}%, ${l}%, ${a})`;
1990
+ }
1991
+
1992
+ /**
1993
+ * Returns the HSLA values concatenated into a CSS4 Module format string.
1994
+ * * `hsl(150deg 100% 50%)`
1995
+ * * `hsl(150deg 100% 50% / 50%)`
1996
+ * @returns {string} the CSS valid colour in CSS4 HSL format
1997
+ */
1998
+ toHslCSS4String() {
1999
+ let {
2000
+ h, s, l, a,
2001
+ } = this.toHsl();
2002
+ h = Math.round(h * 360);
2003
+ s = Math.round(s * 100);
2004
+ l = Math.round(l * 100);
2005
+ a = Math.round(a * 100);
2006
+ const A = a < 100 ? ` / ${Math.round(a)}%` : '';
2007
+
2008
+ return `hsl(${h}deg ${s}% ${l}%${A})`;
2009
+ }
2010
+
2011
+ /**
2012
+ * Returns the colour as an HWBA object.
2013
+ * @returns {CP.HWBA} the `{h,w,b,a}` object with [0, 1] ranged values
2014
+ */
2015
+ toHwb() {
2016
+ const {
2017
+ r, g, b, a,
2018
+ } = this;
2019
+ const { h, w, b: bl } = rgbToHwb(r, g, b);
2020
+ return {
2021
+ h, w, b: bl, a,
2022
+ };
2023
+ }
2024
+
2025
+ /**
2026
+ * Returns the HWBA values concatenated into a string.
2027
+ * @returns {string} the CSS valid colour in HWB format
2028
+ */
2029
+ toHwbString() {
2030
+ let {
2031
+ h, w, b, a,
2032
+ } = this.toHwb();
2033
+ h = Math.round(h * 360);
2034
+ w = Math.round(w * 100);
2035
+ b = Math.round(b * 100);
2036
+ a = Math.round(a * 100);
2037
+ const A = a < 100 ? ` / ${Math.round(a)}%` : '';
2038
+
2039
+ return `hwb(${h}deg ${w}% ${b}%${A})`;
2040
+ }
2041
+
2042
+ /**
2043
+ * Sets the alpha value of the current colour.
2044
+ * @param {number} alpha a new alpha value in the [0, 1] range.
2045
+ * @returns {Color} the `Color` instance
2046
+ */
2047
+ setAlpha(alpha) {
2048
+ const self = this;
2049
+ self.a = boundAlpha(alpha);
2050
+ return self;
2051
+ }
2052
+
2053
+ /**
2054
+ * Saturate the colour with a given amount.
2055
+ * @param {number=} amount a value in the [0, 100] range
2056
+ * @returns {Color} the `Color` instance
2057
+ */
2058
+ saturate(amount) {
2059
+ const self = this;
2060
+ if (typeof amount !== 'number') return self;
2061
+ const { h, s, l } = self.toHsl();
2062
+ const { r, g, b } = hslToRgb(h, clamp01(s + amount / 100), l);
2063
+
2064
+ ObjectAssign(self, { r, g, b });
2065
+ return self;
2066
+ }
2067
+
2068
+ /**
2069
+ * Desaturate the colour with a given amount.
2070
+ * @param {number=} amount a value in the [0, 100] range
2071
+ * @returns {Color} the `Color` instance
2072
+ */
2073
+ desaturate(amount) {
2074
+ return typeof amount === 'number' ? this.saturate(-amount) : this;
2075
+ }
2076
+
2077
+ /**
2078
+ * Completely desaturates a colour into greyscale.
2079
+ * Same as calling `desaturate(100)`
2080
+ * @returns {Color} the `Color` instance
2081
+ */
2082
+ greyscale() {
2083
+ return this.saturate(-100);
2084
+ }
2085
+
2086
+ /**
2087
+ * Increase the colour lightness with a given amount.
2088
+ * @param {number=} amount a value in the [0, 100] range
2089
+ * @returns {Color} the `Color` instance
2090
+ */
2091
+ lighten(amount) {
2092
+ const self = this;
2093
+ if (typeof amount !== 'number') return self;
2094
+
2095
+ const { h, s, l } = self.toHsl();
2096
+ const { r, g, b } = hslToRgb(h, s, clamp01(l + amount / 100));
2097
+
2098
+ ObjectAssign(self, { r, g, b });
2099
+ return self;
2100
+ }
2101
+
2102
+ /**
2103
+ * Decrease the colour lightness with a given amount.
2104
+ * @param {number=} amount a value in the [0, 100] range
2105
+ * @returns {Color} the `Color` instance
2106
+ */
2107
+ darken(amount) {
2108
+ return typeof amount === 'number' ? this.lighten(-amount) : this;
2109
+ }
2110
+
2111
+ /**
2112
+ * Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
2113
+ * Values outside of this range will be wrapped into this range.
2114
+ *
2115
+ * @param {number=} amount a value in the [0, 100] range
2116
+ * @returns {Color} the `Color` instance
2117
+ */
2118
+ spin(amount) {
2119
+ const self = this;
2120
+ if (typeof amount !== 'number') return self;
2121
+
2122
+ const { h, s, l } = self.toHsl();
2123
+ const { r, g, b } = hslToRgb(clamp01(((h * 360 + amount) % 360) / 360), s, l);
2124
+
2125
+ ObjectAssign(self, { r, g, b });
2126
+ return self;
2127
+ }
2128
+
2129
+ /** Returns a clone of the current `Color` instance. */
2130
+ clone() {
2131
+ return new Color(this);
2132
+ }
2133
+
2134
+ /**
2135
+ * Returns the colour value in CSS valid string format.
2136
+ * @param {boolean=} allowShort when *true*, HEX values can be shorthand
2137
+ * @returns {string} the CSS valid colour in the configured format
2138
+ */
2139
+ toString(allowShort) {
2140
+ const self = this;
2141
+ const { format } = self;
2142
+
2143
+ if (format === 'hex') return self.toHexString(allowShort);
2144
+ if (format === 'hsl') return self.toHslString();
2145
+ if (format === 'hwb') return self.toHwbString();
2146
+
2147
+ return self.toRgbString();
2148
+ }
2149
+ }
2150
+
2151
+ ObjectAssign(Color, {
2152
+ ANGLES,
2153
+ CSS_ANGLE,
2154
+ CSS_INTEGER,
2155
+ CSS_NUMBER,
2156
+ CSS_UNIT,
2157
+ CSS_UNIT2,
2158
+ PERMISSIVE_MATCH,
2159
+ matchers,
2160
+ isOnePointZero,
2161
+ isPercentage,
2162
+ isValidCSSUnit,
2163
+ pad2,
2164
+ clamp01,
2165
+ bound01,
2166
+ boundAlpha,
2167
+ getRGBFromName,
2168
+ convertHexToDecimal,
2169
+ convertDecimalToHex,
2170
+ rgbToHsl,
2171
+ rgbToHex,
2172
+ rgbToHsv,
2173
+ rgbToHwb,
2174
+ rgbaToHex,
2175
+ hslToRgb,
2176
+ hsvToRgb,
2177
+ hueToRgb,
2178
+ hwbToRgb,
2179
+ parseIntFromHex,
2180
+ numberInputToObject,
2181
+ stringInputToObject,
2182
+ inputToRGB,
2183
+ ObjectAssign,
2184
+ });
2185
+
2186
+ /**
2187
+ * @class
2188
+ * Returns a color palette with a given set of parameters.
2189
+ * @example
2190
+ * new ColorPalette(0, 12, 10);
2191
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2192
+ */
2193
+ class ColorPalette {
2194
+ /**
2195
+ * The `hue` parameter is optional, which would be set to 0.
2196
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
2197
+ * * `args.hue` the starting Hue [0, 360]
2198
+ * * `args.hueSteps` Hue Steps Count [5, 13]
2199
+ * * `args.lightSteps` Lightness Steps Count [8, 10]
2200
+ */
2201
+ constructor(...args) {
2202
+ let hue = 0;
2203
+ let hueSteps = 12;
2204
+ let lightSteps = 10;
2205
+ let lightnessArray = [0.5];
2206
+
2207
+ if (args.length === 3) {
2208
+ [hue, hueSteps, lightSteps] = args;
2209
+ } else if (args.length === 2) {
2210
+ [hueSteps, lightSteps] = args;
2211
+ } else {
2212
+ throw TypeError('The ColorPalette requires minimum 2 arguments');
2213
+ }
2214
+
2215
+ /** @type {string[]} */
2216
+ const colors = [];
2217
+
2218
+ const hueStep = 360 / hueSteps;
2219
+ const lightStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2220
+ const half = Math.round((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2221
+
2222
+ // light tints
2223
+ for (let i = 0; i < half; i += 1) {
2224
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i + 1))];
2225
+ }
2226
+
2227
+ // dark tints
2228
+ for (let i = 0; i < lightSteps - half - 1; i += 1) {
2229
+ lightnessArray = [(0.5 - lightStep * (i + 1)), ...lightnessArray];
2230
+ }
2231
+
2232
+ // feed `colors` Array
2233
+ for (let i = 0; i < hueSteps; i += 1) {
2234
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
2235
+ lightnessArray.forEach((l) => {
2236
+ colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2237
+ });
2238
+ }
2239
+
2240
+ this.hue = hue;
2241
+ this.hueSteps = hueSteps;
2242
+ this.lightSteps = lightSteps;
2243
+ this.colors = colors;
2244
+ }
2245
+ }
2246
+
2247
+ /**
2248
+ * Returns a color-defaults with given values and class.
2249
+ * @param {CP.ColorPicker} self
2250
+ * @param {CP.ColorPalette | string[]} colorsSource
2251
+ * @param {string} menuClass
2252
+ * @returns {HTMLElement | Element}
2253
+ */
2254
+ function getColorMenu(self, colorsSource, menuClass) {
2255
+ const { input, format, componentLabels } = self;
2256
+ const { defaultsLabel, presetsLabel } = componentLabels;
2257
+ const isOptionsMenu = menuClass === 'color-options';
2258
+ const isPalette = colorsSource instanceof ColorPalette;
2259
+ const menuLabel = isOptionsMenu ? presetsLabel : defaultsLabel;
2260
+ let colorsArray = isPalette ? colorsSource.colors : colorsSource;
2261
+ colorsArray = colorsArray instanceof Array ? colorsArray : [];
2262
+ const colorsCount = colorsArray.length;
2263
+ const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
2264
+ let fit = lightSteps
2265
+ || Math.max(...[5, 6, 7, 8, 9, 10].filter((x) => colorsCount > (x * 2) && !(colorsCount % x)));
2266
+ fit = Number.isFinite(fit) ? fit : 5;
2267
+ const isMultiLine = isOptionsMenu && colorsCount > fit;
2268
+ let rowCountHover = 1;
2269
+ rowCountHover = isMultiLine && colorsCount < 27 ? 2 : rowCountHover;
2270
+ rowCountHover = colorsCount >= 27 ? 3 : rowCountHover;
2271
+ rowCountHover = colorsCount >= 36 ? 4 : rowCountHover;
2272
+ rowCountHover = colorsCount >= 45 ? 5 : rowCountHover;
2273
+ const rowCount = rowCountHover - (colorsCount < 27 ? 1 : 2);
2274
+ const isScrollable = isMultiLine && colorsCount > rowCountHover * fit;
2275
+ let finalClass = menuClass;
2276
+ finalClass += isScrollable ? ' scrollable' : '';
2277
+ finalClass += isMultiLine ? ' multiline' : '';
2278
+ const gap = isMultiLine ? '1px' : '0.25rem';
2279
+ let optionSize = isMultiLine ? 1.75 : 2;
2280
+ optionSize = !(colorsCount % 10) && isMultiLine ? 1.5 : optionSize;
2281
+ const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2282
+ const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2283
+ const gridTemplateColumns = `repeat(${fit}, ${optionSize}rem)`;
2284
+ const gridTemplateRows = `repeat(auto-fill, ${optionSize}rem)`;
2285
+
2286
+ const menu = createElement({
2287
+ tagName: 'ul',
2288
+ className: finalClass,
2289
+ });
2290
+ setAttribute(menu, 'role', 'listbox');
2291
+ setAttribute(menu, ariaLabel, `${menuLabel}`);
2292
+
2293
+ if (isOptionsMenu) {
2294
+ if (isScrollable) {
2295
+ const styleText = 'this.style.height=';
2296
+ setAttribute(menu, 'onmouseout', `${styleText}'${menuHeight}'`);
2297
+ setAttribute(menu, 'onmouseover', `${styleText}'${menuHeightHover}'`);
2298
+ }
2299
+ const menuStyle = {
2300
+ height: isScrollable ? menuHeight : '', gridTemplateColumns, gridTemplateRows, gap,
2301
+ };
2302
+ setElementStyle(menu, menuStyle);
2303
+ }
2304
+
2305
+ colorsArray.forEach((x) => {
2306
+ const [value, label] = x.trim().split(':');
2307
+ const xRealColor = new Color(value, format).toString();
2308
+ const isActive = xRealColor === getAttribute(input, 'value');
2309
+ const active = isActive ? ' active' : '';
2310
+
2311
+ const option = createElement({
2312
+ tagName: 'li',
2313
+ className: `color-option${active}`,
2314
+ innerText: `${label || x}`,
2315
+ });
2316
+
2317
+ setAttribute(option, 'tabindex', '0');
2318
+ setAttribute(option, 'data-value', `${value}`);
2319
+ setAttribute(option, 'role', 'option');
2320
+ setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2321
+
2322
+ if (isOptionsMenu) {
2323
+ setElementStyle(option, {
2324
+ width: `${optionSize}rem`, height: `${optionSize}rem`, backgroundColor: x,
2325
+ });
2326
+ }
2327
+
2328
+ menu.append(option);
2329
+ });
2330
+ return menu;
2331
+ }
2332
+
2333
+ /**
2334
+ * Check if a string is valid JSON string.
2335
+ * @param {string} str the string input
2336
+ * @returns {boolean} the query result
2337
+ */
2338
+ function isValidJSON(str) {
2339
+ try {
2340
+ JSON.parse(str);
2341
+ } catch (e) {
2342
+ return false;
2343
+ }
2344
+ return true;
2345
+ }
2346
+
2347
+ var version = "0.0.1alpha2";
2348
+
2349
+ // @ts-ignore
2350
+
2351
+ const Version = version;
2352
+
2353
+ // ColorPicker GC
2354
+ // ==============
2355
+ const colorPickerString = 'color-picker';
2356
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2357
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2358
+ const colorPickerDefaults = {
2359
+ componentLabels: colorPickerLabels,
2360
+ colorLabels: colorNames,
2361
+ format: 'rgb',
2362
+ colorPresets: undefined,
2363
+ colorKeywords: nonColors,
2364
+ };
2365
+
2366
+ // ColorPicker Static Methods
2367
+ // ==========================
2368
+
2369
+ /** @type {CP.GetInstance<ColorPicker>} */
2370
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2371
+
2372
+ /** @type {CP.InitCallback<ColorPicker>} */
2373
+ const initColorPicker = (element) => new ColorPicker(element);
2374
+
2375
+ // ColorPicker Private Methods
2376
+ // ===========================
2377
+
2378
+ /**
2379
+ * Generate HTML markup and update instance properties.
2380
+ * @param {ColorPicker} self
2381
+ */
2382
+ function initCallback(self) {
2383
+ const {
2384
+ input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2385
+ } = self;
2386
+ const colorValue = getAttribute(input, 'value') || '#fff';
2387
+
2388
+ const {
2389
+ toggleLabel, pickerLabel, formatLabel, hexLabel,
2390
+ } = componentLabels;
2391
+
2392
+ // update color
2393
+ const color = nonColors.includes(colorValue) ? '#fff' : colorValue;
2394
+ self.color = new Color(color, format);
2395
+
2396
+ // set initial controls dimensions
2397
+ // make the controls smaller on mobile
2398
+ const dropClass = isMobile ? ' mobile' : '';
2399
+ const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2400
+
2401
+ const pickerBtn = createElement({
2402
+ id: `picker-btn-${id}`,
2403
+ tagName: 'button',
2404
+ className: 'picker-toggle btn-appearance',
2405
+ });
2406
+ setAttribute(pickerBtn, ariaExpanded, 'false');
2407
+ setAttribute(pickerBtn, ariaHasPopup, 'true');
2408
+ pickerBtn.append(createElement({
2409
+ tagName: 'span',
2410
+ className: vHidden,
2411
+ innerText: `${pickerLabel}. ${formatLabel}: ${formatString}`,
2412
+ }));
2413
+
2414
+ const pickerDropdown = createElement({
2415
+ tagName: 'div',
2416
+ className: `color-dropdown picker${dropClass}`,
2417
+ });
2418
+ setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2419
+ setAttribute(pickerDropdown, 'role', 'group');
2420
+
2421
+ const colorControls = getColorControls(self);
2422
+ const colorForm = getColorForm(self);
2423
+
2424
+ pickerDropdown.append(colorControls, colorForm);
2425
+ input.before(pickerBtn);
2426
+ parent.append(pickerDropdown);
2427
+
2428
+ // set colour key menu template
2429
+ if (colorKeywords || colorPresets) {
2430
+ const presetsDropdown = createElement({
2431
+ tagName: 'div',
2432
+ className: `color-dropdown scrollable menu${dropClass}`,
2433
+ });
2434
+
2435
+ // color presets
2436
+ if ((colorPresets instanceof Array && colorPresets.length)
2437
+ || (colorPresets instanceof ColorPalette && colorPresets.colors)) {
2438
+ const presetsMenu = getColorMenu(self, colorPresets, 'color-options');
2439
+ presetsDropdown.append(presetsMenu);
2440
+ }
2441
+
2442
+ // explicit defaults [reset, initial, inherit, transparent, currentColor]
2443
+ if (colorKeywords && colorKeywords.length) {
2444
+ const keywordsMenu = getColorMenu(self, colorKeywords, 'color-defaults');
2445
+ presetsDropdown.append(keywordsMenu);
2446
+ }
2447
+
2448
+ const presetsBtn = createElement({
2449
+ tagName: 'button',
2450
+ className: 'menu-toggle btn-appearance',
2451
+ });
2452
+ setAttribute(presetsBtn, 'tabindex', '-1');
2453
+ setAttribute(presetsBtn, ariaExpanded, 'false');
2454
+ setAttribute(presetsBtn, ariaHasPopup, 'true');
2455
+
2456
+ const xmlns = encodeURI('http://www.w3.org/2000/svg');
2457
+ const presetsIcon = createElementNS(xmlns, { tagName: 'svg' });
2458
+ setAttribute(presetsIcon, 'xmlns', xmlns);
2459
+ setAttribute(presetsIcon, 'viewBox', '0 0 512 512');
2460
+ setAttribute(presetsIcon, ariaHidden, 'true');
2461
+
2462
+ const path = createElementNS(xmlns, { tagName: 'path' });
2463
+ setAttribute(path, 'd', 'M98,158l157,156L411,158l27,27L255,368L71,185L98,158z');
2464
+ setAttribute(path, 'fill', '#fff');
2465
+ presetsIcon.append(path);
2466
+ presetsBtn.append(createElement({
2467
+ tagName: 'span',
2468
+ className: vHidden,
2469
+ innerText: `${toggleLabel}`,
2470
+ }), presetsIcon);
2471
+
2472
+ parent.append(presetsBtn, presetsDropdown);
2473
+ }
2474
+
2475
+ // solve non-colors after settings save
2476
+ if (colorKeywords && nonColors.includes(colorValue)) {
2477
+ self.value = colorValue;
2478
+ }
2479
+ setAttribute(input, 'tabindex', '-1');
2480
+ }
2481
+
2482
+ /**
2483
+ * Add / remove `ColorPicker` main event listeners.
2484
+ * @param {ColorPicker} self
2485
+ * @param {boolean=} action
2486
+ */
2487
+ function toggleEvents(self, action) {
2488
+ const fn = action ? addListener : removeListener;
2489
+ const { input, pickerToggle, menuToggle } = self;
2490
+
2491
+ fn(input, focusinEvent, self.showPicker);
2492
+ fn(pickerToggle, mouseclickEvent, self.togglePicker);
2493
+
2494
+ fn(input, keydownEvent, self.keyToggle);
2495
+
2496
+ if (menuToggle) {
2497
+ fn(menuToggle, mouseclickEvent, self.toggleMenu);
2498
+ }
2499
+ }
2500
+
2501
+ /**
2502
+ * Add / remove `ColorPicker` event listeners active only when open.
2503
+ * @param {ColorPicker} self
2504
+ * @param {boolean=} action
2505
+ */
2506
+ function toggleEventsOnShown(self, action) {
2507
+ const fn = action ? addListener : removeListener;
2508
+ const { input, colorMenu, parent } = self;
2509
+ const doc = getDocument(input);
2510
+ const win = getWindow(input);
2511
+ const pointerEvents = `on${touchstartEvent}` in doc
2512
+ ? { down: touchstartEvent, move: touchmoveEvent, up: touchendEvent }
2513
+ : { down: mousedownEvent, move: mousemoveEvent, up: mouseupEvent };
2514
+
2515
+ fn(self.controls, pointerEvents.down, self.pointerDown);
2516
+ self.controlKnobs.forEach((x) => fn(x, keydownEvent, self.handleKnobs));
2517
+
2518
+ // @ts-ignore -- this is `Window`
2519
+ fn(win, scrollEvent, self.handleScroll);
2520
+ // @ts-ignore -- this is `Window`
2521
+ fn(win, resizeEvent, self.update);
2522
+
2523
+ [input, ...self.inputs].forEach((x) => fn(x, changeEvent, self.changeHandler));
2524
+
2525
+ if (colorMenu) {
2526
+ fn(colorMenu, mouseclickEvent, self.menuClickHandler);
2527
+ fn(colorMenu, keydownEvent, self.menuKeyHandler);
2528
+ }
2529
+
2530
+ fn(doc, pointerEvents.move, self.pointerMove);
2531
+ fn(doc, pointerEvents.up, self.pointerUp);
2532
+ fn(parent, focusoutEvent, self.handleFocusOut);
2533
+ // @ts-ignore -- this is `Window`
2534
+ fn(win, keyupEvent, self.handleDismiss);
2535
+ }
2536
+
2537
+ /**
2538
+ * Triggers the `ColorPicker` original event.
2539
+ * @param {ColorPicker} self
2540
+ */
2541
+ function firePickerChange(self) {
2542
+ dispatchEvent(self.input, new CustomEvent('colorpicker.change'));
2543
+ }
2544
+
2545
+ /**
2546
+ * Hides a visible dropdown.
2547
+ * @param {HTMLElement} element
2548
+ * @returns {void}
2549
+ */
2550
+ function removePosition(element) {
2551
+ if (element) {
2552
+ ['bottom', 'top'].forEach((x) => removeClass(element, x));
2553
+ }
2554
+ }
2555
+
2556
+ /**
2557
+ * Shows a `ColorPicker` dropdown and close the curent open dropdown.
2558
+ * @param {ColorPicker} self
2559
+ * @param {HTMLElement | Element} dropdown
2560
+ */
2561
+ function showDropdown(self, dropdown) {
2562
+ const {
2563
+ colorPicker, colorMenu, menuToggle, pickerToggle, parent,
2564
+ } = self;
2565
+ const isPicker = dropdown === colorPicker;
2566
+ const openDropdown = isPicker ? colorMenu : colorPicker;
2567
+ const activeBtn = isPicker ? menuToggle : pickerToggle;
2568
+ const nextBtn = !isPicker ? menuToggle : pickerToggle;
2569
+
2570
+ if (!hasClass(parent, 'open')) {
2571
+ addClass(parent, 'open');
2572
+ }
2573
+ if (openDropdown) {
2574
+ removeClass(openDropdown, 'show');
2575
+ removePosition(openDropdown);
2576
+ }
2577
+ addClass(dropdown, 'bottom');
2578
+ reflow(dropdown);
2579
+ addClass(dropdown, 'show');
2580
+ if (isPicker) self.update();
2581
+ self.show();
2582
+ setAttribute(nextBtn, ariaExpanded, 'true');
2583
+ if (activeBtn) {
2584
+ setAttribute(activeBtn, ariaExpanded, 'false');
2585
+ }
2586
+ }
2587
+
2588
+ /**
2589
+ * Color Picker Web Component
2590
+ * @see http://thednp.github.io/color-picker
2591
+ */
2592
+ class ColorPicker {
2593
+ /**
2594
+ * Returns a new `ColorPicker` instance. The target of this constructor
2595
+ * must be an `HTMLInputElement`.
2596
+ *
2597
+ * @param {HTMLInputElement | string} target the target `<input>` element
2598
+ * @param {CP.ColorPickerOptions=} config instance options
2599
+ */
2600
+ constructor(target, config) {
2601
+ const self = this;
2602
+ /** @type {HTMLInputElement} */
2603
+ // @ts-ignore
2604
+ const input = querySelector(target);
2605
+
2606
+ // invalidate
2607
+ if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2608
+ self.input = input;
2609
+
2610
+ const parent = closest(input, colorPickerParentSelector);
2611
+ if (!parent) throw new TypeError('ColorPicker requires a specific markup to work.');
2612
+
2613
+ /** @type {HTMLElement} */
2614
+ // @ts-ignore
2615
+ self.parent = parent;
2616
+
2617
+ /** @type {number} */
2618
+ self.id = getUID(input, colorPickerString);
2619
+
2620
+ // set initial state
2621
+ /** @type {HTMLElement?} */
2622
+ self.dragElement = null;
2623
+ /** @type {boolean} */
2624
+ self.isOpen = false;
2625
+ /** @type {Record<string, number>} */
2626
+ self.controlPositions = {
2627
+ c1x: 0, c1y: 0, c2y: 0, c3y: 0,
2628
+ };
2629
+ /** @type {Record<string, string>} */
2630
+ self.colorLabels = {};
2631
+ /** @type {string[]=} */
2632
+ self.colorKeywords = undefined;
2633
+ /** @type {(ColorPalette | string[])=} */
2634
+ self.colorPresets = undefined;
2635
+
2636
+ // process options
2637
+ const {
2638
+ format, componentLabels, colorLabels, colorKeywords, colorPresets,
2639
+ } = normalizeOptions(this.isCE ? parent : input, colorPickerDefaults, config || {});
2640
+
2641
+ let translatedColorLabels = colorNames;
2642
+ if (colorLabels instanceof Array && colorLabels.length === 17) {
2643
+ translatedColorLabels = colorLabels;
2644
+ } else if (colorLabels && colorLabels.split(',').length === 17) {
2645
+ translatedColorLabels = colorLabels.split(',');
2646
+ }
2647
+
2648
+ // expose colour labels to all methods
2649
+ colorNames.forEach((c, i) => {
2650
+ self.colorLabels[c] = translatedColorLabels[i].trim();
2651
+ });
2652
+
2653
+ // update and expose component labels
2654
+ const tempLabels = ObjectAssign({}, colorPickerLabels);
2655
+ const jsonLabels = componentLabels && isValidJSON(componentLabels)
2656
+ ? JSON.parse(componentLabels) : componentLabels || {};
2657
+
2658
+ /** @type {Record<string, string>} */
2659
+ self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2660
+
2661
+ /** @type {Color} */
2662
+ self.color = new Color('white', format);
2663
+
2664
+ /** @type {CP.ColorFormats} */
2665
+ self.format = format;
2666
+
2667
+ // set colour defaults
2668
+ if (colorKeywords instanceof Array) {
2669
+ self.colorKeywords = colorKeywords;
2670
+ } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2671
+ self.colorKeywords = colorKeywords.split(',');
2672
+ }
2673
+
2674
+ // set colour presets
2675
+ if (colorPresets instanceof Array) {
2676
+ self.colorPresets = colorPresets;
2677
+ } else if (typeof colorPresets === 'string' && colorPresets.length) {
2678
+ if (isValidJSON(colorPresets)) {
2679
+ const { hue, hueSteps, lightSteps } = JSON.parse(colorPresets);
2680
+ self.colorPresets = new ColorPalette(hue, hueSteps, lightSteps);
2681
+ } else {
2682
+ self.colorPresets = colorPresets.split(',').map((x) => x.trim());
2683
+ }
2684
+ }
2685
+
2686
+ // bind events
2687
+ self.showPicker = self.showPicker.bind(self);
2688
+ self.togglePicker = self.togglePicker.bind(self);
2689
+ self.toggleMenu = self.toggleMenu.bind(self);
2690
+ self.menuClickHandler = self.menuClickHandler.bind(self);
2691
+ self.menuKeyHandler = self.menuKeyHandler.bind(self);
2692
+ self.pointerDown = self.pointerDown.bind(self);
2693
+ self.pointerMove = self.pointerMove.bind(self);
2694
+ self.pointerUp = self.pointerUp.bind(self);
2695
+ self.update = self.update.bind(self);
2696
+ self.handleScroll = self.handleScroll.bind(self);
2697
+ self.handleFocusOut = self.handleFocusOut.bind(self);
2698
+ self.changeHandler = self.changeHandler.bind(self);
2699
+ self.handleDismiss = self.handleDismiss.bind(self);
2700
+ self.keyToggle = self.keyToggle.bind(self);
2701
+ self.handleKnobs = self.handleKnobs.bind(self);
2702
+
2703
+ // generate markup
2704
+ initCallback(self);
2705
+
2706
+ const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2707
+ // set main elements
2708
+ /** @type {HTMLElement} */
2709
+ // @ts-ignore
2710
+ self.pickerToggle = querySelector('.picker-toggle', parent);
2711
+ /** @type {HTMLElement} */
2712
+ // @ts-ignore
2713
+ self.menuToggle = querySelector('.menu-toggle', parent);
2714
+ /** @type {HTMLElement} */
2715
+ // @ts-ignore
2716
+ self.colorPicker = colorPicker;
2717
+ /** @type {HTMLElement} */
2718
+ // @ts-ignore
2719
+ self.colorMenu = colorMenu;
2720
+ /** @type {HTMLInputElement[]} */
2721
+ // @ts-ignore
2722
+ self.inputs = [...getElementsByClassName('color-input', parent)];
2723
+ const [controls] = getElementsByClassName('color-controls', parent);
2724
+ self.controls = controls;
2725
+ /** @type {(HTMLElement | Element)[]} */
2726
+ self.controlKnobs = [...getElementsByClassName('knob', controls)];
2727
+ /** @type {(HTMLElement)[]} */
2728
+ // @ts-ignore
2729
+ self.visuals = [...getElementsByClassName('visual-control', controls)];
2730
+
2731
+ // update colour picker controls, inputs and visuals
2732
+ self.update();
2733
+
2734
+ // add main events listeners
2735
+ toggleEvents(self, true);
2736
+
2737
+ // set component data
2738
+ Data.set(input, colorPickerString, self);
2739
+ }
2740
+
2741
+ /** Returns the current colour value */
2742
+ get value() { return this.input.value; }
2743
+
2744
+ /**
2745
+ * Sets a new colour value.
2746
+ * @param {string} v new colour value
2747
+ */
2748
+ set value(v) { this.input.value = v; }
2749
+
2750
+ /** Check if the colour presets include any non-colour. */
2751
+ get includeNonColor() {
2752
+ return this.colorKeywords instanceof Array
2753
+ && this.colorKeywords.some((x) => nonColors.includes(x));
2754
+ }
2755
+
2756
+ /** Check if the parent of the target is a `ColorPickerElement` instance. */
2757
+ get isCE() { return this.parent.localName === colorPickerString; }
2758
+
2759
+ /** Returns hexadecimal value of the current colour. */
2760
+ get hex() { return this.color.toHex(true); }
2761
+
2762
+ /** Returns the current colour value in {h,s,v,a} object format. */
2763
+ get hsv() { return this.color.toHsv(); }
2764
+
2765
+ /** Returns the current colour value in {h,s,l,a} object format. */
2766
+ get hsl() { return this.color.toHsl(); }
2767
+
2768
+ /** Returns the current colour value in {h,w,b,a} object format. */
2769
+ get hwb() { return this.color.toHwb(); }
2770
+
2771
+ /** Returns the current colour value in {r,g,b,a} object format. */
2772
+ get rgb() { return this.color.toRgb(); }
2773
+
2774
+ /** Returns the current colour brightness. */
2775
+ get brightness() { return this.color.brightness; }
2776
+
2777
+ /** Returns the current colour luminance. */
2778
+ get luminance() { return this.color.luminance; }
2779
+
2780
+ /** Checks if the current colour requires a light text colour. */
2781
+ get isDark() {
2782
+ const { color, brightness } = this;
2783
+ return brightness < 120 && color.a > 0.33;
2784
+ }
2785
+
2786
+ /** Checks if the current input value is a valid colour. */
2787
+ get isValid() {
2788
+ const inputValue = this.input.value;
2789
+ return inputValue !== '' && new Color(inputValue).isValid;
2790
+ }
2791
+
2792
+ /** Updates `ColorPicker` visuals. */
2793
+ updateVisuals() {
2794
+ const self = this;
2795
+ const {
2796
+ format, controlPositions, visuals,
2797
+ } = self;
2798
+ const [v1, v2, v3] = visuals;
2799
+ const { offsetWidth, offsetHeight } = v1;
2800
+ const hue = format === 'hsl'
2801
+ ? controlPositions.c1x / offsetWidth
2802
+ : controlPositions.c2y / offsetHeight;
2803
+ // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2804
+ const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2805
+ const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2806
+ const alpha = 1 - controlPositions.c3y / offsetHeight;
2807
+ const roundA = Math.round((alpha * 100)) / 100;
2808
+
2809
+ if (format !== 'hsl') {
2810
+ const fill = new Color({
2811
+ h: hue, s: 1, l: 0.5, a: alpha,
2812
+ }).toRgbString();
2813
+ const hueGradient = `linear-gradient(
2814
+ rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2815
+ rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2816
+ rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2817
+ rgb(255,0,0) 100%)`;
2818
+ setElementStyle(v1, {
2819
+ background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2820
+ linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2821
+ ${whiteGrad}`,
2822
+ });
2823
+ setElementStyle(v2, { background: hueGradient });
2824
+ } else {
2825
+ const saturation = Math.round((controlPositions.c2y / offsetHeight) * 100);
2826
+ const fill0 = new Color({
2827
+ r: 255, g: 0, b: 0, a: alpha,
2828
+ }).saturate(-saturation).toRgbString();
2829
+ const fill1 = new Color({
2830
+ r: 255, g: 255, b: 0, a: alpha,
2831
+ }).saturate(-saturation).toRgbString();
2832
+ const fill2 = new Color({
2833
+ r: 0, g: 255, b: 0, a: alpha,
2834
+ }).saturate(-saturation).toRgbString();
2835
+ const fill3 = new Color({
2836
+ r: 0, g: 255, b: 255, a: alpha,
2837
+ }).saturate(-saturation).toRgbString();
2838
+ const fill4 = new Color({
2839
+ r: 0, g: 0, b: 255, a: alpha,
2840
+ }).saturate(-saturation).toRgbString();
2841
+ const fill5 = new Color({
2842
+ r: 255, g: 0, b: 255, a: alpha,
2843
+ }).saturate(-saturation).toRgbString();
2844
+ const fill6 = new Color({
2845
+ r: 255, g: 0, b: 0, a: alpha,
2846
+ }).saturate(-saturation).toRgbString();
2847
+ const fillGradient = `linear-gradient(to right,
2848
+ ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2849
+ ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2850
+ const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2851
+ linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2852
+
2853
+ setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2854
+ const {
2855
+ r: gr, g: gg, b: gb,
2856
+ } = new Color({ r, g, b }).greyscale().toRgb();
2857
+
2858
+ setElementStyle(v2, {
2859
+ background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2860
+ });
2861
+ }
2862
+ setElementStyle(v3, {
2863
+ background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2864
+ });
2865
+ }
2866
+
2867
+ /**
2868
+ * The `ColorPicker` *focusout* event listener when open.
2869
+ * @param {FocusEvent} e
2870
+ * @this {ColorPicker}
2871
+ */
2872
+ handleFocusOut({ relatedTarget }) {
2873
+ // @ts-ignore
2874
+ if (relatedTarget && !this.parent.contains(relatedTarget)) {
2875
+ this.hide(true);
2876
+ }
2877
+ }
2878
+
2879
+ /**
2880
+ * The `ColorPicker` *keyup* event listener when open.
2881
+ * @param {KeyboardEvent} e
2882
+ * @this {ColorPicker}
2883
+ */
2884
+ handleDismiss({ code }) {
2885
+ const self = this;
2886
+ if (self.isOpen && code === keyEscape) {
2887
+ self.hide();
2888
+ }
2889
+ }
2890
+
2891
+ /**
2892
+ * The `ColorPicker` *scroll* event listener when open.
2893
+ * @param {Event} e
2894
+ * @this {ColorPicker}
2895
+ */
2896
+ handleScroll(e) {
2897
+ const self = this;
2898
+ const { activeElement } = getDocument(self.input);
2899
+
2900
+ if ((isMobile && self.dragElement)
2901
+ || (activeElement && self.controlKnobs.includes(activeElement))) {
2902
+ e.stopPropagation();
2903
+ e.preventDefault();
2904
+ }
2905
+
2906
+ self.updateDropdownPosition();
2907
+ }
2908
+
2909
+ /**
2910
+ * The `ColorPicker` keyboard event listener for menu navigation.
2911
+ * @param {KeyboardEvent} e
2912
+ * @this {ColorPicker}
2913
+ */
2914
+ menuKeyHandler(e) {
2915
+ const { target, code } = e;
2916
+ // @ts-ignore
2917
+ const { previousElementSibling, nextElementSibling, parentElement } = target;
2918
+ const isColorOptionsMenu = parentElement && hasClass(parentElement, 'color-options');
2919
+ const allSiblings = [...parentElement.children];
2920
+ const columnsCount = isColorOptionsMenu
2921
+ && getElementStyle(parentElement, 'grid-template-columns').split(' ').length;
2922
+ const currentIndex = allSiblings.indexOf(target);
2923
+ const previousElement = currentIndex > -1
2924
+ && columnsCount && allSiblings[currentIndex - columnsCount];
2925
+ const nextElement = currentIndex > -1
2926
+ && columnsCount && allSiblings[currentIndex + columnsCount];
2927
+
2928
+ if ([keyArrowDown, keyArrowUp, keySpace].includes(code)) {
2929
+ // prevent scroll when navigating the menu via arrow keys / Space
2930
+ e.preventDefault();
2931
+ }
2932
+ if (isColorOptionsMenu) {
2933
+ if (previousElement && code === keyArrowUp) {
2934
+ focus(previousElement);
2935
+ } else if (nextElement && code === keyArrowDown) {
2936
+ focus(nextElement);
2937
+ } else if (previousElementSibling && code === keyArrowLeft) {
2938
+ focus(previousElementSibling);
2939
+ } else if (nextElementSibling && code === keyArrowRight) {
2940
+ focus(nextElementSibling);
2941
+ }
2942
+ } else if (previousElementSibling && [keyArrowLeft, keyArrowUp].includes(code)) {
2943
+ focus(previousElementSibling);
2944
+ } else if (nextElementSibling && [keyArrowRight, keyArrowDown].includes(code)) {
2945
+ focus(nextElementSibling);
2946
+ }
2947
+
2948
+ if ([keyEnter, keySpace].includes(code)) {
2949
+ this.menuClickHandler({ target });
2950
+ }
2951
+ }
2952
+
2953
+ /**
2954
+ * The `ColorPicker` click event listener for the colour menu presets / defaults.
2955
+ * @param {Partial<Event>} e
2956
+ * @this {ColorPicker}
2957
+ */
2958
+ menuClickHandler(e) {
2959
+ const self = this;
2960
+ /** @type {*} */
2961
+ const { target } = e;
2962
+ const { colorMenu } = self;
2963
+ const newOption = (getAttribute(target, 'data-value') || '').trim();
2964
+ // invalidate for targets other than color options
2965
+ if (!newOption.length) return;
2966
+ const currentActive = querySelector('li.active', colorMenu);
2967
+ let newColor = nonColors.includes(newOption) ? 'white' : newOption;
2968
+ newColor = newOption === 'transparent' ? 'rgba(0,0,0,0)' : newOption;
2969
+
2970
+ const {
2971
+ r, g, b, a,
2972
+ } = new Color(newColor);
2973
+
2974
+ ObjectAssign(self.color, {
2975
+ r, g, b, a,
2976
+ });
2977
+
2978
+ self.update();
2979
+
2980
+ if (currentActive) {
2981
+ removeClass(currentActive, 'active');
2982
+ removeAttribute(currentActive, ariaSelected);
2983
+ }
2984
+
2985
+ if (currentActive !== target) {
2986
+ addClass(target, 'active');
2987
+ setAttribute(target, ariaSelected, 'true');
2988
+
2989
+ if (nonColors.includes(newOption)) {
2990
+ self.value = newOption;
2991
+ }
2992
+ firePickerChange(self);
2993
+ }
2994
+ }
2995
+
2996
+ /**
2997
+ * The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
2998
+ * @param {TouchEvent} e
2999
+ * @this {ColorPicker}
3000
+ */
3001
+ pointerDown(e) {
3002
+ const self = this;
3003
+ /** @type {*} */
3004
+ const {
3005
+ type, target, touches, pageX, pageY,
3006
+ } = e;
3007
+ const { colorMenu, visuals, controlKnobs } = self;
3008
+ const [v1, v2, v3] = visuals;
3009
+ const [c1, c2, c3] = controlKnobs;
3010
+ /** @type {HTMLElement} */
3011
+ const visual = hasClass(target, 'visual-control')
3012
+ ? target : querySelector('.visual-control', target.parentElement);
3013
+ const visualRect = getBoundingClientRect(visual);
3014
+ const X = type === 'touchstart' ? touches[0].pageX : pageX;
3015
+ const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3016
+ const offsetX = X - window.pageXOffset - visualRect.left;
3017
+ const offsetY = Y - window.pageYOffset - visualRect.top;
3018
+
3019
+ if (target === v1 || target === c1) {
3020
+ self.dragElement = visual;
3021
+ self.changeControl1(offsetX, offsetY);
3022
+ } else if (target === v2 || target === c2) {
3023
+ self.dragElement = visual;
3024
+ self.changeControl2(offsetY);
3025
+ } else if (target === v3 || target === c3) {
3026
+ self.dragElement = visual;
3027
+ self.changeAlpha(offsetY);
3028
+ }
3029
+
3030
+ if (colorMenu) {
3031
+ const currentActive = querySelector('li.active', colorMenu);
3032
+ if (currentActive) {
3033
+ removeClass(currentActive, 'active');
3034
+ removeAttribute(currentActive, ariaSelected);
3035
+ }
3036
+ }
3037
+ e.preventDefault();
3038
+ }
3039
+
3040
+ /**
3041
+ * The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
3042
+ * @param {TouchEvent} e
3043
+ * @this {ColorPicker}
3044
+ */
3045
+ pointerUp({ target }) {
3046
+ const self = this;
3047
+ const { parent } = self;
3048
+ const doc = getDocument(parent);
3049
+ const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
3050
+ const selection = doc.getSelection();
3051
+ // @ts-ignore
3052
+ if (!self.dragElement && !selection.toString().length
3053
+ // @ts-ignore
3054
+ && !parent.contains(target)) {
3055
+ self.hide(currentOpen);
3056
+ }
3057
+
3058
+ self.dragElement = null;
3059
+ }
3060
+
3061
+ /**
3062
+ * The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
3063
+ * @param {TouchEvent} e
3064
+ */
3065
+ pointerMove(e) {
3066
+ const self = this;
3067
+ const { dragElement, visuals } = self;
3068
+ const [v1, v2, v3] = visuals;
3069
+ const {
3070
+ // @ts-ignore
3071
+ type, touches, pageX, pageY,
3072
+ } = e;
3073
+
3074
+ if (!dragElement) return;
3075
+
3076
+ const controlRect = getBoundingClientRect(dragElement);
3077
+ const X = type === 'touchmove' ? touches[0].pageX : pageX;
3078
+ const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3079
+ const offsetX = X - window.pageXOffset - controlRect.left;
3080
+ const offsetY = Y - window.pageYOffset - controlRect.top;
3081
+
3082
+ if (dragElement === v1) {
3083
+ self.changeControl1(offsetX, offsetY);
3084
+ }
3085
+
3086
+ if (dragElement === v2) {
3087
+ self.changeControl2(offsetY);
3088
+ }
3089
+
3090
+ if (dragElement === v3) {
3091
+ self.changeAlpha(offsetY);
3092
+ }
3093
+ }
3094
+
3095
+ /**
3096
+ * The `ColorPicker` *keydown* event listener for control knobs.
3097
+ * @param {KeyboardEvent} e
3098
+ */
3099
+ handleKnobs(e) {
3100
+ const { target, code } = e;
3101
+ const self = this;
3102
+
3103
+ // only react to arrow buttons
3104
+ if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3105
+ e.preventDefault();
3106
+
3107
+ const { controlKnobs } = self;
3108
+ const [c1, c2, c3] = controlKnobs;
3109
+ const { activeElement } = getDocument(c1);
3110
+ const currentKnob = controlKnobs.find((x) => x === activeElement);
3111
+
3112
+ if (currentKnob) {
3113
+ let offsetX = 0;
3114
+ let offsetY = 0;
3115
+ if (target === c1) {
3116
+ if ([keyArrowLeft, keyArrowRight].includes(code)) {
3117
+ self.controlPositions.c1x += code === keyArrowRight ? +1 : -1;
3118
+ } else if ([keyArrowUp, keyArrowDown].includes(code)) {
3119
+ self.controlPositions.c1y += code === keyArrowDown ? +1 : -1;
3120
+ }
3121
+
3122
+ offsetX = self.controlPositions.c1x;
3123
+ offsetY = self.controlPositions.c1y;
3124
+ self.changeControl1(offsetX, offsetY);
3125
+ } else if (target === c2) {
3126
+ self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3127
+ offsetY = self.controlPositions.c2y;
3128
+ self.changeControl2(offsetY);
3129
+ } else if (target === c3) {
3130
+ self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3131
+ offsetY = self.controlPositions.c3y;
3132
+ self.changeAlpha(offsetY);
3133
+ }
3134
+ self.handleScroll(e);
3135
+ }
3136
+ }
3137
+
3138
+ /** The event listener of the colour form inputs. */
3139
+ changeHandler() {
3140
+ const self = this;
3141
+ let colorSource;
3142
+ const {
3143
+ inputs, format, value: currentValue, input, controlPositions, visuals,
3144
+ } = self;
3145
+ /** @type {*} */
3146
+ const { activeElement } = getDocument(input);
3147
+ const { offsetHeight } = visuals[0];
3148
+ const [i1,,, i4] = inputs;
3149
+ const [v1, v2, v3, v4] = format === 'rgb'
3150
+ ? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
3151
+ : inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
3152
+ const isNonColorValue = self.includeNonColor && nonColors.includes(currentValue);
3153
+ const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
3154
+
3155
+ if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3156
+ if (activeElement === input) {
3157
+ if (isNonColorValue) {
3158
+ colorSource = 'white';
3159
+ } else {
3160
+ colorSource = currentValue;
3161
+ }
3162
+ } else if (format === 'hex') {
3163
+ colorSource = i1.value;
3164
+ } else if (format === 'hsl') {
3165
+ colorSource = {
3166
+ h: v1, s: v2, l: v3, a: alpha,
3167
+ };
3168
+ } else if (format === 'hwb') {
3169
+ colorSource = {
3170
+ h: v1, w: v2, b: v3, a: alpha,
3171
+ };
3172
+ } else {
3173
+ colorSource = {
3174
+ r: v1, g: v2, b: v3, a: alpha,
3175
+ };
3176
+ }
3177
+
3178
+ const {
3179
+ r, g, b, a,
3180
+ } = new Color(colorSource);
3181
+
3182
+ ObjectAssign(self.color, {
3183
+ r, g, b, a,
3184
+ });
3185
+ self.setControlPositions();
3186
+ self.updateAppearance();
3187
+ self.updateInputs();
3188
+ self.updateControls();
3189
+ self.updateVisuals();
3190
+
3191
+ // set non-color keyword
3192
+ if (activeElement === input && isNonColorValue) {
3193
+ self.value = currentValue;
3194
+ }
3195
+ }
3196
+ }
3197
+
3198
+ /**
3199
+ * Updates `ColorPicker` first control:
3200
+ * * `lightness` and `saturation` for HEX/RGB;
3201
+ * * `lightness` and `hue` for HSL.
3202
+ *
3203
+ * @param {number} X the X component of the offset
3204
+ * @param {number} Y the Y component of the offset
3205
+ */
3206
+ changeControl1(X, Y) {
3207
+ const self = this;
3208
+ let [offsetX, offsetY] = [0, 0];
3209
+ const {
3210
+ format, controlPositions, visuals,
3211
+ } = self;
3212
+ const { offsetHeight, offsetWidth } = visuals[0];
3213
+
3214
+ if (X > offsetWidth) offsetX = offsetWidth;
3215
+ else if (X >= 0) offsetX = X;
3216
+
3217
+ if (Y > offsetHeight) offsetY = offsetHeight;
3218
+ else if (Y >= 0) offsetY = Y;
3219
+
3220
+ const hue = format === 'hsl'
3221
+ ? offsetX / offsetWidth
3222
+ : controlPositions.c2y / offsetHeight;
3223
+
3224
+ const saturation = format === 'hsl'
3225
+ ? 1 - controlPositions.c2y / offsetHeight
3226
+ : offsetX / offsetWidth;
3227
+
3228
+ const lightness = 1 - offsetY / offsetHeight;
3229
+ const alpha = 1 - controlPositions.c3y / offsetHeight;
3230
+
3231
+ const colorObject = format === 'hsl'
3232
+ ? {
3233
+ h: hue, s: saturation, l: lightness, a: alpha,
3234
+ }
3235
+ : {
3236
+ h: hue, s: saturation, v: lightness, a: alpha,
3237
+ };
3238
+
3239
+ // new color
3240
+ const {
3241
+ r, g, b, a,
3242
+ } = new Color(colorObject);
3243
+
3244
+ ObjectAssign(self.color, {
3245
+ r, g, b, a,
3246
+ });
3247
+
3248
+ // new positions
3249
+ self.controlPositions.c1x = offsetX;
3250
+ self.controlPositions.c1y = offsetY;
3251
+
3252
+ // update color picker
3253
+ self.updateAppearance();
3254
+ self.updateInputs();
3255
+ self.updateControls();
3256
+ self.updateVisuals();
3257
+ }
3258
+
3259
+ /**
3260
+ * Updates `ColorPicker` second control:
3261
+ * * `hue` for HEX/RGB/HWB;
3262
+ * * `saturation` for HSL.
3263
+ *
3264
+ * @param {number} Y the Y offset
3265
+ */
3266
+ changeControl2(Y) {
3267
+ const self = this;
3268
+ const {
3269
+ format, controlPositions, visuals,
3270
+ } = self;
3271
+ const { offsetHeight, offsetWidth } = visuals[0];
3272
+
3273
+ let offsetY = 0;
3274
+
3275
+ if (Y > offsetHeight) offsetY = offsetHeight;
3276
+ else if (Y >= 0) offsetY = Y;
3277
+
3278
+ const hue = format === 'hsl'
3279
+ ? controlPositions.c1x / offsetWidth
3280
+ : offsetY / offsetHeight;
3281
+ const saturation = format === 'hsl'
3282
+ ? 1 - offsetY / offsetHeight
3283
+ : controlPositions.c1x / offsetWidth;
3284
+ const lightness = 1 - controlPositions.c1y / offsetHeight;
3285
+ const alpha = 1 - controlPositions.c3y / offsetHeight;
3286
+ const colorObject = format === 'hsl'
3287
+ ? {
3288
+ h: hue, s: saturation, l: lightness, a: alpha,
3289
+ }
3290
+ : {
3291
+ h: hue, s: saturation, v: lightness, a: alpha,
3292
+ };
3293
+
3294
+ // new color
3295
+ const {
3296
+ r, g, b, a,
3297
+ } = new Color(colorObject);
3298
+
3299
+ ObjectAssign(self.color, {
3300
+ r, g, b, a,
3301
+ });
3302
+
3303
+ // new position
3304
+ self.controlPositions.c2y = offsetY;
3305
+ // update color picker
3306
+ self.updateAppearance();
3307
+ self.updateInputs();
3308
+ self.updateControls();
3309
+ self.updateVisuals();
3310
+ }
3311
+
3312
+ /**
3313
+ * Updates `ColorPicker` last control,
3314
+ * the `alpha` channel.
3315
+ *
3316
+ * @param {number} Y
3317
+ */
3318
+ changeAlpha(Y) {
3319
+ const self = this;
3320
+ const { visuals } = self;
3321
+ const { offsetHeight } = visuals[0];
3322
+ let offsetY = 0;
3323
+
3324
+ if (Y > offsetHeight) offsetY = offsetHeight;
3325
+ else if (Y >= 0) offsetY = Y;
3326
+
3327
+ // update color alpha
3328
+ const alpha = 1 - offsetY / offsetHeight;
3329
+ self.color.setAlpha(alpha);
3330
+ // update position
3331
+ self.controlPositions.c3y = offsetY;
3332
+ // update color picker
3333
+ self.updateAppearance();
3334
+ self.updateInputs();
3335
+ self.updateControls();
3336
+ self.updateVisuals();
3337
+ }
3338
+
3339
+ /**
3340
+ * Updates `ColorPicker` control positions on:
3341
+ * * initialization
3342
+ * * window resize
3343
+ */
3344
+ update() {
3345
+ const self = this;
3346
+ self.updateDropdownPosition();
3347
+ self.updateAppearance();
3348
+ self.setControlPositions();
3349
+ self.updateInputs(true);
3350
+ self.updateControls();
3351
+ self.updateVisuals();
3352
+ }
3353
+
3354
+ /** Updates the open dropdown position on *scroll* event. */
3355
+ updateDropdownPosition() {
3356
+ const self = this;
3357
+ const { input, colorPicker, colorMenu } = self;
3358
+ const elRect = getBoundingClientRect(input);
3359
+ const { top, bottom } = elRect;
3360
+ const { offsetHeight: elHeight } = input;
3361
+ const windowHeight = getDocumentElement(input).clientHeight;
3362
+ const isPicker = hasClass(colorPicker, 'show');
3363
+ const dropdown = isPicker ? colorPicker : colorMenu;
3364
+ if (!dropdown) return;
3365
+ const { offsetHeight: dropHeight } = dropdown;
3366
+ const distanceBottom = windowHeight - bottom;
3367
+ const distanceTop = top;
3368
+ const bottomExceed = top + dropHeight + elHeight > windowHeight; // show
3369
+ const topExceed = top - dropHeight < 0; // show-top
3370
+
3371
+ if ((hasClass(dropdown, 'bottom') || !topExceed) && distanceBottom < distanceTop && bottomExceed) {
3372
+ removeClass(dropdown, 'bottom');
3373
+ addClass(dropdown, 'top');
3374
+ } else {
3375
+ removeClass(dropdown, 'top');
3376
+ addClass(dropdown, 'bottom');
3377
+ }
3378
+ }
3379
+
3380
+ /** Updates control knobs' positions. */
3381
+ setControlPositions() {
3382
+ const self = this;
3383
+ const {
3384
+ format, visuals, color, hsl, hsv,
3385
+ } = self;
3386
+ const { offsetHeight, offsetWidth } = visuals[0];
3387
+ const alpha = color.a;
3388
+ const hue = hsl.h;
3389
+
3390
+ const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3391
+ const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3392
+
3393
+ self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3394
+ self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3395
+ self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3396
+ self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3397
+ }
3398
+
3399
+ /** Update the visual appearance label and control knob labels. */
3400
+ updateAppearance() {
3401
+ const self = this;
3402
+ const {
3403
+ componentLabels, colorLabels, color, parent,
3404
+ hsl, hsv, hex, format, controlKnobs,
3405
+ } = self;
3406
+ const {
3407
+ appearanceLabel, hexLabel, valueLabel,
3408
+ } = componentLabels;
3409
+ const { r, g, b } = color.toRgb();
3410
+ const [knob1, knob2, knob3] = controlKnobs;
3411
+ const hue = Math.round(hsl.h * 360);
3412
+ const alpha = color.a;
3413
+ const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3414
+ const saturation = Math.round(saturationSource * 100);
3415
+ const lightness = Math.round(hsl.l * 100);
3416
+ const hsvl = hsv.v * 100;
3417
+ let colorName;
3418
+
3419
+ // determine color appearance
3420
+ if (lightness === 100 && saturation === 0) {
3421
+ colorName = colorLabels.white;
3422
+ } else if (lightness === 0) {
3423
+ colorName = colorLabels.black;
3424
+ } else if (saturation === 0) {
3425
+ colorName = colorLabels.grey;
3426
+ } else if (hue < 15 || hue >= 345) {
3427
+ colorName = colorLabels.red;
3428
+ } else if (hue >= 15 && hue < 45) {
3429
+ colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3430
+ } else if (hue >= 45 && hue < 75) {
3431
+ const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3432
+ const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3433
+ colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3434
+ colorName = isOlive ? colorLabels.olive : colorName;
3435
+ } else if (hue >= 75 && hue < 155) {
3436
+ colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3437
+ } else if (hue >= 155 && hue < 175) {
3438
+ colorName = colorLabels.teal;
3439
+ } else if (hue >= 175 && hue < 195) {
3440
+ colorName = colorLabels.cyan;
3441
+ } else if (hue >= 195 && hue < 255) {
3442
+ colorName = colorLabels.blue;
3443
+ } else if (hue >= 255 && hue < 270) {
3444
+ colorName = colorLabels.violet;
3445
+ } else if (hue >= 270 && hue < 295) {
3446
+ colorName = colorLabels.magenta;
3447
+ } else if (hue >= 295 && hue < 345) {
3448
+ colorName = colorLabels.pink;
3449
+ }
3450
+
3451
+ let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3452
+
3453
+ if (format === 'hsl') {
3454
+ colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3455
+ setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3456
+ setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3457
+ setAttribute(knob1, ariaValueNow, `${hue}`);
3458
+ setAttribute(knob2, ariaValueText, `${saturation}%`);
3459
+ setAttribute(knob2, ariaValueNow, `${saturation}`);
3460
+ } else if (format === 'hwb') {
3461
+ const { hwb } = self;
3462
+ const whiteness = Math.round(hwb.w * 100);
3463
+ const blackness = Math.round(hwb.b * 100);
3464
+ colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3465
+ setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3466
+ setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3467
+ setAttribute(knob1, ariaValueNow, `${whiteness}`);
3468
+ setAttribute(knob2, ariaValueText, `${hue}%`);
3469
+ setAttribute(knob2, ariaValueNow, `${hue}`);
3470
+ } else {
3471
+ colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3472
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3473
+ setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3474
+ setAttribute(knob1, ariaValueNow, `${lightness}`);
3475
+ setAttribute(knob2, ariaValueText, `${hue}°`);
3476
+ setAttribute(knob2, ariaValueNow, `${hue}`);
3477
+ }
3478
+
3479
+ const alphaValue = Math.round(alpha * 100);
3480
+ setAttribute(knob3, ariaValueText, `${alphaValue}%`);
3481
+ setAttribute(knob3, ariaValueNow, `${alphaValue}`);
3482
+
3483
+ // update the input backgroundColor
3484
+ const newColor = color.toString();
3485
+ setElementStyle(self.input, { backgroundColor: newColor });
3486
+
3487
+ // toggle dark/light classes will also style the placeholder
3488
+ // dark sets color white, light sets color black
3489
+ // isDark ? '#000' : '#fff'
3490
+ if (!self.isDark) {
3491
+ if (hasClass(parent, 'txt-dark')) removeClass(parent, 'txt-dark');
3492
+ if (!hasClass(parent, 'txt-light')) addClass(parent, 'txt-light');
3493
+ } else {
3494
+ if (hasClass(parent, 'txt-light')) removeClass(parent, 'txt-light');
3495
+ if (!hasClass(parent, 'txt-dark')) addClass(parent, 'txt-dark');
3496
+ }
3497
+ }
3498
+
3499
+ /** Updates the control knobs actual positions. */
3500
+ updateControls() {
3501
+ const { controlKnobs, controlPositions } = this;
3502
+ const [control1, control2, control3] = controlKnobs;
3503
+ setElementStyle(control1, { transform: `translate3d(${controlPositions.c1x - 4}px,${controlPositions.c1y - 4}px,0)` });
3504
+ setElementStyle(control2, { transform: `translate3d(0,${controlPositions.c2y - 4}px,0)` });
3505
+ setElementStyle(control3, { transform: `translate3d(0,${controlPositions.c3y - 4}px,0)` });
3506
+ }
3507
+
3508
+ /**
3509
+ * Updates all color form inputs.
3510
+ * @param {boolean=} isPrevented when `true`, the component original event is prevented
3511
+ */
3512
+ updateInputs(isPrevented) {
3513
+ const self = this;
3514
+ const {
3515
+ value: oldColor, format, inputs, color, hsl,
3516
+ } = self;
3517
+ const [i1, i2, i3, i4] = inputs;
3518
+ const alpha = Math.round(color.a * 100);
3519
+ const hue = Math.round(hsl.h * 360);
3520
+ let newColor;
3521
+
3522
+ if (format === 'hex') {
3523
+ newColor = self.color.toHexString(true);
3524
+ i1.value = self.hex;
3525
+ } else if (format === 'hsl') {
3526
+ const lightness = Math.round(hsl.l * 100);
3527
+ const saturation = Math.round(hsl.s * 100);
3528
+ newColor = self.color.toHslString();
3529
+ i1.value = `${hue}`;
3530
+ i2.value = `${saturation}`;
3531
+ i3.value = `${lightness}`;
3532
+ i4.value = `${alpha}`;
3533
+ } else if (format === 'hwb') {
3534
+ const { w, b } = self.hwb;
3535
+ const whiteness = Math.round(w * 100);
3536
+ const blackness = Math.round(b * 100);
3537
+
3538
+ newColor = self.color.toHwbString();
3539
+ i1.value = `${hue}`;
3540
+ i2.value = `${whiteness}`;
3541
+ i3.value = `${blackness}`;
3542
+ i4.value = `${alpha}`;
3543
+ } else if (format === 'rgb') {
3544
+ const { r, g, b } = self.rgb;
3545
+
3546
+ newColor = self.color.toRgbString();
3547
+ i1.value = `${r}`;
3548
+ i2.value = `${g}`;
3549
+ i3.value = `${b}`;
3550
+ i4.value = `${alpha}`;
3551
+ }
3552
+
3553
+ // update the color value
3554
+ self.value = `${newColor}`;
3555
+
3556
+ // don't trigger the custom event unless it's really changed
3557
+ if (!isPrevented && newColor !== oldColor) {
3558
+ firePickerChange(self);
3559
+ }
3560
+ }
3561
+
3562
+ /**
3563
+ * The `Space` & `Enter` keys specific event listener.
3564
+ * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3565
+ * @param {KeyboardEvent} e
3566
+ * @this {ColorPicker}
3567
+ */
3568
+ keyToggle(e) {
3569
+ const self = this;
3570
+ const { menuToggle } = self;
3571
+ const { activeElement } = getDocument(menuToggle);
3572
+ const { code } = e;
3573
+
3574
+ if ([keyEnter, keySpace].includes(code)) {
3575
+ if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3576
+ e.preventDefault();
3577
+ if (!activeElement) {
3578
+ self.togglePicker(e);
3579
+ } else {
3580
+ self.toggleMenu();
3581
+ }
3582
+ }
3583
+ }
3584
+ }
3585
+
3586
+ /**
3587
+ * Toggle the `ColorPicker` dropdown visibility.
3588
+ * @param {Event} e
3589
+ * @this {ColorPicker}
3590
+ */
3591
+ togglePicker(e) {
3592
+ e.preventDefault();
3593
+ const self = this;
3594
+ const { colorPicker } = self;
3595
+
3596
+ if (self.isOpen && hasClass(colorPicker, 'show')) {
3597
+ self.hide(true);
3598
+ } else {
3599
+ showDropdown(self, colorPicker);
3600
+ }
3601
+ }
3602
+
3603
+ /** Shows the `ColorPicker` dropdown. */
3604
+ showPicker() {
3605
+ const self = this;
3606
+ const { colorPicker } = self;
3607
+
3608
+ if (!hasClass(colorPicker, 'show')) {
3609
+ showDropdown(self, colorPicker);
3610
+ }
3611
+ }
3612
+
3613
+ /** Toggles the visibility of the `ColorPicker` presets menu. */
3614
+ toggleMenu() {
3615
+ const self = this;
3616
+ const { colorMenu } = self;
3617
+
3618
+ if (self.isOpen && hasClass(colorMenu, 'show')) {
3619
+ self.hide(true);
3620
+ } else {
3621
+ showDropdown(self, colorMenu);
3622
+ }
3623
+ }
3624
+
3625
+ /** Shows the `ColorPicker` dropdown or the presets menu. */
3626
+ show() {
3627
+ const self = this;
3628
+ const { menuToggle } = self;
3629
+ if (!self.isOpen) {
3630
+ toggleEventsOnShown(self, true);
3631
+ self.updateDropdownPosition();
3632
+ self.isOpen = true;
3633
+ setAttribute(self.input, 'tabindex', '0');
3634
+ if (menuToggle) {
3635
+ setAttribute(menuToggle, 'tabindex', '0');
3636
+ }
3637
+ }
3638
+ }
3639
+
3640
+ /**
3641
+ * Hides the currently open `ColorPicker` dropdown.
3642
+ * @param {boolean=} focusPrevented
3643
+ */
3644
+ hide(focusPrevented) {
3645
+ const self = this;
3646
+ if (self.isOpen) {
3647
+ const {
3648
+ pickerToggle, menuToggle, colorPicker, colorMenu, parent, input,
3649
+ } = self;
3650
+ const openPicker = hasClass(colorPicker, 'show');
3651
+ const openDropdown = openPicker ? colorPicker : colorMenu;
3652
+ const relatedBtn = openPicker ? pickerToggle : menuToggle;
3653
+ const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3654
+
3655
+ if (openDropdown) {
3656
+ removeClass(openDropdown, 'show');
3657
+ setAttribute(relatedBtn, ariaExpanded, 'false');
3658
+ setTimeout(() => {
3659
+ removePosition(openDropdown);
3660
+ if (!querySelector('.show', parent)) {
3661
+ removeClass(parent, 'open');
3662
+ toggleEventsOnShown(self);
3663
+ self.isOpen = false;
3664
+ }
3665
+ }, animationDuration);
3666
+ }
3667
+
3668
+ if (!self.isValid) {
3669
+ self.value = self.color.toString();
3670
+ }
3671
+ if (!focusPrevented) {
3672
+ focus(pickerToggle);
3673
+ }
3674
+ setAttribute(input, 'tabindex', '-1');
3675
+ if (menuToggle) {
3676
+ setAttribute(menuToggle, 'tabindex', '-1');
3677
+ }
3678
+ }
3679
+ }
3680
+
3681
+ /** Removes `ColorPicker` from target `<input>`. */
3682
+ dispose() {
3683
+ const self = this;
3684
+ const { input, parent } = self;
3685
+ self.hide(true);
3686
+ toggleEvents(self);
3687
+ [...parent.children].forEach((el) => {
3688
+ if (el !== input) el.remove();
3689
+ });
3690
+ setElementStyle(input, { backgroundColor: '' });
3691
+ ['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
3692
+ Data.remove(input, colorPickerString);
3693
+ }
3694
+ }
3695
+
3696
+ ObjectAssign(ColorPicker, {
3697
+ Color,
3698
+ Version,
3699
+ getInstance: getColorPickerInstance,
3700
+ init: initColorPicker,
3701
+ selector: colorPickerSelector,
3702
+ });
3703
+
3704
+ export default ColorPicker;