@bornova/colorpicker 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,836 @@
1
+ /* @bornova/colorpicker v1.0.0 */
2
+ var ColorPicker = function() {
3
+ "use strict";
4
+ const settings = {
5
+ el: "[data-colorpicker]",
6
+ parent: "body",
7
+ theme: "default",
8
+ themeMode: "light",
9
+ rtl: false,
10
+ wrap: true,
11
+ margin: 2,
12
+ format: "hex",
13
+ formatToggle: false,
14
+ swatches: [],
15
+ swatchesOnly: false,
16
+ alpha: true,
17
+ forceAlpha: false,
18
+ focusInput: true,
19
+ selectInput: false,
20
+ inline: false,
21
+ defaultColor: "#000000",
22
+ clearButton: false,
23
+ clearLabel: "Clear",
24
+ closeButton: false,
25
+ closeLabel: "Close",
26
+ onChange: () => undefined,
27
+ a11y: {
28
+ open: "Open color picker",
29
+ close: "Close color picker",
30
+ clear: "Clear the selected color",
31
+ marker: "Saturation: {s}. Brightness: {v}.",
32
+ hueSlider: "Hue slider",
33
+ alphaSlider: "Opacity slider",
34
+ input: "Color value field",
35
+ format: "Color format",
36
+ swatch: "Color swatch",
37
+ instruction: "Saturation and brightness selector. Use up, down, left and right arrow keys to select."
38
+ }
39
+ };
40
+ const ctx = document.createElement("canvas").getContext("2d");
41
+ function HSVAtoRGBA(hsva) {
42
+ const saturation = hsva.s / 100;
43
+ const value = hsva.v / 100;
44
+ let chroma = saturation * value;
45
+ let hueBy60 = hsva.h / 60;
46
+ let x = chroma * (1 - Math.abs(hueBy60 % 2 - 1));
47
+ let m = value - chroma;
48
+ chroma += m;
49
+ x += m;
50
+ const index = Math.floor(hueBy60) % 6;
51
+ const red = [ chroma, x, m, m, x, chroma ][index];
52
+ const green = [ x, chroma, chroma, x, m, m ][index];
53
+ const blue = [ m, m, x, chroma, chroma, x ][index];
54
+ return {
55
+ r: Math.round(red * 255),
56
+ g: Math.round(green * 255),
57
+ b: Math.round(blue * 255),
58
+ a: hsva.a
59
+ };
60
+ }
61
+ function HSVAtoHSLA(hsva) {
62
+ const value = hsva.v / 100;
63
+ const lightness = value * (1 - hsva.s / 100 / 2);
64
+ let saturation;
65
+ if (lightness > 0 && lightness < 1) {
66
+ saturation = Math.round((value - lightness) / Math.min(lightness, 1 - lightness) * 100);
67
+ }
68
+ return {
69
+ h: hsva.h,
70
+ s: saturation || 0,
71
+ l: Math.round(lightness * 100),
72
+ a: hsva.a
73
+ };
74
+ }
75
+ function RGBAtoHSVA(rgba) {
76
+ const red = rgba.r / 255;
77
+ const green = rgba.g / 255;
78
+ const blue = rgba.b / 255;
79
+ const xmax = Math.max(red, green, blue);
80
+ const xmin = Math.min(red, green, blue);
81
+ const chroma = xmax - xmin;
82
+ const value = xmax;
83
+ let hue = 0;
84
+ let saturation = 0;
85
+ if (chroma) {
86
+ saturation = chroma / xmax;
87
+ if (xmax === red) {
88
+ hue = (green - blue) / chroma;
89
+ } else if (xmax === green) {
90
+ hue = 2 + (blue - red) / chroma;
91
+ } else {
92
+ hue = 4 + (red - green) / chroma;
93
+ }
94
+ }
95
+ hue = Math.floor(hue * 60);
96
+ return {
97
+ h: hue < 0 ? hue + 360 : hue,
98
+ s: Math.round(saturation * 100),
99
+ v: Math.round(value * 100),
100
+ a: rgba.a
101
+ };
102
+ }
103
+ function strToRGBA(str) {
104
+ const regex = /^((rgba)|rgb)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i;
105
+ let match, rgba;
106
+ ctx.fillStyle = "#000";
107
+ ctx.fillStyle = str;
108
+ match = regex.exec(ctx.fillStyle);
109
+ if (match) {
110
+ rgba = {
111
+ r: Number(match[3]),
112
+ g: Number(match[4]),
113
+ b: Number(match[5]),
114
+ a: Number(match[6])
115
+ };
116
+ } else {
117
+ match = ctx.fillStyle.replace("#", "").match(/.{2}/g).map(h => parseInt(h, 16));
118
+ rgba = {
119
+ r: match[0],
120
+ g: match[1],
121
+ b: match[2],
122
+ a: 1
123
+ };
124
+ }
125
+ return rgba;
126
+ }
127
+ function RGBAToHex(rgba) {
128
+ const R = rgba.r.toString(16).padStart(2, "0");
129
+ const G = rgba.g.toString(16).padStart(2, "0");
130
+ const B = rgba.b.toString(16).padStart(2, "0");
131
+ let A = "";
132
+ if (settings.alpha && (rgba.a < 1 || settings.forceAlpha)) {
133
+ A = (rgba.a * 255 | 0).toString(16).padStart(2, "0");
134
+ }
135
+ return "#" + R + G + B + A;
136
+ }
137
+ function RGBAToStr(rgba) {
138
+ return !settings.alpha || rgba.a === 1 && !settings.forceAlpha ? `rgb(${rgba.r}, ${rgba.g}, ${rgba.b})` : `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`;
139
+ }
140
+ function HSLAToStr(hsla) {
141
+ return !settings.alpha || hsla.a === 1 && !settings.forceAlpha ? `hsl(${hsla.h}, ${hsla.s}%, ${hsla.l}%)` : `hsla(${hsla.h}, ${hsla.s}%, ${hsla.l}%, ${hsla.a})`;
142
+ }
143
+ function getEl(id) {
144
+ return document.getElementById(id);
145
+ }
146
+ function addListener(context, type, selector, fn) {
147
+ if (typeof selector === "string") {
148
+ context.addEventListener(type, event => {
149
+ if (event.target.matches(selector)) {
150
+ fn.call(event.target, event);
151
+ }
152
+ });
153
+ } else {
154
+ context.addEventListener(type, selector);
155
+ }
156
+ }
157
+ function DOMReady(fn, args = []) {
158
+ if (document.readyState !== "loading") {
159
+ fn(...args);
160
+ } else {
161
+ document.addEventListener("DOMContentLoaded", () => {
162
+ fn(...args);
163
+ });
164
+ }
165
+ }
166
+ const currentColor = {
167
+ r: 0,
168
+ g: 0,
169
+ b: 0,
170
+ h: 0,
171
+ s: 0,
172
+ v: 0,
173
+ a: 1
174
+ };
175
+ let container, picker, colorArea, colorMarker, colorPreview, colorValue, clearButton, closeButton, hueSlider, hueMarker, alphaSlider, alphaMarker, currentEl, currentFormat, oldColor, keyboardNav, colorAreaDims = {};
176
+ const instances = {};
177
+ let currentInstanceId = "";
178
+ let defaultInstance = {};
179
+ let hasInstance = false;
180
+ const MARKER_MOVEMENTS = {
181
+ ArrowUp: [ 0, -1 ],
182
+ ArrowDown: [ 0, 1 ],
183
+ ArrowLeft: [ -1, 0 ],
184
+ ArrowRight: [ 1, 0 ]
185
+ };
186
+ function configure(options) {
187
+ if (typeof options !== "object") {
188
+ return;
189
+ }
190
+ for (const key of Object.keys(options)) {
191
+ switch (key) {
192
+ case "el":
193
+ bindFields(options.el);
194
+ if (options.wrap !== false) {
195
+ wrapFields(options.el);
196
+ }
197
+ break;
198
+
199
+ case "parent":
200
+ container = options.parent instanceof HTMLElement ? options.parent : document.querySelector(options.parent);
201
+ if (container) {
202
+ container.appendChild(picker);
203
+ settings.parent = options.parent;
204
+ if (container === document.body) {
205
+ container = undefined;
206
+ }
207
+ }
208
+ break;
209
+
210
+ case "themeMode":
211
+ settings.themeMode = options.themeMode;
212
+ if (options.themeMode === "auto" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
213
+ settings.themeMode = "dark";
214
+ }
215
+
216
+ case "theme":
217
+ if (options.theme) {
218
+ settings.theme = options.theme;
219
+ }
220
+ picker.className = `clr-picker clr-${settings.theme} clr-${settings.themeMode}`;
221
+ if (settings.inline) {
222
+ updatePickerPosition();
223
+ }
224
+ break;
225
+
226
+ case "rtl":
227
+ settings.rtl = !!options.rtl;
228
+ for (const field of document.getElementsByClassName("clr-field")) {
229
+ field.classList.toggle("clr-rtl", settings.rtl);
230
+ }
231
+ break;
232
+
233
+ case "margin":
234
+ {
235
+ const margin = Number(options.margin);
236
+ settings.margin = !isNaN(margin) ? margin : settings.margin;
237
+ break;
238
+ }
239
+
240
+ case "wrap":
241
+ if (options.el && options.wrap) {
242
+ wrapFields(options.el);
243
+ }
244
+ break;
245
+
246
+ case "formatToggle":
247
+ settings.formatToggle = !!options.formatToggle;
248
+ getEl("clr-format").style.display = settings.formatToggle ? "block" : "none";
249
+ if (settings.formatToggle) {
250
+ settings.format = "auto";
251
+ }
252
+ break;
253
+
254
+ case "swatches":
255
+ if (Array.isArray(options.swatches)) {
256
+ const swatchesContainer = getEl("clr-swatches");
257
+ swatchesContainer.textContent = "";
258
+ if (options.swatches.length) {
259
+ const swatches = document.createElement("div");
260
+ options.swatches.forEach((swatch, i) => {
261
+ const button = document.createElement("button");
262
+ button.setAttribute("type", "button");
263
+ button.setAttribute("id", `clr-swatch-${i}`);
264
+ button.setAttribute("aria-labelledby", `clr-swatch-label clr-swatch-${i}`);
265
+ button.style.color = swatch;
266
+ button.textContent = swatch;
267
+ swatches.appendChild(button);
268
+ });
269
+ swatchesContainer.appendChild(swatches);
270
+ }
271
+ settings.swatches = [ ...options.swatches ];
272
+ }
273
+ break;
274
+
275
+ case "swatchesOnly":
276
+ settings.swatchesOnly = !!options.swatchesOnly;
277
+ picker.setAttribute("data-minimal", settings.swatchesOnly);
278
+ break;
279
+
280
+ case "alpha":
281
+ settings.alpha = !!options.alpha;
282
+ picker.setAttribute("data-alpha", settings.alpha);
283
+ break;
284
+
285
+ case "inline":
286
+ settings.inline = !!options.inline;
287
+ picker.setAttribute("data-inline", settings.inline);
288
+ if (settings.inline) {
289
+ const defaultColor = options.defaultColor || settings.defaultColor;
290
+ currentFormat = getColorFormatFromStr(defaultColor);
291
+ updatePickerPosition();
292
+ setColorFromStr(defaultColor);
293
+ }
294
+ break;
295
+
296
+ case "clearButton":
297
+ {
298
+ if (typeof options.clearButton === "object" && options.clearButton.label) {
299
+ settings.clearLabel = options.clearButton.label;
300
+ clearButton.innerHTML = settings.clearLabel;
301
+ }
302
+ const show = typeof options.clearButton === "object" ? options.clearButton.show : options.clearButton;
303
+ settings.clearButton = !!show;
304
+ clearButton.style.display = settings.clearButton ? "block" : "none";
305
+ break;
306
+ }
307
+
308
+ case "clearLabel":
309
+ settings.clearLabel = options.clearLabel;
310
+ clearButton.innerHTML = settings.clearLabel;
311
+ break;
312
+
313
+ case "closeButton":
314
+ settings.closeButton = !!options.closeButton;
315
+ if (settings.closeButton) {
316
+ picker.insertBefore(closeButton, colorPreview);
317
+ } else {
318
+ colorPreview.appendChild(closeButton);
319
+ }
320
+ break;
321
+
322
+ case "closeLabel":
323
+ settings.closeLabel = options.closeLabel;
324
+ closeButton.innerHTML = settings.closeLabel;
325
+ break;
326
+
327
+ case "a11y":
328
+ {
329
+ const labels = options.a11y;
330
+ let update = false;
331
+ if (typeof labels === "object") {
332
+ for (const label of Object.keys(labels)) {
333
+ if (labels[label] && settings.a11y[label]) {
334
+ settings.a11y[label] = labels[label];
335
+ update = true;
336
+ }
337
+ }
338
+ }
339
+ if (update) {
340
+ const openLabel = getEl("clr-open-label");
341
+ const swatchLabel = getEl("clr-swatch-label");
342
+ openLabel.innerHTML = settings.a11y.open;
343
+ swatchLabel.innerHTML = settings.a11y.swatch;
344
+ closeButton.setAttribute("aria-label", settings.a11y.close);
345
+ clearButton.setAttribute("aria-label", settings.a11y.clear);
346
+ hueSlider.setAttribute("aria-label", settings.a11y.hueSlider);
347
+ alphaSlider.setAttribute("aria-label", settings.a11y.alphaSlider);
348
+ colorValue.setAttribute("aria-label", settings.a11y.input);
349
+ colorArea.setAttribute("aria-label", settings.a11y.instruction);
350
+ }
351
+ break;
352
+ }
353
+
354
+ default:
355
+ settings[key] = options[key];
356
+ }
357
+ }
358
+ }
359
+ function setVirtualInstance(selector, options) {
360
+ if (typeof selector === "string" && typeof options === "object") {
361
+ instances[selector] = options;
362
+ hasInstance = true;
363
+ }
364
+ }
365
+ function removeVirtualInstance(selector) {
366
+ delete instances[selector];
367
+ if (Object.keys(instances).length === 0) {
368
+ hasInstance = false;
369
+ if (selector === currentInstanceId) {
370
+ resetVirtualInstance();
371
+ }
372
+ }
373
+ }
374
+ function attachVirtualInstance(element) {
375
+ if (hasInstance) {
376
+ const unsupportedOptions = [ "el", "wrap", "rtl", "inline", "defaultColor", "a11y" ];
377
+ for (const selector of Object.keys(instances)) {
378
+ const options = instances[selector];
379
+ if (element.matches(selector)) {
380
+ currentInstanceId = selector;
381
+ defaultInstance = {};
382
+ unsupportedOptions.forEach(option => delete options[option]);
383
+ for (const option of Object.keys(options)) {
384
+ defaultInstance[option] = Array.isArray(settings[option]) ? [ ...settings[option] ] : settings[option];
385
+ }
386
+ configure(options);
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ }
392
+ function resetVirtualInstance() {
393
+ if (Object.keys(defaultInstance).length > 0) {
394
+ configure(defaultInstance);
395
+ currentInstanceId = "";
396
+ defaultInstance = {};
397
+ }
398
+ }
399
+ function bindFields(selector) {
400
+ const fields = selector instanceof HTMLElement ? [ selector ] : selector;
401
+ if (Array.isArray(fields)) {
402
+ fields.forEach(field => {
403
+ addListener(field, "click", openPicker);
404
+ addListener(field, "input", updateColorPreview);
405
+ });
406
+ } else {
407
+ addListener(document, "click", fields, openPicker);
408
+ addListener(document, "input", fields, updateColorPreview);
409
+ }
410
+ }
411
+ function openPicker(event) {
412
+ if (settings.inline) {
413
+ return;
414
+ }
415
+ attachVirtualInstance(event.target);
416
+ currentEl = event.target;
417
+ oldColor = currentEl.value;
418
+ currentFormat = getColorFormatFromStr(oldColor);
419
+ picker.classList.add("clr-open");
420
+ updatePickerPosition();
421
+ setColorFromStr(oldColor);
422
+ if (settings.focusInput || settings.selectInput) {
423
+ colorValue.focus({
424
+ preventScroll: true
425
+ });
426
+ colorValue.setSelectionRange(currentEl.selectionStart, currentEl.selectionEnd);
427
+ }
428
+ if (settings.selectInput) {
429
+ colorValue.select();
430
+ }
431
+ if (keyboardNav || settings.swatchesOnly) {
432
+ getFocusableElements().shift().focus();
433
+ }
434
+ currentEl.dispatchEvent(new Event("open", {
435
+ bubbles: false
436
+ }));
437
+ }
438
+ function updatePickerPosition() {
439
+ const parent = container;
440
+ const scrollY = window.scrollY;
441
+ const pickerWidth = picker.offsetWidth;
442
+ const pickerHeight = picker.offsetHeight;
443
+ const reposition = {
444
+ left: false,
445
+ top: false
446
+ };
447
+ let parentStyle, parentMarginTop, parentBorderTop;
448
+ let offset = {
449
+ x: 0,
450
+ y: 0
451
+ };
452
+ if (parent) {
453
+ parentStyle = window.getComputedStyle(parent);
454
+ parentMarginTop = parseFloat(parentStyle.marginTop);
455
+ parentBorderTop = parseFloat(parentStyle.borderTopWidth);
456
+ offset = parent.getBoundingClientRect();
457
+ offset.y += parentBorderTop + scrollY;
458
+ }
459
+ if (!settings.inline) {
460
+ const coords = currentEl.getBoundingClientRect();
461
+ let left = coords.x;
462
+ let top = scrollY + coords.y + coords.height + settings.margin;
463
+ if (parent) {
464
+ left -= offset.x;
465
+ top -= offset.y;
466
+ if (left + pickerWidth > parent.clientWidth) {
467
+ left += coords.width - pickerWidth;
468
+ reposition.left = true;
469
+ }
470
+ if (top + pickerHeight > parent.clientHeight - parentMarginTop) {
471
+ if (pickerHeight + settings.margin <= coords.top - (offset.y - scrollY)) {
472
+ top -= coords.height + pickerHeight + settings.margin * 2;
473
+ reposition.top = true;
474
+ }
475
+ }
476
+ top += parent.scrollTop;
477
+ } else {
478
+ if (left + pickerWidth > document.documentElement.clientWidth) {
479
+ left += coords.width - pickerWidth;
480
+ reposition.left = true;
481
+ }
482
+ if (top + pickerHeight - scrollY > document.documentElement.clientHeight) {
483
+ if (pickerHeight + settings.margin <= coords.top) {
484
+ top = scrollY + coords.y - pickerHeight - settings.margin;
485
+ reposition.top = true;
486
+ }
487
+ }
488
+ }
489
+ picker.classList.toggle("clr-left", reposition.left);
490
+ picker.classList.toggle("clr-top", reposition.top);
491
+ picker.style.left = `${left}px`;
492
+ picker.style.top = `${top}px`;
493
+ offset.x += picker.offsetLeft;
494
+ offset.y += picker.offsetTop;
495
+ }
496
+ colorAreaDims = {
497
+ width: colorArea.offsetWidth,
498
+ height: colorArea.offsetHeight,
499
+ x: colorArea.offsetLeft + offset.x,
500
+ y: colorArea.offsetTop + offset.y
501
+ };
502
+ }
503
+ function wrapFields(selector) {
504
+ const fields = selector instanceof HTMLElement ? [ selector ] : Array.isArray(selector) ? selector : [ ...document.querySelectorAll(selector) ];
505
+ fields.forEach(wrapColorField);
506
+ }
507
+ function wrapColorField(field) {
508
+ const parentNode = field.parentNode;
509
+ if (!parentNode.classList.contains("clr-field")) {
510
+ const wrapper = document.createElement("div");
511
+ let classes = "clr-field";
512
+ if (settings.rtl || field.classList.contains("clr-rtl")) {
513
+ classes += " clr-rtl";
514
+ }
515
+ wrapper.innerHTML = '<button type="button" aria-labelledby="clr-open-label"></button>';
516
+ parentNode.insertBefore(wrapper, field);
517
+ wrapper.className = classes;
518
+ wrapper.style.color = field.value;
519
+ wrapper.appendChild(field);
520
+ }
521
+ }
522
+ function updateColorPreview(event) {
523
+ const parent = event.target.parentNode;
524
+ if (parent.classList.contains("clr-field")) {
525
+ parent.style.color = event.target.value;
526
+ }
527
+ }
528
+ function closePicker(revert) {
529
+ if (currentEl && !settings.inline) {
530
+ const prevEl = currentEl;
531
+ if (revert) {
532
+ currentEl = undefined;
533
+ if (oldColor !== prevEl.value) {
534
+ prevEl.value = oldColor;
535
+ prevEl.dispatchEvent(new Event("input", {
536
+ bubbles: true
537
+ }));
538
+ }
539
+ }
540
+ setTimeout(() => {
541
+ if (oldColor !== prevEl.value) {
542
+ prevEl.dispatchEvent(new Event("change", {
543
+ bubbles: true
544
+ }));
545
+ }
546
+ });
547
+ picker.classList.remove("clr-open");
548
+ if (hasInstance) {
549
+ resetVirtualInstance();
550
+ }
551
+ prevEl.dispatchEvent(new Event("close", {
552
+ bubbles: false
553
+ }));
554
+ if (settings.focusInput) {
555
+ prevEl.focus({
556
+ preventScroll: true
557
+ });
558
+ }
559
+ currentEl = undefined;
560
+ }
561
+ }
562
+ function setColorFromStr(str) {
563
+ const rgba = strToRGBA(str);
564
+ const hsva = RGBAtoHSVA(rgba);
565
+ updateMarkerA11yLabel(hsva.s, hsva.v);
566
+ updateColor(rgba, hsva);
567
+ hueSlider.value = hsva.h;
568
+ picker.style.color = `hsl(${hsva.h}, 100%, 50%)`;
569
+ hueMarker.style.left = `${hsva.h / 360 * 100}%`;
570
+ colorMarker.style.left = `${colorAreaDims.width * hsva.s / 100}px`;
571
+ colorMarker.style.top = `${colorAreaDims.height - colorAreaDims.height * hsva.v / 100}px`;
572
+ alphaSlider.value = hsva.a * 100;
573
+ alphaMarker.style.left = `${hsva.a * 100}%`;
574
+ }
575
+ function getColorFormatFromStr(str) {
576
+ const fmt = str.slice(0, 3).toLowerCase();
577
+ return fmt === "rgb" || fmt === "hsl" ? fmt : "hex";
578
+ }
579
+ function pickColor(color) {
580
+ color = color ?? colorValue.value;
581
+ if (currentEl) {
582
+ currentEl.value = color;
583
+ currentEl.dispatchEvent(new Event("input", {
584
+ bubbles: true
585
+ }));
586
+ }
587
+ if (settings.onChange) {
588
+ settings.onChange.call(window, color, currentEl);
589
+ }
590
+ document.dispatchEvent(new CustomEvent("colorpicker:pick", {
591
+ detail: {
592
+ color: color,
593
+ currentEl: currentEl
594
+ }
595
+ }));
596
+ }
597
+ function setColorAtPosition(x, y) {
598
+ const hsva = {
599
+ h: Number(hueSlider.value),
600
+ s: x / colorAreaDims.width * 100,
601
+ v: 100 - y / colorAreaDims.height * 100,
602
+ a: alphaSlider.value / 100
603
+ };
604
+ const rgba = HSVAtoRGBA(hsva);
605
+ updateMarkerA11yLabel(hsva.s, hsva.v);
606
+ updateColor(rgba, hsva);
607
+ pickColor();
608
+ }
609
+ function updateMarkerA11yLabel(saturation, value) {
610
+ colorMarker.setAttribute("aria-label", settings.a11y.marker.replace("{s}", Number(saturation.toFixed(1))).replace("{v}", Number(value.toFixed(1))));
611
+ }
612
+ function moveMarker(event) {
613
+ const touch = event.changedTouches?.[0];
614
+ let x = (touch?.pageX ?? event.pageX) - colorAreaDims.x;
615
+ let y = (touch?.pageY ?? event.pageY) - colorAreaDims.y;
616
+ if (container) {
617
+ y += container.scrollTop;
618
+ }
619
+ setMarkerPosition(x, y);
620
+ event.preventDefault();
621
+ event.stopPropagation();
622
+ }
623
+ function moveMarkerOnKeydown(offsetX, offsetY) {
624
+ const x = parseFloat(colorMarker.style.left) + offsetX;
625
+ const y = parseFloat(colorMarker.style.top) + offsetY;
626
+ setMarkerPosition(x, y);
627
+ }
628
+ function setMarkerPosition(x, y) {
629
+ x = Math.max(0, Math.min(x, colorAreaDims.width));
630
+ y = Math.max(0, Math.min(y, colorAreaDims.height));
631
+ colorMarker.style.left = `${x}px`;
632
+ colorMarker.style.top = `${y}px`;
633
+ setColorAtPosition(x, y);
634
+ colorMarker.focus();
635
+ }
636
+ function updateColor(rgba = {}, hsva = {}) {
637
+ let format = settings.format;
638
+ Object.assign(currentColor, rgba, hsva);
639
+ const hex = RGBAToHex(currentColor);
640
+ const opaqueHex = hex.substring(0, 7);
641
+ colorMarker.style.color = opaqueHex;
642
+ alphaMarker.parentNode.style.color = opaqueHex;
643
+ alphaMarker.style.color = hex;
644
+ colorPreview.style.color = hex;
645
+ colorArea.style.display = "none";
646
+ colorArea.offsetHeight;
647
+ colorArea.style.display = "";
648
+ alphaMarker.nextElementSibling.style.display = "none";
649
+ alphaMarker.nextElementSibling.offsetHeight;
650
+ alphaMarker.nextElementSibling.style.display = "";
651
+ if (format === "mixed") {
652
+ format = currentColor.a === 1 ? "hex" : "rgb";
653
+ } else if (format === "auto") {
654
+ format = currentFormat;
655
+ }
656
+ switch (format) {
657
+ case "hex":
658
+ colorValue.value = hex;
659
+ break;
660
+
661
+ case "rgb":
662
+ colorValue.value = RGBAToStr(currentColor);
663
+ break;
664
+
665
+ case "hsl":
666
+ colorValue.value = HSLAToStr(HSVAtoHSLA(currentColor));
667
+ break;
668
+ }
669
+ document.querySelector(`.clr-format [value="${format}"]`).checked = true;
670
+ }
671
+ function setHue() {
672
+ const hue = Number(hueSlider.value);
673
+ const x = parseFloat(colorMarker.style.left);
674
+ const y = parseFloat(colorMarker.style.top);
675
+ picker.style.color = `hsl(${hue}, 100%, 50%)`;
676
+ hueMarker.style.left = `${hue / 360 * 100}%`;
677
+ setColorAtPosition(x, y);
678
+ }
679
+ function setAlpha() {
680
+ const alpha = alphaSlider.value / 100;
681
+ alphaMarker.style.left = `${alpha * 100}%`;
682
+ updateColor({
683
+ a: alpha
684
+ });
685
+ pickColor();
686
+ }
687
+ function getFocusableElements() {
688
+ return [ ...picker.querySelectorAll("input, button") ].filter(node => !!node.offsetWidth);
689
+ }
690
+ function init() {
691
+ container = undefined;
692
+ picker = document.createElement("div");
693
+ picker.setAttribute("id", "clr-picker");
694
+ picker.className = "clr-picker";
695
+ picker.innerHTML = `\n <input id="clr-color-value" name="clr-color-value" class="clr-color" type="text" value="" spellcheck="false" aria-label="${settings.a11y.input}">\n <div id="clr-color-area" class="clr-gradient" role="application" aria-label="${settings.a11y.instruction}">\n <div id="clr-color-marker" class="clr-marker" tabindex="0"></div>\n </div>\n <div class="clr-hue">\n <input id="clr-hue-slider" name="clr-hue-slider" type="range" min="0" max="360" step="1" aria-label="${settings.a11y.hueSlider}">\n <div id="clr-hue-marker"></div>\n </div>\n <div class="clr-alpha">\n <input id="clr-alpha-slider" name="clr-alpha-slider" type="range" min="0" max="100" step="1" aria-label="${settings.a11y.alphaSlider}">\n <div id="clr-alpha-marker"></div>\n <span></span>\n </div>\n <div id="clr-format" class="clr-format">\n <fieldset class="clr-segmented">\n <legend>${settings.a11y.format}</legend>\n <input id="clr-f1" type="radio" name="clr-format" value="hex">\n <label for="clr-f1">Hex</label>\n <input id="clr-f2" type="radio" name="clr-format" value="rgb">\n <label for="clr-f2">RGB</label>\n <input id="clr-f3" type="radio" name="clr-format" value="hsl">\n <label for="clr-f3">HSL</label>\n <span></span>\n </fieldset>\n </div>\n <div id="clr-swatches" class="clr-swatches"></div>\n <button type="button" id="clr-clear" class="clr-clear" aria-label="${settings.a11y.clear}">${settings.clearLabel}</button>\n <div id="clr-color-preview" class="clr-preview">\n <button type="button" id="clr-close" class="clr-close" aria-label="${settings.a11y.close}">${settings.closeLabel}</button>\n </div>\n <span id="clr-open-label" hidden>${settings.a11y.open}</span>\n <span id="clr-swatch-label" hidden>${settings.a11y.swatch}</span>\n `;
696
+ document.body.appendChild(picker);
697
+ colorArea = getEl("clr-color-area");
698
+ colorMarker = getEl("clr-color-marker");
699
+ clearButton = getEl("clr-clear");
700
+ closeButton = getEl("clr-close");
701
+ colorPreview = getEl("clr-color-preview");
702
+ colorValue = getEl("clr-color-value");
703
+ hueSlider = getEl("clr-hue-slider");
704
+ hueMarker = getEl("clr-hue-marker");
705
+ alphaSlider = getEl("clr-alpha-slider");
706
+ alphaMarker = getEl("clr-alpha-marker");
707
+ bindFields(settings.el);
708
+ wrapFields(settings.el);
709
+ addListener(picker, "mousedown", event => {
710
+ picker.classList.remove("clr-keyboard-nav");
711
+ event.stopPropagation();
712
+ });
713
+ addListener(colorArea, "mousedown", () => {
714
+ addListener(document, "mousemove", moveMarker);
715
+ });
716
+ addListener(colorArea, "contextmenu", event => {
717
+ event.preventDefault();
718
+ });
719
+ addListener(colorArea, "touchstart", () => {
720
+ document.addEventListener("touchmove", moveMarker, {
721
+ passive: false
722
+ });
723
+ });
724
+ addListener(colorMarker, "mousedown", () => {
725
+ addListener(document, "mousemove", moveMarker);
726
+ });
727
+ addListener(colorMarker, "touchstart", () => {
728
+ document.addEventListener("touchmove", moveMarker, {
729
+ passive: false
730
+ });
731
+ });
732
+ addListener(colorValue, "change", () => {
733
+ const value = colorValue.value;
734
+ if (currentEl || settings.inline) {
735
+ const color = value === "" ? value : setColorFromStr(value);
736
+ pickColor(color);
737
+ }
738
+ });
739
+ addListener(clearButton, "click", () => {
740
+ pickColor("");
741
+ closePicker();
742
+ });
743
+ addListener(closeButton, "click", () => {
744
+ pickColor();
745
+ closePicker();
746
+ });
747
+ addListener(getEl("clr-format"), "click", ".clr-format input", event => {
748
+ currentFormat = event.target.value;
749
+ updateColor();
750
+ pickColor();
751
+ });
752
+ addListener(picker, "click", ".clr-swatches button", event => {
753
+ setColorFromStr(event.target.textContent);
754
+ pickColor();
755
+ if (settings.swatchesOnly) {
756
+ closePicker();
757
+ }
758
+ });
759
+ addListener(document, "mouseup", () => {
760
+ document.removeEventListener("mousemove", moveMarker);
761
+ });
762
+ addListener(document, "touchend", () => {
763
+ document.removeEventListener("touchmove", moveMarker);
764
+ });
765
+ addListener(document, "mousedown", () => {
766
+ keyboardNav = false;
767
+ picker.classList.remove("clr-keyboard-nav");
768
+ closePicker();
769
+ });
770
+ addListener(document, "keydown", event => {
771
+ const key = event.key;
772
+ const target = event.target;
773
+ const shiftKey = event.shiftKey;
774
+ const navKeys = [ "Tab", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight" ];
775
+ if (key === "Escape") {
776
+ closePicker(true);
777
+ return;
778
+ } else if (key === "Enter" && target.tagName !== "BUTTON") {
779
+ closePicker();
780
+ return;
781
+ } else if (navKeys.includes(key)) {
782
+ keyboardNav = true;
783
+ picker.classList.add("clr-keyboard-nav");
784
+ }
785
+ if (key === "Tab" && target.matches(".clr-picker *")) {
786
+ const focusables = getFocusableElements();
787
+ const firstFocusable = focusables.shift();
788
+ const lastFocusable = focusables.pop();
789
+ if (shiftKey && target === firstFocusable) {
790
+ lastFocusable.focus();
791
+ event.preventDefault();
792
+ } else if (!shiftKey && target === lastFocusable) {
793
+ firstFocusable.focus();
794
+ event.preventDefault();
795
+ }
796
+ }
797
+ });
798
+ addListener(document, "click", ".clr-field button", event => {
799
+ if (hasInstance) {
800
+ resetVirtualInstance();
801
+ }
802
+ event.target.nextElementSibling.dispatchEvent(new Event("click", {
803
+ bubbles: true
804
+ }));
805
+ });
806
+ addListener(colorMarker, "keydown", event => {
807
+ if (event.key in MARKER_MOVEMENTS) {
808
+ moveMarkerOnKeydown(...MARKER_MOVEMENTS[event.key]);
809
+ event.preventDefault();
810
+ }
811
+ });
812
+ addListener(colorArea, "click", moveMarker);
813
+ addListener(hueSlider, "input", setHue);
814
+ addListener(alphaSlider, "input", setAlpha);
815
+ }
816
+ function ColorPicker(options) {
817
+ DOMReady(() => {
818
+ if (options) {
819
+ if (typeof options === "string") {
820
+ bindFields(options);
821
+ } else {
822
+ configure(options);
823
+ }
824
+ }
825
+ });
826
+ }
827
+ ColorPicker.set = (...args) => DOMReady(configure, args);
828
+ ColorPicker.wrap = (...args) => DOMReady(wrapFields, args);
829
+ ColorPicker.close = (...args) => DOMReady(closePicker, args);
830
+ ColorPicker.setInstance = (...args) => DOMReady(setVirtualInstance, args);
831
+ ColorPicker.removeInstance = (...args) => DOMReady(removeVirtualInstance, args);
832
+ ColorPicker.updatePosition = (...args) => DOMReady(updatePickerPosition, args);
833
+ ColorPicker.ready = DOMReady;
834
+ DOMReady(init);
835
+ return ColorPicker;
836
+ }();