spectrum.js (84923B)
1 // Spectrum Colorpicker v1.8.0 2 // https://github.com/bgrins/spectrum 3 // Author: Brian Grinstead 4 // License: MIT 5 6 (function (factory) { 7 "use strict"; 8 9 if (typeof define === 'function' && define.amd) { // AMD 10 define(['jquery'], factory); 11 } else if (typeof exports == "object" && typeof module == "object") { // CommonJS 12 module.exports = factory(require('jquery')); 13 } else { // Browser 14 factory(jQuery); 15 } 16 })(function ($, undefined) { 17 "use strict"; 18 19 var defaultOpts = { 20 21 // Callbacks 22 beforeShow: noop, 23 move: noop, 24 change: noop, 25 show: noop, 26 hide: noop, 27 28 // Options 29 color: false, 30 flat: false, 31 showInput: false, 32 allowEmpty: false, 33 showButtons: true, 34 clickoutFiresChange: true, 35 showInitial: false, 36 showPalette: false, 37 showPaletteOnly: false, 38 hideAfterPaletteSelect: false, 39 togglePaletteOnly: false, 40 showSelectionPalette: true, 41 localStorageKey: false, 42 appendTo: "body", 43 maxSelectionSize: 7, 44 cancelText: "cancel", 45 chooseText: "choose", 46 togglePaletteMoreText: "more", 47 togglePaletteLessText: "less", 48 clearText: "Clear Color Selection", 49 noColorSelectedText: "No Color Selected", 50 preferredFormat: false, 51 className: "", // Deprecated - use containerClassName and replacerClassName instead. 52 containerClassName: "", 53 replacerClassName: "", 54 showAlpha: false, 55 theme: "sp-light", 56 palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]], 57 selectionPalette: [], 58 disabled: false, 59 offset: null 60 }, 61 spectrums = [], 62 IE = !!/msie/i.exec(window.navigator.userAgent), 63 rgbaSupport = (function () { 64 function contains(str, substr) { 65 return !!~('' + str).indexOf(substr); 66 } 67 68 var elem = document.createElement('div'); 69 var style = elem.style; 70 style.cssText = 'background-color:rgba(0,0,0,.5)'; 71 return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla'); 72 })(), 73 replaceInput = [ 74 "<div class='sp-replacer'>", 75 "<div class='sp-preview'><div class='sp-preview-inner'></div></div>", 76 "<div class='sp-dd'>▼</div>", 77 "</div>" 78 ].join(''), 79 markup = (function () { 80 81 // IE does not support gradients with multiple stops, so we need to simulate 82 // that for the rainbow slider with 8 divs that each have a single gradient 83 var gradientFix = ""; 84 if (IE) { 85 for (var i = 1; i <= 6; i++) { 86 gradientFix += "<div class='sp-" + i + "'></div>"; 87 } 88 } 89 90 return [ 91 "<div class='sp-container sp-hidden'>", 92 "<div class='sp-palette-container'>", 93 "<div class='sp-palette sp-thumb sp-cf'></div>", 94 "<div class='sp-palette-button-container sp-cf'>", 95 "<button type='button' class='sp-palette-toggle'></button>", 96 "</div>", 97 "</div>", 98 "<div class='sp-picker-container'>", 99 "<div class='sp-top sp-cf'>", 100 "<div class='sp-fill'></div>", 101 "<div class='sp-top-inner'>", 102 "<div class='sp-color'>", 103 "<div class='sp-sat'>", 104 "<div class='sp-val'>", 105 "<div class='sp-dragger'></div>", 106 "</div>", 107 "</div>", 108 "</div>", 109 "<div class='sp-clear sp-clear-display'>", 110 "</div>", 111 "<div class='sp-hue'>", 112 "<div class='sp-slider'></div>", 113 gradientFix, 114 "</div>", 115 "</div>", 116 "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>", 117 "</div>", 118 "<div class='sp-input-container sp-cf'>", 119 "<input class='sp-input' type='text' spellcheck='false' />", 120 "</div>", 121 "<div class='sp-initial sp-thumb sp-cf'></div>", 122 "<div class='sp-button-container sp-cf'>", 123 "<a class='sp-cancel' href='#'></a>", 124 "<button type='button' class='sp-choose'></button>", 125 "</div>", 126 "</div>", 127 "</div>" 128 ].join(""); 129 })(); 130 131 function paletteTemplate(p, color, className, opts) { 132 var html = []; 133 for (var i = 0; i < p.length; i++) { 134 var current = p[i]; 135 if (current) { 136 var tiny = tinycolor(current); 137 var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light"; 138 c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : ""; 139 var formattedString = tiny.toString(opts.preferredFormat || "rgb"); 140 var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter(); 141 html.push('<span title="' + formattedString + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" ></span></span>'); 142 } else { 143 var cls = 'sp-clear-display'; 144 html.push($('<div />') 145 .append($('<span data-color="" style="background-color:transparent;" class="' + cls + '"></span>') 146 .attr('title', opts.noColorSelectedText) 147 ) 148 .html() 149 ); 150 } 151 } 152 return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>"; 153 } 154 155 function hideAll() { 156 for (var i = 0; i < spectrums.length; i++) { 157 if (spectrums[i]) { 158 spectrums[i].hide(); 159 } 160 } 161 } 162 163 function instanceOptions(o, callbackContext) { 164 var opts = $.extend({}, defaultOpts, o); 165 opts.callbacks = { 166 'move': bind(opts.move, callbackContext), 167 'change': bind(opts.change, callbackContext), 168 'show': bind(opts.show, callbackContext), 169 'hide': bind(opts.hide, callbackContext), 170 'beforeShow': bind(opts.beforeShow, callbackContext) 171 }; 172 173 return opts; 174 } 175 176 function spectrum(element, o) { 177 178 179 var opts = instanceOptions(o, element), 180 flat = opts.flat, 181 showSelectionPalette = opts.showSelectionPalette, 182 localStorageKey = opts.localStorageKey, 183 theme = opts.theme, 184 callbacks = opts.callbacks, 185 resize = throttle(reflow, 10), 186 visible = false, 187 isDragging = false, 188 dragWidth = 0, 189 dragHeight = 0, 190 dragHelperHeight = 0, 191 slideHeight = 0, 192 slideWidth = 0, 193 alphaWidth = 0, 194 alphaSlideHelperWidth = 0, 195 slideHelperHeight = 0, 196 currentHue = 0, 197 currentSaturation = 0, 198 currentValue = 0, 199 currentAlpha = 1, 200 palette = [], 201 paletteArray = [], 202 paletteLookup = {}, 203 selectionPalette = opts.selectionPalette.slice(0), 204 maxSelectionSize = opts.maxSelectionSize, 205 draggingClass = "sp-dragging", 206 shiftMovementDirection = null; 207 208 var doc = element.ownerDocument, 209 body = doc.body, 210 boundElement = $(element), 211 disabled = false, 212 container = $(markup, doc).addClass(theme), 213 pickerContainer = container.find(".sp-picker-container"), 214 dragger = container.find(".sp-color"), 215 dragHelper = container.find(".sp-dragger"), 216 slider = container.find(".sp-hue"), 217 slideHelper = container.find(".sp-slider"), 218 alphaSliderInner = container.find(".sp-alpha-inner"), 219 alphaSlider = container.find(".sp-alpha"), 220 alphaSlideHelper = container.find(".sp-alpha-handle"), 221 textInput = container.find(".sp-input"), 222 paletteContainer = container.find(".sp-palette"), 223 initialColorContainer = container.find(".sp-initial"), 224 cancelButton = container.find(".sp-cancel"), 225 clearButton = container.find(".sp-clear"), 226 chooseButton = container.find(".sp-choose"), 227 toggleButton = container.find(".sp-palette-toggle"), 228 isInput = boundElement.is("input"), 229 isInputTypeColor = isInput && boundElement.attr("type") === "color" && inputTypeColorSupport(), 230 shouldReplace = isInput && !flat, 231 replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]), 232 offsetElement = (shouldReplace) ? replacer : boundElement, 233 previewElement = replacer.find(".sp-preview-inner"), 234 initialColor = opts.color || (isInput && boundElement.val()), 235 colorOnShow = false, 236 currentPreferredFormat = opts.preferredFormat, 237 clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange, 238 isEmpty = !initialColor, 239 allowEmpty = opts.allowEmpty && !isInputTypeColor; 240 241 var observer = null; 242 243 function applyOptions() { 244 245 if (opts.showPaletteOnly) { 246 opts.showPalette = true; 247 } 248 249 toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); 250 251 if (opts.palette) { 252 palette = opts.palette.slice(0); 253 paletteArray = $.isArray(palette[0]) ? palette : [palette]; 254 paletteLookup = {}; 255 for (var i = 0; i < paletteArray.length; i++) { 256 for (var j = 0; j < paletteArray[i].length; j++) { 257 var rgb = tinycolor(paletteArray[i][j]).toRgbString(); 258 paletteLookup[rgb] = true; 259 } 260 } 261 } 262 263 container.toggleClass("sp-flat", flat); 264 container.toggleClass("sp-input-disabled", !opts.showInput); 265 container.toggleClass("sp-alpha-enabled", opts.showAlpha); 266 container.toggleClass("sp-clear-enabled", allowEmpty); 267 container.toggleClass("sp-buttons-disabled", !opts.showButtons); 268 container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly); 269 container.toggleClass("sp-palette-disabled", !opts.showPalette); 270 container.toggleClass("sp-palette-only", opts.showPaletteOnly); 271 container.toggleClass("sp-initial-disabled", !opts.showInitial); 272 container.addClass(opts.className).addClass(opts.containerClassName); 273 274 reflow(); 275 } 276 277 function initialize() { 278 279 if (IE) { 280 container.find("*:not(input)").attr("unselectable", "on"); 281 } 282 283 applyOptions(); 284 285 if (shouldReplace) { 286 boundElement.after(replacer).hide(); 287 } 288 289 if (!allowEmpty) { 290 clearButton.hide(); 291 } 292 293 if (flat) { 294 boundElement.after(container).hide(); 295 } else { 296 297 var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo); 298 if (appendTo.length !== 1) { 299 appendTo = $("body"); 300 } 301 302 appendTo.append(container); 303 } 304 305 updateSelectionPaletteFromStorage(); 306 307 308 offsetElement.on("click.spectrum touchstart.spectrum", function (e) { 309 if (!disabled) { 310 toggle(); 311 } 312 313 e.stopPropagation(); 314 315 if (!$(e.target).is("input")) { 316 e.preventDefault(); 317 } 318 }); 319 320 if (boundElement.is(":disabled") || (opts.disabled === true)) { 321 disable(); 322 } 323 324 // Prevent clicks from bubbling up to document. This would cause it to be hidden. 325 container.click(stopPropagation); 326 327 // Handle user typed input 328 textInput.change(setFromTextInput); 329 textInput.on("paste", function () { 330 setTimeout(setFromTextInput, 1); 331 }); 332 textInput.keydown(function (e) { 333 if (e.keyCode == 13) { 334 setFromTextInput(); 335 } 336 }); 337 338 cancelButton.text(opts.cancelText); 339 cancelButton.on("click.spectrum", function (e) { 340 e.stopPropagation(); 341 e.preventDefault(); 342 revert(); 343 hide(); 344 }); 345 346 clearButton.attr("title", opts.clearText); 347 clearButton.on("click.spectrum", function (e) { 348 e.stopPropagation(); 349 e.preventDefault(); 350 isEmpty = true; 351 move(); 352 353 if (flat) { 354 //for the flat style, this is a change event 355 updateOriginalInput(true); 356 } 357 }); 358 359 chooseButton.text(opts.chooseText); 360 chooseButton.on("click.spectrum", function (e) { 361 e.stopPropagation(); 362 e.preventDefault(); 363 364 if (IE && textInput.is(":focus")) { 365 textInput.trigger('change'); 366 } 367 368 if (isValid()) { 369 updateOriginalInput(true); 370 hide(); 371 } 372 }); 373 374 toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); 375 toggleButton.on("click.spectrum", function (e) { 376 e.stopPropagation(); 377 e.preventDefault(); 378 379 opts.showPaletteOnly = !opts.showPaletteOnly; 380 381 // To make sure the Picker area is drawn on the right, next to the 382 // Palette area (and not below the palette), first move the Palette 383 // to the left to make space for the picker, plus 5px extra. 384 // The 'applyOptions' function puts the whole container back into place 385 // and takes care of the button-text and the sp-palette-only CSS class. 386 if (!opts.showPaletteOnly && !flat) { 387 container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5)); 388 } 389 applyOptions(); 390 }); 391 392 draggable(alphaSlider, function (dragX, dragY, e) { 393 currentAlpha = (dragX / alphaWidth); 394 isEmpty = false; 395 if (e.shiftKey) { 396 currentAlpha = Math.round(currentAlpha * 10) / 10; 397 } 398 399 move(); 400 }, dragStart, dragStop); 401 402 draggable(slider, function (dragX, dragY) { 403 currentHue = parseFloat(dragY / slideHeight); 404 isEmpty = false; 405 if (!opts.showAlpha) { 406 currentAlpha = 1; 407 } 408 move(); 409 }, dragStart, dragStop); 410 411 draggable(dragger, function (dragX, dragY, e) { 412 413 // shift+drag should snap the movement to either the x or y axis. 414 if (!e.shiftKey) { 415 shiftMovementDirection = null; 416 } else if (!shiftMovementDirection) { 417 var oldDragX = currentSaturation * dragWidth; 418 var oldDragY = dragHeight - (currentValue * dragHeight); 419 var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY); 420 421 shiftMovementDirection = furtherFromX ? "x" : "y"; 422 } 423 424 var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x"; 425 var setValue = !shiftMovementDirection || shiftMovementDirection === "y"; 426 427 if (setSaturation) { 428 currentSaturation = parseFloat(dragX / dragWidth); 429 } 430 if (setValue) { 431 currentValue = parseFloat((dragHeight - dragY) / dragHeight); 432 } 433 434 isEmpty = false; 435 if (!opts.showAlpha) { 436 currentAlpha = 1; 437 } 438 439 move(); 440 441 }, dragStart, dragStop); 442 443 if (!!initialColor) { 444 set(initialColor); 445 446 // In case color was black - update the preview UI and set the format 447 // since the set function will not run (default color is black). 448 updateUI(); 449 currentPreferredFormat = opts.preferredFormat || tinycolor(initialColor).format; 450 451 addColorToSelectionPalette(initialColor); 452 } else { 453 updateUI(); 454 } 455 456 if (flat) { 457 show(); 458 } 459 460 function paletteElementClick(e) { 461 if (e.data && e.data.ignore) { 462 set($(e.target).closest(".sp-thumb-el").data("color")); 463 move(); 464 } else { 465 set($(e.target).closest(".sp-thumb-el").data("color")); 466 move(); 467 468 // If the picker is going to close immediately, a palette selection 469 // is a change. Otherwise, it's a move only. 470 if (opts.hideAfterPaletteSelect) { 471 updateOriginalInput(true); 472 hide(); 473 } else { 474 updateOriginalInput(); 475 } 476 } 477 478 return false; 479 } 480 481 var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum"; 482 paletteContainer.on(paletteEvent, ".sp-thumb-el", paletteElementClick); 483 initialColorContainer.on(paletteEvent, ".sp-thumb-el:nth-child(1)", {ignore: true}, paletteElementClick); 484 } 485 486 function updateSelectionPaletteFromStorage() { 487 488 if (localStorageKey && window.localStorage) { 489 490 // Migrate old palettes over to new format. May want to remove this eventually. 491 try { 492 var oldPalette = window.localStorage[localStorageKey].split(",#"); 493 if (oldPalette.length > 1) { 494 delete window.localStorage[localStorageKey]; 495 $.each(oldPalette, function (i, c) { 496 addColorToSelectionPalette(c); 497 }); 498 } 499 } catch (e) { 500 } 501 502 try { 503 selectionPalette = window.localStorage[localStorageKey].split(";"); 504 } catch (e) { 505 } 506 } 507 } 508 509 function addColorToSelectionPalette(color) { 510 if (showSelectionPalette) { 511 var rgb = tinycolor(color).toRgbString(); 512 if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) { 513 selectionPalette.push(rgb); 514 while (selectionPalette.length > maxSelectionSize) { 515 selectionPalette.shift(); 516 } 517 } 518 519 if (localStorageKey && window.localStorage) { 520 try { 521 window.localStorage[localStorageKey] = selectionPalette.join(";"); 522 } catch (e) { 523 } 524 } 525 } 526 } 527 528 function getUniqueSelectionPalette() { 529 var unique = []; 530 if (opts.showPalette) { 531 for (var i = 0; i < selectionPalette.length; i++) { 532 var rgb = tinycolor(selectionPalette[i]).toRgbString(); 533 534 if (!paletteLookup[rgb]) { 535 unique.push(selectionPalette[i]); 536 } 537 } 538 } 539 540 return unique.reverse().slice(0, opts.maxSelectionSize); 541 } 542 543 function drawPalette() { 544 545 var currentColor = get(); 546 547 var html = $.map(paletteArray, function (palette, i) { 548 return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts); 549 }); 550 551 updateSelectionPaletteFromStorage(); 552 553 if (selectionPalette) { 554 html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts)); 555 } 556 557 paletteContainer.html(html.join("")); 558 } 559 560 function drawInitial() { 561 if (opts.showInitial) { 562 var initial = colorOnShow; 563 var current = get(); 564 initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts)); 565 } 566 } 567 568 function dragStart() { 569 if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) { 570 reflow(); 571 } 572 isDragging = true; 573 container.addClass(draggingClass); 574 shiftMovementDirection = null; 575 boundElement.trigger('dragstart.spectrum', [get()]); 576 } 577 578 function dragStop() { 579 isDragging = false; 580 container.removeClass(draggingClass); 581 boundElement.trigger('dragstop.spectrum', [get()]); 582 } 583 584 function setFromTextInput() { 585 586 var value = textInput.val(); 587 588 if ((value === null || value === "") && allowEmpty) { 589 set(null); 590 move(); 591 updateOriginalInput(); 592 } else { 593 var tiny = tinycolor(value); 594 if (tiny.isValid()) { 595 set(tiny); 596 move(); 597 updateOriginalInput(); 598 } else { 599 textInput.addClass("sp-validation-error"); 600 } 601 } 602 } 603 604 function toggle() { 605 if (visible) { 606 hide(); 607 } else { 608 show(); 609 } 610 } 611 612 function show() { 613 var event = $.Event('beforeShow.spectrum'); 614 615 if (visible) { 616 reflow(); 617 return; 618 } 619 620 621 boundElement.trigger(event, [get()]); 622 623 if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) { 624 return; 625 } 626 627 hideAll(); 628 visible = true; 629 630 $(doc).on("keydown.spectrum", onkeydown); 631 $(doc).on("click.spectrum", clickout); 632 $(window).on("resize.spectrum", resize); 633 replacer.addClass("sp-active"); 634 container.removeClass("sp-hidden"); 635 636 reflow(); 637 updateUI(); 638 639 colorOnShow = get(); 640 641 drawInitial(); 642 callbacks.show(colorOnShow); 643 boundElement.trigger('show.spectrum', [colorOnShow]); 644 645 646 var observerCallback = function () { 647 var parentEl = boundElement.parent(); 648 649 if (!parentEl.is(':visible') || parentEl.css('visibility') === 'hidden' 650 ) { 651 clearInterval(observer); 652 hide(); 653 } 654 }; 655 656 observer = setInterval(observerCallback, 200) 657 } 658 659 function onkeydown(e) { 660 // Close on ESC 661 if (e.keyCode === 27) { 662 hide(); 663 } 664 } 665 666 function clickout(e) { 667 // Return on right click. 668 if (e.button == 2) { 669 return; 670 } 671 672 // If a drag event was happening during the mouseup, don't hide 673 // on click. 674 if (isDragging) { 675 return; 676 } 677 678 if (clickoutFiresChange) { 679 updateOriginalInput(true); 680 } else { 681 revert(); 682 } 683 hide(); 684 } 685 686 function hide() { 687 // Return if hiding is unnecessary 688 if (!visible || flat) { 689 return; 690 } 691 visible = false; 692 693 $(doc).off("keydown.spectrum", onkeydown); 694 $(doc).off("click.spectrum", clickout); 695 $(window).off("resize.spectrum", resize); 696 697 replacer.removeClass("sp-active"); 698 container.addClass("sp-hidden"); 699 700 callbacks.hide(get()); 701 boundElement.trigger('hide.spectrum', [get()]); 702 } 703 704 function revert() { 705 set(colorOnShow, true); 706 updateOriginalInput(true); 707 } 708 709 function set(color, ignoreFormatChange) { 710 if (tinycolor.equals(color, get())) { 711 // Update UI just in case a validation error needs 712 // to be cleared. 713 updateUI(); 714 return; 715 } 716 717 var newColor, newHsv; 718 if (!color && allowEmpty) { 719 isEmpty = true; 720 } else { 721 isEmpty = false; 722 newColor = tinycolor(color); 723 newHsv = newColor.toHsv(); 724 725 currentHue = (newHsv.h % 360) / 360; 726 currentSaturation = newHsv.s; 727 currentValue = newHsv.v; 728 currentAlpha = newHsv.a; 729 } 730 updateUI(); 731 732 if (newColor && newColor.isValid() && !ignoreFormatChange) { 733 currentPreferredFormat = opts.preferredFormat || newColor.getFormat(); 734 } 735 } 736 737 function get(opts) { 738 opts = opts || {}; 739 740 if (allowEmpty && isEmpty) { 741 return null; 742 } 743 744 return tinycolor.fromRatio({ 745 h: currentHue, 746 s: currentSaturation, 747 v: currentValue, 748 a: Math.round(currentAlpha * 1000) / 1000 749 }, {format: opts.format || currentPreferredFormat}); 750 } 751 752 function isValid() { 753 return !textInput.hasClass("sp-validation-error"); 754 } 755 756 function move() { 757 updateUI(); 758 759 callbacks.move(get()); 760 boundElement.trigger('move.spectrum', [get()]); 761 } 762 763 function updateUI() { 764 765 textInput.removeClass("sp-validation-error"); 766 767 updateHelperLocations(); 768 769 // Update dragger background color (gradients take care of saturation and value). 770 var flatColor = tinycolor.fromRatio({h: currentHue, s: 1, v: 1}); 771 dragger.css("background-color", flatColor.toHexString()); 772 773 // Get a format that alpha will be included in (hex and names ignore alpha) 774 var format = currentPreferredFormat; 775 if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) { 776 if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") { 777 format = "rgb"; 778 } 779 } 780 781 var realColor = get({format: format}), 782 displayColor = ''; 783 784 //reset background info for preview element 785 previewElement.removeClass("sp-clear-display"); 786 previewElement.css('background-color', 'transparent'); 787 788 if (!realColor && allowEmpty) { 789 // Update the replaced elements background with icon indicating no color selection 790 previewElement.addClass("sp-clear-display"); 791 } else { 792 var realHex = realColor.toHexString(), 793 realRgb = realColor.toRgbString(); 794 795 // Update the replaced elements background color (with actual selected color) 796 if (rgbaSupport || realColor.alpha === 1) { 797 previewElement.css("background-color", realRgb); 798 } else { 799 previewElement.css("background-color", "transparent"); 800 previewElement.css("filter", realColor.toFilter()); 801 } 802 803 if (opts.showAlpha) { 804 var rgb = realColor.toRgb(); 805 rgb.a = 0; 806 var realAlpha = tinycolor(rgb).toRgbString(); 807 var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")"; 808 809 if (IE) { 810 alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({gradientType: 1}, realHex)); 811 } else { 812 alphaSliderInner.css("background", "-webkit-" + gradient); 813 alphaSliderInner.css("background", "-moz-" + gradient); 814 alphaSliderInner.css("background", "-ms-" + gradient); 815 // Use current syntax gradient on unprefixed property. 816 alphaSliderInner.css("background", 817 "linear-gradient(to right, " + realAlpha + ", " + realHex + ")"); 818 } 819 } 820 821 displayColor = realColor.toString(format); 822 } 823 824 // Update the text entry input as it changes happen 825 if (opts.showInput) { 826 textInput.val(displayColor); 827 } 828 829 if (opts.showPalette) { 830 drawPalette(); 831 } 832 833 drawInitial(); 834 } 835 836 function updateHelperLocations() { 837 var s = currentSaturation; 838 var v = currentValue; 839 840 if (allowEmpty && isEmpty) { 841 //if selected color is empty, hide the helpers 842 alphaSlideHelper.hide(); 843 slideHelper.hide(); 844 dragHelper.hide(); 845 } else { 846 //make sure helpers are visible 847 alphaSlideHelper.show(); 848 slideHelper.show(); 849 dragHelper.show(); 850 851 // Where to show the little circle in that displays your current selected color 852 var dragX = s * dragWidth; 853 var dragY = dragHeight - (v * dragHeight); 854 dragX = Math.max( 855 -dragHelperHeight, 856 Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight) 857 ); 858 dragY = Math.max( 859 -dragHelperHeight, 860 Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight) 861 ); 862 dragHelper.css({ 863 "top": dragY + "px", 864 "left": dragX + "px" 865 }); 866 867 var alphaX = currentAlpha * alphaWidth; 868 alphaSlideHelper.css({ 869 "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px" 870 }); 871 872 // Where to show the bar that displays your current selected hue 873 var slideY = (currentHue) * slideHeight; 874 slideHelper.css({ 875 "top": (slideY - slideHelperHeight) + "px" 876 }); 877 } 878 } 879 880 function updateOriginalInput(fireCallback) { 881 var color = get(), 882 displayColor = '', 883 hasChanged = !tinycolor.equals(color, colorOnShow); 884 885 if (color) { 886 displayColor = color.toString(currentPreferredFormat); 887 // Update the selection palette with the current color 888 addColorToSelectionPalette(color); 889 } 890 891 if (isInput) { 892 boundElement.val(displayColor); 893 } 894 895 if (fireCallback && hasChanged) { 896 callbacks.change(color); 897 boundElement.trigger('change', [color]); 898 } 899 } 900 901 function reflow() { 902 if (!visible) { 903 return; // Calculations would be useless and wouldn't be reliable anyways 904 } 905 dragWidth = dragger.width(); 906 dragHeight = dragger.height(); 907 dragHelperHeight = dragHelper.height(); 908 slideWidth = slider.width(); 909 slideHeight = slider.height(); 910 slideHelperHeight = slideHelper.height(); 911 alphaWidth = alphaSlider.width(); 912 alphaSlideHelperWidth = alphaSlideHelper.width(); 913 914 if (!flat) { 915 container.css("position", "absolute"); 916 if (opts.offset) { 917 container.offset(opts.offset); 918 } else { 919 container.offset(getOffset(container, offsetElement)); 920 } 921 } 922 923 updateHelperLocations(); 924 925 if (opts.showPalette) { 926 drawPalette(); 927 } 928 929 boundElement.trigger('reflow.spectrum'); 930 } 931 932 function destroy() { 933 boundElement.show(); 934 offsetElement.off("click.spectrum touchstart.spectrum"); 935 container.remove(); 936 replacer.remove(); 937 spectrums[spect.id] = null; 938 } 939 940 function option(optionName, optionValue) { 941 if (optionName === undefined) { 942 return $.extend({}, opts); 943 } 944 if (optionValue === undefined) { 945 return opts[optionName]; 946 } 947 948 opts[optionName] = optionValue; 949 950 if (optionName === "preferredFormat") { 951 currentPreferredFormat = opts.preferredFormat; 952 } 953 applyOptions(); 954 } 955 956 function enable() { 957 disabled = false; 958 boundElement.attr("disabled", false); 959 offsetElement.removeClass("sp-disabled"); 960 } 961 962 function disable() { 963 hide(); 964 disabled = true; 965 boundElement.attr("disabled", true); 966 offsetElement.addClass("sp-disabled"); 967 } 968 969 function setOffset(coord) { 970 opts.offset = coord; 971 reflow(); 972 } 973 974 initialize(); 975 976 var spect = { 977 show: show, 978 hide: hide, 979 toggle: toggle, 980 reflow: reflow, 981 option: option, 982 enable: enable, 983 disable: disable, 984 offset: setOffset, 985 set: function (c) { 986 set(c); 987 updateOriginalInput(); 988 }, 989 get: get, 990 destroy: destroy, 991 container: container 992 }; 993 994 spect.id = spectrums.push(spect) - 1; 995 996 return spect; 997 } 998 999 /** 1000 * checkOffset - get the offset below/above and left/right element depending on screen position 1001 * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js 1002 */ 1003 function getOffset(picker, input) { 1004 var extraY = 0; 1005 var dpWidth = picker.outerWidth(); 1006 var dpHeight = picker.outerHeight(); 1007 var inputHeight = input.outerHeight(); 1008 var doc = picker[0].ownerDocument; 1009 var docElem = doc.documentElement; 1010 var viewWidth = docElem.clientWidth + $(doc).scrollLeft(); 1011 var viewHeight = docElem.clientHeight + $(doc).scrollTop(); 1012 var offset = input.offset(); 1013 var offsetLeft = offset.left; 1014 var offsetTop = offset.top; 1015 1016 offsetTop += inputHeight; 1017 1018 offsetLeft -= 1019 Math.min(offsetLeft, (offsetLeft + dpWidth > viewWidth && viewWidth > dpWidth) ? 1020 Math.abs(offsetLeft + dpWidth - viewWidth) : 0); 1021 1022 offsetTop -= 1023 Math.min(offsetTop, ((offsetTop + dpHeight > viewHeight && viewHeight > dpHeight) ? 1024 Math.abs(dpHeight + inputHeight - extraY) : extraY)); 1025 1026 return { 1027 top: offsetTop, 1028 bottom: offset.bottom, 1029 left: offsetLeft, 1030 right: offset.right, 1031 width: offset.width, 1032 height: offset.height 1033 }; 1034 } 1035 1036 /** 1037 * noop - do nothing 1038 */ 1039 function noop() { 1040 1041 } 1042 1043 /** 1044 * stopPropagation - makes the code only doing this a little easier to read in line 1045 */ 1046 function stopPropagation(e) { 1047 e.stopPropagation(); 1048 } 1049 1050 /** 1051 * Create a function bound to a given object 1052 * Thanks to underscore.js 1053 */ 1054 function bind(func, obj) { 1055 var slice = Array.prototype.slice; 1056 var args = slice.call(arguments, 2); 1057 return function () { 1058 return func.apply(obj, args.concat(slice.call(arguments))); 1059 }; 1060 } 1061 1062 /** 1063 * Lightweight drag helper. Handles containment within the element, so that 1064 * when dragging, the x is within [0,element.width] and y is within [0,element.height] 1065 */ 1066 function draggable(element, onmove, onstart, onstop) { 1067 onmove = onmove || function () { 1068 }; 1069 onstart = onstart || function () { 1070 }; 1071 onstop = onstop || function () { 1072 }; 1073 var doc = document; 1074 var dragging = false; 1075 var offset = {}; 1076 var maxHeight = 0; 1077 var maxWidth = 0; 1078 var hasTouch = ('ontouchstart' in window); 1079 1080 var duringDragEvents = {}; 1081 duringDragEvents["selectstart"] = prevent; 1082 duringDragEvents["dragstart"] = prevent; 1083 duringDragEvents["touchmove mousemove"] = move; 1084 duringDragEvents["touchend mouseup"] = stop; 1085 1086 function prevent(e) { 1087 if (e.stopPropagation) { 1088 e.stopPropagation(); 1089 } 1090 if (e.preventDefault) { 1091 e.preventDefault(); 1092 } 1093 e.returnValue = false; 1094 } 1095 1096 function move(e) { 1097 if (dragging) { 1098 // Mouseup happened outside of window 1099 if (IE && doc.documentMode < 9 && !e.button) { 1100 return stop(); 1101 } 1102 1103 var t0 = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0]; 1104 var pageX = t0 && t0.pageX || e.pageX; 1105 var pageY = t0 && t0.pageY || e.pageY; 1106 1107 var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth)); 1108 var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight)); 1109 1110 if (hasTouch) { 1111 // Stop scrolling in iOS 1112 prevent(e); 1113 } 1114 1115 onmove.apply(element, [dragX, dragY, e]); 1116 } 1117 } 1118 1119 function start(e) { 1120 var rightclick = (e.which) ? (e.which == 3) : (e.button == 2); 1121 1122 if (!rightclick && !dragging) { 1123 if (onstart.apply(element, arguments) !== false) { 1124 dragging = true; 1125 maxHeight = $(element).height(); 1126 maxWidth = $(element).width(); 1127 offset = $(element).offset(); 1128 1129 $(doc).on(duringDragEvents); 1130 $(doc.body).addClass("sp-dragging"); 1131 1132 move(e); 1133 1134 prevent(e); 1135 } 1136 } 1137 } 1138 1139 function stop() { 1140 if (dragging) { 1141 $(doc).off(duringDragEvents); 1142 $(doc.body).removeClass("sp-dragging"); 1143 1144 // Wait a tick before notifying observers to allow the click event 1145 // to fire in Chrome. 1146 setTimeout(function () { 1147 onstop.apply(element, arguments); 1148 }, 0); 1149 } 1150 dragging = false; 1151 } 1152 1153 $(element).on("touchstart mousedown", start); 1154 } 1155 1156 function throttle(func, wait, debounce) { 1157 var timeout; 1158 return function () { 1159 var context = this, args = arguments; 1160 var throttler = function () { 1161 timeout = null; 1162 func.apply(context, args); 1163 }; 1164 if (debounce) clearTimeout(timeout); 1165 if (debounce || !timeout) timeout = setTimeout(throttler, wait); 1166 }; 1167 } 1168 1169 function debounce(func, wait, immediate) { 1170 var timeout; 1171 return function () { 1172 var context = this, args = arguments; 1173 var later = function () { 1174 timeout = null; 1175 if (!immediate) func.apply(context, args); 1176 }; 1177 var callNow = immediate && !timeout; 1178 clearTimeout(timeout); 1179 timeout = setTimeout(later, wait); 1180 if (callNow) func.apply(context, args); 1181 }; 1182 }; 1183 1184 function inputTypeColorSupport() { 1185 return $.fn.spectrum.inputTypeColorSupport(); 1186 } 1187 1188 /** 1189 * Define a jQuery plugin 1190 */ 1191 var dataID = "spectrum.id"; 1192 $.fn.spectrum = function (opts, extra) { 1193 1194 if (typeof opts == "string") { 1195 1196 var returnValue = this; 1197 var args = Array.prototype.slice.call(arguments, 1); 1198 1199 this.each(function () { 1200 var spect = spectrums[$(this).data(dataID)]; 1201 if (spect) { 1202 var method = spect[opts]; 1203 if (!method) { 1204 throw new Error("Spectrum: no such method: '" + opts + "'"); 1205 } 1206 1207 if (opts == "get") { 1208 returnValue = spect.get(); 1209 } else if (opts == "container") { 1210 returnValue = spect.container; 1211 } else if (opts == "option") { 1212 returnValue = spect.option.apply(spect, args); 1213 } else if (opts == "destroy") { 1214 spect.destroy(); 1215 $(this).removeData(dataID); 1216 } else { 1217 method.apply(spect, args); 1218 } 1219 } 1220 }); 1221 1222 return returnValue; 1223 } 1224 1225 // Initializing a new instance of spectrum 1226 return this.spectrum("destroy").each(function () { 1227 var options = $.extend({}, $(this).data(), opts); 1228 var spect = spectrum(this, options); 1229 $(this).data(dataID, spect.id); 1230 }); 1231 }; 1232 1233 $.fn.spectrum.load = true; 1234 $.fn.spectrum.loadOpts = {}; 1235 $.fn.spectrum.draggable = draggable; 1236 $.fn.spectrum.defaults = defaultOpts; 1237 $.fn.spectrum.inputTypeColorSupport = function inputTypeColorSupport() { 1238 if (typeof inputTypeColorSupport._cachedResult === "undefined") { 1239 var colorInput = $("<input type='color'/>")[0]; // if color element is supported, value will default to not null 1240 inputTypeColorSupport._cachedResult = colorInput.type === "color" && colorInput.value !== ""; 1241 } 1242 return inputTypeColorSupport._cachedResult; 1243 }; 1244 1245 $.spectrum = {}; 1246 $.spectrum.localization = {}; 1247 $.spectrum.palettes = {}; 1248 1249 $.fn.spectrum.processNativeColorInputs = function () { 1250 var colorInputs = $("input[type=color]"); 1251 if (colorInputs.length && !inputTypeColorSupport()) { 1252 colorInputs.spectrum({ 1253 preferredFormat: "hex6" 1254 }); 1255 } 1256 }; 1257 1258 // TinyColor v1.1.2 1259 // https://github.com/bgrins/TinyColor 1260 // Brian Grinstead, MIT License 1261 1262 (function () { 1263 1264 var trimLeft = /^[\s,#]+/, 1265 trimRight = /\s+$/, 1266 tinyCounter = 0, 1267 math = Math, 1268 mathRound = math.round, 1269 mathMin = math.min, 1270 mathMax = math.max, 1271 mathRandom = math.random; 1272 1273 var tinycolor = function (color, opts) { 1274 1275 color = (color) ? color : ''; 1276 opts = opts || {}; 1277 1278 // If input is already a tinycolor, return itself 1279 if (color instanceof tinycolor) { 1280 return color; 1281 } 1282 // If we are called as a function, call using new instead 1283 if (!(this instanceof tinycolor)) { 1284 return new tinycolor(color, opts); 1285 } 1286 1287 var rgb = inputToRGB(color); 1288 this._originalInput = color, 1289 this._r = rgb.r, 1290 this._g = rgb.g, 1291 this._b = rgb.b, 1292 this._a = rgb.a, 1293 this._roundA = mathRound(1000 * this._a) / 1000, 1294 this._format = opts.format || rgb.format; 1295 this._gradientType = opts.gradientType; 1296 1297 // Don't let the range of [0,255] come back in [0,1]. 1298 // Potentially lose a little bit of precision here, but will fix issues where 1299 // .5 gets interpreted as half of the total, instead of half of 1 1300 // If it was supposed to be 128, this was already taken care of by `inputToRgb` 1301 if (this._r < 1) { 1302 this._r = mathRound(this._r); 1303 } 1304 if (this._g < 1) { 1305 this._g = mathRound(this._g); 1306 } 1307 if (this._b < 1) { 1308 this._b = mathRound(this._b); 1309 } 1310 1311 this._ok = rgb.ok; 1312 this._tc_id = tinyCounter++; 1313 }; 1314 1315 tinycolor.prototype = { 1316 isDark: function () { 1317 return this.getBrightness() < 128; 1318 }, 1319 isLight: function () { 1320 return !this.isDark(); 1321 }, 1322 isValid: function () { 1323 return this._ok; 1324 }, 1325 getOriginalInput: function () { 1326 return this._originalInput; 1327 }, 1328 getFormat: function () { 1329 return this._format; 1330 }, 1331 getAlpha: function () { 1332 return this._a; 1333 }, 1334 getBrightness: function () { 1335 var rgb = this.toRgb(); 1336 return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; 1337 }, 1338 setAlpha: function (value) { 1339 this._a = boundAlpha(value); 1340 this._roundA = mathRound(1000 * this._a) / 1000; 1341 return this; 1342 }, 1343 toHsv: function () { 1344 var hsv = rgbToHsv(this._r, this._g, this._b); 1345 return {h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a}; 1346 }, 1347 toHsvString: function () { 1348 var hsv = rgbToHsv(this._r, this._g, this._b); 1349 var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); 1350 return (this._a == 1) ? 1351 "hsv(" + h + ", " + s + "%, " + v + "%)" : 1352 "hsva(" + h + ", " + s + "%, " + v + "%, " + this._roundA + ")"; 1353 }, 1354 toHsl: function () { 1355 var hsl = rgbToHsl(this._r, this._g, this._b); 1356 return {h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a}; 1357 }, 1358 toHslString: function () { 1359 var hsl = rgbToHsl(this._r, this._g, this._b); 1360 var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); 1361 return (this._a == 1) ? 1362 "hsl(" + h + ", " + s + "%, " + l + "%)" : 1363 "hsla(" + h + ", " + s + "%, " + l + "%, " + this._roundA + ")"; 1364 }, 1365 toHex: function (allow3Char) { 1366 return rgbToHex(this._r, this._g, this._b, allow3Char); 1367 }, 1368 toHexString: function (allow3Char) { 1369 return '#' + this.toHex(allow3Char); 1370 }, 1371 toHex8: function () { 1372 return rgbaToHex(this._r, this._g, this._b, this._a); 1373 }, 1374 toHex8String: function () { 1375 return '#' + this.toHex8(); 1376 }, 1377 toRgb: function () { 1378 return {r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a}; 1379 }, 1380 toRgbString: function () { 1381 return (this._a == 1) ? 1382 "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : 1383 "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")"; 1384 }, 1385 toPercentageRgb: function () { 1386 return { 1387 r: mathRound(bound01(this._r, 255) * 100) + "%", 1388 g: mathRound(bound01(this._g, 255) * 100) + "%", 1389 b: mathRound(bound01(this._b, 255) * 100) + "%", 1390 a: this._a 1391 }; 1392 }, 1393 toPercentageRgbString: function () { 1394 return (this._a == 1) ? 1395 "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : 1396 "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")"; 1397 }, 1398 toName: function () { 1399 if (this._a === 0) { 1400 return "transparent"; 1401 } 1402 1403 if (this._a < 1) { 1404 return false; 1405 } 1406 1407 return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false; 1408 }, 1409 toFilter: function (secondColor) { 1410 var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a); 1411 var secondHex8String = hex8String; 1412 var gradientType = this._gradientType ? "GradientType = 1, " : ""; 1413 1414 if (secondColor) { 1415 var s = tinycolor(secondColor); 1416 secondHex8String = s.toHex8String(); 1417 } 1418 1419 return "progid:DXImageTransform.Microsoft.gradient(" + gradientType + "startColorstr=" + hex8String + ",endColorstr=" + secondHex8String + ")"; 1420 }, 1421 toString: function (format) { 1422 var formatSet = !!format; 1423 format = format || this._format; 1424 1425 var formattedString = false; 1426 var hasAlpha = this._a < 1 && this._a >= 0; 1427 var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name"); 1428 1429 if (needsAlphaFormat) { 1430 // Special case for "transparent", all other non-alpha formats 1431 // will return rgba when there is transparency. 1432 if (format === "name" && this._a === 0) { 1433 return this.toName(); 1434 } 1435 return this.toRgbString(); 1436 } 1437 if (format === "rgb") { 1438 formattedString = this.toRgbString(); 1439 } 1440 if (format === "prgb") { 1441 formattedString = this.toPercentageRgbString(); 1442 } 1443 if (format === "hex" || format === "hex6") { 1444 formattedString = this.toHexString(); 1445 } 1446 if (format === "hex3") { 1447 formattedString = this.toHexString(true); 1448 } 1449 if (format === "hex8") { 1450 formattedString = this.toHex8String(); 1451 } 1452 if (format === "name") { 1453 formattedString = this.toName(); 1454 } 1455 if (format === "hsl") { 1456 formattedString = this.toHslString(); 1457 } 1458 if (format === "hsv") { 1459 formattedString = this.toHsvString(); 1460 } 1461 1462 return formattedString || this.toHexString(); 1463 }, 1464 1465 _applyModification: function (fn, args) { 1466 var color = fn.apply(null, [this].concat([].slice.call(args))); 1467 this._r = color._r; 1468 this._g = color._g; 1469 this._b = color._b; 1470 this.setAlpha(color._a); 1471 return this; 1472 }, 1473 lighten: function () { 1474 return this._applyModification(lighten, arguments); 1475 }, 1476 brighten: function () { 1477 return this._applyModification(brighten, arguments); 1478 }, 1479 darken: function () { 1480 return this._applyModification(darken, arguments); 1481 }, 1482 desaturate: function () { 1483 return this._applyModification(desaturate, arguments); 1484 }, 1485 saturate: function () { 1486 return this._applyModification(saturate, arguments); 1487 }, 1488 greyscale: function () { 1489 return this._applyModification(greyscale, arguments); 1490 }, 1491 spin: function () { 1492 return this._applyModification(spin, arguments); 1493 }, 1494 1495 _applyCombination: function (fn, args) { 1496 return fn.apply(null, [this].concat([].slice.call(args))); 1497 }, 1498 analogous: function () { 1499 return this._applyCombination(analogous, arguments); 1500 }, 1501 complement: function () { 1502 return this._applyCombination(complement, arguments); 1503 }, 1504 monochromatic: function () { 1505 return this._applyCombination(monochromatic, arguments); 1506 }, 1507 splitcomplement: function () { 1508 return this._applyCombination(splitcomplement, arguments); 1509 }, 1510 triad: function () { 1511 return this._applyCombination(triad, arguments); 1512 }, 1513 tetrad: function () { 1514 return this._applyCombination(tetrad, arguments); 1515 } 1516 }; 1517 1518 // If input is an object, force 1 into "1.0" to handle ratios properly 1519 // String input requires "1.0" as input, so 1 will be treated as 1 1520 tinycolor.fromRatio = function (color, opts) { 1521 if (typeof color == "object") { 1522 var newColor = {}; 1523 for (var i in color) { 1524 if (color.hasOwnProperty(i)) { 1525 if (i === "a") { 1526 newColor[i] = color[i]; 1527 } else { 1528 newColor[i] = convertToPercentage(color[i]); 1529 } 1530 } 1531 } 1532 color = newColor; 1533 } 1534 1535 return tinycolor(color, opts); 1536 }; 1537 1538 // Given a string or object, convert that input to RGB 1539 // Possible string inputs: 1540 // 1541 // "red" 1542 // "#f00" or "f00" 1543 // "#ff0000" or "ff0000" 1544 // "#ff000000" or "ff000000" 1545 // "rgb 255 0 0" or "rgb (255, 0, 0)" 1546 // "rgb 1.0 0 0" or "rgb (1, 0, 0)" 1547 // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" 1548 // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" 1549 // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" 1550 // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" 1551 // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" 1552 // 1553 function inputToRGB(color) { 1554 1555 var rgb = {r: 0, g: 0, b: 0}; 1556 var a = 1; 1557 var ok = false; 1558 var format = false; 1559 1560 if (typeof color == "string") { 1561 color = stringInputToObject(color); 1562 } 1563 1564 if (typeof color == "object") { 1565 if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { 1566 rgb = rgbToRgb(color.r, color.g, color.b); 1567 ok = true; 1568 format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; 1569 } else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { 1570 color.s = convertToPercentage(color.s); 1571 color.v = convertToPercentage(color.v); 1572 rgb = hsvToRgb(color.h, color.s, color.v); 1573 ok = true; 1574 format = "hsv"; 1575 } else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { 1576 color.s = convertToPercentage(color.s); 1577 color.l = convertToPercentage(color.l); 1578 rgb = hslToRgb(color.h, color.s, color.l); 1579 ok = true; 1580 format = "hsl"; 1581 } 1582 1583 if (color.hasOwnProperty("a")) { 1584 a = color.a; 1585 } 1586 } 1587 1588 a = boundAlpha(a); 1589 1590 return { 1591 ok: ok, 1592 format: color.format || format, 1593 r: mathMin(255, mathMax(rgb.r, 0)), 1594 g: mathMin(255, mathMax(rgb.g, 0)), 1595 b: mathMin(255, mathMax(rgb.b, 0)), 1596 a: a 1597 }; 1598 } 1599 1600 1601 // Conversion Functions 1602 // -------------------- 1603 1604 // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: 1605 // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript> 1606 1607 // `rgbToRgb` 1608 // Handle bounds / percentage checking to conform to CSS color spec 1609 // <http://www.w3.org/TR/css3-color/> 1610 // *Assumes:* r, g, b in [0, 255] or [0, 1] 1611 // *Returns:* { r, g, b } in [0, 255] 1612 function rgbToRgb(r, g, b) { 1613 return { 1614 r: bound01(r, 255) * 255, 1615 g: bound01(g, 255) * 255, 1616 b: bound01(b, 255) * 255 1617 }; 1618 } 1619 1620 // `rgbToHsl` 1621 // Converts an RGB color value to HSL. 1622 // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] 1623 // *Returns:* { h, s, l } in [0,1] 1624 function rgbToHsl(r, g, b) { 1625 1626 r = bound01(r, 255); 1627 g = bound01(g, 255); 1628 b = bound01(b, 255); 1629 1630 var max = mathMax(r, g, b), min = mathMin(r, g, b); 1631 var h, s, l = (max + min) / 2; 1632 1633 if (max == min) { 1634 h = s = 0; // achromatic 1635 } else { 1636 var d = max - min; 1637 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 1638 switch (max) { 1639 case r: 1640 h = (g - b) / d + (g < b ? 6 : 0); 1641 break; 1642 case g: 1643 h = (b - r) / d + 2; 1644 break; 1645 case b: 1646 h = (r - g) / d + 4; 1647 break; 1648 } 1649 1650 h /= 6; 1651 } 1652 1653 return {h: h, s: s, l: l}; 1654 } 1655 1656 // `hslToRgb` 1657 // Converts an HSL color value to RGB. 1658 // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] 1659 // *Returns:* { r, g, b } in the set [0, 255] 1660 function hslToRgb(h, s, l) { 1661 var r, g, b; 1662 1663 h = bound01(h, 360); 1664 s = bound01(s, 100); 1665 l = bound01(l, 100); 1666 1667 function hue2rgb(p, q, t) { 1668 if (t < 0) t += 1; 1669 if (t > 1) t -= 1; 1670 if (t < 1 / 6) return p + (q - p) * 6 * t; 1671 if (t < 1 / 2) return q; 1672 if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; 1673 return p; 1674 } 1675 1676 if (s === 0) { 1677 r = g = b = l; // achromatic 1678 } else { 1679 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 1680 var p = 2 * l - q; 1681 r = hue2rgb(p, q, h + 1 / 3); 1682 g = hue2rgb(p, q, h); 1683 b = hue2rgb(p, q, h - 1 / 3); 1684 } 1685 1686 return {r: r * 255, g: g * 255, b: b * 255}; 1687 } 1688 1689 // `rgbToHsv` 1690 // Converts an RGB color value to HSV 1691 // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] 1692 // *Returns:* { h, s, v } in [0,1] 1693 function rgbToHsv(r, g, b) { 1694 1695 r = bound01(r, 255); 1696 g = bound01(g, 255); 1697 b = bound01(b, 255); 1698 1699 var max = mathMax(r, g, b), min = mathMin(r, g, b); 1700 var h, s, v = max; 1701 1702 var d = max - min; 1703 s = max === 0 ? 0 : d / max; 1704 1705 if (max == min) { 1706 h = 0; // achromatic 1707 } else { 1708 switch (max) { 1709 case r: 1710 h = (g - b) / d + (g < b ? 6 : 0); 1711 break; 1712 case g: 1713 h = (b - r) / d + 2; 1714 break; 1715 case b: 1716 h = (r - g) / d + 4; 1717 break; 1718 } 1719 h /= 6; 1720 } 1721 return {h: h, s: s, v: v}; 1722 } 1723 1724 // `hsvToRgb` 1725 // Converts an HSV color value to RGB. 1726 // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] 1727 // *Returns:* { r, g, b } in the set [0, 255] 1728 function hsvToRgb(h, s, v) { 1729 1730 h = bound01(h, 360) * 6; 1731 s = bound01(s, 100); 1732 v = bound01(v, 100); 1733 1734 var i = math.floor(h), 1735 f = h - i, 1736 p = v * (1 - s), 1737 q = v * (1 - f * s), 1738 t = v * (1 - (1 - f) * s), 1739 mod = i % 6, 1740 r = [v, q, p, p, t, v][mod], 1741 g = [t, v, v, q, p, p][mod], 1742 b = [p, p, t, v, v, q][mod]; 1743 1744 return {r: r * 255, g: g * 255, b: b * 255}; 1745 } 1746 1747 // `rgbToHex` 1748 // Converts an RGB color to hex 1749 // Assumes r, g, and b are contained in the set [0, 255] 1750 // Returns a 3 or 6 character hex 1751 function rgbToHex(r, g, b, allow3Char) { 1752 1753 var hex = [ 1754 pad2(mathRound(r).toString(16)), 1755 pad2(mathRound(g).toString(16)), 1756 pad2(mathRound(b).toString(16)) 1757 ]; 1758 1759 // Return a 3 character hex if possible 1760 if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { 1761 return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); 1762 } 1763 1764 return hex.join(""); 1765 } 1766 1767 // `rgbaToHex` 1768 // Converts an RGBA color plus alpha transparency to hex 1769 // Assumes r, g, b and a are contained in the set [0, 255] 1770 // Returns an 8 character hex 1771 function rgbaToHex(r, g, b, a) { 1772 1773 var hex = [ 1774 pad2(convertDecimalToHex(a)), 1775 pad2(mathRound(r).toString(16)), 1776 pad2(mathRound(g).toString(16)), 1777 pad2(mathRound(b).toString(16)) 1778 ]; 1779 1780 return hex.join(""); 1781 } 1782 1783 // `equals` 1784 // Can be called with any tinycolor input 1785 tinycolor.equals = function (color1, color2) { 1786 if (!color1 || !color2) { 1787 return false; 1788 } 1789 return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); 1790 }; 1791 tinycolor.random = function () { 1792 return tinycolor.fromRatio({ 1793 r: mathRandom(), 1794 g: mathRandom(), 1795 b: mathRandom() 1796 }); 1797 }; 1798 1799 1800 // Modification Functions 1801 // ---------------------- 1802 // Thanks to less.js for some of the basics here 1803 // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js> 1804 1805 function desaturate(color, amount) { 1806 amount = (amount === 0) ? 0 : (amount || 10); 1807 var hsl = tinycolor(color).toHsl(); 1808 hsl.s -= amount / 100; 1809 hsl.s = clamp01(hsl.s); 1810 return tinycolor(hsl); 1811 } 1812 1813 function saturate(color, amount) { 1814 amount = (amount === 0) ? 0 : (amount || 10); 1815 var hsl = tinycolor(color).toHsl(); 1816 hsl.s += amount / 100; 1817 hsl.s = clamp01(hsl.s); 1818 return tinycolor(hsl); 1819 } 1820 1821 function greyscale(color) { 1822 return tinycolor(color).desaturate(100); 1823 } 1824 1825 function lighten(color, amount) { 1826 amount = (amount === 0) ? 0 : (amount || 10); 1827 var hsl = tinycolor(color).toHsl(); 1828 hsl.l += amount / 100; 1829 hsl.l = clamp01(hsl.l); 1830 return tinycolor(hsl); 1831 } 1832 1833 function brighten(color, amount) { 1834 amount = (amount === 0) ? 0 : (amount || 10); 1835 var rgb = tinycolor(color).toRgb(); 1836 rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * -(amount / 100)))); 1837 rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * -(amount / 100)))); 1838 rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * -(amount / 100)))); 1839 return tinycolor(rgb); 1840 } 1841 1842 function darken(color, amount) { 1843 amount = (amount === 0) ? 0 : (amount || 10); 1844 var hsl = tinycolor(color).toHsl(); 1845 hsl.l -= amount / 100; 1846 hsl.l = clamp01(hsl.l); 1847 return tinycolor(hsl); 1848 } 1849 1850 // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. 1851 // Values outside of this range will be wrapped into this range. 1852 function spin(color, amount) { 1853 var hsl = tinycolor(color).toHsl(); 1854 var hue = (mathRound(hsl.h) + amount) % 360; 1855 hsl.h = hue < 0 ? 360 + hue : hue; 1856 return tinycolor(hsl); 1857 } 1858 1859 // Combination Functions 1860 // --------------------- 1861 // Thanks to jQuery xColor for some of the ideas behind these 1862 // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js> 1863 1864 function complement(color) { 1865 var hsl = tinycolor(color).toHsl(); 1866 hsl.h = (hsl.h + 180) % 360; 1867 return tinycolor(hsl); 1868 } 1869 1870 function triad(color) { 1871 var hsl = tinycolor(color).toHsl(); 1872 var h = hsl.h; 1873 return [ 1874 tinycolor(color), 1875 tinycolor({h: (h + 120) % 360, s: hsl.s, l: hsl.l}), 1876 tinycolor({h: (h + 240) % 360, s: hsl.s, l: hsl.l}) 1877 ]; 1878 } 1879 1880 function tetrad(color) { 1881 var hsl = tinycolor(color).toHsl(); 1882 var h = hsl.h; 1883 return [ 1884 tinycolor(color), 1885 tinycolor({h: (h + 90) % 360, s: hsl.s, l: hsl.l}), 1886 tinycolor({h: (h + 180) % 360, s: hsl.s, l: hsl.l}), 1887 tinycolor({h: (h + 270) % 360, s: hsl.s, l: hsl.l}) 1888 ]; 1889 } 1890 1891 function splitcomplement(color) { 1892 var hsl = tinycolor(color).toHsl(); 1893 var h = hsl.h; 1894 return [ 1895 tinycolor(color), 1896 tinycolor({h: (h + 72) % 360, s: hsl.s, l: hsl.l}), 1897 tinycolor({h: (h + 216) % 360, s: hsl.s, l: hsl.l}) 1898 ]; 1899 } 1900 1901 function analogous(color, results, slices) { 1902 results = results || 6; 1903 slices = slices || 30; 1904 1905 var hsl = tinycolor(color).toHsl(); 1906 var part = 360 / slices; 1907 var ret = [tinycolor(color)]; 1908 1909 for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results;) { 1910 hsl.h = (hsl.h + part) % 360; 1911 ret.push(tinycolor(hsl)); 1912 } 1913 return ret; 1914 } 1915 1916 function monochromatic(color, results) { 1917 results = results || 6; 1918 var hsv = tinycolor(color).toHsv(); 1919 var h = hsv.h, s = hsv.s, v = hsv.v; 1920 var ret = []; 1921 var modification = 1 / results; 1922 1923 while (results--) { 1924 ret.push(tinycolor({h: h, s: s, v: v})); 1925 v = (v + modification) % 1; 1926 } 1927 1928 return ret; 1929 } 1930 1931 // Utility Functions 1932 // --------------------- 1933 1934 tinycolor.mix = function (color1, color2, amount) { 1935 amount = (amount === 0) ? 0 : (amount || 50); 1936 1937 var rgb1 = tinycolor(color1).toRgb(); 1938 var rgb2 = tinycolor(color2).toRgb(); 1939 1940 var p = amount / 100; 1941 var w = p * 2 - 1; 1942 var a = rgb2.a - rgb1.a; 1943 1944 var w1; 1945 1946 if (w * a == -1) { 1947 w1 = w; 1948 } else { 1949 w1 = (w + a) / (1 + w * a); 1950 } 1951 1952 w1 = (w1 + 1) / 2; 1953 1954 var w2 = 1 - w1; 1955 1956 var rgba = { 1957 r: rgb2.r * w1 + rgb1.r * w2, 1958 g: rgb2.g * w1 + rgb1.g * w2, 1959 b: rgb2.b * w1 + rgb1.b * w2, 1960 a: rgb2.a * p + rgb1.a * (1 - p) 1961 }; 1962 1963 return tinycolor(rgba); 1964 }; 1965 1966 1967 // Readability Functions 1968 // --------------------- 1969 // <http://www.w3.org/TR/AERT#color-contrast> 1970 1971 // `readability` 1972 // Analyze the 2 colors and returns an object with the following properties: 1973 // `brightness`: difference in brightness between the two colors 1974 // `color`: difference in color/hue between the two colors 1975 tinycolor.readability = function (color1, color2) { 1976 var c1 = tinycolor(color1); 1977 var c2 = tinycolor(color2); 1978 var rgb1 = c1.toRgb(); 1979 var rgb2 = c2.toRgb(); 1980 var brightnessA = c1.getBrightness(); 1981 var brightnessB = c2.getBrightness(); 1982 var colorDiff = ( 1983 Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) + 1984 Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) + 1985 Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b) 1986 ); 1987 1988 return { 1989 brightness: Math.abs(brightnessA - brightnessB), 1990 color: colorDiff 1991 }; 1992 }; 1993 1994 // `readable` 1995 // http://www.w3.org/TR/AERT#color-contrast 1996 // Ensure that foreground and background color combinations provide sufficient contrast. 1997 // *Example* 1998 // tinycolor.isReadable("#000", "#111") => false 1999 tinycolor.isReadable = function (color1, color2) { 2000 var readability = tinycolor.readability(color1, color2); 2001 return readability.brightness > 125 && readability.color > 500; 2002 }; 2003 2004 // `mostReadable` 2005 // Given a base color and a list of possible foreground or background 2006 // colors for that base, returns the most readable color. 2007 // *Example* 2008 // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000" 2009 tinycolor.mostReadable = function (baseColor, colorList) { 2010 var bestColor = null; 2011 var bestScore = 0; 2012 var bestIsReadable = false; 2013 for (var i = 0; i < colorList.length; i++) { 2014 2015 // We normalize both around the "acceptable" breaking point, 2016 // but rank brightness constrast higher than hue. 2017 2018 var readability = tinycolor.readability(baseColor, colorList[i]); 2019 var readable = readability.brightness > 125 && readability.color > 500; 2020 var score = 3 * (readability.brightness / 125) + (readability.color / 500); 2021 2022 if ((readable && !bestIsReadable) || 2023 (readable && bestIsReadable && score > bestScore) || 2024 ((!readable) && (!bestIsReadable) && score > bestScore)) { 2025 bestIsReadable = readable; 2026 bestScore = score; 2027 bestColor = tinycolor(colorList[i]); 2028 } 2029 } 2030 return bestColor; 2031 }; 2032 2033 2034 // Big List of Colors 2035 // ------------------ 2036 // <http://www.w3.org/TR/css3-color/#svg-color> 2037 var names = tinycolor.names = { 2038 aliceblue: "f0f8ff", 2039 antiquewhite: "faebd7", 2040 aqua: "0ff", 2041 aquamarine: "7fffd4", 2042 azure: "f0ffff", 2043 beige: "f5f5dc", 2044 bisque: "ffe4c4", 2045 black: "000", 2046 blanchedalmond: "ffebcd", 2047 blue: "00f", 2048 blueviolet: "8a2be2", 2049 brown: "a52a2a", 2050 burlywood: "deb887", 2051 burntsienna: "ea7e5d", 2052 cadetblue: "5f9ea0", 2053 chartreuse: "7fff00", 2054 chocolate: "d2691e", 2055 coral: "ff7f50", 2056 cornflowerblue: "6495ed", 2057 cornsilk: "fff8dc", 2058 crimson: "dc143c", 2059 cyan: "0ff", 2060 darkblue: "00008b", 2061 darkcyan: "008b8b", 2062 darkgoldenrod: "b8860b", 2063 darkgray: "a9a9a9", 2064 darkgreen: "006400", 2065 darkgrey: "a9a9a9", 2066 darkkhaki: "bdb76b", 2067 darkmagenta: "8b008b", 2068 darkolivegreen: "556b2f", 2069 darkorange: "ff8c00", 2070 darkorchid: "9932cc", 2071 darkred: "8b0000", 2072 darksalmon: "e9967a", 2073 darkseagreen: "8fbc8f", 2074 darkslateblue: "483d8b", 2075 darkslategray: "2f4f4f", 2076 darkslategrey: "2f4f4f", 2077 darkturquoise: "00ced1", 2078 darkviolet: "9400d3", 2079 deeppink: "ff1493", 2080 deepskyblue: "00bfff", 2081 dimgray: "696969", 2082 dimgrey: "696969", 2083 dodgerblue: "1e90ff", 2084 firebrick: "b22222", 2085 floralwhite: "fffaf0", 2086 forestgreen: "228b22", 2087 fuchsia: "f0f", 2088 gainsboro: "dcdcdc", 2089 ghostwhite: "f8f8ff", 2090 gold: "ffd700", 2091 goldenrod: "daa520", 2092 gray: "808080", 2093 green: "008000", 2094 greenyellow: "adff2f", 2095 grey: "808080", 2096 honeydew: "f0fff0", 2097 hotpink: "ff69b4", 2098 indianred: "cd5c5c", 2099 indigo: "4b0082", 2100 ivory: "fffff0", 2101 khaki: "f0e68c", 2102 lavender: "e6e6fa", 2103 lavenderblush: "fff0f5", 2104 lawngreen: "7cfc00", 2105 lemonchiffon: "fffacd", 2106 lightblue: "add8e6", 2107 lightcoral: "f08080", 2108 lightcyan: "e0ffff", 2109 lightgoldenrodyellow: "fafad2", 2110 lightgray: "d3d3d3", 2111 lightgreen: "90ee90", 2112 lightgrey: "d3d3d3", 2113 lightpink: "ffb6c1", 2114 lightsalmon: "ffa07a", 2115 lightseagreen: "20b2aa", 2116 lightskyblue: "87cefa", 2117 lightslategray: "789", 2118 lightslategrey: "789", 2119 lightsteelblue: "b0c4de", 2120 lightyellow: "ffffe0", 2121 lime: "0f0", 2122 limegreen: "32cd32", 2123 linen: "faf0e6", 2124 magenta: "f0f", 2125 maroon: "800000", 2126 mediumaquamarine: "66cdaa", 2127 mediumblue: "0000cd", 2128 mediumorchid: "ba55d3", 2129 mediumpurple: "9370db", 2130 mediumseagreen: "3cb371", 2131 mediumslateblue: "7b68ee", 2132 mediumspringgreen: "00fa9a", 2133 mediumturquoise: "48d1cc", 2134 mediumvioletred: "c71585", 2135 midnightblue: "191970", 2136 mintcream: "f5fffa", 2137 mistyrose: "ffe4e1", 2138 moccasin: "ffe4b5", 2139 navajowhite: "ffdead", 2140 navy: "000080", 2141 oldlace: "fdf5e6", 2142 olive: "808000", 2143 olivedrab: "6b8e23", 2144 orange: "ffa500", 2145 orangered: "ff4500", 2146 orchid: "da70d6", 2147 palegoldenrod: "eee8aa", 2148 palegreen: "98fb98", 2149 paleturquoise: "afeeee", 2150 palevioletred: "db7093", 2151 papayawhip: "ffefd5", 2152 peachpuff: "ffdab9", 2153 peru: "cd853f", 2154 pink: "ffc0cb", 2155 plum: "dda0dd", 2156 powderblue: "b0e0e6", 2157 purple: "800080", 2158 rebeccapurple: "663399", 2159 red: "f00", 2160 rosybrown: "bc8f8f", 2161 royalblue: "4169e1", 2162 saddlebrown: "8b4513", 2163 salmon: "fa8072", 2164 sandybrown: "f4a460", 2165 seagreen: "2e8b57", 2166 seashell: "fff5ee", 2167 sienna: "a0522d", 2168 silver: "c0c0c0", 2169 skyblue: "87ceeb", 2170 slateblue: "6a5acd", 2171 slategray: "708090", 2172 slategrey: "708090", 2173 snow: "fffafa", 2174 springgreen: "00ff7f", 2175 steelblue: "4682b4", 2176 tan: "d2b48c", 2177 teal: "008080", 2178 thistle: "d8bfd8", 2179 tomato: "ff6347", 2180 turquoise: "40e0d0", 2181 violet: "ee82ee", 2182 wheat: "f5deb3", 2183 white: "fff", 2184 whitesmoke: "f5f5f5", 2185 yellow: "ff0", 2186 yellowgreen: "9acd32" 2187 }; 2188 2189 // Make it easy to access colors via `hexNames[hex]` 2190 var hexNames = tinycolor.hexNames = flip(names); 2191 2192 2193 // Utilities 2194 // --------- 2195 2196 // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` 2197 function flip(o) { 2198 var flipped = {}; 2199 for (var i in o) { 2200 if (o.hasOwnProperty(i)) { 2201 flipped[o[i]] = i; 2202 } 2203 } 2204 return flipped; 2205 } 2206 2207 // Return a valid alpha value [0,1] with all invalid values being set to 1 2208 function boundAlpha(a) { 2209 a = parseFloat(a); 2210 2211 if (isNaN(a) || a < 0 || a > 1) { 2212 a = 1; 2213 } 2214 2215 return a; 2216 } 2217 2218 // Take input from [0, n] and return it as [0, 1] 2219 function bound01(n, max) { 2220 if (isOnePointZero(n)) { 2221 n = "100%"; 2222 } 2223 2224 var processPercent = isPercentage(n); 2225 n = mathMin(max, mathMax(0, parseFloat(n))); 2226 2227 // Automatically convert percentage into number 2228 if (processPercent) { 2229 n = parseInt(n * max, 10) / 100; 2230 } 2231 2232 // Handle floating point rounding errors 2233 if ((math.abs(n - max) < 0.000001)) { 2234 return 1; 2235 } 2236 2237 // Convert into [0, 1] range if it isn't already 2238 return (n % max) / parseFloat(max); 2239 } 2240 2241 // Force a number between 0 and 1 2242 function clamp01(val) { 2243 return mathMin(1, mathMax(0, val)); 2244 } 2245 2246 // Parse a base-16 hex value into a base-10 integer 2247 function parseIntFromHex(val) { 2248 return parseInt(val, 16); 2249 } 2250 2251 // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 2252 // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0> 2253 function isOnePointZero(n) { 2254 return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; 2255 } 2256 2257 // Check to see if string passed in is a percentage 2258 function isPercentage(n) { 2259 return typeof n === "string" && n.indexOf('%') != -1; 2260 } 2261 2262 // Force a hex value to have 2 characters 2263 function pad2(c) { 2264 return c.length == 1 ? '0' + c : '' + c; 2265 } 2266 2267 // Replace a decimal with it's percentage value 2268 function convertToPercentage(n) { 2269 if (n <= 1) { 2270 n = (n * 100) + "%"; 2271 } 2272 2273 return n; 2274 } 2275 2276 // Converts a decimal to a hex value 2277 function convertDecimalToHex(d) { 2278 return Math.round(parseFloat(d) * 255).toString(16); 2279 } 2280 2281 // Converts a hex value to a decimal 2282 function convertHexToDecimal(h) { 2283 return (parseIntFromHex(h) / 255); 2284 } 2285 2286 var matchers = (function () { 2287 2288 // <http://www.w3.org/TR/css3-values/#integers> 2289 var CSS_INTEGER = "[-\\+]?\\d+%?"; 2290 2291 // <http://www.w3.org/TR/css3-values/#number-value> 2292 var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; 2293 2294 // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. 2295 var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; 2296 2297 // Actual matching. 2298 // Parentheses and commas are optional, but not required. 2299 // Whitespace can take the place of commas or opening paren 2300 var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 2301 var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 2302 2303 return { 2304 rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), 2305 rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), 2306 hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), 2307 hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), 2308 hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), 2309 hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), 2310 hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, 2311 hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, 2312 hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ 2313 }; 2314 })(); 2315 2316 // `stringInputToObject` 2317 // Permissive string parsing. Take in a number of formats, and output an object 2318 // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` 2319 function stringInputToObject(color) { 2320 2321 color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase(); 2322 var named = false; 2323 if (names[color]) { 2324 color = names[color]; 2325 named = true; 2326 } else if (color == 'transparent') { 2327 return {r: 0, g: 0, b: 0, a: 0, format: "name"}; 2328 } 2329 2330 // Try to match string input using regular expressions. 2331 // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] 2332 // Just return an object and let the conversion functions handle that. 2333 // This way the result will be the same whether the tinycolor is initialized with string or object. 2334 var match; 2335 if ((match = matchers.rgb.exec(color))) { 2336 return {r: match[1], g: match[2], b: match[3]}; 2337 } 2338 if ((match = matchers.rgba.exec(color))) { 2339 return {r: match[1], g: match[2], b: match[3], a: match[4]}; 2340 } 2341 if ((match = matchers.hsl.exec(color))) { 2342 return {h: match[1], s: match[2], l: match[3]}; 2343 } 2344 if ((match = matchers.hsla.exec(color))) { 2345 return {h: match[1], s: match[2], l: match[3], a: match[4]}; 2346 } 2347 if ((match = matchers.hsv.exec(color))) { 2348 return {h: match[1], s: match[2], v: match[3]}; 2349 } 2350 if ((match = matchers.hsva.exec(color))) { 2351 return {h: match[1], s: match[2], v: match[3], a: match[4]}; 2352 } 2353 if ((match = matchers.hex8.exec(color))) { 2354 return { 2355 a: convertHexToDecimal(match[1]), 2356 r: parseIntFromHex(match[2]), 2357 g: parseIntFromHex(match[3]), 2358 b: parseIntFromHex(match[4]), 2359 format: named ? "name" : "hex8" 2360 }; 2361 } 2362 if ((match = matchers.hex6.exec(color))) { 2363 return { 2364 r: parseIntFromHex(match[1]), 2365 g: parseIntFromHex(match[2]), 2366 b: parseIntFromHex(match[3]), 2367 format: named ? "name" : "hex" 2368 }; 2369 } 2370 if ((match = matchers.hex3.exec(color))) { 2371 return { 2372 r: parseIntFromHex(match[1] + '' + match[1]), 2373 g: parseIntFromHex(match[2] + '' + match[2]), 2374 b: parseIntFromHex(match[3] + '' + match[3]), 2375 format: named ? "name" : "hex" 2376 }; 2377 } 2378 2379 return false; 2380 } 2381 2382 window.tinycolor = tinycolor; 2383 })(); 2384 2385 $(function () { 2386 if ($.fn.spectrum.load) { 2387 $.fn.spectrum.processNativeColorInputs(); 2388 } 2389 }); 2390 2391 2392 });