color.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /**
  2. * @file
  3. * Attaches the behaviors for the Color module.
  4. */
  5. (function ($, Drupal) {
  6. 'use strict';
  7. /**
  8. * Displays farbtastic color selector and initialize color administration UI.
  9. *
  10. * @type {Drupal~behavior}
  11. *
  12. * @prop {Drupal~behaviorAttach} attach
  13. * Attach color selection behavior to relevant context.
  14. */
  15. Drupal.behaviors.color = {
  16. attach: function (context, settings) {
  17. var i;
  18. var j;
  19. var colors;
  20. // This behavior attaches by ID, so is only valid once on a page.
  21. var form = $(context).find('#system-theme-settings .color-form').once('color');
  22. if (form.length === 0) {
  23. return;
  24. }
  25. var inputs = [];
  26. var hooks = [];
  27. var locks = [];
  28. var focused = null;
  29. // Add Farbtastic.
  30. $('<div class="color-placeholder"></div>').once('color').prependTo(form);
  31. var farb = $.farbtastic('.color-placeholder');
  32. // Decode reference colors to HSL.
  33. var reference = settings.color.reference;
  34. for (i in reference) {
  35. if (reference.hasOwnProperty(i)) {
  36. reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
  37. }
  38. }
  39. // Build a preview.
  40. var height = [];
  41. var width = [];
  42. // Loop through all defined gradients.
  43. for (i in settings.gradients) {
  44. if (settings.gradients.hasOwnProperty(i)) {
  45. // Add element to display the gradient.
  46. $('.color-preview').once('color').append('<div id="gradient-' + i + '"></div>');
  47. var gradient = $('.color-preview #gradient-' + i);
  48. // Add height of current gradient to the list (divided by 10).
  49. height.push(parseInt(gradient.css('height'), 10) / 10);
  50. // Add width of current gradient to the list (divided by 10).
  51. width.push(parseInt(gradient.css('width'), 10) / 10);
  52. // Add rows (or columns for horizontal gradients).
  53. // Each gradient line should have a height (or width for horizontal
  54. // gradients) of 10px (because we divided the height/width by 10
  55. // above).
  56. for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) {
  57. gradient.append('<div class="gradient-line"></div>');
  58. }
  59. }
  60. }
  61. // Set up colorScheme selector.
  62. form.find('#edit-scheme').on('change', function () {
  63. var schemes = settings.color.schemes;
  64. var colorScheme = this.options[this.selectedIndex].value;
  65. if (colorScheme !== '' && schemes[colorScheme]) {
  66. // Get colors of active scheme.
  67. colors = schemes[colorScheme];
  68. for (var fieldName in colors) {
  69. if (colors.hasOwnProperty(fieldName)) {
  70. callback($('#edit-palette-' + fieldName), colors[fieldName], false, true);
  71. }
  72. }
  73. preview();
  74. }
  75. });
  76. /**
  77. * Renders the preview.
  78. */
  79. function preview() {
  80. Drupal.color.callback(context, settings, form, farb, height, width);
  81. }
  82. /**
  83. * Shifts a given color, using a reference pair (ref in HSL).
  84. *
  85. * This algorithm ensures relative ordering on the saturation and
  86. * luminance axes is preserved, and performs a simple hue shift.
  87. *
  88. * It is also symmetrical. If: shift_color(c, a, b) === d, then
  89. * shift_color(d, b, a) === c.
  90. *
  91. * @function Drupal.color~shift_color
  92. *
  93. * @param {string} given
  94. * A hex color code to shift.
  95. * @param {Array.<number>} ref1
  96. * First HSL color reference.
  97. * @param {Array.<number>} ref2
  98. * Second HSL color reference.
  99. *
  100. * @return {string}
  101. * A hex color, shifted.
  102. */
  103. function shift_color(given, ref1, ref2) {
  104. var d;
  105. // Convert to HSL.
  106. given = farb.RGBToHSL(farb.unpack(given));
  107. // Hue: apply delta.
  108. given[0] += ref2[0] - ref1[0];
  109. // Saturation: interpolate.
  110. if (ref1[1] === 0 || ref2[1] === 0) {
  111. given[1] = ref2[1];
  112. }
  113. else {
  114. d = ref1[1] / ref2[1];
  115. if (d > 1) {
  116. given[1] /= d;
  117. }
  118. else {
  119. given[1] = 1 - (1 - given[1]) * d;
  120. }
  121. }
  122. // Luminance: interpolate.
  123. if (ref1[2] === 0 || ref2[2] === 0) {
  124. given[2] = ref2[2];
  125. }
  126. else {
  127. d = ref1[2] / ref2[2];
  128. if (d > 1) {
  129. given[2] /= d;
  130. }
  131. else {
  132. given[2] = 1 - (1 - given[2]) * d;
  133. }
  134. }
  135. return farb.pack(farb.HSLToRGB(given));
  136. }
  137. /**
  138. * Callback for Farbtastic when a new color is chosen.
  139. *
  140. * @param {HTMLElement} input
  141. * The input element where the color is chosen.
  142. * @param {string} color
  143. * The color that was chosen through the input.
  144. * @param {bool} propagate
  145. * Whether or not to propagate the color to a locked pair value
  146. * @param {bool} colorScheme
  147. * Flag to indicate if the user is using a color scheme when changing
  148. * the color.
  149. */
  150. function callback(input, color, propagate, colorScheme) {
  151. var matched;
  152. // Set background/foreground colors.
  153. $(input).css({
  154. backgroundColor: color,
  155. color: farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff'
  156. });
  157. // Change input value.
  158. if ($(input).val() && $(input).val() !== color) {
  159. $(input).val(color);
  160. // Update locked values.
  161. if (propagate) {
  162. i = input.i;
  163. for (j = i + 1; ; ++j) {
  164. if (!locks[j - 1] || $(locks[j - 1]).is('.is-unlocked')) {
  165. break;
  166. }
  167. matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
  168. callback(inputs[j], matched, false);
  169. }
  170. for (j = i - 1; ; --j) {
  171. if (!locks[j] || $(locks[j]).is('.is-unlocked')) {
  172. break;
  173. }
  174. matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
  175. callback(inputs[j], matched, false);
  176. }
  177. // Update preview.
  178. preview();
  179. }
  180. // Reset colorScheme selector.
  181. if (!colorScheme) {
  182. resetScheme();
  183. }
  184. }
  185. }
  186. /**
  187. * Resets the color scheme selector.
  188. */
  189. function resetScheme() {
  190. form.find('#edit-scheme').each(function () {
  191. this.selectedIndex = this.options.length - 1;
  192. });
  193. }
  194. /**
  195. * Focuses Farbtastic on a particular field.
  196. *
  197. * @param {jQuery.Event} e
  198. * The focus event on the field.
  199. */
  200. function focus(e) {
  201. var input = e.target;
  202. // Remove old bindings.
  203. if (focused) {
  204. $(focused).off('keyup', farb.updateValue)
  205. .off('keyup', preview).off('keyup', resetScheme)
  206. .parent().removeClass('item-selected');
  207. }
  208. // Add new bindings.
  209. focused = input;
  210. farb.linkTo(function (color) { callback(input, color, true, false); });
  211. farb.setColor(input.value);
  212. $(focused).on('keyup', farb.updateValue).on('keyup', preview).on('keyup', resetScheme)
  213. .parent().addClass('item-selected');
  214. }
  215. // Initialize color fields.
  216. form.find('.js-color-palette input.form-text')
  217. .each(function () {
  218. // Extract palette field name.
  219. this.key = this.id.substring(13);
  220. // Link to color picker temporarily to initialize.
  221. farb.linkTo(function () {}).setColor('#000').linkTo(this);
  222. // Add lock.
  223. var i = inputs.length;
  224. if (inputs.length) {
  225. var toggleClick = true;
  226. var lock = $('<button class="color-palette__lock">' + Drupal.t('Unlock') + '</button>').on('click', function (e) {
  227. e.preventDefault();
  228. if (toggleClick) {
  229. $(this).addClass('is-unlocked').html(Drupal.t('Lock'));
  230. $(hooks[i - 1]).attr('class',
  231. locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-up' : 'color-palette__hook'
  232. );
  233. $(hooks[i]).attr('class',
  234. locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-down' : 'color-palette__hook'
  235. );
  236. }
  237. else {
  238. $(this).removeClass('is-unlocked').html(Drupal.t('Unlock'));
  239. $(hooks[i - 1]).attr('class',
  240. locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-down'
  241. );
  242. $(hooks[i]).attr('class',
  243. locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-up'
  244. );
  245. }
  246. toggleClick = !toggleClick;
  247. });
  248. $(this).after(lock);
  249. locks.push(lock);
  250. }
  251. // Add hook.
  252. var hook = $('<div class="color-palette__hook"></div>');
  253. $(this).after(hook);
  254. hooks.push(hook);
  255. $(this).parent().find('.color-palette__lock').trigger('click');
  256. this.i = i;
  257. inputs.push(this);
  258. })
  259. .on('focus', focus);
  260. form.find('.js-color-palette label');
  261. // Focus first color.
  262. inputs[0].focus();
  263. // Render preview.
  264. preview();
  265. }
  266. };
  267. })(jQuery, Drupal);