3 * Attaches the behaviors for the Color module.
6 (function ($, Drupal) {
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).find('#system-theme-settings .color-form').once('color');
22 if (form.length === 0) {
31 $('<div class="color-placeholder"></div>').once('color').prependTo(form);
32 const farb = $.farbtastic('.color-placeholder');
34 // Decode reference colors to HSL.
35 const reference = settings.color.reference;
36 for (i in reference) {
37 if (reference.hasOwnProperty(i)) {
38 reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
45 // Loop through all defined gradients.
46 for (i in settings.gradients) {
47 if (settings.gradients.hasOwnProperty(i)) {
48 // Add element to display the gradient.
49 $('.color-preview').once('color').append(`<div id="gradient-${i}"></div>`);
50 const gradient = $(`.color-preview #gradient-${i}`);
51 // Add height of current gradient to the list (divided by 10).
52 height.push(parseInt(gradient.css('height'), 10) / 10);
53 // Add width of current gradient to the list (divided by 10).
54 width.push(parseInt(gradient.css('width'), 10) / 10);
55 // Add rows (or columns for horizontal gradients).
56 // Each gradient line should have a height (or width for horizontal
57 // gradients) of 10px (because we divided the height/width by 10
59 for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) {
60 gradient.append('<div class="gradient-line"></div>');
65 // Set up colorScheme selector.
66 form.find('#edit-scheme').on('change', function () {
67 const schemes = settings.color.schemes;
68 const colorScheme = this.options[this.selectedIndex].value;
69 if (colorScheme !== '' && schemes[colorScheme]) {
70 // Get colors of active scheme.
71 colors = schemes[colorScheme];
72 for (const fieldName in colors) {
73 if (colors.hasOwnProperty(fieldName)) {
74 callback($(`#edit-palette-${fieldName}`), colors[fieldName], false, true);
82 * Renders the preview.
85 Drupal.color.callback(context, settings, form, farb, height, width);
89 * Shifts a given color, using a reference pair (ref in HSL).
91 * This algorithm ensures relative ordering on the saturation and
92 * luminance axes is preserved, and performs a simple hue shift.
94 * It is also symmetrical. If: shift_color(c, a, b) === d, then
95 * shift_color(d, b, a) === c.
97 * @function Drupal.color~shift_color
99 * @param {string} given
100 * A hex color code to shift.
101 * @param {Array.<number>} ref1
102 * First HSL color reference.
103 * @param {Array.<number>} ref2
104 * Second HSL color reference.
107 * A hex color, shifted.
109 function shift_color(given, ref1, ref2) {
112 given = farb.RGBToHSL(farb.unpack(given));
115 given[0] += ref2[0] - ref1[0];
117 // Saturation: interpolate.
118 if (ref1[1] === 0 || ref2[1] === 0) {
122 d = ref1[1] / ref2[1];
127 given[1] = 1 - (1 - given[1]) * d;
131 // Luminance: interpolate.
132 if (ref1[2] === 0 || ref2[2] === 0) {
136 d = ref1[2] / ref2[2];
141 given[2] = 1 - (1 - given[2]) * d;
145 return farb.pack(farb.HSLToRGB(given));
149 * Callback for Farbtastic when a new color is chosen.
151 * @param {HTMLElement} input
152 * The input element where the color is chosen.
153 * @param {string} color
154 * The color that was chosen through the input.
155 * @param {bool} propagate
156 * Whether or not to propagate the color to a locked pair value
157 * @param {bool} colorScheme
158 * Flag to indicate if the user is using a color scheme when changing
161 function callback(input, color, propagate, colorScheme) {
163 // Set background/foreground colors.
165 backgroundColor: color,
166 color: farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff',
169 // Change input value.
170 if ($(input).val() && $(input).val() !== color) {
173 // Update locked values.
176 for (j = i + 1; ; ++j) {
177 if (!locks[j - 1] || $(locks[j - 1]).is('.is-unlocked')) {
180 matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
181 callback(inputs[j], matched, false);
183 for (j = i - 1; ; --j) {
184 if (!locks[j] || $(locks[j]).is('.is-unlocked')) {
187 matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
188 callback(inputs[j], matched, false);
195 // Reset colorScheme selector.
203 * Resets the color scheme selector.
205 function resetScheme() {
206 form.find('#edit-scheme').each(function () {
207 this.selectedIndex = this.options.length - 1;
212 * Focuses Farbtastic on a particular field.
214 * @param {jQuery.Event} e
215 * The focus event on the field.
218 const input = e.target;
219 // Remove old bindings.
221 $(focused).off('keyup', farb.updateValue)
222 .off('keyup', preview).off('keyup', resetScheme)
223 .parent().removeClass('item-selected');
228 farb.linkTo((color) => {
229 callback(input, color, true, false);
231 farb.setColor(input.value);
232 $(focused).on('keyup', farb.updateValue).on('keyup', preview).on('keyup', resetScheme)
233 .parent().addClass('item-selected');
236 // Initialize color fields.
237 form.find('.js-color-palette input.form-text')
239 // Extract palette field name.
240 this.key = this.id.substring(13);
242 // Link to color picker temporarily to initialize.
243 farb.linkTo(() => {}).setColor('#000').linkTo(this);
246 const i = inputs.length;
248 let toggleClick = true;
249 const lock = $(`<button class="color-palette__lock">${Drupal.t('Unlock')}</button>`).on('click', function (e) {
252 $(this).addClass('is-unlocked').html(Drupal.t('Lock'));
253 $(hooks[i - 1]).attr('class',
254 locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-up' : 'color-palette__hook',
256 $(hooks[i]).attr('class',
257 locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-down' : 'color-palette__hook',
261 $(this).removeClass('is-unlocked').html(Drupal.t('Unlock'));
262 $(hooks[i - 1]).attr('class',
263 locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-down',
265 $(hooks[i]).attr('class',
266 locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-up',
269 toggleClick = !toggleClick;
276 const hook = $('<div class="color-palette__hook"></div>');
280 $(this).parent().find('.color-palette__lock').trigger('click');
286 form.find('.js-color-palette label');
288 // Focus first color.