jquery.minicolors.js 35 KB

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