@thednp/color-picker 1.0.0 → 2.0.0-alpha1

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