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