jquery.minicolors.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. /*
  2. * jQuery MiniColors: A tiny color picker built on jQuery
  3. *
  4. * Copyright Cory LaViska for A Beautiful Site, LLC. (http://www.abeautifulsite.net/)
  5. *
  6. * Licensed under the MIT license: http://opensource.org/licenses/MIT
  7. *
  8. */
  9. if(jQuery) (function($) {
  10. // Defaults
  11. $.minicolors = {
  12. defaults: {
  13. animationSpeed: 50,
  14. animationEasing: 'swing',
  15. change: null,
  16. changeDelay: 0,
  17. control: 'hue',
  18. defaultValue: '',
  19. hide: null,
  20. hideSpeed: 100,
  21. inline: false,
  22. letterCase: 'lowercase',
  23. opacity: false,
  24. position: 'bottom left',
  25. show: null,
  26. showSpeed: 100,
  27. theme: 'default'
  28. }
  29. };
  30. // Public methods
  31. $.extend($.fn, {
  32. minicolors: function(method, data) {
  33. switch(method) {
  34. // Destroy the control
  35. case 'destroy':
  36. $(this).each( function() {
  37. destroy($(this));
  38. });
  39. return $(this);
  40. // Hide the color picker
  41. case 'hide':
  42. hide();
  43. return $(this);
  44. // Get/set opacity
  45. case 'opacity':
  46. // Getter
  47. if( data === undefined ) {
  48. // Getter
  49. return $(this).attr('data-opacity');
  50. } else {
  51. // Setter
  52. $(this).each( function() {
  53. updateFromInput($(this).attr('data-opacity', data));
  54. });
  55. }
  56. return $(this);
  57. // Get an RGB(A) object based on the current color/opacity
  58. case 'rgbObject':
  59. return rgbObject($(this), method === 'rgbaObject');
  60. // Get an RGB(A) string based on the current color/opacity
  61. case 'rgbString':
  62. case 'rgbaString':
  63. return rgbString($(this), method === 'rgbaString');
  64. // Get/set settings on the fly
  65. case 'settings':
  66. if( data === undefined ) {
  67. return $(this).data('minicolors-settings');
  68. } else {
  69. // Setter
  70. $(this).each( function() {
  71. var settings = $(this).data('minicolors-settings') || {};
  72. destroy($(this));
  73. $(this).minicolors($.extend(true, settings, data));
  74. });
  75. }
  76. return $(this);
  77. // Show the color picker
  78. case 'show':
  79. show( $(this).eq(0) );
  80. return $(this);
  81. // Get/set the hex color value
  82. case 'value':
  83. if( data === undefined ) {
  84. // Getter
  85. return $(this).val();
  86. } else {
  87. // Setter
  88. $(this).each( function() {
  89. updateFromInput($(this).val(data));
  90. });
  91. }
  92. return $(this);
  93. // Initializes the control
  94. default:
  95. if( method !== 'create' ) data = method;
  96. $(this).each( function() {
  97. init($(this), data);
  98. });
  99. return $(this);
  100. }
  101. }
  102. });
  103. // Initialize input elements
  104. function init(input, settings) {
  105. var minicolors = $('<div class="minicolors" />'),
  106. defaults = $.minicolors.defaults;
  107. // Do nothing if already initialized
  108. if( input.data('minicolors-initialized') ) return;
  109. // Handle settings
  110. settings = $.extend(true, {}, defaults, settings);
  111. // The wrapper
  112. minicolors
  113. .addClass('minicolors-theme-' + settings.theme)
  114. .toggleClass('minicolors-with-opacity', settings.opacity);
  115. // Custom positioning
  116. if( settings.position !== undefined ) {
  117. $.each(settings.position.split(' '), function() {
  118. minicolors.addClass('minicolors-position-' + this);
  119. });
  120. }
  121. // The input
  122. input
  123. .addClass('minicolors-input')
  124. .data('minicolors-initialized', false)
  125. .data('minicolors-settings', settings)
  126. .prop('size', 7)
  127. .wrap(minicolors)
  128. .after(
  129. '<div class="minicolors-panel minicolors-slider-' + settings.control + '">' +
  130. '<div class="minicolors-slider">' +
  131. '<div class="minicolors-picker"></div>' +
  132. '</div>' +
  133. '<div class="minicolors-opacity-slider">' +
  134. '<div class="minicolors-picker"></div>' +
  135. '</div>' +
  136. '<div class="minicolors-grid">' +
  137. '<div class="minicolors-grid-inner"></div>' +
  138. '<div class="minicolors-picker"><div></div></div>' +
  139. '</div>' +
  140. '</div>'
  141. );
  142. // The swatch
  143. if( !settings.inline ) {
  144. input.after('<span class="minicolors-swatch"><span class="minicolors-swatch-color"></span></span>');
  145. input.next('.minicolors-swatch').on('click', function(event) {
  146. event.preventDefault();
  147. input.focus();
  148. });
  149. }
  150. // Prevent text selection in IE
  151. input.parent().find('.minicolors-panel').on('selectstart', function() { return false; }).end();
  152. // Inline controls
  153. if( settings.inline ) input.parent().addClass('minicolors-inline');
  154. updateFromInput(input, false);
  155. input.data('minicolors-initialized', true);
  156. }
  157. // Returns the input back to its original state
  158. function destroy(input) {
  159. var minicolors = input.parent();
  160. // Revert the input element
  161. input
  162. .removeData('minicolors-initialized')
  163. .removeData('minicolors-settings')
  164. .removeProp('size')
  165. .removeClass('minicolors-input');
  166. // Remove the wrap and destroy whatever remains
  167. minicolors.before(input).remove();
  168. }
  169. // Shows the specified dropdown panel
  170. function show(input) {
  171. var minicolors = input.parent(),
  172. panel = minicolors.find('.minicolors-panel'),
  173. settings = input.data('minicolors-settings');
  174. // Do nothing if uninitialized, disabled, inline, or already open
  175. if( !input.data('minicolors-initialized') ||
  176. input.prop('disabled') ||
  177. minicolors.hasClass('minicolors-inline') ||
  178. minicolors.hasClass('minicolors-focus')
  179. ) return;
  180. hide();
  181. minicolors.addClass('minicolors-focus');
  182. panel
  183. .stop(true, true)
  184. .fadeIn(settings.showSpeed, function() {
  185. if( settings.show ) settings.show.call(input.get(0));
  186. });
  187. }
  188. // Hides all dropdown panels
  189. function hide() {
  190. $('.minicolors-input').each( function() {
  191. var input = $(this),
  192. settings = input.data('minicolors-settings'),
  193. minicolors = input.parent();
  194. // Don't hide inline controls
  195. if( settings.inline ) return;
  196. minicolors.find('.minicolors-panel').fadeOut(settings.hideSpeed, function() {
  197. if(minicolors.hasClass('minicolors-focus')) {
  198. if( settings.hide ) settings.hide.call(input.get(0));
  199. }
  200. minicolors.removeClass('minicolors-focus');
  201. });
  202. });
  203. }
  204. // Moves the selected picker
  205. function move(target, event, animate) {
  206. var input = target.parents('.minicolors').find('.minicolors-input'),
  207. settings = input.data('minicolors-settings'),
  208. picker = target.find('[class$=-picker]'),
  209. offsetX = target.offset().left,
  210. offsetY = target.offset().top,
  211. x = Math.round(event.pageX - offsetX),
  212. y = Math.round(event.pageY - offsetY),
  213. duration = animate ? settings.animationSpeed : 0,
  214. wx, wy, r, phi;
  215. // Touch support
  216. if( event.originalEvent.changedTouches ) {
  217. x = event.originalEvent.changedTouches[0].pageX - offsetX;
  218. y = event.originalEvent.changedTouches[0].pageY - offsetY;
  219. }
  220. // Constrain picker to its container
  221. if( x < 0 ) x = 0;
  222. if( y < 0 ) y = 0;
  223. if( x > target.width() ) x = target.width();
  224. if( y > target.height() ) y = target.height();
  225. // Constrain color wheel values to the wheel
  226. if( target.parent().is('.minicolors-slider-wheel') && picker.parent().is('.minicolors-grid') ) {
  227. wx = 75 - x;
  228. wy = 75 - y;
  229. r = Math.sqrt(wx * wx + wy * wy);
  230. phi = Math.atan2(wy, wx);
  231. if( phi < 0 ) phi += Math.PI * 2;
  232. if( r > 75 ) {
  233. r = 75;
  234. x = 75 - (75 * Math.cos(phi));
  235. y = 75 - (75 * Math.sin(phi));
  236. }
  237. x = Math.round(x);
  238. y = Math.round(y);
  239. }
  240. // Move the picker
  241. if( target.is('.minicolors-grid') ) {
  242. picker
  243. .stop(true)
  244. .animate({
  245. top: y + 'px',
  246. left: x + 'px'
  247. }, duration, settings.animationEasing, function() {
  248. updateFromControl(input, target);
  249. });
  250. } else {
  251. picker
  252. .stop(true)
  253. .animate({
  254. top: y + 'px'
  255. }, duration, settings.animationEasing, function() {
  256. updateFromControl(input, target);
  257. });
  258. }
  259. }
  260. // Sets the input based on the color picker values
  261. function updateFromControl(input, target) {
  262. function getCoords(picker, container) {
  263. var left, top;
  264. if( !picker.length || !container ) return null;
  265. left = picker.offset().left;
  266. top = picker.offset().top;
  267. return {
  268. x: left - container.offset().left + (picker.outerWidth() / 2),
  269. y: top - container.offset().top + (picker.outerHeight() / 2)
  270. };
  271. }
  272. var hue, saturation, brightness, x, y, r, phi,
  273. hex = input.val(),
  274. opacity = input.attr('data-opacity'),
  275. // Helpful references
  276. minicolors = input.parent(),
  277. settings = input.data('minicolors-settings'),
  278. swatch = minicolors.find('.minicolors-swatch'),
  279. // Panel objects
  280. grid = minicolors.find('.minicolors-grid'),
  281. slider = minicolors.find('.minicolors-slider'),
  282. opacitySlider = minicolors.find('.minicolors-opacity-slider'),
  283. // Picker objects
  284. gridPicker = grid.find('[class$=-picker]'),
  285. sliderPicker = slider.find('[class$=-picker]'),
  286. opacityPicker = opacitySlider.find('[class$=-picker]'),
  287. // Picker positions
  288. gridPos = getCoords(gridPicker, grid),
  289. sliderPos = getCoords(sliderPicker, slider),
  290. opacityPos = getCoords(opacityPicker, opacitySlider);
  291. // Handle colors
  292. if( target.is('.minicolors-grid, .minicolors-slider') ) {
  293. // Determine HSB values
  294. switch(settings.control) {
  295. case 'wheel':
  296. // Calculate hue, saturation, and brightness
  297. x = (grid.width() / 2) - gridPos.x;
  298. y = (grid.height() / 2) - gridPos.y;
  299. r = Math.sqrt(x * x + y * y);
  300. phi = Math.atan2(y, x);
  301. if( phi < 0 ) phi += Math.PI * 2;
  302. if( r > 75 ) {
  303. r = 75;
  304. gridPos.x = 69 - (75 * Math.cos(phi));
  305. gridPos.y = 69 - (75 * Math.sin(phi));
  306. }
  307. saturation = keepWithin(r / 0.75, 0, 100);
  308. hue = keepWithin(phi * 180 / Math.PI, 0, 360);
  309. brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
  310. hex = hsb2hex({
  311. h: hue,
  312. s: saturation,
  313. b: brightness
  314. });
  315. // Update UI
  316. slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 }));
  317. break;
  318. case 'saturation':
  319. // Calculate hue, saturation, and brightness
  320. hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360);
  321. saturation = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
  322. brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
  323. hex = hsb2hex({
  324. h: hue,
  325. s: saturation,
  326. b: brightness
  327. });
  328. // Update UI
  329. slider.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: brightness }));
  330. minicolors.find('.minicolors-grid-inner').css('opacity', saturation / 100);
  331. break;
  332. case 'brightness':
  333. // Calculate hue, saturation, and brightness
  334. hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360);
  335. saturation = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
  336. brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
  337. hex = hsb2hex({
  338. h: hue,
  339. s: saturation,
  340. b: brightness
  341. });
  342. // Update UI
  343. slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 }));
  344. minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (brightness / 100));
  345. break;
  346. default:
  347. // Calculate hue, saturation, and brightness
  348. hue = keepWithin(360 - parseInt(sliderPos.y * (360 / slider.height()), 10), 0, 360);
  349. saturation = keepWithin(Math.floor(gridPos.x * (100 / grid.width())), 0, 100);
  350. brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
  351. hex = hsb2hex({
  352. h: hue,
  353. s: saturation,
  354. b: brightness
  355. });
  356. // Update UI
  357. grid.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: 100 }));
  358. break;
  359. }
  360. // Adjust case
  361. input.val( convertCase(hex, settings.letterCase) );
  362. }
  363. // Handle opacity
  364. if( target.is('.minicolors-opacity-slider') ) {
  365. if( settings.opacity ) {
  366. opacity = parseFloat(1 - (opacityPos.y / opacitySlider.height())).toFixed(2);
  367. } else {
  368. opacity = 1;
  369. }
  370. if( settings.opacity ) input.attr('data-opacity', opacity);
  371. }
  372. // Set swatch color
  373. swatch.find('SPAN').css({
  374. backgroundColor: hex,
  375. opacity: opacity
  376. });
  377. // Handle change event
  378. doChange(input, hex, opacity);
  379. }
  380. // Sets the color picker values from the input
  381. function updateFromInput(input, preserveInputValue) {
  382. var hex,
  383. hsb,
  384. opacity,
  385. x, y, r, phi,
  386. // Helpful references
  387. minicolors = input.parent(),
  388. settings = input.data('minicolors-settings'),
  389. swatch = minicolors.find('.minicolors-swatch'),
  390. // Panel objects
  391. grid = minicolors.find('.minicolors-grid'),
  392. slider = minicolors.find('.minicolors-slider'),
  393. opacitySlider = minicolors.find('.minicolors-opacity-slider'),
  394. // Picker objects
  395. gridPicker = grid.find('[class$=-picker]'),
  396. sliderPicker = slider.find('[class$=-picker]'),
  397. opacityPicker = opacitySlider.find('[class$=-picker]');
  398. // Determine hex/HSB values
  399. hex = convertCase(parseHex(input.val(), true), settings.letterCase);
  400. if( !hex ){
  401. hex = convertCase(parseHex(settings.defaultValue, true), settings.letterCase);
  402. }
  403. hsb = hex2hsb(hex);
  404. // Update input value
  405. if( !preserveInputValue ) input.val(hex);
  406. // Determine opacity value
  407. if( settings.opacity ) {
  408. // Get from data-opacity attribute and keep within 0-1 range
  409. opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1);
  410. if( isNaN(opacity) ) opacity = 1;
  411. input.attr('data-opacity', opacity);
  412. swatch.find('SPAN').css('opacity', opacity);
  413. // Set opacity picker position
  414. y = keepWithin(opacitySlider.height() - (opacitySlider.height() * opacity), 0, opacitySlider.height());
  415. opacityPicker.css('top', y + 'px');
  416. }
  417. // Update swatch
  418. swatch.find('SPAN').css('backgroundColor', hex);
  419. // Determine picker locations
  420. switch(settings.control) {
  421. case 'wheel':
  422. // Set grid position
  423. r = keepWithin(Math.ceil(hsb.s * 0.75), 0, grid.height() / 2);
  424. phi = hsb.h * Math.PI / 180;
  425. x = keepWithin(75 - Math.cos(phi) * r, 0, grid.width());
  426. y = keepWithin(75 - Math.sin(phi) * r, 0, grid.height());
  427. gridPicker.css({
  428. top: y + 'px',
  429. left: x + 'px'
  430. });
  431. // Set slider position
  432. y = 150 - (hsb.b / (100 / grid.height()));
  433. if( hex === '' ) y = 0;
  434. sliderPicker.css('top', y + 'px');
  435. // Update panel color
  436. slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 }));
  437. break;
  438. case 'saturation':
  439. // Set grid position
  440. x = keepWithin((5 * hsb.h) / 12, 0, 150);
  441. y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height());
  442. gridPicker.css({
  443. top: y + 'px',
  444. left: x + 'px'
  445. });
  446. // Set slider position
  447. y = keepWithin(slider.height() - (hsb.s * (slider.height() / 100)), 0, slider.height());
  448. sliderPicker.css('top', y + 'px');
  449. // Update UI
  450. slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: hsb.b }));
  451. minicolors.find('.minicolors-grid-inner').css('opacity', hsb.s / 100);
  452. break;
  453. case 'brightness':
  454. // Set grid position
  455. x = keepWithin((5 * hsb.h) / 12, 0, 150);
  456. y = keepWithin(grid.height() - Math.ceil(hsb.s / (100 / grid.height())), 0, grid.height());
  457. gridPicker.css({
  458. top: y + 'px',
  459. left: x + 'px'
  460. });
  461. // Set slider position
  462. y = keepWithin(slider.height() - (hsb.b * (slider.height() / 100)), 0, slider.height());
  463. sliderPicker.css('top', y + 'px');
  464. // Update UI
  465. slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 }));
  466. minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (hsb.b / 100));
  467. break;
  468. default:
  469. // Set grid position
  470. x = keepWithin(Math.ceil(hsb.s / (100 / grid.width())), 0, grid.width());
  471. y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height());
  472. gridPicker.css({
  473. top: y + 'px',
  474. left: x + 'px'
  475. });
  476. // Set slider position
  477. y = keepWithin(slider.height() - (hsb.h / (360 / slider.height())), 0, slider.height());
  478. sliderPicker.css('top', y + 'px');
  479. // Update panel color
  480. grid.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: 100 }));
  481. break;
  482. }
  483. // Fire change event, but only if minicolors is fully initialized
  484. if( input.data('minicolors-initialized') ) {
  485. doChange(input, hex, opacity);
  486. }
  487. }
  488. // Runs the change and changeDelay callbacks
  489. function doChange(input, hex, opacity) {
  490. var settings = input.data('minicolors-settings'),
  491. lastChange = input.data('minicolors-lastChange');
  492. // Only run if it actually changed
  493. if( !lastChange || lastChange.hex !== hex || lastChange.opacity !== opacity ) {
  494. // Remember last-changed value
  495. input.data('minicolors-lastChange', {
  496. hex: hex,
  497. opacity: opacity
  498. });
  499. // Fire change event
  500. if( settings.change ) {
  501. if( settings.changeDelay ) {
  502. // Call after a delay
  503. clearTimeout(input.data('minicolors-changeTimeout'));
  504. input.data('minicolors-changeTimeout', setTimeout( function() {
  505. settings.change.call(input.get(0), hex, opacity);
  506. }, settings.changeDelay));
  507. } else {
  508. // Call immediately
  509. settings.change.call(input.get(0), hex, opacity);
  510. }
  511. }
  512. input.trigger('change').trigger('input');
  513. }
  514. }
  515. // Generates an RGB(A) object based on the input's value
  516. function rgbObject(input) {
  517. var hex = parseHex($(input).val(), true),
  518. rgb = hex2rgb(hex),
  519. opacity = $(input).attr('data-opacity');
  520. if( !rgb ) return null;
  521. if( opacity !== undefined ) $.extend(rgb, { a: parseFloat(opacity) });
  522. return rgb;
  523. }
  524. // Genearates an RGB(A) string based on the input's value
  525. function rgbString(input, alpha) {
  526. var hex = parseHex($(input).val(), true),
  527. rgb = hex2rgb(hex),
  528. opacity = $(input).attr('data-opacity');
  529. if( !rgb ) return null;
  530. if( opacity === undefined ) opacity = 1;
  531. if( alpha ) {
  532. return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')';
  533. } else {
  534. return 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')';
  535. }
  536. }
  537. // Converts to the letter case specified in settings
  538. function convertCase(string, letterCase) {
  539. return letterCase === 'uppercase' ? string.toUpperCase() : string.toLowerCase();
  540. }
  541. // Parses a string and returns a valid hex string when possible
  542. function parseHex(string, expand) {
  543. string = string.replace(/[^A-F0-9]/ig, '');
  544. if( string.length !== 3 && string.length !== 6 ) return '';
  545. if( string.length === 3 && expand ) {
  546. string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2];
  547. }
  548. return '#' + string;
  549. }
  550. // Keeps value within min and max
  551. function keepWithin(value, min, max) {
  552. if( value < min ) value = min;
  553. if( value > max ) value = max;
  554. return value;
  555. }
  556. // Converts an HSB object to an RGB object
  557. function hsb2rgb(hsb) {
  558. var rgb = {};
  559. var h = Math.round(hsb.h);
  560. var s = Math.round(hsb.s * 255 / 100);
  561. var v = Math.round(hsb.b * 255 / 100);
  562. if(s === 0) {
  563. rgb.r = rgb.g = rgb.b = v;
  564. } else {
  565. var t1 = v;
  566. var t2 = (255 - s) * v / 255;
  567. var t3 = (t1 - t2) * (h % 60) / 60;
  568. if( h === 360 ) h = 0;
  569. if( h < 60 ) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; }
  570. else if( h < 120 ) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; }
  571. else if( h < 180 ) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; }
  572. else if( h < 240 ) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; }
  573. else if( h < 300 ) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; }
  574. else if( h < 360 ) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; }
  575. else { rgb.r = 0; rgb.g = 0; rgb.b = 0; }
  576. }
  577. return {
  578. r: Math.round(rgb.r),
  579. g: Math.round(rgb.g),
  580. b: Math.round(rgb.b)
  581. };
  582. }
  583. // Converts an RGB object to a hex string
  584. function rgb2hex(rgb) {
  585. var hex = [
  586. rgb.r.toString(16),
  587. rgb.g.toString(16),
  588. rgb.b.toString(16)
  589. ];
  590. $.each(hex, function(nr, val) {
  591. if (val.length === 1) hex[nr] = '0' + val;
  592. });
  593. return '#' + hex.join('');
  594. }
  595. // Converts an HSB object to a hex string
  596. function hsb2hex(hsb) {
  597. return rgb2hex(hsb2rgb(hsb));
  598. }
  599. // Converts a hex string to an HSB object
  600. function hex2hsb(hex) {
  601. var hsb = rgb2hsb(hex2rgb(hex));
  602. if( hsb.s === 0 ) hsb.h = 360;
  603. return hsb;
  604. }
  605. // Converts an RGB object to an HSB object
  606. function rgb2hsb(rgb) {
  607. var hsb = { h: 0, s: 0, b: 0 };
  608. var min = Math.min(rgb.r, rgb.g, rgb.b);
  609. var max = Math.max(rgb.r, rgb.g, rgb.b);
  610. var delta = max - min;
  611. hsb.b = max;
  612. hsb.s = max !== 0 ? 255 * delta / max : 0;
  613. if( hsb.s !== 0 ) {
  614. if( rgb.r === max ) {
  615. hsb.h = (rgb.g - rgb.b) / delta;
  616. } else if( rgb.g === max ) {
  617. hsb.h = 2 + (rgb.b - rgb.r) / delta;
  618. } else {
  619. hsb.h = 4 + (rgb.r - rgb.g) / delta;
  620. }
  621. } else {
  622. hsb.h = -1;
  623. }
  624. hsb.h *= 60;
  625. if( hsb.h < 0 ) {
  626. hsb.h += 360;
  627. }
  628. hsb.s *= 100/255;
  629. hsb.b *= 100/255;
  630. return hsb;
  631. }
  632. // Converts a hex string to an RGB object
  633. function hex2rgb(hex) {
  634. hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
  635. return {
  636. r: hex >> 16,
  637. g: (hex & 0x00FF00) >> 8,
  638. b: (hex & 0x0000FF)
  639. };
  640. }
  641. // Handle events
  642. $(document)
  643. // Hide on clicks outside of the control
  644. .on('mousedown.minicolors touchstart.minicolors', function(event) {
  645. if( !$(event.target).parents().add(event.target).hasClass('minicolors') ) {
  646. hide();
  647. }
  648. })
  649. // Start moving
  650. .on('mousedown.minicolors touchstart.minicolors', '.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider', function(event) {
  651. var target = $(this);
  652. event.preventDefault();
  653. $(document).data('minicolors-target', target);
  654. move(target, event, true);
  655. })
  656. // Move pickers
  657. .on('mousemove.minicolors touchmove.minicolors', function(event) {
  658. var target = $(document).data('minicolors-target');
  659. if( target ) move(target, event);
  660. })
  661. // Stop moving
  662. .on('mouseup.minicolors touchend.minicolors', function() {
  663. $(this).removeData('minicolors-target');
  664. })
  665. // Show panel when swatch is clicked
  666. .on('mousedown.minicolors touchstart.minicolors', '.minicolors-swatch', function(event) {
  667. var input = $(this).parent().find('.minicolors-input');
  668. event.preventDefault();
  669. show(input);
  670. })
  671. // Show on focus
  672. .on('focus.minicolors', '.minicolors-input', function() {
  673. var input = $(this);
  674. if( !input.data('minicolors-initialized') ) return;
  675. show(input);
  676. })
  677. // Fix hex on blur
  678. .on('blur.minicolors', '.minicolors-input', function() {
  679. var input = $(this),
  680. settings = input.data('minicolors-settings');
  681. if( !input.data('minicolors-initialized') ) return;
  682. // Parse Hex
  683. input.val(parseHex(input.val(), true));
  684. // Is it blank?
  685. if( input.val() === '' ) input.val(parseHex(settings.defaultValue, true));
  686. // Adjust case
  687. input.val( convertCase(input.val(), settings.letterCase) );
  688. })
  689. // Handle keypresses
  690. .on('keydown.minicolors', '.minicolors-input', function(event) {
  691. var input = $(this);
  692. if( !input.data('minicolors-initialized') ) return;
  693. switch(event.keyCode) {
  694. case 9: // tab
  695. hide();
  696. break;
  697. case 13: // enter
  698. case 27: // esc
  699. hide();
  700. input.blur();
  701. break;
  702. }
  703. })
  704. // Update on keyup
  705. .on('keyup.minicolors', '.minicolors-input', function() {
  706. var input = $(this);
  707. if( !input.data('minicolors-initialized') ) return;
  708. updateFromInput(input, true);
  709. })
  710. // Update on paste
  711. .on('paste.minicolors', '.minicolors-input', function() {
  712. var input = $(this);
  713. if( !input.data('minicolors-initialized') ) return;
  714. setTimeout( function() {
  715. updateFromInput(input, true);
  716. }, 1);
  717. });
  718. })(jQuery);