source: extensions/EasyCaptcha/template/bgrins-spectrum/spectrum.js @ 24215

Last change on this file since 24215 was 24215, checked in by mistic100, 11 years ago

add extension EasyCaptcha

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