Updated to Drupal 8.6.4, which is PHP 7.3 friendly. Also updated HTMLaw library....
[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)
22         .find('#system-theme-settings .color-form')
23         .once('color');
24       if (form.length === 0) {
25         return;
26       }
27       const inputs = [];
28       const hooks = [];
29       const locks = [];
30       let focused = null;
31
32       // Add Farbtastic.
33       $('<div class="color-placeholder"></div>')
34         .once('color')
35         .prependTo(form);
36       const farb = $.farbtastic('.color-placeholder');
37
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]));
42       });
43
44       // Build a preview.
45       const height = [];
46       const width = [];
47
48       /**
49        * Renders the preview.
50        */
51       function preview() {
52         Drupal.color.callback(context, settings, form, farb, height, width);
53       }
54
55       /**
56        * Resets the color scheme selector.
57        */
58       function resetScheme() {
59         form.find('#edit-scheme').each(function() {
60           this.selectedIndex = this.options.length - 1;
61         });
62       }
63
64       /**
65        * Shifts a given color, using a reference pair (ref in HSL).
66        *
67        * This algorithm ensures relative ordering on the saturation and
68        * luminance axes is preserved, and performs a simple hue shift.
69        *
70        * It is also symmetrical. If: shiftColor(c, a, b) === d, then
71        * shiftColor(d, b, a) === c.
72        *
73        * @function Drupal.color~shiftColor
74        *
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.
81        *
82        * @return {string}
83        *   A hex color, shifted.
84        */
85       function shiftColor(given, ref1, ref2) {
86         let d;
87         // Convert to HSL.
88         given = farb.RGBToHSL(farb.unpack(given));
89
90         // Hue: apply delta.
91         given[0] += ref2[0] - ref1[0];
92
93         // Saturation: interpolate.
94         if (ref1[1] === 0 || ref2[1] === 0) {
95           given[1] = ref2[1];
96         } else {
97           d = ref1[1] / ref2[1];
98           if (d > 1) {
99             given[1] /= d;
100           } else {
101             given[1] = 1 - (1 - given[1]) * d;
102           }
103         }
104
105         // Luminance: interpolate.
106         if (ref1[2] === 0 || ref2[2] === 0) {
107           given[2] = ref2[2];
108         } else {
109           d = ref1[2] / ref2[2];
110           if (d > 1) {
111             given[2] /= d;
112           } else {
113             given[2] = 1 - (1 - given[2]) * d;
114           }
115         }
116
117         return farb.pack(farb.HSLToRGB(given));
118       }
119
120       /**
121        * Callback for Farbtastic when a new color is chosen.
122        *
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
131        *   the color.
132        */
133       function callback(input, color, propagate, colorScheme) {
134         let matched;
135         // Set background/foreground colors.
136         $(input).css({
137           backgroundColor: color,
138           color: farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff',
139         });
140
141         // Change input value.
142         if ($(input).val() && $(input).val() !== color) {
143           $(input).val(color);
144
145           // Update locked values.
146           if (propagate) {
147             i = input.i;
148             for (j = i + 1; ; ++j) {
149               if (!locks[j - 1] || $(locks[j - 1]).is('.is-unlocked')) {
150                 break;
151               }
152               matched = shiftColor(
153                 color,
154                 reference[input.key],
155                 reference[inputs[j].key],
156               );
157               callback(inputs[j], matched, false);
158             }
159             for (j = i - 1; ; --j) {
160               if (!locks[j] || $(locks[j]).is('.is-unlocked')) {
161                 break;
162               }
163               matched = shiftColor(
164                 color,
165                 reference[input.key],
166                 reference[inputs[j].key],
167               );
168               callback(inputs[j], matched, false);
169             }
170
171             // Update preview.
172             preview();
173           }
174
175           // Reset colorScheme selector.
176           if (!colorScheme) {
177             resetScheme();
178           }
179         }
180       }
181
182       // Loop through all defined gradients.
183       Object.keys(settings.gradients || {}).forEach(i => {
184         // Add element to display the gradient.
185         $('.color-preview')
186           .once('color')
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
196         // above).
197         for (
198           j = 0;
199           j <
200           (settings.gradients[i].direction === 'vertical'
201             ? height[i]
202             : width[i]);
203           ++j
204         ) {
205           gradient.append('<div class="gradient-line"></div>');
206         }
207       });
208
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 => {
217             callback(
218               $(`#edit-palette-${fieldName}`),
219               colors[fieldName],
220               false,
221               true,
222             );
223           });
224           preview();
225         }
226       });
227
228       /**
229        * Focuses Farbtastic on a particular field.
230        *
231        * @param {jQuery.Event} e
232        *   The focus event on the field.
233        */
234       function focus(e) {
235         const input = e.target;
236         // Remove old bindings.
237         if (focused) {
238           $(focused)
239             .off('keyup', farb.updateValue)
240             .off('keyup', preview)
241             .off('keyup', resetScheme)
242             .parent()
243             .removeClass('item-selected');
244         }
245
246         // Add new bindings.
247         focused = input;
248         farb.linkTo(color => {
249           callback(input, color, true, false);
250         });
251         farb.setColor(input.value);
252         $(focused)
253           .on('keyup', farb.updateValue)
254           .on('keyup', preview)
255           .on('keyup', resetScheme)
256           .parent()
257           .addClass('item-selected');
258       }
259
260       // Initialize color fields.
261       form
262         .find('.js-color-palette input.form-text')
263         .each(function() {
264           // Extract palette field name.
265           this.key = this.id.substring(13);
266
267           // Link to color picker temporarily to initialize.
268           farb
269             .linkTo(() => {})
270             .setColor('#000')
271             .linkTo(this);
272
273           // Add lock.
274           const i = inputs.length;
275           if (inputs.length) {
276             let toggleClick = true;
277             const lock = $(
278               `<button class="color-palette__lock">${Drupal.t(
279                 'Unlock',
280               )}</button>`,
281             ).on('click', function(e) {
282               e.preventDefault();
283               if (toggleClick) {
284                 $(this)
285                   .addClass('is-unlocked')
286                   .html(Drupal.t('Lock'));
287                 $(hooks[i - 1]).attr(
288                   'class',
289                   locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)')
290                     ? 'color-palette__hook is-up'
291                     : 'color-palette__hook',
292                 );
293                 $(hooks[i]).attr(
294                   'class',
295                   locks[i] && $(locks[i]).is(':not(.is-unlocked)')
296                     ? 'color-palette__hook is-down'
297                     : 'color-palette__hook',
298                 );
299               } else {
300                 $(this)
301                   .removeClass('is-unlocked')
302                   .html(Drupal.t('Unlock'));
303                 $(hooks[i - 1]).attr(
304                   'class',
305                   locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)')
306                     ? 'color-palette__hook is-both'
307                     : 'color-palette__hook is-down',
308                 );
309                 $(hooks[i]).attr(
310                   'class',
311                   locks[i] && $(locks[i]).is(':not(.is-unlocked)')
312                     ? 'color-palette__hook is-both'
313                     : 'color-palette__hook is-up',
314                 );
315               }
316               toggleClick = !toggleClick;
317             });
318             $(this).after(lock);
319             locks.push(lock);
320           }
321
322           // Add hook.
323           const hook = $('<div class="color-palette__hook"></div>');
324           $(this).after(hook);
325           hooks.push(hook);
326
327           $(this)
328             .parent()
329             .find('.color-palette__lock')
330             .trigger('click');
331           this.i = i;
332           inputs.push(this);
333         })
334         .on('focus', focus);
335
336       form.find('.js-color-palette label');
337
338       // Focus first color.
339       inputs[0].focus();
340
341       // Render preview.
342       preview();
343     },
344   };
345 })(jQuery, Drupal);