1 // Spectrum Colorpicker v1.2.0
2 // https://github.com/bgrins/spectrum
3 // Author: Brian Grinstead
6 (function (window, $, undefined) {
22 clickoutFiresChange: false,
25 showPaletteOnly: false,
26 showSelectionPalette: true,
27 localStorageKey: false,
32 preferredFormat: false,
36 palette: ['fff', '000'],
41 IE = !!/msie/i.exec( window.navigator.userAgent ),
42 rgbaSupport = (function() {
43 function contains( str, substr ) {
44 return !!~('' + str).indexOf(substr);
47 var elem = document.createElement('div');
48 var style = elem.style;
49 style.cssText = 'background-color:rgba(0,0,0,.5)';
50 return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
52 inputTypeColorSupport = (function() {
53 var colorInput = $("<input type='color' value='!' />")[0];
54 return colorInput.type === "color" && colorInput.value !== "!";
57 "<div class='sp-replacer'>",
58 "<div class='sp-preview'><div class='sp-preview-inner'></div></div>",
59 "<div class='sp-dd'>▼</div>",
62 markup = (function () {
64 // IE does not support gradients with multiple stops, so we need to simulate
65 // that for the rainbow slider with 8 divs that each have a single gradient
68 for (var i = 1; i <= 6; i++) {
69 gradientFix += "<div class='sp-" + i + "'></div>";
74 "<div class='sp-container sp-hidden'>",
75 "<div class='sp-palette-container'>",
76 "<div class='sp-palette sp-thumb sp-cf'></div>",
78 "<div class='sp-picker-container'>",
79 "<div class='sp-top sp-cf'>",
80 "<div class='sp-fill'></div>",
81 "<div class='sp-top-inner'>",
82 "<div class='sp-color'>",
83 "<div class='sp-sat'>",
84 "<div class='sp-val'>",
85 "<div class='sp-dragger'></div>",
89 "<div class='sp-clear sp-clear-display' title='Clear Color Selection'>",
91 "<div class='sp-hue'>",
92 "<div class='sp-slider'></div>",
96 "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>",
98 "<div class='sp-input-container sp-cf'>",
99 "<input class='sp-input' type='text' spellcheck='false' />",
101 "<div class='sp-initial sp-thumb sp-cf'></div>",
102 "<div class='sp-button-container sp-cf'>",
103 "<a class='sp-cancel' href='#'></a>",
104 "<button class='sp-choose'></button>",
111 function paletteTemplate (p, color, className) {
113 for (var i = 0; i < p.length; i++) {
116 var tiny = tinycolor(current);
117 var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light";
118 c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : "";
120 var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter();
121 html.push('<span title="' + tiny.toRgbString() + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" /></span>');
123 var cls = 'sp-clear-display';
124 html.push('<span title="No Color Selected" data-color="" style="background-color:transparent;" class="' + cls + '"></span>');
127 return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>";
131 for (var i = 0; i < spectrums.length; i++) {
138 function instanceOptions(o, callbackContext) {
139 var opts = $.extend({}, defaultOpts, o);
141 'move': bind(opts.move, callbackContext),
142 'change': bind(opts.change, callbackContext),
143 'show': bind(opts.show, callbackContext),
144 'hide': bind(opts.hide, callbackContext),
145 'beforeShow': bind(opts.beforeShow, callbackContext)
151 function spectrum(element, o) {
153 var opts = instanceOptions(o, element),
155 showSelectionPalette = opts.showSelectionPalette,
156 localStorageKey = opts.localStorageKey,
158 callbacks = opts.callbacks,
159 resize = throttle(reflow, 10),
163 dragHelperHeight = 0,
167 alphaSlideHelperWidth = 0,
168 slideHelperHeight = 0,
170 currentSaturation = 0,
173 palette = opts.palette.slice(0),
174 paletteArray = $.isArray(palette[0]) ? palette : [palette],
175 selectionPalette = opts.selectionPalette.slice(0),
176 maxSelectionSize = opts.maxSelectionSize,
177 draggingClass = "sp-dragging",
178 shiftMovementDirection = null;
180 var doc = element.ownerDocument,
182 boundElement = $(element),
184 container = $(markup, doc).addClass(theme),
185 dragger = container.find(".sp-color"),
186 dragHelper = container.find(".sp-dragger"),
187 slider = container.find(".sp-hue"),
188 slideHelper = container.find(".sp-slider"),
189 alphaSliderInner = container.find(".sp-alpha-inner"),
190 alphaSlider = container.find(".sp-alpha"),
191 alphaSlideHelper = container.find(".sp-alpha-handle"),
192 textInput = container.find(".sp-input"),
193 paletteContainer = container.find(".sp-palette"),
194 initialColorContainer = container.find(".sp-initial"),
195 cancelButton = container.find(".sp-cancel"),
196 clearButton = container.find(".sp-clear"),
197 chooseButton = container.find(".sp-choose"),
198 isInput = boundElement.is("input"),
199 isInputTypeColor = isInput && inputTypeColorSupport && boundElement.attr("type") === "color",
200 shouldReplace = isInput && !flat,
201 replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className) : $([]),
202 offsetElement = (shouldReplace) ? replacer : boundElement,
203 previewElement = replacer.find(".sp-preview-inner"),
204 initialColor = opts.color || (isInput && boundElement.val()),
206 preferredFormat = opts.preferredFormat,
207 currentPreferredFormat = preferredFormat,
208 clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange,
209 isEmpty = !initialColor,
210 allowEmpty = opts.allowEmpty && !isInputTypeColor;
212 function applyOptions() {
214 if (opts.showPaletteOnly) {
215 opts.showPalette = true;
218 container.toggleClass("sp-flat", flat);
219 container.toggleClass("sp-input-disabled", !opts.showInput);
220 container.toggleClass("sp-alpha-enabled", opts.showAlpha);
221 container.toggleClass("sp-clear-enabled", allowEmpty);
222 container.toggleClass("sp-buttons-disabled", !opts.showButtons);
223 container.toggleClass("sp-palette-disabled", !opts.showPalette);
224 container.toggleClass("sp-palette-only", opts.showPaletteOnly);
225 container.toggleClass("sp-initial-disabled", !opts.showInitial);
226 container.addClass(opts.className);
231 function initialize() {
234 container.find("*:not(input)").attr("unselectable", "on");
240 boundElement.after(replacer).hide();
248 boundElement.after(container).hide();
252 var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo);
253 if (appendTo.length !== 1) {
254 appendTo = $("body");
257 appendTo.append(container);
260 if (localStorageKey && window.localStorage) {
262 // Migrate old palettes over to new format. May want to remove this eventually.
264 var oldPalette = window.localStorage[localStorageKey].split(",#");
265 if (oldPalette.length > 1) {
266 delete window.localStorage[localStorageKey];
267 $.each(oldPalette, function(i, c) {
268 addColorToSelectionPalette(c);
275 selectionPalette = window.localStorage[localStorageKey].split(";");
280 offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
287 if (!$(e.target).is("input")) {
292 if(boundElement.is(":disabled") || (opts.disabled === true)) {
296 // Prevent clicks from bubbling up to document. This would cause it to be hidden.
297 container.click(stopPropagation);
299 // Handle user typed input
300 textInput.change(setFromTextInput);
301 textInput.bind("paste", function () {
302 setTimeout(setFromTextInput, 1);
304 textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } });
306 cancelButton.text(opts.cancelText);
307 cancelButton.bind("click.spectrum", function (e) {
314 clearButton.bind("click.spectrum", function (e) {
322 //for the flat style, this is a change event
323 updateOriginalInput(true);
328 chooseButton.text(opts.chooseText);
329 chooseButton.bind("click.spectrum", function (e) {
334 updateOriginalInput(true);
339 draggable(alphaSlider, function (dragX, dragY, e) {
340 currentAlpha = (dragX / alphaWidth);
343 currentAlpha = Math.round(currentAlpha * 10) / 10;
349 draggable(slider, function (dragX, dragY) {
350 currentHue = parseFloat(dragY / slideHeight);
353 }, dragStart, dragStop);
355 draggable(dragger, function (dragX, dragY, e) {
357 // shift+drag should snap the movement to either the x or y axis.
359 shiftMovementDirection = null;
361 else if (!shiftMovementDirection) {
362 var oldDragX = currentSaturation * dragWidth;
363 var oldDragY = dragHeight - (currentValue * dragHeight);
364 var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY);
366 shiftMovementDirection = furtherFromX ? "x" : "y";
369 var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x";
370 var setValue = !shiftMovementDirection || shiftMovementDirection === "y";
373 currentSaturation = parseFloat(dragX / dragWidth);
376 currentValue = parseFloat((dragHeight - dragY) / dragHeight);
383 }, dragStart, dragStop);
385 if (!!initialColor) {
388 // In case color was black - update the preview UI and set the format
389 // since the set function will not run (default color is black).
391 currentPreferredFormat = preferredFormat || tinycolor(initialColor).format;
393 addColorToSelectionPalette(initialColor);
403 function palletElementClick(e) {
404 if (e.data && e.data.ignore) {
405 set($(this).data("color"));
409 set($(this).data("color"));
410 updateOriginalInput(true);
418 var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
419 paletteContainer.delegate(".sp-thumb-el", paletteEvent, palletElementClick);
420 initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, palletElementClick);
423 function addColorToSelectionPalette(color) {
424 if (showSelectionPalette) {
425 var colorRgb = tinycolor(color).toRgbString();
426 if ($.inArray(colorRgb, selectionPalette) === -1) {
427 selectionPalette.push(colorRgb);
428 while(selectionPalette.length > maxSelectionSize) {
429 selectionPalette.shift();
433 if (localStorageKey && window.localStorage) {
435 window.localStorage[localStorageKey] = selectionPalette.join(";");
442 function getUniqueSelectionPalette() {
444 var p = selectionPalette;
445 var paletteLookup = {};
448 if (opts.showPalette) {
450 for (var i = 0; i < paletteArray.length; i++) {
451 for (var j = 0; j < paletteArray[i].length; j++) {
452 rgb = tinycolor(paletteArray[i][j]).toRgbString();
453 paletteLookup[rgb] = true;
457 for (i = 0; i < p.length; i++) {
458 rgb = tinycolor(p[i]).toRgbString();
460 if (!paletteLookup.hasOwnProperty(rgb)) {
462 paletteLookup[rgb] = true;
467 return unique.reverse().slice(0, opts.maxSelectionSize);
470 function drawPalette() {
472 var currentColor = get();
474 var html = $.map(paletteArray, function (palette, i) {
475 return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i);
478 if (selectionPalette) {
479 html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection"));
482 paletteContainer.html(html.join(""));
485 function drawInitial() {
486 if (opts.showInitial) {
487 var initial = colorOnShow;
489 initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial"));
493 function dragStart() {
494 if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) {
497 container.addClass(draggingClass);
498 shiftMovementDirection = null;
501 function dragStop() {
502 container.removeClass(draggingClass);
505 function setFromTextInput() {
507 var value = textInput.val();
509 if ((value === null || value === "") && allowEmpty) {
513 var tiny = tinycolor(value);
518 textInput.addClass("sp-validation-error");
533 var event = $.Event('beforeShow.spectrum');
540 boundElement.trigger(event, [ get() ]);
542 if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) {
549 $(doc).bind("click.spectrum", hide);
550 $(window).bind("resize.spectrum", resize);
551 replacer.addClass("sp-active");
552 container.removeClass("sp-hidden");
554 if (opts.showPalette) {
563 callbacks.show(colorOnShow);
564 boundElement.trigger('show.spectrum', [ colorOnShow ]);
569 // Return on right click
570 if (e && e.type == "click" && e.button == 2) { return; }
572 // Return if hiding is unnecessary
573 if (!visible || flat) { return; }
576 $(doc).unbind("click.spectrum", hide);
577 $(window).unbind("resize.spectrum", resize);
579 replacer.removeClass("sp-active");
580 container.addClass("sp-hidden");
582 var colorHasChanged = !tinycolor.equals(get(), colorOnShow);
584 if (colorHasChanged) {
585 if (clickoutFiresChange && e !== "cancel") {
586 updateOriginalInput(true);
593 callbacks.hide(get());
594 boundElement.trigger('hide.spectrum', [ get() ]);
598 set(colorOnShow, true);
601 function set(color, ignoreFormatChange) {
602 if (tinycolor.equals(color, get())) {
607 if (!color && allowEmpty) {
611 newColor = tinycolor(color);
612 var newHsv = newColor.toHsv();
614 currentHue = (newHsv.h % 360) / 360;
615 currentSaturation = newHsv.s;
616 currentValue = newHsv.v;
617 currentAlpha = newHsv.a;
621 if (newColor && newColor.ok && !ignoreFormatChange) {
622 currentPreferredFormat = preferredFormat || newColor.format;
629 if (allowEmpty && isEmpty) {
633 return tinycolor.fromRatio({
635 s: currentSaturation,
637 a: Math.round(currentAlpha * 100) / 100
638 }, { format: opts.format || currentPreferredFormat });
642 return !textInput.hasClass("sp-validation-error");
648 callbacks.move(get());
649 boundElement.trigger('move.spectrum', [ get() ]);
652 function updateUI() {
654 textInput.removeClass("sp-validation-error");
656 updateHelperLocations();
658 // Update dragger background color (gradients take care of saturation and value).
659 var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 });
660 dragger.css("background-color", flatColor.toHexString());
662 // Get a format that alpha will be included in (hex and names ignore alpha)
663 var format = currentPreferredFormat;
664 if (currentAlpha < 1) {
665 if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") {
670 var realColor = get({ format: format }),
673 //reset background info for preview element
674 previewElement.removeClass("sp-clear-display");
675 previewElement.css('background-color', 'transparent');
677 if (!realColor && allowEmpty) {
678 // Update the replaced elements background with icon indicating no color selection
679 previewElement.addClass("sp-clear-display");
682 var realHex = realColor.toHexString(),
683 realRgb = realColor.toRgbString();
685 // Update the replaced elements background color (with actual selected color)
686 if (rgbaSupport || realColor.alpha === 1) {
687 previewElement.css("background-color", realRgb);
690 previewElement.css("background-color", "transparent");
691 previewElement.css("filter", realColor.toFilter());
694 if (opts.showAlpha) {
695 var rgb = realColor.toRgb();
697 var realAlpha = tinycolor(rgb).toRgbString();
698 var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")";
701 alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex));
704 alphaSliderInner.css("background", "-webkit-" + gradient);
705 alphaSliderInner.css("background", "-moz-" + gradient);
706 alphaSliderInner.css("background", "-ms-" + gradient);
707 alphaSliderInner.css("background", gradient);
711 displayColor = realColor.toString(format);
713 // Update the text entry input as it changes happen
714 if (opts.showInput) {
715 textInput.val(displayColor);
718 if (opts.showPalette) {
725 function updateHelperLocations() {
726 var s = currentSaturation;
727 var v = currentValue;
729 if(allowEmpty && isEmpty) {
730 //if selected color is empty, hide the helpers
731 alphaSlideHelper.hide();
736 //make sure helpers are visible
737 alphaSlideHelper.show();
741 // Where to show the little circle in that displays your current selected color
742 var dragX = s * dragWidth;
743 var dragY = dragHeight - (v * dragHeight);
746 Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
750 Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
757 var alphaX = currentAlpha * alphaWidth;
758 alphaSlideHelper.css({
759 "left": alphaX - (alphaSlideHelperWidth / 2)
762 // Where to show the bar that displays your current selected hue
763 var slideY = (currentHue) * slideHeight;
765 "top": slideY - slideHelperHeight
770 function updateOriginalInput(fireCallback) {
773 hasChanged = !tinycolor.equals(color, colorOnShow);
776 displayColor = color.toString(currentPreferredFormat);
777 // Update the selection palette with the current color
778 addColorToSelectionPalette(color);
782 boundElement.val(displayColor);
787 if (fireCallback && hasChanged) {
788 callbacks.change(color);
789 boundElement.trigger('change', [ color ]);
794 dragWidth = dragger.width();
795 dragHeight = dragger.height();
796 dragHelperHeight = dragHelper.height();
797 slideWidth = slider.width();
798 slideHeight = slider.height();
799 slideHelperHeight = slideHelper.height();
800 alphaWidth = alphaSlider.width();
801 alphaSlideHelperWidth = alphaSlideHelper.width();
804 container.css("position", "absolute");
805 container.offset(getOffset(container, offsetElement));
808 updateHelperLocations();
813 offsetElement.unbind("click.spectrum touchstart.spectrum");
816 spectrums[spect.id] = null;
819 function option(optionName, optionValue) {
820 if (optionName === undefined) {
821 return $.extend({}, opts);
823 if (optionValue === undefined) {
824 return opts[optionName];
827 opts[optionName] = optionValue;
833 boundElement.attr("disabled", false);
834 offsetElement.removeClass("sp-disabled");
840 boundElement.attr("disabled", true);
841 offsetElement.addClass("sp-disabled");
856 updateOriginalInput();
863 spect.id = spectrums.push(spect) - 1;
869 * checkOffset - get the offset below/above and left/right element depending on screen position
870 * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
872 function getOffset(picker, input) {
874 var dpWidth = picker.outerWidth();
875 var dpHeight = picker.outerHeight();
876 var inputHeight = input.outerHeight();
877 var doc = picker[0].ownerDocument;
878 var docElem = doc.documentElement;
879 var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
880 var viewHeight = docElem.clientHeight + $(doc).scrollTop();
881 var offset = input.offset();
882 offset.top += inputHeight;
885 Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
886 Math.abs(offset.left + dpWidth - viewWidth) : 0);
889 Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
890 Math.abs(dpHeight + inputHeight - extraY) : extraY));
903 * stopPropagation - makes the code only doing this a little easier to read in line
905 function stopPropagation(e) {
910 * Create a function bound to a given object
911 * Thanks to underscore.js
913 function bind(func, obj) {
914 var slice = Array.prototype.slice;
915 var args = slice.call(arguments, 2);
917 return func.apply(obj, args.concat(slice.call(arguments)));
922 * Lightweight drag helper. Handles containment within the element, so that
923 * when dragging, the x is within [0,element.width] and y is within [0,element.height]
925 function draggable(element, onmove, onstart, onstop) {
926 onmove = onmove || function () { };
927 onstart = onstart || function () { };
928 onstop = onstop || function () { };
929 var doc = element.ownerDocument || document;
930 var dragging = false;
934 var hasTouch = ('ontouchstart' in window);
936 var duringDragEvents = {};
937 duringDragEvents["selectstart"] = prevent;
938 duringDragEvents["dragstart"] = prevent;
939 duringDragEvents["touchmove mousemove"] = move;
940 duringDragEvents["touchend mouseup"] = stop;
942 function prevent(e) {
943 if (e.stopPropagation) {
946 if (e.preventDefault) {
949 e.returnValue = false;
954 // Mouseup happened outside of window
955 if (IE && document.documentMode < 9 && !e.button) {
959 var touches = e.originalEvent.touches;
960 var pageX = touches ? touches[0].pageX : e.pageX;
961 var pageY = touches ? touches[0].pageY : e.pageY;
963 var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
964 var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
967 // Stop scrolling in iOS
971 onmove.apply(element, [dragX, dragY, e]);
975 var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);
976 var touches = e.originalEvent.touches;
978 if (!rightclick && !dragging) {
979 if (onstart.apply(element, arguments) !== false) {
981 maxHeight = $(element).height();
982 maxWidth = $(element).width();
983 offset = $(element).offset();
985 $(doc).bind(duringDragEvents);
986 $(doc.body).addClass("sp-dragging");
998 $(doc).unbind(duringDragEvents);
999 $(doc.body).removeClass("sp-dragging");
1000 onstop.apply(element, arguments);
1005 $(element).bind("touchstart mousedown", start);
1008 function throttle(func, wait, debounce) {
1010 return function () {
1011 var context = this, args = arguments;
1012 var throttler = function () {
1014 func.apply(context, args);
1016 if (debounce) clearTimeout(timeout);
1017 if (debounce || !timeout) timeout = setTimeout(throttler, wait);
1022 function log(){/* jshint -W021 */if(window.console){if(Function.prototype.bind)log=Function.prototype.bind.call(console.log,console);else log=function(){Function.prototype.apply.call(console.log,console,arguments);};log.apply(this,arguments);}}
1025 * Define a jQuery plugin
1027 var dataID = "spectrum.id";
1028 $.fn.spectrum = function (opts, extra) {
1030 if (typeof opts == "string") {
1032 var returnValue = this;
1033 var args = Array.prototype.slice.call( arguments, 1 );
1035 this.each(function () {
1036 var spect = spectrums[$(this).data(dataID)];
1039 var method = spect[opts];
1041 throw new Error( "Spectrum: no such method: '" + opts + "'" );
1044 if (opts == "get") {
1045 returnValue = spect.get();
1047 else if (opts == "container") {
1048 returnValue = spect.container;
1050 else if (opts == "option") {
1051 returnValue = spect.option.apply(spect, args);
1053 else if (opts == "destroy") {
1055 $(this).removeData(dataID);
1058 method.apply(spect, args);
1066 // Initializing a new instance of spectrum
1067 return this.spectrum("destroy").each(function () {
1068 var spect = spectrum(this, opts);
1069 $(this).data(dataID, spect.id);
1073 $.fn.spectrum.load = true;
1074 $.fn.spectrum.loadOpts = {};
1075 $.fn.spectrum.draggable = draggable;
1076 $.fn.spectrum.defaults = defaultOpts;
1079 $.spectrum.localization = { };
1080 $.spectrum.palettes = { };
1082 $.fn.spectrum.processNativeColorInputs = function () {
1083 if (!inputTypeColorSupport) {
1084 $("input[type=color]").spectrum({
1085 preferredFormat: "hex6"
1090 // TinyColor v0.9.16
1091 // https://github.com/bgrins/TinyColor
1092 // 2013-08-10, Brian Grinstead, MIT License
1096 var trimLeft = /^[\s,#]+/,
1100 mathRound = math.round,
1103 mathRandom = math.random;
1105 function tinycolor (color, opts) {
1107 color = (color) ? color : '';
1110 // If input is already a tinycolor, return itself
1111 if (typeof color == "object" && color.hasOwnProperty("_tc_id")) {
1115 var rgb = inputToRGB(color);
1120 roundA = mathRound(100*a) / 100,
1121 format = opts.format || rgb.format;
1123 // Don't let the range of [0,255] come back in [0,1].
1124 // Potentially lose a little bit of precision here, but will fix issues where
1125 // .5 gets interpreted as half of the total, instead of half of 1
1126 // If it was supposed to be 128, this was already taken care of by `inputToRgb`
1127 if (r < 1) { r = mathRound(r); }
1128 if (g < 1) { g = mathRound(g); }
1129 if (b < 1) { b = mathRound(b); }
1134 _tc_id: tinyCounter++,
1136 getAlpha: function() {
1139 setAlpha: function(value) {
1140 a = boundAlpha(value);
1141 roundA = mathRound(100*a) / 100;
1144 var hsv = rgbToHsv(r, g, b);
1145 return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: a };
1147 toHsvString: function() {
1148 var hsv = rgbToHsv(r, g, b);
1149 var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
1151 "hsv(" + h + ", " + s + "%, " + v + "%)" :
1152 "hsva(" + h + ", " + s + "%, " + v + "%, "+ roundA + ")";
1155 var hsl = rgbToHsl(r, g, b);
1156 return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: a };
1158 toHslString: function() {
1159 var hsl = rgbToHsl(r, g, b);
1160 var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
1162 "hsl(" + h + ", " + s + "%, " + l + "%)" :
1163 "hsla(" + h + ", " + s + "%, " + l + "%, "+ roundA + ")";
1165 toHex: function(allow3Char) {
1166 return rgbToHex(r, g, b, allow3Char);
1168 toHexString: function(allow3Char) {
1169 return '#' + rgbToHex(r, g, b, allow3Char);
1172 return { r: mathRound(r), g: mathRound(g), b: mathRound(b), a: a };
1174 toRgbString: function() {
1176 "rgb(" + mathRound(r) + ", " + mathRound(g) + ", " + mathRound(b) + ")" :
1177 "rgba(" + mathRound(r) + ", " + mathRound(g) + ", " + mathRound(b) + ", " + roundA + ")";
1179 toPercentageRgb: function() {
1180 return { r: mathRound(bound01(r, 255) * 100) + "%", g: mathRound(bound01(g, 255) * 100) + "%", b: mathRound(bound01(b, 255) * 100) + "%", a: a };
1182 toPercentageRgbString: function() {
1184 "rgb(" + mathRound(bound01(r, 255) * 100) + "%, " + mathRound(bound01(g, 255) * 100) + "%, " + mathRound(bound01(b, 255) * 100) + "%)" :
1185 "rgba(" + mathRound(bound01(r, 255) * 100) + "%, " + mathRound(bound01(g, 255) * 100) + "%, " + mathRound(bound01(b, 255) * 100) + "%, " + roundA + ")";
1187 toName: function() {
1189 return "transparent";
1192 return hexNames[rgbToHex(r, g, b, true)] || false;
1194 toFilter: function(secondColor) {
1195 var hex = rgbToHex(r, g, b);
1196 var secondHex = hex;
1197 var alphaHex = Math.round(parseFloat(a) * 255).toString(16);
1198 var secondAlphaHex = alphaHex;
1199 var gradientType = opts && opts.gradientType ? "GradientType = 1, " : "";
1202 var s = tinycolor(secondColor);
1203 secondHex = s.toHex();
1204 secondAlphaHex = Math.round(parseFloat(s.alpha) * 255).toString(16);
1207 return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr=#" + pad2(alphaHex) + hex + ",endColorstr=#" + pad2(secondAlphaHex) + secondHex + ")";
1209 toString: function(format) {
1210 var formatSet = !!format;
1211 format = format || this.format;
1213 var formattedString = false;
1214 var hasAlphaAndFormatNotSet = !formatSet && a < 1 && a > 0;
1215 var formatWithAlpha = hasAlphaAndFormatNotSet && (format === "hex" || format === "hex6" || format === "hex3" || format === "name");
1217 if (format === "rgb") {
1218 formattedString = this.toRgbString();
1220 if (format === "prgb") {
1221 formattedString = this.toPercentageRgbString();
1223 if (format === "hex" || format === "hex6") {
1224 formattedString = this.toHexString();
1226 if (format === "hex3") {
1227 formattedString = this.toHexString(true);
1229 if (format === "name") {
1230 formattedString = this.toName();
1232 if (format === "hsl") {
1233 formattedString = this.toHslString();
1235 if (format === "hsv") {
1236 formattedString = this.toHsvString();
1239 if (formatWithAlpha) {
1240 return this.toRgbString();
1243 return formattedString || this.toHexString();
1248 // If input is an object, force 1 into "1.0" to handle ratios properly
1249 // String input requires "1.0" as input, so 1 will be treated as 1
1250 tinycolor.fromRatio = function(color, opts) {
1251 if (typeof color == "object") {
1253 for (var i in color) {
1254 if (color.hasOwnProperty(i)) {
1256 newColor[i] = color[i];
1259 newColor[i] = convertToPercentage(color[i]);
1266 return tinycolor(color, opts);
1269 // Given a string or object, convert that input to RGB
1270 // Possible string inputs:
1274 // "#ff0000" or "ff0000"
1275 // "rgb 255 0 0" or "rgb (255, 0, 0)"
1276 // "rgb 1.0 0 0" or "rgb (1, 0, 0)"
1277 // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
1278 // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
1279 // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
1280 // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
1281 // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
1283 function inputToRGB(color) {
1285 var rgb = { r: 0, g: 0, b: 0 };
1290 if (typeof color == "string") {
1291 color = stringInputToObject(color);
1294 if (typeof color == "object") {
1295 if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
1296 rgb = rgbToRgb(color.r, color.g, color.b);
1298 format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
1300 else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
1301 color.s = convertToPercentage(color.s);
1302 color.v = convertToPercentage(color.v);
1303 rgb = hsvToRgb(color.h, color.s, color.v);
1307 else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
1308 color.s = convertToPercentage(color.s);
1309 color.l = convertToPercentage(color.l);
1310 rgb = hslToRgb(color.h, color.s, color.l);
1315 if (color.hasOwnProperty("a")) {
1324 format: color.format || format,
1325 r: mathMin(255, mathMax(rgb.r, 0)),
1326 g: mathMin(255, mathMax(rgb.g, 0)),
1327 b: mathMin(255, mathMax(rgb.b, 0)),
1333 // Conversion Functions
1334 // --------------------
1336 // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
1337 // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
1340 // Handle bounds / percentage checking to conform to CSS color spec
1341 // <http://www.w3.org/TR/css3-color/>
1342 // *Assumes:* r, g, b in [0, 255] or [0, 1]
1343 // *Returns:* { r, g, b } in [0, 255]
1344 function rgbToRgb(r, g, b){
1346 r: bound01(r, 255) * 255,
1347 g: bound01(g, 255) * 255,
1348 b: bound01(b, 255) * 255
1353 // Converts an RGB color value to HSL.
1354 // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
1355 // *Returns:* { h, s, l } in [0,1]
1356 function rgbToHsl(r, g, b) {
1358 r = bound01(r, 255);
1359 g = bound01(g, 255);
1360 b = bound01(b, 255);
1362 var max = mathMax(r, g, b), min = mathMin(r, g, b);
1363 var h, s, l = (max + min) / 2;
1366 h = s = 0; // achromatic
1370 s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
1372 case r: h = (g - b) / d + (g < b ? 6 : 0); break;
1373 case g: h = (b - r) / d + 2; break;
1374 case b: h = (r - g) / d + 4; break;
1380 return { h: h, s: s, l: l };
1384 // Converts an HSL color value to RGB.
1385 // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
1386 // *Returns:* { r, g, b } in the set [0, 255]
1387 function hslToRgb(h, s, l) {
1390 h = bound01(h, 360);
1391 s = bound01(s, 100);
1392 l = bound01(l, 100);
1394 function hue2rgb(p, q, t) {
1397 if(t < 1/6) return p + (q - p) * 6 * t;
1398 if(t < 1/2) return q;
1399 if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
1404 r = g = b = l; // achromatic
1407 var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1409 r = hue2rgb(p, q, h + 1/3);
1410 g = hue2rgb(p, q, h);
1411 b = hue2rgb(p, q, h - 1/3);
1414 return { r: r * 255, g: g * 255, b: b * 255 };
1418 // Converts an RGB color value to HSV
1419 // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
1420 // *Returns:* { h, s, v } in [0,1]
1421 function rgbToHsv(r, g, b) {
1423 r = bound01(r, 255);
1424 g = bound01(g, 255);
1425 b = bound01(b, 255);
1427 var max = mathMax(r, g, b), min = mathMin(r, g, b);
1431 s = max === 0 ? 0 : d / max;
1434 h = 0; // achromatic
1438 case r: h = (g - b) / d + (g < b ? 6 : 0); break;
1439 case g: h = (b - r) / d + 2; break;
1440 case b: h = (r - g) / d + 4; break;
1444 return { h: h, s: s, v: v };
1448 // Converts an HSV color value to RGB.
1449 // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
1450 // *Returns:* { r, g, b } in the set [0, 255]
1451 function hsvToRgb(h, s, v) {
1453 h = bound01(h, 360) * 6;
1454 s = bound01(s, 100);
1455 v = bound01(v, 100);
1457 var i = math.floor(h),
1460 q = v * (1 - f * s),
1461 t = v * (1 - (1 - f) * s),
1463 r = [v, q, p, p, t, v][mod],
1464 g = [t, v, v, q, p, p][mod],
1465 b = [p, p, t, v, v, q][mod];
1467 return { r: r * 255, g: g * 255, b: b * 255 };
1471 // Converts an RGB color to hex
1472 // Assumes r, g, and b are contained in the set [0, 255]
1473 // Returns a 3 or 6 character hex
1474 function rgbToHex(r, g, b, allow3Char) {
1477 pad2(mathRound(r).toString(16)),
1478 pad2(mathRound(g).toString(16)),
1479 pad2(mathRound(b).toString(16))
1482 // Return a 3 character hex if possible
1483 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)) {
1484 return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
1487 return hex.join("");
1491 // Can be called with any tinycolor input
1492 tinycolor.equals = function (color1, color2) {
1493 if (!color1 || !color2) { return false; }
1494 return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
1496 tinycolor.random = function() {
1497 return tinycolor.fromRatio({
1505 // Modification Functions
1506 // ----------------------
1507 // Thanks to less.js for some of the basics here
1508 // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>
1510 tinycolor.desaturate = function (color, amount) {
1511 amount = (amount === 0) ? 0 : (amount || 10);
1512 var hsl = tinycolor(color).toHsl();
1513 hsl.s -= amount / 100;
1514 hsl.s = clamp01(hsl.s);
1515 return tinycolor(hsl);
1517 tinycolor.saturate = function (color, amount) {
1518 amount = (amount === 0) ? 0 : (amount || 10);
1519 var hsl = tinycolor(color).toHsl();
1520 hsl.s += amount / 100;
1521 hsl.s = clamp01(hsl.s);
1522 return tinycolor(hsl);
1524 tinycolor.greyscale = function(color) {
1525 return tinycolor.desaturate(color, 100);
1527 tinycolor.lighten = function(color, amount) {
1528 amount = (amount === 0) ? 0 : (amount || 10);
1529 var hsl = tinycolor(color).toHsl();
1530 hsl.l += amount / 100;
1531 hsl.l = clamp01(hsl.l);
1532 return tinycolor(hsl);
1534 tinycolor.darken = function (color, amount) {
1535 amount = (amount === 0) ? 0 : (amount || 10);
1536 var hsl = tinycolor(color).toHsl();
1537 hsl.l -= amount / 100;
1538 hsl.l = clamp01(hsl.l);
1539 return tinycolor(hsl);
1541 tinycolor.complement = function(color) {
1542 var hsl = tinycolor(color).toHsl();
1543 hsl.h = (hsl.h + 180) % 360;
1544 return tinycolor(hsl);
1548 // Combination Functions
1549 // ---------------------
1550 // Thanks to jQuery xColor for some of the ideas behind these
1551 // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>
1553 tinycolor.triad = function(color) {
1554 var hsl = tinycolor(color).toHsl();
1558 tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
1559 tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
1562 tinycolor.tetrad = function(color) {
1563 var hsl = tinycolor(color).toHsl();
1567 tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
1568 tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
1569 tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
1572 tinycolor.splitcomplement = function(color) {
1573 var hsl = tinycolor(color).toHsl();
1577 tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
1578 tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l})
1581 tinycolor.analogous = function(color, results, slices) {
1582 results = results || 6;
1583 slices = slices || 30;
1585 var hsl = tinycolor(color).toHsl();
1586 var part = 360 / slices;
1587 var ret = [tinycolor(color)];
1589 for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
1590 hsl.h = (hsl.h + part) % 360;
1591 ret.push(tinycolor(hsl));
1595 tinycolor.monochromatic = function(color, results) {
1596 results = results || 6;
1597 var hsv = tinycolor(color).toHsv();
1598 var h = hsv.h, s = hsv.s, v = hsv.v;
1600 var modification = 1 / results;
1603 ret.push(tinycolor({ h: h, s: s, v: v}));
1604 v = (v + modification) % 1;
1611 // Readability Functions
1612 // ---------------------
1613 // <http://www.w3.org/TR/AERT#color-contrast>
1616 // Analyze the 2 colors and returns an object with the following properties:
1617 // `brightness`: difference in brightness between the two colors
1618 // `color`: difference in color/hue between the two colors
1619 tinycolor.readability = function(color1, color2) {
1620 var a = tinycolor(color1).toRgb();
1621 var b = tinycolor(color2).toRgb();
1622 var brightnessA = (a.r * 299 + a.g * 587 + a.b * 114) / 1000;
1623 var brightnessB = (b.r * 299 + b.g * 587 + b.b * 114) / 1000;
1625 Math.max(a.r, b.r) - Math.min(a.r, b.r) +
1626 Math.max(a.g, b.g) - Math.min(a.g, b.g) +
1627 Math.max(a.b, b.b) - Math.min(a.b, b.b)
1631 brightness: Math.abs(brightnessA - brightnessB),
1637 // http://www.w3.org/TR/AERT#color-contrast
1638 // Ensure that foreground and background color combinations provide sufficient contrast.
1640 // tinycolor.readable("#000", "#111") => false
1641 tinycolor.readable = function(color1, color2) {
1642 var readability = tinycolor.readability(color1, color2);
1643 return readability.brightness > 125 && readability.color > 500;
1647 // Given a base color and a list of possible foreground or background
1648 // colors for that base, returns the most readable color.
1650 // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000"
1651 tinycolor.mostReadable = function(baseColor, colorList) {
1652 var bestColor = null;
1654 var bestIsReadable = false;
1655 for (var i=0; i < colorList.length; i++) {
1657 // We normalize both around the "acceptable" breaking point,
1658 // but rank brightness constrast higher than hue.
1660 var readability = tinycolor.readability(baseColor, colorList[i]);
1661 var readable = readability.brightness > 125 && readability.color > 500;
1662 var score = 3 * (readability.brightness / 125) + (readability.color / 500);
1664 if ((readable && ! bestIsReadable) ||
1665 (readable && bestIsReadable && score > bestScore) ||
1666 ((! readable) && (! bestIsReadable) && score > bestScore)) {
1667 bestIsReadable = readable;
1669 bestColor = tinycolor(colorList[i]);
1676 // Big List of Colors
1677 // ------------------
1678 // <http://www.w3.org/TR/css3-color/#svg-color>
1679 var names = tinycolor.names = {
1680 aliceblue: "f0f8ff",
1681 antiquewhite: "faebd7",
1683 aquamarine: "7fffd4",
1688 blanchedalmond: "ffebcd",
1690 blueviolet: "8a2be2",
1692 burlywood: "deb887",
1693 burntsienna: "ea7e5d",
1694 cadetblue: "5f9ea0",
1695 chartreuse: "7fff00",
1696 chocolate: "d2691e",
1698 cornflowerblue: "6495ed",
1704 darkgoldenrod: "b8860b",
1706 darkgreen: "006400",
1708 darkkhaki: "bdb76b",
1709 darkmagenta: "8b008b",
1710 darkolivegreen: "556b2f",
1711 darkorange: "ff8c00",
1712 darkorchid: "9932cc",
1714 darksalmon: "e9967a",
1715 darkseagreen: "8fbc8f",
1716 darkslateblue: "483d8b",
1717 darkslategray: "2f4f4f",
1718 darkslategrey: "2f4f4f",
1719 darkturquoise: "00ced1",
1720 darkviolet: "9400d3",
1722 deepskyblue: "00bfff",
1725 dodgerblue: "1e90ff",
1726 firebrick: "b22222",
1727 floralwhite: "fffaf0",
1728 forestgreen: "228b22",
1730 gainsboro: "dcdcdc",
1731 ghostwhite: "f8f8ff",
1733 goldenrod: "daa520",
1736 greenyellow: "adff2f",
1740 indianred: "cd5c5c",
1745 lavenderblush: "fff0f5",
1746 lawngreen: "7cfc00",
1747 lemonchiffon: "fffacd",
1748 lightblue: "add8e6",
1749 lightcoral: "f08080",
1750 lightcyan: "e0ffff",
1751 lightgoldenrodyellow: "fafad2",
1752 lightgray: "d3d3d3",
1753 lightgreen: "90ee90",
1754 lightgrey: "d3d3d3",
1755 lightpink: "ffb6c1",
1756 lightsalmon: "ffa07a",
1757 lightseagreen: "20b2aa",
1758 lightskyblue: "87cefa",
1759 lightslategray: "789",
1760 lightslategrey: "789",
1761 lightsteelblue: "b0c4de",
1762 lightyellow: "ffffe0",
1764 limegreen: "32cd32",
1768 mediumaquamarine: "66cdaa",
1769 mediumblue: "0000cd",
1770 mediumorchid: "ba55d3",
1771 mediumpurple: "9370db",
1772 mediumseagreen: "3cb371",
1773 mediumslateblue: "7b68ee",
1774 mediumspringgreen: "00fa9a",
1775 mediumturquoise: "48d1cc",
1776 mediumvioletred: "c71585",
1777 midnightblue: "191970",
1778 mintcream: "f5fffa",
1779 mistyrose: "ffe4e1",
1781 navajowhite: "ffdead",
1785 olivedrab: "6b8e23",
1787 orangered: "ff4500",
1789 palegoldenrod: "eee8aa",
1790 palegreen: "98fb98",
1791 paleturquoise: "afeeee",
1792 palevioletred: "db7093",
1793 papayawhip: "ffefd5",
1794 peachpuff: "ffdab9",
1798 powderblue: "b0e0e6",
1801 rosybrown: "bc8f8f",
1802 royalblue: "4169e1",
1803 saddlebrown: "8b4513",
1805 sandybrown: "f4a460",
1811 slateblue: "6a5acd",
1812 slategray: "708090",
1813 slategrey: "708090",
1815 springgreen: "00ff7f",
1816 steelblue: "4682b4",
1821 turquoise: "40e0d0",
1825 whitesmoke: "f5f5f5",
1827 yellowgreen: "9acd32"
1830 // Make it easy to access colors via `hexNames[hex]`
1831 var hexNames = tinycolor.hexNames = flip(names);
1837 // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
1841 if (o.hasOwnProperty(i)) {
1848 // Return a valid alpha value [0,1] with all invalid values being set to 1
1849 function boundAlpha(a) {
1852 if (isNaN(a) || a < 0 || a > 1) {
1859 // Take input from [0, n] and return it as [0, 1]
1860 function bound01(n, max) {
1861 if (isOnePointZero(n)) { n = "100%"; }
1863 var processPercent = isPercentage(n);
1864 n = mathMin(max, mathMax(0, parseFloat(n)));
1866 // Automatically convert percentage into number
1867 if (processPercent) {
1868 n = parseInt(n * max, 10) / 100;
1871 // Handle floating point rounding errors
1872 if ((math.abs(n - max) < 0.000001)) {
1876 // Convert into [0, 1] range if it isn't already
1877 return (n % max) / parseFloat(max);
1880 // Force a number between 0 and 1
1881 function clamp01(val) {
1882 return mathMin(1, mathMax(0, val));
1885 // Parse an integer into hex
1886 function parseHex(val) {
1887 return parseInt(val, 16);
1890 // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
1891 // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
1892 function isOnePointZero(n) {
1893 return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
1896 // Check to see if string passed in is a percentage
1897 function isPercentage(n) {
1898 return typeof n === "string" && n.indexOf('%') != -1;
1901 // Force a hex value to have 2 characters
1903 return c.length == 1 ? '0' + c : '' + c;
1906 // Replace a decimal with it's percentage value
1907 function convertToPercentage(n) {
1909 n = (n * 100) + "%";
1915 var matchers = (function() {
1917 // <http://www.w3.org/TR/css3-values/#integers>
1918 var CSS_INTEGER = "[-\\+]?\\d+%?";
1920 // <http://www.w3.org/TR/css3-values/#number-value>
1921 var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
1923 // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
1924 var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
1927 // Parentheses and commas are optional, but not required.
1928 // Whitespace can take the place of commas or opening paren
1929 var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
1930 var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
1933 rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
1934 rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
1935 hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
1936 hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
1937 hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
1938 hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
1939 hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
1943 // `stringInputToObject`
1944 // Permissive string parsing. Take in a number of formats, and output an object
1945 // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
1946 function stringInputToObject(color) {
1948 color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
1951 color = names[color];
1954 else if (color == 'transparent') {
1955 return { r: 0, g: 0, b: 0, a: 0, format: "name" };
1958 // Try to match string input using regular expressions.
1959 // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
1960 // Just return an object and let the conversion functions handle that.
1961 // This way the result will be the same whether the tinycolor is initialized with string or object.
1963 if ((match = matchers.rgb.exec(color))) {
1964 return { r: match[1], g: match[2], b: match[3] };
1966 if ((match = matchers.rgba.exec(color))) {
1967 return { r: match[1], g: match[2], b: match[3], a: match[4] };
1969 if ((match = matchers.hsl.exec(color))) {
1970 return { h: match[1], s: match[2], l: match[3] };
1972 if ((match = matchers.hsla.exec(color))) {
1973 return { h: match[1], s: match[2], l: match[3], a: match[4] };
1975 if ((match = matchers.hsv.exec(color))) {
1976 return { h: match[1], s: match[2], v: match[3] };
1978 if ((match = matchers.hex6.exec(color))) {
1980 r: parseHex(match[1]),
1981 g: parseHex(match[2]),
1982 b: parseHex(match[3]),
1983 format: named ? "name" : "hex"
1986 if ((match = matchers.hex3.exec(color))) {
1988 r: parseHex(match[1] + '' + match[1]),
1989 g: parseHex(match[2] + '' + match[2]),
1990 b: parseHex(match[3] + '' + match[3]),
1991 format: named ? "name" : "hex"
1998 // Expose tinycolor to window, does not need to run in non-browser context.
1999 window.tinycolor = tinycolor;
2005 if ($.fn.spectrum.load) {
2006 $.fn.spectrum.processNativeColorInputs();