'+C.a11y.open+''+C.a11y.swatch+"",p.body.appendChild(f),h=V("clr-color-area"),i=V("clr-color-marker"),v=V("clr-clear"),m=V("clr-close"),b=V("clr-color-preview"),y=V("clr-color-value"),g=V("clr-hue-slider"),l=V("clr-hue-marker"),w=V("clr-alpha-slider"),k=V("clr-alpha-marker"),D(C.el),R(C.el),Z(f,"mousedown",function(e){f.classList.remove("clr-keyboard-nav"),e.stopPropagation()}),Z(h,"mousedown",function(e){Z(p,"mousemove",K)}),Z(h,"contextmenu",function(e){e.preventDefault()}),Z(h,"touchstart",function(e){p.addEventListener("touchmove",K,{passive:!1})}),Z(i,"mousedown",function(e){Z(p,"mousemove",K)}),Z(i,"touchstart",function(e){p.addEventListener("touchmove",K,{passive:!1})}),Z(y,"change",function(e){var t=y.value;(L||C.inline)&&U(""===t?t:Y(t))}),Z(v,"click",function(e){U(""),F()}),Z(m,"click",function(e){U(),F()}),Z(V("clr-format"),"click",".clr-format input",function(e){E=e.target.value,z(),U()}),Z(f,"click",".clr-swatches button",function(e){Y(e.target.textContent),U(),C.swatchesOnly&&F()}),Z(p,"mouseup",function(e){p.removeEventListener("mousemove",K)}),Z(p,"touchend",function(e){p.removeEventListener("touchmove",K)}),Z(p,"mousedown",function(e){n=!1,f.classList.remove("clr-keyboard-nav"),F()}),Z(p,"keydown",function(e){var t,a=e.key,l=e.target,r=e.shiftKey;"Escape"===a?F(!0):["Tab","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(a)&&(n=!0,f.classList.add("clr-keyboard-nav")),"Tab"===a&&l.matches(".clr-picker *")&&(a=(t=Q()).shift(),t=t.pop(),r&&l===a?(t.focus(),e.preventDefault()):r||l!==t||(a.focus(),e.preventDefault()))}),Z(p,"click",".clr-field button",function(e){B&&O(),e.target.nextElementSibling.dispatchEvent(new Event("click",{bubbles:!0}))}),Z(i,"keydown",function(e){var t={ArrowUp:[0,-1],ArrowDown:[0,1],ArrowLeft:[-1,0],ArrowRight:[1,0]};Object.keys(t).includes(e.key)&&(!function(e,t){$(+i.style.left.replace("px","")+e,+i.style.top.replace("px","")+t)}.apply(void 0,t[e.key]),e.preventDefault())}),Z(h,"click",K),Z(g,"input",e),Z(w,"input",J)})}(window,document,Math);
7 |
--------------------------------------------------------------------------------
/colorfield/static/colorfield/coloris/coloris.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Copyright (c) 2021 Momo Bassit.
3 | * Licensed under the MIT License (MIT)
4 | * https://github.com/mdbassit/Coloris
5 | */
6 |
7 | (function (window, document, Math, undefined) {
8 | var ctx = document.createElement('canvas').getContext('2d');
9 | var currentColor = { r: 0, g: 0, b: 0, h: 0, s: 0, v: 0, a: 1 };
10 | var container,picker,colorArea,colorMarker,colorPreview,colorValue,clearButton,closeButton,
11 | hueSlider,hueMarker,alphaSlider,alphaMarker,currentEl,currentFormat,oldColor,keyboardNav,
12 | colorAreaDims = {};
13 |
14 | // Default settings
15 | var settings = {
16 | el: '[data-coloris]',
17 | parent: 'body',
18 | theme: 'default',
19 | themeMode: 'light',
20 | rtl: false,
21 | wrap: true,
22 | margin: 2,
23 | format: 'hex',
24 | formatToggle: false,
25 | swatches: [],
26 | swatchesOnly: false,
27 | alpha: true,
28 | forceAlpha: false,
29 | focusInput: true,
30 | selectInput: false,
31 | inline: false,
32 | defaultColor: '#000000',
33 | clearButton: false,
34 | clearLabel: 'Clear',
35 | closeButton: false,
36 | closeLabel: 'Close',
37 | onChange: function onChange() {return undefined;},
38 | a11y: {
39 | open: 'Open color picker',
40 | close: 'Close color picker',
41 | clear: 'Clear the selected color',
42 | marker: 'Saturation: {s}. Brightness: {v}.',
43 | hueSlider: 'Hue slider',
44 | alphaSlider: 'Opacity slider',
45 | input: 'Color value field',
46 | format: 'Color format',
47 | swatch: 'Color swatch',
48 | instruction: 'Saturation and brightness selector. Use up, down, left and right arrow keys to select.' } };
49 |
50 |
51 |
52 | // Virtual instances cache
53 | var instances = {};
54 | var currentInstanceId = '';
55 | var defaultInstance = {};
56 | var hasInstance = false;
57 |
58 | /**
59 | * Configure the color picker.
60 | * @param {object} options Configuration options.
61 | */
62 | function configure(options) {
63 | if (typeof options !== 'object') {
64 | return;
65 | }
66 |
67 | for (var key in options) {
68 | switch (key) {
69 | case 'el':
70 | bindFields(options.el);
71 | if (options.wrap !== false) {
72 | wrapFields(options.el);
73 | }
74 | break;
75 | case 'parent':
76 | container = options.parent instanceof HTMLElement ? options.parent : document.querySelector(options.parent);
77 | if (container) {
78 | container.appendChild(picker);
79 | settings.parent = options.parent;
80 |
81 | // document.body is special
82 | if (container === document.body) {
83 | container = undefined;
84 | }
85 | }
86 | break;
87 | case 'themeMode':
88 | settings.themeMode = options.themeMode;
89 | if (options.themeMode === 'auto' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
90 | settings.themeMode = 'dark';
91 | }
92 | // The lack of a break statement is intentional
93 | case 'theme':
94 | if (options.theme) {
95 | settings.theme = options.theme;
96 | }
97 |
98 | // Set the theme and color scheme
99 | picker.className = "clr-picker clr-" + settings.theme + " clr-" + settings.themeMode;
100 |
101 | // Update the color picker's position if inline mode is in use
102 | if (settings.inline) {
103 | updatePickerPosition();
104 | }
105 | break;
106 | case 'rtl':
107 | settings.rtl = !!options.rtl;
108 | Array.from(document.getElementsByClassName('clr-field')).forEach(function (field) {return field.classList.toggle('clr-rtl', settings.rtl);});
109 | break;
110 | case 'margin':
111 | options.margin *= 1;
112 | settings.margin = !isNaN(options.margin) ? options.margin : settings.margin;
113 | break;
114 | case 'wrap':
115 | if (options.el && options.wrap) {
116 | wrapFields(options.el);
117 | }
118 | break;
119 | case 'formatToggle':
120 | settings.formatToggle = !!options.formatToggle;
121 | getEl('clr-format').style.display = settings.formatToggle ? 'block' : 'none';
122 | if (settings.formatToggle) {
123 | settings.format = 'auto';
124 | }
125 | break;
126 | case 'swatches':
127 | if (Array.isArray(options.swatches)) {(function () {
128 | var swatchesContainer = getEl('clr-swatches');
129 | var swatches = document.createElement('div');
130 |
131 | // Clear current swatches
132 | swatchesContainer.textContent = '';
133 |
134 | // Build new swatches
135 | options.swatches.forEach(function (swatch, i) {
136 | var button = document.createElement('button');
137 |
138 | button.setAttribute('type', "button");
139 | button.setAttribute('id', "clr-swatch-" + i);
140 | button.setAttribute('aria-labelledby', "clr-swatch-label clr-swatch-" + i);
141 | button.style.color = swatch;
142 | button.textContent = swatch;
143 |
144 | swatches.appendChild(button);
145 | });
146 |
147 | // Append new swatches if any
148 | if (options.swatches.length) {
149 | swatchesContainer.appendChild(swatches);
150 | }
151 |
152 | settings.swatches = options.swatches.slice();})();
153 | }
154 | break;
155 | case 'swatchesOnly':
156 | settings.swatchesOnly = !!options.swatchesOnly;
157 | picker.setAttribute('data-minimal', settings.swatchesOnly);
158 | break;
159 | case 'alpha':
160 | settings.alpha = !!options.alpha;
161 | picker.setAttribute('data-alpha', settings.alpha);
162 | break;
163 | case 'inline':
164 | settings.inline = !!options.inline;
165 | picker.setAttribute('data-inline', settings.inline);
166 |
167 | if (settings.inline) {
168 | var defaultColor = options.defaultColor || settings.defaultColor;
169 |
170 | currentFormat = getColorFormatFromStr(defaultColor);
171 | updatePickerPosition();
172 | setColorFromStr(defaultColor);
173 | }
174 | break;
175 | case 'clearButton':
176 | // Backward compatibility
177 | if (typeof options.clearButton === 'object') {
178 | if (options.clearButton.label) {
179 | settings.clearLabel = options.clearButton.label;
180 | clearButton.innerHTML = settings.clearLabel;
181 | }
182 |
183 | options.clearButton = options.clearButton.show;
184 | }
185 |
186 | settings.clearButton = !!options.clearButton;
187 | clearButton.style.display = settings.clearButton ? 'block' : 'none';
188 | break;
189 | case 'clearLabel':
190 | settings.clearLabel = options.clearLabel;
191 | clearButton.innerHTML = settings.clearLabel;
192 | break;
193 | case 'closeButton':
194 | settings.closeButton = !!options.closeButton;
195 |
196 | if (settings.closeButton) {
197 | picker.insertBefore(closeButton, colorPreview);
198 | } else {
199 | colorPreview.appendChild(closeButton);
200 | }
201 |
202 | break;
203 | case 'closeLabel':
204 | settings.closeLabel = options.closeLabel;
205 | closeButton.innerHTML = settings.closeLabel;
206 | break;
207 | case 'a11y':
208 | var labels = options.a11y;
209 | var update = false;
210 |
211 | if (typeof labels === 'object') {
212 | for (var label in labels) {
213 | if (labels[label] && settings.a11y[label]) {
214 | settings.a11y[label] = labels[label];
215 | update = true;
216 | }
217 | }
218 | }
219 |
220 | if (update) {
221 | var openLabel = getEl('clr-open-label');
222 | var swatchLabel = getEl('clr-swatch-label');
223 |
224 | openLabel.innerHTML = settings.a11y.open;
225 | swatchLabel.innerHTML = settings.a11y.swatch;
226 | closeButton.setAttribute('aria-label', settings.a11y.close);
227 | clearButton.setAttribute('aria-label', settings.a11y.clear);
228 | hueSlider.setAttribute('aria-label', settings.a11y.hueSlider);
229 | alphaSlider.setAttribute('aria-label', settings.a11y.alphaSlider);
230 | colorValue.setAttribute('aria-label', settings.a11y.input);
231 | colorArea.setAttribute('aria-label', settings.a11y.instruction);
232 | }
233 | break;
234 | default:
235 | settings[key] = options[key];}
236 |
237 | }
238 | }
239 |
240 | /**
241 | * Add or update a virtual instance.
242 | * @param {String} selector The CSS selector of the elements to which the instance is attached.
243 | * @param {Object} options Per-instance options to apply.
244 | */
245 | function setVirtualInstance(selector, options) {
246 | if (typeof selector === 'string' && typeof options === 'object') {
247 | instances[selector] = options;
248 | hasInstance = true;
249 | }
250 | }
251 |
252 | /**
253 | * Remove a virtual instance.
254 | * @param {String} selector The CSS selector of the elements to which the instance is attached.
255 | */
256 | function removeVirtualInstance(selector) {
257 | delete instances[selector];
258 |
259 | if (Object.keys(instances).length === 0) {
260 | hasInstance = false;
261 |
262 | if (selector === currentInstanceId) {
263 | resetVirtualInstance();
264 | }
265 | }
266 | }
267 |
268 | /**
269 | * Attach a virtual instance to an element if it matches a selector.
270 | * @param {Object} element Target element that will receive a virtual instance if applicable.
271 | */
272 | function attachVirtualInstance(element) {
273 | if (hasInstance) {
274 | // These options can only be set globally, not per instance
275 | var unsupportedOptions = ['el', 'wrap', 'rtl', 'inline', 'defaultColor', 'a11y'];var _loop = function _loop(
276 |
277 | selector) {
278 | var options = instances[selector];
279 |
280 | // If the element matches an instance's CSS selector
281 | if (element.matches(selector)) {
282 | currentInstanceId = selector;
283 | defaultInstance = {};
284 |
285 | // Delete unsupported options
286 | unsupportedOptions.forEach(function (option) {return delete options[option];});
287 |
288 | // Back up the default options so we can restore them later
289 | for (var option in options) {
290 | defaultInstance[option] = Array.isArray(settings[option]) ? settings[option].slice() : settings[option];
291 | }
292 |
293 | // Set the instance's options
294 | configure(options);
295 | return "break";
296 | }};for (var selector in instances) {var _ret = _loop(selector);if (_ret === "break") break;
297 | }
298 | }
299 | }
300 |
301 | /**
302 | * Revert any per-instance options that were previously applied.
303 | */
304 | function resetVirtualInstance() {
305 | if (Object.keys(defaultInstance).length > 0) {
306 | configure(defaultInstance);
307 | currentInstanceId = '';
308 | defaultInstance = {};
309 | }
310 | }
311 |
312 | /**
313 | * Bind the color picker to input fields that match the selector.
314 | * @param {(string|HTMLElement|HTMLElement[])} selector A CSS selector string, a DOM element or a list of DOM elements.
315 | */
316 | function bindFields(selector) {
317 | if (selector instanceof HTMLElement) {
318 | selector = [selector];
319 | }
320 |
321 | if (Array.isArray(selector)) {
322 | selector.forEach(function (field) {
323 | addListener(field, 'click', openPicker);
324 | addListener(field, 'input', updateColorPreview);
325 | });
326 | } else {
327 | addListener(document, 'click', selector, openPicker);
328 | addListener(document, 'input', selector, updateColorPreview);
329 | }
330 | }
331 |
332 | /**
333 | * Open the color picker.
334 | * @param {object} event The event that opens the color picker.
335 | */
336 | function openPicker(event) {
337 | // Skip if inline mode is in use
338 | if (settings.inline) {
339 | return;
340 | }
341 |
342 | // Apply any per-instance options first
343 | attachVirtualInstance(event.target);
344 |
345 | currentEl = event.target;
346 | oldColor = currentEl.value;
347 | currentFormat = getColorFormatFromStr(oldColor);
348 | picker.classList.add('clr-open');
349 |
350 | updatePickerPosition();
351 | setColorFromStr(oldColor);
352 |
353 | if (settings.focusInput || settings.selectInput) {
354 | colorValue.focus({ preventScroll: true });
355 | colorValue.setSelectionRange(currentEl.selectionStart, currentEl.selectionEnd);
356 | }
357 |
358 | if (settings.selectInput) {
359 | colorValue.select();
360 | }
361 |
362 | // Always focus the first element when using keyboard navigation
363 | if (keyboardNav || settings.swatchesOnly) {
364 | getFocusableElements().shift().focus();
365 | }
366 |
367 | // Trigger an "open" event
368 | currentEl.dispatchEvent(new Event('open', { bubbles: true }));
369 | }
370 |
371 | /**
372 | * Update the color picker's position and the color gradient's offset
373 | */
374 | function updatePickerPosition() {
375 | var parent = container;
376 | var scrollY = window.scrollY;
377 | var pickerWidth = picker.offsetWidth;
378 | var pickerHeight = picker.offsetHeight;
379 | var reposition = { left: false, top: false };
380 | var parentStyle, parentMarginTop, parentBorderTop;
381 | var offset = { x: 0, y: 0 };
382 |
383 | if (parent) {
384 | parentStyle = window.getComputedStyle(parent);
385 | parentMarginTop = parseFloat(parentStyle.marginTop);
386 | parentBorderTop = parseFloat(parentStyle.borderTopWidth);
387 |
388 | offset = parent.getBoundingClientRect();
389 | offset.y += parentBorderTop + scrollY;
390 | }
391 |
392 | if (!settings.inline) {
393 | var coords = currentEl.getBoundingClientRect();
394 | var left = coords.x;
395 | var top = scrollY + coords.y + coords.height + settings.margin;
396 |
397 | // If the color picker is inside a custom container
398 | // set the position relative to it
399 | if (parent) {
400 | left -= offset.x;
401 | top -= offset.y;
402 |
403 | if (left + pickerWidth > parent.clientWidth) {
404 | left += coords.width - pickerWidth;
405 | reposition.left = true;
406 | }
407 |
408 | if (top + pickerHeight > parent.clientHeight - parentMarginTop) {
409 | if (pickerHeight + settings.margin <= coords.top - (offset.y - scrollY)) {
410 | top -= coords.height + pickerHeight + settings.margin * 2;
411 | reposition.top = true;
412 | }
413 | }
414 |
415 | top += parent.scrollTop;
416 |
417 | // Otherwise set the position relative to the whole document
418 | } else {
419 | if (left + pickerWidth > document.documentElement.clientWidth) {
420 | left += coords.width - pickerWidth;
421 | reposition.left = true;
422 | }
423 |
424 | if (top + pickerHeight - scrollY > document.documentElement.clientHeight) {
425 | if (pickerHeight + settings.margin <= coords.top) {
426 | top = scrollY + coords.y - pickerHeight - settings.margin;
427 | reposition.top = true;
428 | }
429 | }
430 | }
431 |
432 | picker.classList.toggle('clr-left', reposition.left);
433 | picker.classList.toggle('clr-top', reposition.top);
434 | picker.style.left = left + "px";
435 | picker.style.top = top + "px";
436 | offset.x += picker.offsetLeft;
437 | offset.y += picker.offsetTop;
438 | }
439 |
440 | colorAreaDims = {
441 | width: colorArea.offsetWidth,
442 | height: colorArea.offsetHeight,
443 | x: colorArea.offsetLeft + offset.x,
444 | y: colorArea.offsetTop + offset.y };
445 |
446 | }
447 |
448 | /**
449 | * Wrap the linked input fields in a div that adds a color preview.
450 | * @param {(string|HTMLElement|HTMLElement[])} selector A CSS selector string, a DOM element or a list of DOM elements.
451 | */
452 | function wrapFields(selector) {
453 | if (selector instanceof HTMLElement) {
454 | wrapColorField(selector);
455 | } else if (Array.isArray(selector)) {
456 | selector.forEach(wrapColorField);
457 | } else {
458 | document.querySelectorAll(selector).forEach(wrapColorField);
459 | }
460 | }
461 |
462 | /**
463 | * Wrap an input field in a div that adds a color preview.
464 | * @param {object} field The input field.
465 | */
466 | function wrapColorField(field) {
467 | var parentNode = field.parentNode;
468 |
469 | if (!parentNode.classList.contains('clr-field')) {
470 | var wrapper = document.createElement('div');
471 | var classes = 'clr-field';
472 |
473 | if (settings.rtl || field.classList.contains('clr-rtl')) {
474 | classes += ' clr-rtl';
475 | }
476 |
477 | wrapper.innerHTML = '';
478 | parentNode.insertBefore(wrapper, field);
479 | wrapper.className = classes;
480 | wrapper.style.color = field.value;
481 | wrapper.appendChild(field);
482 | }
483 | }
484 |
485 | /**
486 | * Update the color preview of an input field
487 | * @param {object} event The "input" event that triggers the color change.
488 | */
489 | function updateColorPreview(event) {
490 | var parent = event.target.parentNode;
491 |
492 | // Only update the preview if the field has been previously wrapped
493 | if (parent.classList.contains('clr-field')) {
494 | parent.style.color = event.target.value;
495 | }
496 | }
497 |
498 | /**
499 | * Close the color picker.
500 | * @param {boolean} [revert] If true, revert the color to the original value.
501 | */
502 | function closePicker(revert) {
503 | if (currentEl && !settings.inline) {
504 | var prevEl = currentEl;
505 |
506 | // Revert the color to the original value if needed
507 | if (revert) {
508 | // This will prevent the "change" event on the colorValue input to execute its handler
509 | currentEl = undefined;
510 |
511 | if (oldColor !== prevEl.value) {
512 | prevEl.value = oldColor;
513 |
514 | // Trigger an "input" event to force update the thumbnail next to the input field
515 | prevEl.dispatchEvent(new Event('input', { bubbles: true }));
516 | }
517 | }
518 |
519 | // Trigger a "change" event if needed
520 | setTimeout(function () {// Add this to the end of the event loop
521 | if (oldColor !== prevEl.value) {
522 | prevEl.dispatchEvent(new Event('change', { bubbles: true }));
523 | }
524 | });
525 |
526 | // Hide the picker dialog
527 | picker.classList.remove('clr-open');
528 |
529 | // Reset any previously set per-instance options
530 | if (hasInstance) {
531 | resetVirtualInstance();
532 | }
533 |
534 | // Trigger a "close" event
535 | prevEl.dispatchEvent(new Event('close', { bubbles: true }));
536 |
537 | if (settings.focusInput) {
538 | prevEl.focus({ preventScroll: true });
539 | }
540 |
541 | // This essentially marks the picker as closed
542 | currentEl = undefined;
543 | }
544 | }
545 |
546 | /**
547 | * Set the active color from a string.
548 | * @param {string} str String representing a color.
549 | */
550 | function setColorFromStr(str) {
551 | var rgba = strToRGBA(str);
552 | var hsva = RGBAtoHSVA(rgba);
553 |
554 | updateMarkerA11yLabel(hsva.s, hsva.v);
555 | updateColor(rgba, hsva);
556 |
557 | // Update the UI
558 | hueSlider.value = hsva.h;
559 | picker.style.color = "hsl(" + hsva.h + ", 100%, 50%)";
560 | hueMarker.style.left = hsva.h / 360 * 100 + "%";
561 |
562 | colorMarker.style.left = colorAreaDims.width * hsva.s / 100 + "px";
563 | colorMarker.style.top = colorAreaDims.height - colorAreaDims.height * hsva.v / 100 + "px";
564 |
565 | alphaSlider.value = hsva.a * 100;
566 | alphaMarker.style.left = hsva.a * 100 + "%";
567 | }
568 |
569 | /**
570 | * Guess the color format from a string.
571 | * @param {string} str String representing a color.
572 | * @return {string} The color format.
573 | */
574 | function getColorFormatFromStr(str) {
575 | var format = str.substring(0, 3).toLowerCase();
576 |
577 | if (format === 'rgb' || format === 'hsl') {
578 | return format;
579 | }
580 |
581 | return 'hex';
582 | }
583 |
584 | /**
585 | * Copy the active color to the linked input field.
586 | * @param {number} [color] Color value to override the active color.
587 | */
588 | function pickColor(color) {
589 | color = color !== undefined ? color : colorValue.value;
590 |
591 | if (currentEl) {
592 | currentEl.value = color;
593 | currentEl.dispatchEvent(new Event('input', { bubbles: true }));
594 | }
595 |
596 | if (settings.onChange) {
597 | settings.onChange.call(window, color, currentEl);
598 | }
599 |
600 | document.dispatchEvent(new CustomEvent('coloris:pick', { detail: { color: color, currentEl: currentEl } }));
601 | }
602 |
603 | /**
604 | * Set the active color based on a specific point in the color gradient.
605 | * @param {number} x Left position.
606 | * @param {number} y Top position.
607 | */
608 | function setColorAtPosition(x, y) {
609 | var hsva = {
610 | h: hueSlider.value * 1,
611 | s: x / colorAreaDims.width * 100,
612 | v: 100 - y / colorAreaDims.height * 100,
613 | a: alphaSlider.value / 100 };
614 |
615 | var rgba = HSVAtoRGBA(hsva);
616 |
617 | updateMarkerA11yLabel(hsva.s, hsva.v);
618 | updateColor(rgba, hsva);
619 | pickColor();
620 | }
621 |
622 | /**
623 | * Update the color marker's accessibility label.
624 | * @param {number} saturation
625 | * @param {number} value
626 | */
627 | function updateMarkerA11yLabel(saturation, value) {
628 | var label = settings.a11y.marker;
629 |
630 | saturation = saturation.toFixed(1) * 1;
631 | value = value.toFixed(1) * 1;
632 | label = label.replace('{s}', saturation);
633 | label = label.replace('{v}', value);
634 | colorMarker.setAttribute('aria-label', label);
635 | }
636 |
637 | //
638 | /**
639 | * Get the pageX and pageY positions of the pointer.
640 | * @param {object} event The MouseEvent or TouchEvent object.
641 | * @return {object} The pageX and pageY positions.
642 | */
643 | function getPointerPosition(event) {
644 | return {
645 | pageX: event.changedTouches ? event.changedTouches[0].pageX : event.pageX,
646 | pageY: event.changedTouches ? event.changedTouches[0].pageY : event.pageY };
647 |
648 | }
649 |
650 | /**
651 | * Move the color marker when dragged.
652 | * @param {object} event The MouseEvent object.
653 | */
654 | function moveMarker(event) {
655 | var pointer = getPointerPosition(event);
656 | var x = pointer.pageX - colorAreaDims.x;
657 | var y = pointer.pageY - colorAreaDims.y;
658 |
659 | if (container) {
660 | y += container.scrollTop;
661 | }
662 |
663 | setMarkerPosition(x, y);
664 |
665 | // Prevent scrolling while dragging the marker
666 | event.preventDefault();
667 | event.stopPropagation();
668 | }
669 |
670 | /**
671 | * Move the color marker when the arrow keys are pressed.
672 | * @param {number} offsetX The horizontal amount to move.
673 | * @param {number} offsetY The vertical amount to move.
674 | */
675 | function moveMarkerOnKeydown(offsetX, offsetY) {
676 | var x = colorMarker.style.left.replace('px', '') * 1 + offsetX;
677 | var y = colorMarker.style.top.replace('px', '') * 1 + offsetY;
678 |
679 | setMarkerPosition(x, y);
680 | }
681 |
682 | /**
683 | * Set the color marker's position.
684 | * @param {number} x Left position.
685 | * @param {number} y Top position.
686 | */
687 | function setMarkerPosition(x, y) {
688 | // Make sure the marker doesn't go out of bounds
689 | x = x < 0 ? 0 : x > colorAreaDims.width ? colorAreaDims.width : x;
690 | y = y < 0 ? 0 : y > colorAreaDims.height ? colorAreaDims.height : y;
691 |
692 | // Set the position
693 | colorMarker.style.left = x + "px";
694 | colorMarker.style.top = y + "px";
695 |
696 | // Update the color
697 | setColorAtPosition(x, y);
698 |
699 | // Make sure the marker is focused
700 | colorMarker.focus();
701 | }
702 |
703 | /**
704 | * Update the color picker's input field and preview thumb.
705 | * @param {Object} rgba Red, green, blue and alpha values.
706 | * @param {Object} [hsva] Hue, saturation, value and alpha values.
707 | */
708 | function updateColor(rgba, hsva) {if (rgba === void 0) {rgba = {};}if (hsva === void 0) {hsva = {};}
709 | var format = settings.format;
710 |
711 | for (var key in rgba) {
712 | currentColor[key] = rgba[key];
713 | }
714 |
715 | for (var _key in hsva) {
716 | currentColor[_key] = hsva[_key];
717 | }
718 |
719 | var hex = RGBAToHex(currentColor);
720 | var opaqueHex = hex.substring(0, 7);
721 |
722 | colorMarker.style.color = opaqueHex;
723 | alphaMarker.parentNode.style.color = opaqueHex;
724 | alphaMarker.style.color = hex;
725 | colorPreview.style.color = hex;
726 |
727 | // Force repaint the color and alpha gradients as a workaround for a Google Chrome bug
728 | colorArea.style.display = 'none';
729 | colorArea.offsetHeight;
730 | colorArea.style.display = '';
731 | alphaMarker.nextElementSibling.style.display = 'none';
732 | alphaMarker.nextElementSibling.offsetHeight;
733 | alphaMarker.nextElementSibling.style.display = '';
734 |
735 | if (format === 'mixed') {
736 | format = currentColor.a === 1 ? 'hex' : 'rgb';
737 | } else if (format === 'auto') {
738 | format = currentFormat;
739 | }
740 |
741 | switch (format) {
742 | case 'hex':
743 | colorValue.value = hex;
744 | break;
745 | case 'rgb':
746 | colorValue.value = RGBAToStr(currentColor);
747 | break;
748 | case 'hsl':
749 | colorValue.value = HSLAToStr(HSVAtoHSLA(currentColor));
750 | break;}
751 |
752 |
753 | // Select the current format in the format switcher
754 | document.querySelector(".clr-format [value=\"" + format + "\"]").checked = true;
755 | }
756 |
757 | /**
758 | * Set the hue when its slider is moved.
759 | */
760 | function setHue() {
761 | var hue = hueSlider.value * 1;
762 | var x = colorMarker.style.left.replace('px', '') * 1;
763 | var y = colorMarker.style.top.replace('px', '') * 1;
764 |
765 | picker.style.color = "hsl(" + hue + ", 100%, 50%)";
766 | hueMarker.style.left = hue / 360 * 100 + "%";
767 |
768 | setColorAtPosition(x, y);
769 | }
770 |
771 | /**
772 | * Set the alpha when its slider is moved.
773 | */
774 | function setAlpha() {
775 | var alpha = alphaSlider.value / 100;
776 |
777 | alphaMarker.style.left = alpha * 100 + "%";
778 | updateColor({ a: alpha });
779 | pickColor();
780 | }
781 |
782 | /**
783 | * Convert HSVA to RGBA.
784 | * @param {object} hsva Hue, saturation, value and alpha values.
785 | * @return {object} Red, green, blue and alpha values.
786 | */
787 | function HSVAtoRGBA(hsva) {
788 | var saturation = hsva.s / 100;
789 | var value = hsva.v / 100;
790 | var chroma = saturation * value;
791 | var hueBy60 = hsva.h / 60;
792 | var x = chroma * (1 - Math.abs(hueBy60 % 2 - 1));
793 | var m = value - chroma;
794 |
795 | chroma = chroma + m;
796 | x = x + m;
797 |
798 | var index = Math.floor(hueBy60) % 6;
799 | var red = [chroma, x, m, m, x, chroma][index];
800 | var green = [x, chroma, chroma, x, m, m][index];
801 | var blue = [m, m, x, chroma, chroma, x][index];
802 |
803 | return {
804 | r: Math.round(red * 255),
805 | g: Math.round(green * 255),
806 | b: Math.round(blue * 255),
807 | a: hsva.a };
808 |
809 | }
810 |
811 | /**
812 | * Convert HSVA to HSLA.
813 | * @param {object} hsva Hue, saturation, value and alpha values.
814 | * @return {object} Hue, saturation, lightness and alpha values.
815 | */
816 | function HSVAtoHSLA(hsva) {
817 | var value = hsva.v / 100;
818 | var lightness = value * (1 - hsva.s / 100 / 2);
819 | var saturation;
820 |
821 | if (lightness > 0 && lightness < 1) {
822 | saturation = Math.round((value - lightness) / Math.min(lightness, 1 - lightness) * 100);
823 | }
824 |
825 | return {
826 | h: hsva.h,
827 | s: saturation || 0,
828 | l: Math.round(lightness * 100),
829 | a: hsva.a };
830 |
831 | }
832 |
833 | /**
834 | * Convert RGBA to HSVA.
835 | * @param {object} rgba Red, green, blue and alpha values.
836 | * @return {object} Hue, saturation, value and alpha values.
837 | */
838 | function RGBAtoHSVA(rgba) {
839 | var red = rgba.r / 255;
840 | var green = rgba.g / 255;
841 | var blue = rgba.b / 255;
842 | var xmax = Math.max(red, green, blue);
843 | var xmin = Math.min(red, green, blue);
844 | var chroma = xmax - xmin;
845 | var value = xmax;
846 | var hue = 0;
847 | var saturation = 0;
848 |
849 | if (chroma) {
850 | if (xmax === red) {hue = (green - blue) / chroma;}
851 | if (xmax === green) {hue = 2 + (blue - red) / chroma;}
852 | if (xmax === blue) {hue = 4 + (red - green) / chroma;}
853 | if (xmax) {saturation = chroma / xmax;}
854 | }
855 |
856 | hue = Math.floor(hue * 60);
857 |
858 | return {
859 | h: hue < 0 ? hue + 360 : hue,
860 | s: Math.round(saturation * 100),
861 | v: Math.round(value * 100),
862 | a: rgba.a };
863 |
864 | }
865 |
866 | /**
867 | * Parse a string to RGBA.
868 | * @param {string} str String representing a color.
869 | * @return {object} Red, green, blue and alpha values.
870 | */
871 | function strToRGBA(str) {
872 | var regex = /^((rgba)|rgb)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i;
873 | var match, rgba;
874 |
875 | // Default to black for invalid color strings
876 | ctx.fillStyle = '#000';
877 |
878 | // Use canvas to convert the string to a valid color string
879 | ctx.fillStyle = str;
880 | match = regex.exec(ctx.fillStyle);
881 |
882 | if (match) {
883 | rgba = {
884 | r: match[3] * 1,
885 | g: match[4] * 1,
886 | b: match[5] * 1,
887 | a: match[6] * 1 };
888 |
889 |
890 | } else {
891 | match = ctx.fillStyle.replace('#', '').match(/.{2}/g).map(function (h) {return parseInt(h, 16);});
892 | rgba = {
893 | r: match[0],
894 | g: match[1],
895 | b: match[2],
896 | a: 1 };
897 |
898 | }
899 |
900 | return rgba;
901 | }
902 |
903 | /**
904 | * Convert RGBA to Hex.
905 | * @param {object} rgba Red, green, blue and alpha values.
906 | * @return {string} Hex color string.
907 | */
908 | function RGBAToHex(rgba) {
909 | var R = rgba.r.toString(16);
910 | var G = rgba.g.toString(16);
911 | var B = rgba.b.toString(16);
912 | var A = '';
913 |
914 | if (rgba.r < 16) {
915 | R = '0' + R;
916 | }
917 |
918 | if (rgba.g < 16) {
919 | G = '0' + G;
920 | }
921 |
922 | if (rgba.b < 16) {
923 | B = '0' + B;
924 | }
925 |
926 | if (settings.alpha && (rgba.a < 1 || settings.forceAlpha)) {
927 | var alpha = rgba.a * 255 | 0;
928 | A = alpha.toString(16);
929 |
930 | if (alpha < 16) {
931 | A = '0' + A;
932 | }
933 | }
934 |
935 | return '#' + R + G + B + A;
936 | }
937 |
938 | /**
939 | * Convert RGBA values to a CSS rgb/rgba string.
940 | * @param {object} rgba Red, green, blue and alpha values.
941 | * @return {string} CSS color string.
942 | */
943 | function RGBAToStr(rgba) {
944 | if (!settings.alpha || rgba.a === 1 && !settings.forceAlpha) {
945 | return "rgb(" + rgba.r + ", " + rgba.g + ", " + rgba.b + ")";
946 | } else {
947 | return "rgba(" + rgba.r + ", " + rgba.g + ", " + rgba.b + ", " + rgba.a + ")";
948 | }
949 | }
950 |
951 | /**
952 | * Convert HSLA values to a CSS hsl/hsla string.
953 | * @param {object} hsla Hue, saturation, lightness and alpha values.
954 | * @return {string} CSS color string.
955 | */
956 | function HSLAToStr(hsla) {
957 | if (!settings.alpha || hsla.a === 1 && !settings.forceAlpha) {
958 | return "hsl(" + hsla.h + ", " + hsla.s + "%, " + hsla.l + "%)";
959 | } else {
960 | return "hsla(" + hsla.h + ", " + hsla.s + "%, " + hsla.l + "%, " + hsla.a + ")";
961 | }
962 | }
963 |
964 | /**
965 | * Init the color picker.
966 | */
967 | function init() {
968 | // Render the UI
969 | container = undefined;
970 | picker = document.createElement('div');
971 | picker.setAttribute('id', 'clr-picker');
972 | picker.className = 'clr-picker';
973 | picker.innerHTML =
974 | "" + ("' +
978 | '' + ("
") +
980 | '
' +
981 | '
' +
982 | '' + ("
") +
984 | '
' +
985 | '
' +
986 | '
' +
987 | '' +
988 | '
' +
998 | '' +
999 | '' + ("") +
1001 | '' + ("") +
1003 | '
' + ("" +
1004 | settings.a11y.open + "") + ("" +
1005 | settings.a11y.swatch + "");
1006 |
1007 | // Append the color picker to the DOM
1008 | document.body.appendChild(picker);
1009 |
1010 | // Reference the UI elements
1011 | colorArea = getEl('clr-color-area');
1012 | colorMarker = getEl('clr-color-marker');
1013 | clearButton = getEl('clr-clear');
1014 | closeButton = getEl('clr-close');
1015 | colorPreview = getEl('clr-color-preview');
1016 | colorValue = getEl('clr-color-value');
1017 | hueSlider = getEl('clr-hue-slider');
1018 | hueMarker = getEl('clr-hue-marker');
1019 | alphaSlider = getEl('clr-alpha-slider');
1020 | alphaMarker = getEl('clr-alpha-marker');
1021 |
1022 | // Bind the picker to the default selector
1023 | bindFields(settings.el);
1024 | wrapFields(settings.el);
1025 |
1026 | addListener(picker, 'mousedown', function (event) {
1027 | picker.classList.remove('clr-keyboard-nav');
1028 | event.stopPropagation();
1029 | });
1030 |
1031 | addListener(colorArea, 'mousedown', function (event) {
1032 | addListener(document, 'mousemove', moveMarker);
1033 | });
1034 |
1035 | addListener(colorArea, 'contextmenu', function (event) {
1036 | event.preventDefault();
1037 | });
1038 |
1039 | addListener(colorArea, 'touchstart', function (event) {
1040 | document.addEventListener('touchmove', moveMarker, { passive: false });
1041 | });
1042 |
1043 | addListener(colorMarker, 'mousedown', function (event) {
1044 | addListener(document, 'mousemove', moveMarker);
1045 | });
1046 |
1047 | addListener(colorMarker, 'touchstart', function (event) {
1048 | document.addEventListener('touchmove', moveMarker, { passive: false });
1049 | });
1050 |
1051 | addListener(colorValue, 'change', function (event) {
1052 | var value = colorValue.value;
1053 |
1054 | if (currentEl || settings.inline) {
1055 | var color = value === '' ? value : setColorFromStr(value);
1056 | pickColor(color);
1057 | }
1058 | });
1059 |
1060 | addListener(clearButton, 'click', function (event) {
1061 | pickColor('');
1062 | closePicker();
1063 | });
1064 |
1065 | addListener(closeButton, 'click', function (event) {
1066 | pickColor();
1067 | closePicker();
1068 | });
1069 |
1070 | addListener(getEl('clr-format'), 'click', '.clr-format input', function (event) {
1071 | currentFormat = event.target.value;
1072 | updateColor();
1073 | pickColor();
1074 | });
1075 |
1076 | addListener(picker, 'click', '.clr-swatches button', function (event) {
1077 | setColorFromStr(event.target.textContent);
1078 | pickColor();
1079 |
1080 | if (settings.swatchesOnly) {
1081 | closePicker();
1082 | }
1083 | });
1084 |
1085 | addListener(document, 'mouseup', function (event) {
1086 | document.removeEventListener('mousemove', moveMarker);
1087 | });
1088 |
1089 | addListener(document, 'touchend', function (event) {
1090 | document.removeEventListener('touchmove', moveMarker);
1091 | });
1092 |
1093 | addListener(document, 'mousedown', function (event) {
1094 | keyboardNav = false;
1095 | picker.classList.remove('clr-keyboard-nav');
1096 | closePicker();
1097 | });
1098 |
1099 | addListener(document, 'keydown', function (event) {
1100 | var key = event.key;
1101 | var target = event.target;
1102 | var shiftKey = event.shiftKey;
1103 | var navKeys = ['Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
1104 |
1105 | if (key === 'Escape') {
1106 | closePicker(true);
1107 |
1108 | // Display focus rings when using the keyboard
1109 | } else if (navKeys.includes(key)) {
1110 | keyboardNav = true;
1111 | picker.classList.add('clr-keyboard-nav');
1112 | }
1113 |
1114 | // Trap the focus within the color picker while it's open
1115 | if (key === 'Tab' && target.matches('.clr-picker *')) {
1116 | var focusables = getFocusableElements();
1117 | var firstFocusable = focusables.shift();
1118 | var lastFocusable = focusables.pop();
1119 |
1120 | if (shiftKey && target === firstFocusable) {
1121 | lastFocusable.focus();
1122 | event.preventDefault();
1123 | } else if (!shiftKey && target === lastFocusable) {
1124 | firstFocusable.focus();
1125 | event.preventDefault();
1126 | }
1127 | }
1128 | });
1129 |
1130 | addListener(document, 'click', '.clr-field button', function (event) {
1131 | // Reset any previously set per-instance options
1132 | if (hasInstance) {
1133 | resetVirtualInstance();
1134 | }
1135 |
1136 | // Open the color picker
1137 | event.target.nextElementSibling.dispatchEvent(new Event('click', { bubbles: true }));
1138 | });
1139 |
1140 | addListener(colorMarker, 'keydown', function (event) {
1141 | var movements = {
1142 | ArrowUp: [0, -1],
1143 | ArrowDown: [0, 1],
1144 | ArrowLeft: [-1, 0],
1145 | ArrowRight: [1, 0] };
1146 |
1147 |
1148 | if (Object.keys(movements).includes(event.key)) {
1149 | moveMarkerOnKeydown.apply(void 0, movements[event.key]);
1150 | event.preventDefault();
1151 | }
1152 | });
1153 |
1154 | addListener(colorArea, 'click', moveMarker);
1155 | addListener(hueSlider, 'input', setHue);
1156 | addListener(alphaSlider, 'input', setAlpha);
1157 | }
1158 |
1159 | /**
1160 | * Return a list of focusable elements within the color picker.
1161 | * @return {array} The list of focusable DOM elemnts.
1162 | */
1163 | function getFocusableElements() {
1164 | var controls = Array.from(picker.querySelectorAll('input, button'));
1165 | var focusables = controls.filter(function (node) {return !!node.offsetWidth;});
1166 |
1167 | return focusables;
1168 | }
1169 |
1170 | /**
1171 | * Shortcut for getElementById to optimize the minified JS.
1172 | * @param {string} id The element id.
1173 | * @return {object} The DOM element with the provided id.
1174 | */
1175 | function getEl(id) {
1176 | return document.getElementById(id);
1177 | }
1178 |
1179 | /**
1180 | * Shortcut for addEventListener to optimize the minified JS.
1181 | * @param {object} context The context to which the listener is attached.
1182 | * @param {string} type Event type.
1183 | * @param {(string|function)} selector Event target if delegation is used, event handler if not.
1184 | * @param {function} [fn] Event handler if delegation is used.
1185 | */
1186 | function addListener(context, type, selector, fn) {
1187 | var matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
1188 |
1189 | // Delegate event to the target of the selector
1190 | if (typeof selector === 'string') {
1191 | context.addEventListener(type, function (event) {
1192 | if (matches.call(event.target, selector)) {
1193 | fn.call(event.target, event);
1194 | }
1195 | });
1196 |
1197 | // If the selector is not a string then it's a function
1198 | // in which case we need a regular event listener
1199 | } else {
1200 | fn = selector;
1201 | context.addEventListener(type, fn);
1202 | }
1203 | }
1204 |
1205 | /**
1206 | * Call a function only when the DOM is ready.
1207 | * @param {function} fn The function to call.
1208 | * @param {array} [args] Arguments to pass to the function.
1209 | */
1210 | function DOMReady(fn, args) {
1211 | args = args !== undefined ? args : [];
1212 |
1213 | if (document.readyState !== 'loading') {
1214 | fn.apply(void 0, args);
1215 | } else {
1216 | document.addEventListener('DOMContentLoaded', function () {
1217 | fn.apply(void 0, args);
1218 | });
1219 | }
1220 | }
1221 |
1222 | // Polyfill for Nodelist.forEach
1223 | if (NodeList !== undefined && NodeList.prototype && !NodeList.prototype.forEach) {
1224 | NodeList.prototype.forEach = Array.prototype.forEach;
1225 | }
1226 |
1227 | // Expose the color picker to the global scope
1228 | window.Coloris = function () {
1229 | var methods = {
1230 | set: configure,
1231 | wrap: wrapFields,
1232 | close: closePicker,
1233 | setInstance: setVirtualInstance,
1234 | removeInstance: removeVirtualInstance,
1235 | updatePosition: updatePickerPosition,
1236 | ready: DOMReady };
1237 |
1238 |
1239 | function Coloris(options) {
1240 | DOMReady(function () {
1241 | if (options) {
1242 | if (typeof options === 'string') {
1243 | bindFields(options);
1244 | } else {
1245 | configure(options);
1246 | }
1247 | }
1248 | });
1249 | }var _loop2 = function _loop2(
1250 |
1251 | key) {
1252 | Coloris[key] = function () {for (var _len = arguments.length, args = new Array(_len), _key2 = 0; _key2 < _len; _key2++) {args[_key2] = arguments[_key2];}
1253 | DOMReady(methods[key], args);
1254 | };};for (var key in methods) {_loop2(key);
1255 | }
1256 |
1257 | return Coloris;
1258 | }();
1259 |
1260 | // Init the color picker when the DOM is ready
1261 | DOMReady(init);
1262 |
1263 | })(window, document, Math);
1264 |
--------------------------------------------------------------------------------