Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / misc / dialog / off-canvas.es6.js
1 /**
2  * @file
3  * Drupal's off-canvas library.
4  */
5
6 (($, Drupal, debounce, displace) => {
7   /**
8    * Off-canvas dialog implementation using jQuery Dialog.
9    *
10    * Transforms the regular dialogs created using Drupal.dialog when the dialog
11    * element equals '#drupal-off-canvas' into an side-loading dialog.
12    *
13    * @namespace
14    */
15   Drupal.offCanvas = {
16     /**
17      * Storage for position information about the tray.
18      *
19      * @type {?String}
20      */
21     position: null,
22
23     /**
24      * The minimum height of the tray when opened at the top of the page.
25      *
26      * @type {Number}
27      */
28     minimumHeight: 30,
29
30     /**
31      * The minimum width to use body displace needs to match the width at which
32      * the tray will be 100% width. @see core/misc/dialog/off-canvas.css
33      *
34      * @type {Number}
35      */
36     minDisplaceWidth: 768,
37
38     /**
39      * Wrapper used to position off-canvas dialog.
40      *
41      * @type {jQuery}
42      */
43     $mainCanvasWrapper: $('[data-off-canvas-main-canvas]'),
44
45     /**
46      * Determines if an element is an off-canvas dialog.
47      *
48      * @param {jQuery} $element
49      *   The dialog element.
50      *
51      * @return {bool}
52      *   True this is currently an off-canvas dialog.
53      */
54     isOffCanvas($element) {
55       return $element.is('#drupal-off-canvas');
56     },
57
58     /**
59      * Remove off-canvas dialog events.
60      *
61      * @param {jQuery} $element
62      *   The target element.
63      */
64     removeOffCanvasEvents($element) {
65       $element.off('.off-canvas');
66       $(document).off('.off-canvas');
67       $(window).off('.off-canvas');
68     },
69
70     /**
71      * Handler fired before an off-canvas dialog has been opened.
72      *
73      * @param {Object} settings
74      *   Settings related to the composition of the dialog.
75      *
76      * @return {undefined}
77      */
78     beforeCreate({ settings, $element }) {
79       // Clean up previous dialog event handlers.
80       Drupal.offCanvas.removeOffCanvasEvents($element);
81
82       $('body').addClass('js-off-canvas-dialog-open');
83       // @see http://api.jqueryui.com/position/
84       settings.position = {
85         my: 'left top',
86         at: `${Drupal.offCanvas.getEdge()} top`,
87         of: window,
88       };
89
90       /**
91        * Applies initial height and with to dialog based depending on position.
92        * @see http://api.jqueryui.com/dialog for all dialog options.
93        */
94       const position = settings.drupalOffCanvasPosition;
95       const height = position === 'side' ? $(window).height() : settings.height;
96       const width = position === 'side' ? settings.width : '100%';
97       settings.height = height;
98       settings.width = width;
99     },
100
101     /**
102      * Handler fired after an off-canvas dialog has been closed.
103      *
104      * @return {undefined}
105      */
106     beforeClose({ $element }) {
107       $('body').removeClass('js-off-canvas-dialog-open');
108       // Remove all *.off-canvas events
109       Drupal.offCanvas.removeOffCanvasEvents($element);
110       Drupal.offCanvas.resetPadding();
111     },
112
113     /**
114      * Handler fired when an off-canvas dialog has been opened.
115      *
116      * @param {jQuery} $element
117      *   The off-canvas dialog element.
118      * @param {Object} settings
119      *   Settings related to the composition of the dialog.
120      *
121      * @return {undefined}
122      */
123     afterCreate({ $element, settings }) {
124       const eventData = { settings, $element, offCanvasDialog: this };
125
126       $element
127         .on(
128           'dialogContentResize.off-canvas',
129           eventData,
130           Drupal.offCanvas.handleDialogResize,
131         )
132         .on(
133           'dialogContentResize.off-canvas',
134           eventData,
135           Drupal.offCanvas.bodyPadding,
136         );
137
138       Drupal.offCanvas
139         .getContainer($element)
140         .attr(`data-offset-${Drupal.offCanvas.getEdge()}`, '');
141
142       $(window)
143         .on(
144           'resize.off-canvas',
145           eventData,
146           debounce(Drupal.offCanvas.resetSize, 100),
147         )
148         .trigger('resize.off-canvas');
149     },
150
151     /**
152      * Toggle classes based on title existence.
153      * Called with Drupal.offCanvas.afterCreate.
154      *
155      * @param {Object} settings
156      *   Settings related to the composition of the dialog.
157      *
158      * @return {undefined}
159      */
160     render({ settings }) {
161       $(
162         '.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar',
163       ).toggleClass('ui-dialog-empty-title', !settings.title);
164     },
165
166     /**
167      * Adjusts the dialog on resize.
168      *
169      * @param {jQuery.Event} event
170      *   The event triggered.
171      * @param {object} event.data
172      *   Data attached to the event.
173      */
174     handleDialogResize(event) {
175       const $element = event.data.$element;
176       const $container = Drupal.offCanvas.getContainer($element);
177
178       const $offsets = $container.find(
179         '> :not(#drupal-off-canvas, .ui-resizable-handle)',
180       );
181       let offset = 0;
182
183       // Let scroll element take all the height available.
184       $element.css({ height: 'auto' });
185       const modalHeight = $container.height();
186
187       $offsets.each((i, e) => {
188         offset += $(e).outerHeight();
189       });
190
191       // Take internal padding into account.
192       const scrollOffset = $element.outerHeight() - $element.height();
193       $element.height(modalHeight - offset - scrollOffset);
194     },
195
196     /**
197      * Resets the size of the dialog.
198      *
199      * @param {jQuery.Event} event
200      *   The event triggered.
201      * @param {object} event.data
202      *   Data attached to the event.
203      */
204     resetSize(event) {
205       const $element = event.data.$element;
206       const container = Drupal.offCanvas.getContainer($element);
207       const position = event.data.settings.drupalOffCanvasPosition;
208
209       // Only remove the `data-offset-*` attribute if the value previously
210       // exists and the orientation is changing.
211       if (Drupal.offCanvas.position && Drupal.offCanvas.position !== position) {
212         container.removeAttr(`data-offset-${Drupal.offCanvas.position}`);
213       }
214       // Set a minimum height on $element
215       if (position === 'top') {
216         $element.css('min-height', `${Drupal.offCanvas.minimumHeight}px`);
217       }
218
219       displace();
220
221       const offsets = displace.offsets;
222
223       const topPosition =
224         position === 'side' && offsets.top !== 0 ? `+${offsets.top}` : '';
225       const adjustedOptions = {
226         // @see http://api.jqueryui.com/position/
227         position: {
228           my: `${Drupal.offCanvas.getEdge()} top`,
229           at: `${Drupal.offCanvas.getEdge()} top${topPosition}`,
230           of: window,
231         },
232       };
233
234       const height =
235         position === 'side'
236           ? `${$(window).height() - (offsets.top + offsets.bottom)}px`
237           : event.data.settings.height;
238       container.css({
239         position: 'fixed',
240         height,
241       });
242
243       $element
244         .dialog('option', adjustedOptions)
245         .trigger('dialogContentResize.off-canvas');
246
247       Drupal.offCanvas.position = position;
248     },
249
250     /**
251      * Adjusts the body padding when the dialog is resized.
252      *
253      * @param {jQuery.Event} event
254      *   The event triggered.
255      * @param {object} event.data
256      *   Data attached to the event.
257      */
258     bodyPadding(event) {
259       const position = event.data.settings.drupalOffCanvasPosition;
260       if (
261         position === 'side' &&
262         $('body').outerWidth() < Drupal.offCanvas.minDisplaceWidth
263       ) {
264         return;
265       }
266       Drupal.offCanvas.resetPadding();
267       const $element = event.data.$element;
268       const $container = Drupal.offCanvas.getContainer($element);
269       const $mainCanvasWrapper = Drupal.offCanvas.$mainCanvasWrapper;
270
271       const width = $container.outerWidth();
272       const mainCanvasPadding = $mainCanvasWrapper.css(
273         `padding-${Drupal.offCanvas.getEdge()}`,
274       );
275       if (position === 'side' && width !== mainCanvasPadding) {
276         $mainCanvasWrapper.css(
277           `padding-${Drupal.offCanvas.getEdge()}`,
278           `${width}px`,
279         );
280         $container.attr(`data-offset-${Drupal.offCanvas.getEdge()}`, width);
281         displace();
282       }
283
284       const height = $container.outerHeight();
285       if (position === 'top') {
286         $mainCanvasWrapper.css('padding-top', `${height}px`);
287         $container.attr('data-offset-top', height);
288         displace();
289       }
290     },
291
292     /**
293      * The HTML element that surrounds the dialog.
294      * @param {HTMLElement} $element
295      *   The dialog element.
296      *
297      * @return {HTMLElement}
298      *   The containing element.
299      */
300     getContainer($element) {
301       return $element.dialog('widget');
302     },
303
304     /**
305      * The edge of the screen that the dialog should appear on.
306      *
307      * @return {string}
308      *   The edge the tray will be shown on, left or right.
309      */
310     getEdge() {
311       return document.documentElement.dir === 'rtl' ? 'left' : 'right';
312     },
313
314     /**
315      * Resets main canvas wrapper and toolbar padding / margin.
316      */
317     resetPadding() {
318       Drupal.offCanvas.$mainCanvasWrapper.css(
319         `padding-${Drupal.offCanvas.getEdge()}`,
320         0,
321       );
322       Drupal.offCanvas.$mainCanvasWrapper.css('padding-top', 0);
323       displace();
324     },
325   };
326
327   /**
328    * Attaches off-canvas dialog behaviors.
329    *
330    * @type {Drupal~behavior}
331    *
332    * @prop {Drupal~behaviorAttach} attach
333    *   Attaches event listeners for off-canvas dialogs.
334    */
335   Drupal.behaviors.offCanvasEvents = {
336     attach: () => {
337       $(window)
338         .once('off-canvas')
339         .on({
340           'dialog:beforecreate': (event, dialog, $element, settings) => {
341             if (Drupal.offCanvas.isOffCanvas($element)) {
342               Drupal.offCanvas.beforeCreate({ dialog, $element, settings });
343             }
344           },
345           'dialog:aftercreate': (event, dialog, $element, settings) => {
346             if (Drupal.offCanvas.isOffCanvas($element)) {
347               Drupal.offCanvas.render({ dialog, $element, settings });
348               Drupal.offCanvas.afterCreate({ $element, settings });
349             }
350           },
351           'dialog:beforeclose': (event, dialog, $element) => {
352             if (Drupal.offCanvas.isOffCanvas($element)) {
353               Drupal.offCanvas.beforeClose({ dialog, $element });
354             }
355           },
356         });
357     },
358   };
359 })(jQuery, Drupal, Drupal.debounce, Drupal.displace);