Patched to Drupal 8.4.8 level. See https://www.drupal.org/sa-core-2018-004 and patch...
[yaffs-website] / web / core / modules / color / color.es6.js
1 /**
2  * @file
3  * Attaches the behaviors for the Color module.
4  */
5
6 (function ($, Drupal) {
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(context, settings) {
17       let i;
18       let j;
19       let colors;
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) {
23         return;
24       }
25       const inputs = [];
26       const hooks = [];
27       const locks = [];
28       let focused = null;
29
30       // Add Farbtastic.
31       $('<div class="color-placeholder"></div>').once('color').prependTo(form);
32       const farb = $.farbtastic('.color-placeholder');
33
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]));
39         }
40       }
41
42       // Build a preview.
43       const height = [];
44       const width = [];
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
58           // above).
59           for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) {
60             gradient.append('<div class="gradient-line"></div>');
61           }
62         }
63       }
64
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);
75             }
76           }
77           preview();
78         }
79       });
80
81       /**
82        * Renders the preview.
83        */
84       function preview() {
85         Drupal.color.callback(context, settings, form, farb, height, width);
86       }
87
88       /**
89        * Shifts a given color, using a reference pair (ref in HSL).
90        *
91        * This algorithm ensures relative ordering on the saturation and
92        * luminance axes is preserved, and performs a simple hue shift.
93        *
94        * It is also symmetrical. If: shift_color(c, a, b) === d, then
95        * shift_color(d, b, a) === c.
96        *
97        * @function Drupal.color~shift_color
98        *
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.
105        *
106        * @return {string}
107        *   A hex color, shifted.
108        */
109       function shift_color(given, ref1, ref2) {
110         let d;
111         // Convert to HSL.
112         given = farb.RGBToHSL(farb.unpack(given));
113
114         // Hue: apply delta.
115         given[0] += ref2[0] - ref1[0];
116
117         // Saturation: interpolate.
118         if (ref1[1] === 0 || ref2[1] === 0) {
119           given[1] = ref2[1];
120         }
121         else {
122           d = ref1[1] / ref2[1];
123           if (d > 1) {
124             given[1] /= d;
125           }
126           else {
127             given[1] = 1 - (1 - given[1]) * d;
128           }
129         }
130
131         // Luminance: interpolate.
132         if (ref1[2] === 0 || ref2[2] === 0) {
133           given[2] = ref2[2];
134         }
135         else {
136           d = ref1[2] / ref2[2];
137           if (d > 1) {
138             given[2] /= d;
139           }
140           else {
141             given[2] = 1 - (1 - given[2]) * d;
142           }
143         }
144
145         return farb.pack(farb.HSLToRGB(given));
146       }
147
148       /**
149        * Callback for Farbtastic when a new color is chosen.
150        *
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
159        *   the color.
160        */
161       function callback(input, color, propagate, colorScheme) {
162         let matched;
163         // Set background/foreground colors.
164         $(input).css({
165           backgroundColor: color,
166           color: farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff',
167         });
168
169         // Change input value.
170         if ($(input).val() && $(input).val() !== color) {
171           $(input).val(color);
172
173           // Update locked values.
174           if (propagate) {
175             i = input.i;
176             for (j = i + 1; ; ++j) {
177               if (!locks[j - 1] || $(locks[j - 1]).is('.is-unlocked')) {
178                 break;
179               }
180               matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
181               callback(inputs[j], matched, false);
182             }
183             for (j = i - 1; ; --j) {
184               if (!locks[j] || $(locks[j]).is('.is-unlocked')) {
185                 break;
186               }
187               matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
188               callback(inputs[j], matched, false);
189             }
190
191             // Update preview.
192             preview();
193           }
194
195           // Reset colorScheme selector.
196           if (!colorScheme) {
197             resetScheme();
198           }
199         }
200       }
201
202       /**
203        * Resets the color scheme selector.
204        */
205       function resetScheme() {
206         form.find('#edit-scheme').each(function () {
207           this.selectedIndex = this.options.length - 1;
208         });
209       }
210
211       /**
212        * Focuses Farbtastic on a particular field.
213        *
214        * @param {jQuery.Event} e
215        *   The focus event on the field.
216        */
217       function focus(e) {
218         const input = e.target;
219         // Remove old bindings.
220         if (focused) {
221           $(focused).off('keyup', farb.updateValue)
222             .off('keyup', preview).off('keyup', resetScheme)
223             .parent().removeClass('item-selected');
224         }
225
226         // Add new bindings.
227         focused = input;
228         farb.linkTo((color) => {
229           callback(input, color, true, false);
230         });
231         farb.setColor(input.value);
232         $(focused).on('keyup', farb.updateValue).on('keyup', preview).on('keyup', resetScheme)
233           .parent().addClass('item-selected');
234       }
235
236       // Initialize color fields.
237       form.find('.js-color-palette input.form-text')
238         .each(function () {
239           // Extract palette field name.
240           this.key = this.id.substring(13);
241
242           // Link to color picker temporarily to initialize.
243           farb.linkTo(() => {}).setColor('#000').linkTo(this);
244
245           // Add lock.
246           const i = inputs.length;
247           if (inputs.length) {
248             let toggleClick = true;
249             const lock = $(`<button class="color-palette__lock">${Drupal.t('Unlock')}</button>`).on('click', function (e) {
250               e.preventDefault();
251               if (toggleClick) {
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',
255                 );
256                 $(hooks[i]).attr('class',
257                   locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-down' : 'color-palette__hook',
258                 );
259               }
260               else {
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',
264                 );
265                 $(hooks[i]).attr('class',
266                   locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-up',
267                 );
268               }
269               toggleClick = !toggleClick;
270             });
271             $(this).after(lock);
272             locks.push(lock);
273           }
274
275           // Add hook.
276           const hook = $('<div class="color-palette__hook"></div>');
277           $(this).after(hook);
278           hooks.push(hook);
279
280           $(this).parent().find('.color-palette__lock').trigger('click');
281           this.i = i;
282           inputs.push(this);
283         })
284         .on('focus', focus);
285
286       form.find('.js-color-palette label');
287
288       // Focus first color.
289       inputs[0].focus();
290
291       // Render preview.
292       preview();
293     },
294   };
295 }(jQuery, Drupal));