color.es6.js 9.4 KB

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