3 * Attaches the behaviors for the Color module.
8 * Displays farbtastic color selector and initialize color administration UI.
10 * @type {Drupal~behavior}
12 * @prop {Drupal~behaviorAttach} attach
13 * Attach color selection behavior to relevant context.
15 Drupal.behaviors.color = {
16 attach(context, settings) {
20 // This behavior attaches by ID, so is only valid once on a page.
21 const form = $(context)
22 .find('#system-theme-settings .color-form')
24 if (form.length === 0) {
33 $('<div class="color-placeholder"></div>')
36 const farb = $.farbtastic('.color-placeholder');
38 // Decode reference colors to HSL.
39 const reference = settings.color.reference;
40 Object.keys(reference || {}).forEach(color => {
41 reference[color] = farb.RGBToHSL(farb.unpack(reference[color]));
49 * Renders the preview.
52 Drupal.color.callback(context, settings, form, farb, height, width);
56 * Resets the color scheme selector.
58 function resetScheme() {
59 form.find('#edit-scheme').each(function() {
60 this.selectedIndex = this.options.length - 1;
65 * Shifts a given color, using a reference pair (ref in HSL).
67 * This algorithm ensures relative ordering on the saturation and
68 * luminance axes is preserved, and performs a simple hue shift.
70 * It is also symmetrical. If: shiftColor(c, a, b) === d, then
71 * shiftColor(d, b, a) === c.
73 * @function Drupal.color~shiftColor
75 * @param {string} given
76 * A hex color code to shift.
77 * @param {Array.<number>} ref1
78 * First HSL color reference.
79 * @param {Array.<number>} ref2
80 * Second HSL color reference.
83 * A hex color, shifted.
85 function shiftColor(given, ref1, ref2) {
88 given = farb.RGBToHSL(farb.unpack(given));
91 given[0] += ref2[0] - ref1[0];
93 // Saturation: interpolate.
94 if (ref1[1] === 0 || ref2[1] === 0) {
97 d = ref1[1] / ref2[1];
101 given[1] = 1 - (1 - given[1]) * d;
105 // Luminance: interpolate.
106 if (ref1[2] === 0 || ref2[2] === 0) {
109 d = ref1[2] / ref2[2];
113 given[2] = 1 - (1 - given[2]) * d;
117 return farb.pack(farb.HSLToRGB(given));
121 * Callback for Farbtastic when a new color is chosen.
123 * @param {HTMLElement} input
124 * The input element where the color is chosen.
125 * @param {string} color
126 * The color that was chosen through the input.
127 * @param {bool} propagate
128 * Whether or not to propagate the color to a locked pair value
129 * @param {bool} colorScheme
130 * Flag to indicate if the user is using a color scheme when changing
133 function callback(input, color, propagate, colorScheme) {
135 // Set background/foreground colors.
137 backgroundColor: color,
138 color: farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff',
141 // Change input value.
142 if ($(input).val() && $(input).val() !== color) {
145 // Update locked values.
148 for (j = i + 1; ; ++j) {
149 if (!locks[j - 1] || $(locks[j - 1]).is('.is-unlocked')) {
152 matched = shiftColor(
154 reference[input.key],
155 reference[inputs[j].key],
157 callback(inputs[j], matched, false);
159 for (j = i - 1; ; --j) {
160 if (!locks[j] || $(locks[j]).is('.is-unlocked')) {
163 matched = shiftColor(
165 reference[input.key],
166 reference[inputs[j].key],
168 callback(inputs[j], matched, false);
175 // Reset colorScheme selector.
182 // Loop through all defined gradients.
183 Object.keys(settings.gradients || {}).forEach(i => {
184 // Add element to display the gradient.
187 .append(`<div id="gradient-${i}"></div>`);
188 const gradient = $(`.color-preview #gradient-${i}`);
189 // Add height of current gradient to the list (divided by 10).
190 height.push(parseInt(gradient.css('height'), 10) / 10);
191 // Add width of current gradient to the list (divided by 10).
192 width.push(parseInt(gradient.css('width'), 10) / 10);
193 // Add rows (or columns for horizontal gradients).
194 // Each gradient line should have a height (or width for horizontal
195 // gradients) of 10px (because we divided the height/width by 10
200 (settings.gradients[i].direction === 'vertical'
205 gradient.append('<div class="gradient-line"></div>');
209 // Set up colorScheme selector.
210 form.find('#edit-scheme').on('change', function() {
211 const schemes = settings.color.schemes;
212 const colorScheme = this.options[this.selectedIndex].value;
213 if (colorScheme !== '' && schemes[colorScheme]) {
214 // Get colors of active scheme.
215 colors = schemes[colorScheme];
216 Object.keys(colors || {}).forEach(fieldName => {
218 $(`#edit-palette-${fieldName}`),
229 * Focuses Farbtastic on a particular field.
231 * @param {jQuery.Event} e
232 * The focus event on the field.
235 const input = e.target;
236 // Remove old bindings.
239 .off('keyup', farb.updateValue)
240 .off('keyup', preview)
241 .off('keyup', resetScheme)
243 .removeClass('item-selected');
248 farb.linkTo(color => {
249 callback(input, color, true, false);
251 farb.setColor(input.value);
253 .on('keyup', farb.updateValue)
254 .on('keyup', preview)
255 .on('keyup', resetScheme)
257 .addClass('item-selected');
260 // Initialize color fields.
262 .find('.js-color-palette input.form-text')
264 // Extract palette field name.
265 this.key = this.id.substring(13);
267 // Link to color picker temporarily to initialize.
274 const i = inputs.length;
276 let toggleClick = true;
278 `<button class="color-palette__lock">${Drupal.t(
281 ).on('click', function(e) {
285 .addClass('is-unlocked')
286 .html(Drupal.t('Lock'));
287 $(hooks[i - 1]).attr(
289 locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)')
290 ? 'color-palette__hook is-up'
291 : 'color-palette__hook',
295 locks[i] && $(locks[i]).is(':not(.is-unlocked)')
296 ? 'color-palette__hook is-down'
297 : 'color-palette__hook',
301 .removeClass('is-unlocked')
302 .html(Drupal.t('Unlock'));
303 $(hooks[i - 1]).attr(
305 locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)')
306 ? 'color-palette__hook is-both'
307 : 'color-palette__hook is-down',
311 locks[i] && $(locks[i]).is(':not(.is-unlocked)')
312 ? 'color-palette__hook is-both'
313 : 'color-palette__hook is-up',
316 toggleClick = !toggleClick;
323 const hook = $('<div class="color-palette__hook"></div>');
329 .find('.color-palette__lock')
336 form.find('.js-color-palette label');
338 // Focus first color.