]> defiant.homedns.org Git - ros_wild_thumper.git/blob - www/assets/javascripts/jquery.minicolors.js
www: Added minicolors colorpicker for lights
[ros_wild_thumper.git] / www / assets / javascripts / jquery.minicolors.js
1 //
2 // jQuery MiniColors: A tiny color picker built on jQuery
3 //
4 // Developed by Cory LaViska for A Beautiful Site, LLC
5 //
6 // Licensed under the MIT license: http://opensource.org/licenses/MIT
7 //
8 (function (factory) {
9   if(typeof define === 'function' && define.amd) {
10     // AMD. Register as an anonymous module.
11     define(['jquery'], factory);
12   } else if(typeof exports === 'object') {
13     // Node/CommonJS
14     module.exports = factory(require('jquery'));
15   } else {
16     // Browser globals
17     factory(jQuery);
18   }
19 }(function ($) {
20   'use strict';
21
22   // Defaults
23   $.minicolors = {
24     defaults: {
25       animationSpeed: 50,
26       animationEasing: 'swing',
27       change: null,
28       changeDelay: 0,
29       control: 'hue',
30       defaultValue: '',
31       format: 'hex',
32       hide: null,
33       hideSpeed: 100,
34       inline: false,
35       keywords: '',
36       letterCase: 'lowercase',
37       opacity: false,
38       position: 'bottom left',
39       show: null,
40       showSpeed: 100,
41       theme: 'default',
42       swatches: []
43     }
44   };
45
46   // Public methods
47   $.extend($.fn, {
48     minicolors: function(method, data) {
49
50       switch(method) {
51       // Destroy the control
52       case 'destroy':
53         $(this).each(function() {
54           destroy($(this));
55         });
56         return $(this);
57
58       // Hide the color picker
59       case 'hide':
60         hide();
61         return $(this);
62
63       // Get/set opacity
64       case 'opacity':
65         // Getter
66         if(data === undefined) {
67           // Getter
68           return $(this).attr('data-opacity');
69         } else {
70           // Setter
71           $(this).each(function() {
72             updateFromInput($(this).attr('data-opacity', data));
73           });
74         }
75         return $(this);
76
77       // Get an RGB(A) object based on the current color/opacity
78       case 'rgbObject':
79         return rgbObject($(this), method === 'rgbaObject');
80
81       // Get an RGB(A) string based on the current color/opacity
82       case 'rgbString':
83       case 'rgbaString':
84         return rgbString($(this), method === 'rgbaString');
85
86       // Get/set settings on the fly
87       case 'settings':
88         if(data === undefined) {
89           return $(this).data('minicolors-settings');
90         } else {
91           // Setter
92           $(this).each(function() {
93             var settings = $(this).data('minicolors-settings') || {};
94             destroy($(this));
95             $(this).minicolors($.extend(true, settings, data));
96           });
97         }
98         return $(this);
99
100       // Show the color picker
101       case 'show':
102         show($(this).eq(0));
103         return $(this);
104
105       // Get/set the hex color value
106       case 'value':
107         if(data === undefined) {
108           // Getter
109           return $(this).val();
110         } else {
111           // Setter
112           $(this).each(function() {
113             if(typeof(data) === 'object' && data !== null) {
114               if(data.opacity) {
115                 $(this).attr('data-opacity', keepWithin(data.opacity, 0, 1));
116               }
117               if(data.color) {
118                 $(this).val(data.color);
119               }
120             } else {
121               $(this).val(data);
122             }
123             updateFromInput($(this));
124           });
125         }
126         return $(this);
127
128       // Initializes the control
129       default:
130         if(method !== 'create') data = method;
131         $(this).each(function() {
132           init($(this), data);
133         });
134         return $(this);
135
136       }
137
138     }
139   });
140
141   // Initialize input elements
142   function init(input, settings) {
143     var minicolors = $('<div class="minicolors" />');
144     var defaults = $.minicolors.defaults;
145     var size;
146     var swatches;
147     var swatch;
148     var panel;
149     var i;
150
151     // Do nothing if already initialized
152     if(input.data('minicolors-initialized')) return;
153
154     // Handle settings
155     settings = $.extend(true, {}, defaults, settings);
156
157     // The wrapper
158     minicolors
159     .addClass('minicolors-theme-' + settings.theme)
160     .toggleClass('minicolors-with-opacity', settings.opacity);
161
162     // Custom positioning
163     if(settings.position !== undefined) {
164       $.each(settings.position.split(' '), function() {
165         minicolors.addClass('minicolors-position-' + this);
166       });
167     }
168
169     // Input size
170     if(settings.format === 'rgb') {
171       size = settings.opacity ? '25' : '20';
172     } else {
173       size = settings.keywords ? '11' : '7';
174     }
175
176     // The input
177     input
178     .addClass('minicolors-input')
179     .data('minicolors-initialized', false)
180     .data('minicolors-settings', settings)
181     .prop('size', size)
182     .wrap(minicolors)
183     .after(
184       '<div class="minicolors-panel minicolors-slider-' + settings.control + '">' +
185       '<div class="minicolors-slider minicolors-sprite">' +
186       '<div class="minicolors-picker"></div>' +
187       '</div>' +
188       '<div class="minicolors-opacity-slider minicolors-sprite">' +
189       '<div class="minicolors-picker"></div>' +
190       '</div>' +
191       '<div class="minicolors-grid minicolors-sprite">' +
192       '<div class="minicolors-grid-inner"></div>' +
193       '<div class="minicolors-picker"><div></div></div>' +
194       '</div>' +
195       '</div>'
196     );
197
198     // The swatch
199     if(!settings.inline) {
200       input.after('<span class="minicolors-swatch minicolors-sprite minicolors-input-swatch"><span class="minicolors-swatch-color"></span></span>');
201       input.next('.minicolors-input-swatch').on('click', function(event) {
202         event.preventDefault();
203         input.focus();
204       });
205     }
206
207     // Prevent text selection in IE
208     panel = input.parent().find('.minicolors-panel');
209     panel.on('selectstart', function() { return false; }).end();
210
211     // Swatches
212     if(settings.swatches && settings.swatches.length !== 0) {
213       panel.addClass('minicolors-with-swatches');
214       swatches = $('<ul class="minicolors-swatches"></ul>')
215       .appendTo(panel);
216       for(i = 0; i < settings.swatches.length; ++i) {
217         swatch = settings.swatches[i];
218         swatch = isRgb(swatch) ? parseRgb(swatch, true) : hex2rgb(parseHex(swatch, true));
219         $('<li class="minicolors-swatch minicolors-sprite"><span class="minicolors-swatch-color"></span></li>')
220         .appendTo(swatches)
221         .data('swatch-color', settings.swatches[i])
222         .find('.minicolors-swatch-color')
223         .css({
224           backgroundColor: rgb2hex(swatch),
225           opacity: swatch.a
226         });
227         settings.swatches[i] = swatch;
228       }
229     }
230
231     // Inline controls
232     if(settings.inline) input.parent().addClass('minicolors-inline');
233
234     updateFromInput(input, false);
235
236     input.data('minicolors-initialized', true);
237   }
238
239   // Returns the input back to its original state
240   function destroy(input) {
241     var minicolors = input.parent();
242
243     // Revert the input element
244     input
245       .removeData('minicolors-initialized')
246       .removeData('minicolors-settings')
247       .removeProp('size')
248       .removeClass('minicolors-input');
249
250     // Remove the wrap and destroy whatever remains
251     minicolors.before(input).remove();
252   }
253
254   // Shows the specified dropdown panel
255   function show(input) {
256     var minicolors = input.parent();
257     var panel = minicolors.find('.minicolors-panel');
258     var settings = input.data('minicolors-settings');
259
260     // Do nothing if uninitialized, disabled, inline, or already open
261     if(
262       !input.data('minicolors-initialized') ||
263       input.prop('disabled') ||
264       minicolors.hasClass('minicolors-inline') ||
265       minicolors.hasClass('minicolors-focus')
266     ) return;
267
268     hide();
269
270     minicolors.addClass('minicolors-focus');
271     panel
272     .stop(true, true)
273     .fadeIn(settings.showSpeed, function() {
274       if(settings.show) settings.show.call(input.get(0));
275     });
276   }
277
278   // Hides all dropdown panels
279   function hide() {
280     $('.minicolors-focus').each(function() {
281       var minicolors = $(this);
282       var input = minicolors.find('.minicolors-input');
283       var panel = minicolors.find('.minicolors-panel');
284       var settings = input.data('minicolors-settings');
285
286       panel.fadeOut(settings.hideSpeed, function() {
287         if(settings.hide) settings.hide.call(input.get(0));
288         minicolors.removeClass('minicolors-focus');
289       });
290
291     });
292   }
293
294   // Moves the selected picker
295   function move(target, event, animate) {
296     var input = target.parents('.minicolors').find('.minicolors-input');
297     var settings = input.data('minicolors-settings');
298     var picker = target.find('[class$=-picker]');
299     var offsetX = target.offset().left;
300     var offsetY = target.offset().top;
301     var x = Math.round(event.pageX - offsetX);
302     var y = Math.round(event.pageY - offsetY);
303     var duration = animate ? settings.animationSpeed : 0;
304     var wx, wy, r, phi;
305
306     // Touch support
307     if(event.originalEvent.changedTouches) {
308       x = event.originalEvent.changedTouches[0].pageX - offsetX;
309       y = event.originalEvent.changedTouches[0].pageY - offsetY;
310     }
311
312     // Constrain picker to its container
313     if(x < 0) x = 0;
314     if(y < 0) y = 0;
315     if(x > target.width()) x = target.width();
316     if(y > target.height()) y = target.height();
317
318     // Constrain color wheel values to the wheel
319     if(target.parent().is('.minicolors-slider-wheel') && picker.parent().is('.minicolors-grid')) {
320       wx = 75 - x;
321       wy = 75 - y;
322       r = Math.sqrt(wx * wx + wy * wy);
323       phi = Math.atan2(wy, wx);
324       if(phi < 0) phi += Math.PI * 2;
325       if(r > 75) {
326         r = 75;
327         x = 75 - (75 * Math.cos(phi));
328         y = 75 - (75 * Math.sin(phi));
329       }
330       x = Math.round(x);
331       y = Math.round(y);
332     }
333
334     // Move the picker
335     if(target.is('.minicolors-grid')) {
336       picker
337       .stop(true)
338       .animate({
339         top: y + 'px',
340         left: x + 'px'
341       }, duration, settings.animationEasing, function() {
342         updateFromControl(input, target);
343       });
344     } else {
345       picker
346       .stop(true)
347       .animate({
348         top: y + 'px'
349       }, duration, settings.animationEasing, function() {
350         updateFromControl(input, target);
351       });
352     }
353   }
354
355   // Sets the input based on the color picker values
356   function updateFromControl(input, target) {
357
358     function getCoords(picker, container) {
359       var left, top;
360       if(!picker.length || !container) return null;
361       left = picker.offset().left;
362       top = picker.offset().top;
363
364       return {
365         x: left - container.offset().left + (picker.outerWidth() / 2),
366         y: top - container.offset().top + (picker.outerHeight() / 2)
367       };
368     }
369
370     var hue, saturation, brightness, x, y, r, phi;
371     var hex = input.val();
372     var opacity = input.attr('data-opacity');
373
374     // Helpful references
375     var minicolors = input.parent();
376     var settings = input.data('minicolors-settings');
377     var swatch = minicolors.find('.minicolors-input-swatch');
378
379     // Panel objects
380     var grid = minicolors.find('.minicolors-grid');
381     var slider = minicolors.find('.minicolors-slider');
382     var opacitySlider = minicolors.find('.minicolors-opacity-slider');
383
384     // Picker objects
385     var gridPicker = grid.find('[class$=-picker]');
386     var sliderPicker = slider.find('[class$=-picker]');
387     var opacityPicker = opacitySlider.find('[class$=-picker]');
388
389     // Picker positions
390     var gridPos = getCoords(gridPicker, grid);
391     var sliderPos = getCoords(sliderPicker, slider);
392     var opacityPos = getCoords(opacityPicker, opacitySlider);
393
394     // Handle colors
395     if(target.is('.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider')) {
396
397       // Determine HSB values
398       switch(settings.control) {
399       case 'wheel':
400         // Calculate hue, saturation, and brightness
401         x = (grid.width() / 2) - gridPos.x;
402         y = (grid.height() / 2) - gridPos.y;
403         r = Math.sqrt(x * x + y * y);
404         phi = Math.atan2(y, x);
405         if(phi < 0) phi += Math.PI * 2;
406         if(r > 75) {
407           r = 75;
408           gridPos.x = 69 - (75 * Math.cos(phi));
409           gridPos.y = 69 - (75 * Math.sin(phi));
410         }
411         saturation = keepWithin(r / 0.75, 0, 100);
412         hue = keepWithin(phi * 180 / Math.PI, 0, 360);
413         brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
414         hex = hsb2hex({
415           h: hue,
416           s: saturation,
417           b: brightness
418         });
419
420         // Update UI
421         slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 }));
422         break;
423
424       case 'saturation':
425         // Calculate hue, saturation, and brightness
426         hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360);
427         saturation = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
428         brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
429         hex = hsb2hex({
430           h: hue,
431           s: saturation,
432           b: brightness
433         });
434
435         // Update UI
436         slider.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: brightness }));
437         minicolors.find('.minicolors-grid-inner').css('opacity', saturation / 100);
438         break;
439
440       case 'brightness':
441         // Calculate hue, saturation, and brightness
442         hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360);
443         saturation = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
444         brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
445         hex = hsb2hex({
446           h: hue,
447           s: saturation,
448           b: brightness
449         });
450
451         // Update UI
452         slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 }));
453         minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (brightness / 100));
454         break;
455
456       default:
457         // Calculate hue, saturation, and brightness
458         hue = keepWithin(360 - parseInt(sliderPos.y * (360 / slider.height()), 10), 0, 360);
459         saturation = keepWithin(Math.floor(gridPos.x * (100 / grid.width())), 0, 100);
460         brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
461         hex = hsb2hex({
462           h: hue,
463           s: saturation,
464           b: brightness
465         });
466
467         // Update UI
468         grid.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: 100 }));
469         break;
470       }
471
472       // Handle opacity
473       if(settings.opacity) {
474         opacity = parseFloat(1 - (opacityPos.y / opacitySlider.height())).toFixed(2);
475       } else {
476         opacity = 1;
477       }
478
479       updateInput(input, hex, opacity);
480     }
481     else {
482       // Set swatch color
483       swatch.find('span').css({
484         backgroundColor: hex,
485         opacity: opacity
486       });
487
488       // Handle change event
489       doChange(input, hex, opacity);
490     }
491   }
492
493   // Sets the value of the input and does the appropriate conversions
494   // to respect settings, also updates the swatch
495   function updateInput(input, value, opacity) {
496     var rgb;
497
498     // Helpful references
499     var minicolors = input.parent();
500     var settings = input.data('minicolors-settings');
501     var swatch = minicolors.find('.minicolors-input-swatch');
502
503     if(settings.opacity) input.attr('data-opacity', opacity);
504
505     // Set color string
506     if(settings.format === 'rgb') {
507       // Returns RGB(A) string
508
509       // Checks for input format and does the conversion
510       if(isRgb(value)) {
511         rgb = parseRgb(value, true);
512       }
513       else {
514         rgb = hex2rgb(parseHex(value, true));
515       }
516
517       opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1);
518       if(isNaN(opacity) || !settings.opacity) opacity = 1;
519
520       if(input.minicolors('rgbObject').a <= 1 && rgb && settings.opacity) {
521         // Set RGBA string if alpha
522         value = 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')';
523       } else {
524         // Set RGB string (alpha = 1)
525         value = 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')';
526       }
527     } else {
528       // Returns hex color
529
530       // Checks for input format and does the conversion
531       if(isRgb(value)) {
532         value = rgbString2hex(value);
533       }
534
535       value = convertCase(value, settings.letterCase);
536     }
537
538     // Update value from picker
539     input.val(value);
540
541     // Set swatch color
542     swatch.find('span').css({
543       backgroundColor: value,
544       opacity: opacity
545     });
546
547     // Handle change event
548     doChange(input, value, opacity);
549   }
550
551   // Sets the color picker values from the input
552   function updateFromInput(input, preserveInputValue) {
553     var hex, hsb, opacity, keywords, alpha, value, x, y, r, phi;
554
555     // Helpful references
556     var minicolors = input.parent();
557     var settings = input.data('minicolors-settings');
558     var swatch = minicolors.find('.minicolors-input-swatch');
559
560     // Panel objects
561     var grid = minicolors.find('.minicolors-grid');
562     var slider = minicolors.find('.minicolors-slider');
563     var opacitySlider = minicolors.find('.minicolors-opacity-slider');
564
565     // Picker objects
566     var gridPicker = grid.find('[class$=-picker]');
567     var sliderPicker = slider.find('[class$=-picker]');
568     var opacityPicker = opacitySlider.find('[class$=-picker]');
569
570     // Determine hex/HSB values
571     if(isRgb(input.val())) {
572       // If input value is a rgb(a) string, convert it to hex color and update opacity
573       hex = rgbString2hex(input.val());
574       alpha = keepWithin(parseFloat(getAlpha(input.val())).toFixed(2), 0, 1);
575       if(alpha) {
576         input.attr('data-opacity', alpha);
577       }
578     } else {
579       hex = convertCase(parseHex(input.val(), true), settings.letterCase);
580     }
581
582     if(!hex){
583       hex = convertCase(parseInput(settings.defaultValue, true), settings.letterCase);
584     }
585     hsb = hex2hsb(hex);
586
587     // Get array of lowercase keywords
588     keywords = !settings.keywords ? [] : $.map(settings.keywords.split(','), function(a) {
589       return $.trim(a.toLowerCase());
590     });
591
592     // Set color string
593     if(input.val() !== '' && $.inArray(input.val().toLowerCase(), keywords) > -1) {
594       value = convertCase(input.val());
595     } else {
596       value = isRgb(input.val()) ? parseRgb(input.val()) : hex;
597     }
598
599     // Update input value
600     if(!preserveInputValue) input.val(value);
601
602     // Determine opacity value
603     if(settings.opacity) {
604       // Get from data-opacity attribute and keep within 0-1 range
605       opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1);
606       if(isNaN(opacity)) opacity = 1;
607       input.attr('data-opacity', opacity);
608       swatch.find('span').css('opacity', opacity);
609
610       // Set opacity picker position
611       y = keepWithin(opacitySlider.height() - (opacitySlider.height() * opacity), 0, opacitySlider.height());
612       opacityPicker.css('top', y + 'px');
613     }
614
615     // Set opacity to zero if input value is transparent
616     if(input.val().toLowerCase() === 'transparent') {
617       swatch.find('span').css('opacity', 0);
618     }
619
620     // Update swatch
621     swatch.find('span').css('backgroundColor', hex);
622
623     // Determine picker locations
624     switch(settings.control) {
625     case 'wheel':
626       // Set grid position
627       r = keepWithin(Math.ceil(hsb.s * 0.75), 0, grid.height() / 2);
628       phi = hsb.h * Math.PI / 180;
629       x = keepWithin(75 - Math.cos(phi) * r, 0, grid.width());
630       y = keepWithin(75 - Math.sin(phi) * r, 0, grid.height());
631       gridPicker.css({
632         top: y + 'px',
633         left: x + 'px'
634       });
635
636       // Set slider position
637       y = 150 - (hsb.b / (100 / grid.height()));
638       if(hex === '') y = 0;
639       sliderPicker.css('top', y + 'px');
640
641       // Update panel color
642       slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 }));
643       break;
644
645     case 'saturation':
646       // Set grid position
647       x = keepWithin((5 * hsb.h) / 12, 0, 150);
648       y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height());
649       gridPicker.css({
650         top: y + 'px',
651         left: x + 'px'
652       });
653
654       // Set slider position
655       y = keepWithin(slider.height() - (hsb.s * (slider.height() / 100)), 0, slider.height());
656       sliderPicker.css('top', y + 'px');
657
658       // Update UI
659       slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: hsb.b }));
660       minicolors.find('.minicolors-grid-inner').css('opacity', hsb.s / 100);
661       break;
662
663     case 'brightness':
664       // Set grid position
665       x = keepWithin((5 * hsb.h) / 12, 0, 150);
666       y = keepWithin(grid.height() - Math.ceil(hsb.s / (100 / grid.height())), 0, grid.height());
667       gridPicker.css({
668         top: y + 'px',
669         left: x + 'px'
670       });
671
672       // Set slider position
673       y = keepWithin(slider.height() - (hsb.b * (slider.height() / 100)), 0, slider.height());
674       sliderPicker.css('top', y + 'px');
675
676       // Update UI
677       slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 }));
678       minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (hsb.b / 100));
679       break;
680
681     default:
682       // Set grid position
683       x = keepWithin(Math.ceil(hsb.s / (100 / grid.width())), 0, grid.width());
684       y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height());
685       gridPicker.css({
686         top: y + 'px',
687         left: x + 'px'
688       });
689
690       // Set slider position
691       y = keepWithin(slider.height() - (hsb.h / (360 / slider.height())), 0, slider.height());
692       sliderPicker.css('top', y + 'px');
693
694       // Update panel color
695       grid.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: 100 }));
696       break;
697     }
698
699     // Fire change event, but only if minicolors is fully initialized
700     if(input.data('minicolors-initialized')) {
701       doChange(input, value, opacity);
702     }
703   }
704
705   // Runs the change and changeDelay callbacks
706   function doChange(input, value, opacity) {
707     var settings = input.data('minicolors-settings');
708     var lastChange = input.data('minicolors-lastChange');
709     var obj, sel, i;
710
711     // Only run if it actually changed
712     if(!lastChange || lastChange.value !== value || lastChange.opacity !== opacity) {
713
714       // Remember last-changed value
715       input.data('minicolors-lastChange', {
716         value: value,
717         opacity: opacity
718       });
719
720       // Check and select applicable swatch
721       if(settings.swatches && settings.swatches.length !== 0) {
722         if(!isRgb(value)) {
723           obj = hex2rgb(value);
724         }
725         else {
726           obj = parseRgb(value, true);
727         }
728         sel = -1;
729         for(i = 0; i < settings.swatches.length; ++i) {
730           if(obj.r === settings.swatches[i].r && obj.g === settings.swatches[i].g && obj.b === settings.swatches[i].b && obj.a === settings.swatches[i].a) {
731             sel = i;
732             break;
733           }
734         }
735
736         input.parent().find('.minicolors-swatches .minicolors-swatch').removeClass('selected');
737         if(sel !== -1) {
738           input.parent().find('.minicolors-swatches .minicolors-swatch').eq(i).addClass('selected');
739         }
740       }
741
742       // Fire change event
743       if(settings.change) {
744         if(settings.changeDelay) {
745           // Call after a delay
746           clearTimeout(input.data('minicolors-changeTimeout'));
747           input.data('minicolors-changeTimeout', setTimeout(function() {
748             settings.change.call(input.get(0), value, opacity);
749           }, settings.changeDelay));
750         } else {
751           // Call immediately
752           settings.change.call(input.get(0), value, opacity);
753         }
754       }
755       input.trigger('change').trigger('input');
756     }
757   }
758
759   // Generates an RGB(A) object based on the input's value
760   function rgbObject(input) {
761     var rgb,
762       opacity = $(input).attr('data-opacity');
763     if( isRgb($(input).val()) ) {
764       rgb = parseRgb($(input).val(), true);
765     } else {
766       var hex = parseHex($(input).val(), true);
767       rgb = hex2rgb(hex);
768     }
769     if( !rgb ) return null;
770     if( opacity !== undefined ) $.extend(rgb, { a: parseFloat(opacity) });
771     return rgb;
772   }
773
774   // Generates an RGB(A) string based on the input's value
775   function rgbString(input, alpha) {
776     var rgb,
777       opacity = $(input).attr('data-opacity');
778     if( isRgb($(input).val()) ) {
779       rgb = parseRgb($(input).val(), true);
780     } else {
781       var hex = parseHex($(input).val(), true);
782       rgb = hex2rgb(hex);
783     }
784     if( !rgb ) return null;
785     if( opacity === undefined ) opacity = 1;
786     if( alpha ) {
787       return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')';
788     } else {
789       return 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')';
790     }
791   }
792
793   // Converts to the letter case specified in settings
794   function convertCase(string, letterCase) {
795     return letterCase === 'uppercase' ? string.toUpperCase() : string.toLowerCase();
796   }
797
798   // Parses a string and returns a valid hex string when possible
799   function parseHex(string, expand) {
800     string = string.replace(/^#/g, '');
801     if(!string.match(/^[A-F0-9]{3,6}/ig)) return '';
802     if(string.length !== 3 && string.length !== 6) return '';
803     if(string.length === 3 && expand) {
804       string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2];
805     }
806     return '#' + string;
807   }
808
809   // Parses a string and returns a valid RGB(A) string when possible
810   function parseRgb(string, obj) {
811     var values = string.replace(/[^\d,.]/g, '');
812     var rgba = values.split(',');
813
814     rgba[0] = keepWithin(parseInt(rgba[0], 10), 0, 255);
815     rgba[1] = keepWithin(parseInt(rgba[1], 10), 0, 255);
816     rgba[2] = keepWithin(parseInt(rgba[2], 10), 0, 255);
817     if(rgba[3]) {
818       rgba[3] = keepWithin(parseFloat(rgba[3], 10), 0, 1);
819     }
820
821     // Return RGBA object
822     if( obj ) {
823       if (rgba[3]) {
824         return {
825           r: rgba[0],
826           g: rgba[1],
827           b: rgba[2],
828           a: rgba[3]
829         };
830       } else {
831         return {
832           r: rgba[0],
833           g: rgba[1],
834           b: rgba[2]
835         };
836       }
837     }
838
839     // Return RGBA string
840     if(typeof(rgba[3]) !== 'undefined' && rgba[3] <= 1) {
841       return 'rgba(' + rgba[0] + ', ' + rgba[1] + ', ' + rgba[2] + ', ' + rgba[3] + ')';
842     } else {
843       return 'rgb(' + rgba[0] + ', ' + rgba[1] + ', ' + rgba[2] + ')';
844     }
845
846   }
847
848   // Parses a string and returns a valid color string when possible
849   function parseInput(string, expand) {
850     if(isRgb(string)) {
851       // Returns a valid rgb(a) string
852       return parseRgb(string);
853     } else {
854       return parseHex(string, expand);
855     }
856   }
857
858   // Keeps value within min and max
859   function keepWithin(value, min, max) {
860     if(value < min) value = min;
861     if(value > max) value = max;
862     return value;
863   }
864
865   // Checks if a string is a valid RGB(A) string
866   function isRgb(string) {
867     var rgb = string.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
868     return (rgb && rgb.length === 4) ? true : false;
869   }
870
871   // Function to get alpha from a RGB(A) string
872   function getAlpha(rgba) {
873     rgba = rgba.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+(\.\d{1,2})?|\.\d{1,2})[\s+]?/i);
874     return (rgba && rgba.length === 6) ? rgba[4] : '1';
875   }
876
877   // Converts an HSB object to an RGB object
878   function hsb2rgb(hsb) {
879     var rgb = {};
880     var h = Math.round(hsb.h);
881     var s = Math.round(hsb.s * 255 / 100);
882     var v = Math.round(hsb.b * 255 / 100);
883     if(s === 0) {
884       rgb.r = rgb.g = rgb.b = v;
885     } else {
886       var t1 = v;
887       var t2 = (255 - s) * v / 255;
888       var t3 = (t1 - t2) * (h % 60) / 60;
889       if(h === 360) h = 0;
890       if(h < 60) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; }
891       else if(h < 120) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; }
892       else if(h < 180) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; }
893       else if(h < 240) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; }
894       else if(h < 300) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; }
895       else if(h < 360) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; }
896       else { rgb.r = 0; rgb.g = 0; rgb.b = 0; }
897     }
898     return {
899       r: Math.round(rgb.r),
900       g: Math.round(rgb.g),
901       b: Math.round(rgb.b)
902     };
903   }
904
905   // Converts an RGB string to a hex string
906   function rgbString2hex(rgb){
907     rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
908     return (rgb && rgb.length === 4) ? '#' +
909     ('0' + parseInt(rgb[1],10).toString(16)).slice(-2) +
910     ('0' + parseInt(rgb[2],10).toString(16)).slice(-2) +
911     ('0' + parseInt(rgb[3],10).toString(16)).slice(-2) : '';
912   }
913
914   // Converts an RGB object to a hex string
915   function rgb2hex(rgb) {
916     var hex = [
917       rgb.r.toString(16),
918       rgb.g.toString(16),
919       rgb.b.toString(16)
920     ];
921     $.each(hex, function(nr, val) {
922       if(val.length === 1) hex[nr] = '0' + val;
923     });
924     return '#' + hex.join('');
925   }
926
927   // Converts an HSB object to a hex string
928   function hsb2hex(hsb) {
929     return rgb2hex(hsb2rgb(hsb));
930   }
931
932   // Converts a hex string to an HSB object
933   function hex2hsb(hex) {
934     var hsb = rgb2hsb(hex2rgb(hex));
935     if(hsb.s === 0) hsb.h = 360;
936     return hsb;
937   }
938
939   // Converts an RGB object to an HSB object
940   function rgb2hsb(rgb) {
941     var hsb = { h: 0, s: 0, b: 0 };
942     var min = Math.min(rgb.r, rgb.g, rgb.b);
943     var max = Math.max(rgb.r, rgb.g, rgb.b);
944     var delta = max - min;
945     hsb.b = max;
946     hsb.s = max !== 0 ? 255 * delta / max : 0;
947     if(hsb.s !== 0) {
948       if(rgb.r === max) {
949         hsb.h = (rgb.g - rgb.b) / delta;
950       } else if(rgb.g === max) {
951         hsb.h = 2 + (rgb.b - rgb.r) / delta;
952       } else {
953         hsb.h = 4 + (rgb.r - rgb.g) / delta;
954       }
955     } else {
956       hsb.h = -1;
957     }
958     hsb.h *= 60;
959     if(hsb.h < 0) {
960       hsb.h += 360;
961     }
962     hsb.s *= 100/255;
963     hsb.b *= 100/255;
964     return hsb;
965   }
966
967   // Converts a hex string to an RGB object
968   function hex2rgb(hex) {
969     hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
970     return {
971       r: hex >> 16,
972       g: (hex & 0x00FF00) >> 8,
973       b: (hex & 0x0000FF)
974     };
975   }
976
977   // Handle events
978   $([document])
979     // Hide on clicks outside of the control
980     .on('mousedown.minicolors touchstart.minicolors', function(event) {
981       if(!$(event.target).parents().add(event.target).hasClass('minicolors')) {
982         hide();
983       }
984     })
985     // Start moving
986     .on('mousedown.minicolors touchstart.minicolors', '.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider', function(event) {
987       var target = $(this);
988       event.preventDefault();
989       $(event.delegateTarget).data('minicolors-target', target);
990       move(target, event, true);
991     })
992     // Move pickers
993     .on('mousemove.minicolors touchmove.minicolors', function(event) {
994       var target = $(event.delegateTarget).data('minicolors-target');
995       if(target) move(target, event);
996     })
997     // Stop moving
998     .on('mouseup.minicolors touchend.minicolors', function() {
999       $(this).removeData('minicolors-target');
1000     })
1001     // Selected a swatch
1002     .on('click.minicolors', '.minicolors-swatches li', function(event) {
1003       event.preventDefault();
1004       var target = $(this), input = target.parents('.minicolors').find('.minicolors-input'), color = target.data('swatch-color');
1005       updateInput(input, color, getAlpha(color));
1006       updateFromInput(input);
1007     })
1008     // Show panel when swatch is clicked
1009     .on('mousedown.minicolors touchstart.minicolors', '.minicolors-input-swatch', function(event) {
1010       var input = $(this).parent().find('.minicolors-input');
1011       event.preventDefault();
1012       show(input);
1013     })
1014     // Show on focus
1015     .on('focus.minicolors', '.minicolors-input', function() {
1016       var input = $(this);
1017       if(!input.data('minicolors-initialized')) return;
1018       show(input);
1019     })
1020     // Update value on blur
1021     .on('blur.minicolors', '.minicolors-input', function() {
1022       var input = $(this);
1023       var settings = input.data('minicolors-settings');
1024       var keywords;
1025       var hex;
1026       var rgba;
1027       var swatchOpacity;
1028       var value;
1029
1030       if(!input.data('minicolors-initialized')) return;
1031
1032       // Get array of lowercase keywords
1033       keywords = !settings.keywords ? [] : $.map(settings.keywords.split(','), function(a) {
1034         return $.trim(a.toLowerCase());
1035       });
1036
1037       // Set color string
1038       if(input.val() !== '' && $.inArray(input.val().toLowerCase(), keywords) > -1) {
1039         value = input.val();
1040       } else {
1041         // Get RGBA values for easy conversion
1042         if(isRgb(input.val())) {
1043           rgba = parseRgb(input.val(), true);
1044         } else {
1045           hex = parseHex(input.val(), true);
1046           rgba = hex ? hex2rgb(hex) : null;
1047         }
1048
1049         // Convert to format
1050         if(rgba === null) {
1051           value = settings.defaultValue;
1052         } else if(settings.format === 'rgb') {
1053           value = settings.opacity ?
1054           parseRgb('rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + input.attr('data-opacity') + ')') :
1055           parseRgb('rgb(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ')');
1056         } else {
1057           value = rgb2hex(rgba);
1058         }
1059       }
1060
1061       // Update swatch opacity
1062       swatchOpacity = settings.opacity ? input.attr('data-opacity') : 1;
1063       if(value.toLowerCase() === 'transparent') swatchOpacity = 0;
1064       input
1065       .closest('.minicolors')
1066       .find('.minicolors-input-swatch > span')
1067       .css('opacity', swatchOpacity);
1068
1069       // Set input value
1070       input.val(value);
1071
1072       // Is it blank?
1073       if(input.val() === '') input.val(parseInput(settings.defaultValue, true));
1074
1075       // Adjust case
1076       input.val(convertCase(input.val(), settings.letterCase));
1077
1078     })
1079     // Handle keypresses
1080     .on('keydown.minicolors', '.minicolors-input', function(event) {
1081       var input = $(this);
1082       if(!input.data('minicolors-initialized')) return;
1083       switch(event.keyCode) {
1084       case 9: // tab
1085         hide();
1086         break;
1087       case 13: // enter
1088       case 27: // esc
1089         hide();
1090         input.blur();
1091         break;
1092       }
1093     })
1094     // Update on keyup
1095     .on('keyup.minicolors', '.minicolors-input', function() {
1096       var input = $(this);
1097       if(!input.data('minicolors-initialized')) return;
1098       updateFromInput(input, true);
1099     })
1100     // Update on paste
1101     .on('paste.minicolors', '.minicolors-input', function() {
1102       var input = $(this);
1103       if(!input.data('minicolors-initialized')) return;
1104       setTimeout(function() {
1105         updateFromInput(input, true);
1106       }, 1);
1107     });
1108 }));