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