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