Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / settings_tray / js / off-canvas.es6.js
1 /**
2  * @file
3  * Drupal's off-canvas library.
4  *
5  * @todo This functionality should extracted into a new core library or a part
6  *  of the current drupal.dialog.ajax library.
7  *  https://www.drupal.org/node/2784443
8  *
9  * @private
10  */
11
12 (($, Drupal, debounce, displace) => {
13   /**
14    * Off-canvas dialog implementation using jQuery Dialog.
15    *
16    * Transforms the regular dialogs created using Drupal.dialog when the dialog
17    * element equals '#drupal-off-canvas' into an side-loading dialog.
18    *
19    * @namespace
20    */
21   Drupal.offCanvas = {
22
23     /**
24      * The minimum width to use body displace needs to match the width at which
25      * the tray will be %100 width. @see settings_tray.module.css
26      * @type {Number}
27      */
28     minDisplaceWidth: 768,
29
30     /**
31      * Wrapper used to position off-canvas dialog.
32      * @type {jQuery}
33      */
34     $mainCanvasWrapper: $('[data-off-canvas-main-canvas]'),
35
36     /**
37      * Determines if an element is an off-canvas dialog.
38      *
39      * @param {jQuery} $element
40      *   The dialog element.
41      * @return {bool}
42      *   True this is currently an off-canvas dialog.
43      */
44     isOffCanvas($element) {
45       return $element.is('#drupal-off-canvas');
46     },
47
48     /**
49      * Remove off-canvas dialog events.
50      *
51      * @param {jQuery} $element
52      *   The target element.
53      */
54     removeOffCanvasEvents($element) {
55       $element.off('.off-canvas');
56       $(document).off('.off-canvas');
57       $(window).off('.off-canvas');
58     },
59
60     /**
61      * Handler fired before an off-canvas dialog has been opened.
62      * @param  {Object} settings
63      *   Settings related to the composition of the dialog.
64      * @return {undefined}
65      */
66     beforeCreate({ settings, $element }) {
67       // Clean up previous dialog event handlers.
68       Drupal.offCanvas.removeOffCanvasEvents($element);
69
70       $('body').addClass('js-tray-open');
71       settings.dialogClass += ' ui-dialog-off-canvas';
72       // @see http://api.jqueryui.com/position/
73       settings.position = {
74         my: 'left top',
75         at: `${Drupal.offCanvas.getEdge()} top`,
76         of: window,
77       };
78
79       /**
80        * Applies initial height to dialog based on window height.
81        * @see http://api.jqueryui.com/dialog for all dialog options.
82        */
83       settings.height = $(window).height();
84     },
85
86     /**
87      * Handler fired after an off-canvas dialog has been closed.
88      * @return {undefined}
89      */
90     beforeClose({ $element }) {
91       $('body').removeClass('js-tray-open');
92       // Remove all *.off-canvas events
93       Drupal.offCanvas.removeOffCanvasEvents($element);
94
95       Drupal.offCanvas.$mainCanvasWrapper.css(`padding-${Drupal.offCanvas.getEdge()}`, 0);
96     },
97
98     /**
99      * Handler fired when an off-canvas dialog has been opened.
100      * @param  {jQuery} $element
101      *   The off-canvas dialog element.
102      * @param  {Object} settings
103      *   Settings related to the composition of the dialog.
104      * @return {undefined}
105      */
106     afterCreate({ $element, settings }) {
107       const eventData = { settings, $element, offCanvasDialog: this };
108
109       $element
110         .on('dialogContentResize.off-canvas', eventData, Drupal.offCanvas.handleDialogResize)
111         .on('dialogContentResize.off-canvas', eventData, Drupal.offCanvas.bodyPadding);
112
113       Drupal.offCanvas.getContainer($element).attr(`data-offset-${Drupal.offCanvas.getEdge()}`, '');
114
115       $(window)
116         .on('resize.off-canvas', eventData, debounce(Drupal.offCanvas.resetSize, 100))
117         .trigger('resize.off-canvas');
118     },
119
120     /**
121      * Toggle classes based on title existence.
122      * Called with Drupal.offCanvas.afterCreate.
123      * @param  {Object} settings
124      *   Settings related to the composition of the dialog.
125      * @return {undefined}
126      */
127     render({ settings }) {
128       $('.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar').toggleClass('ui-dialog-empty-title', !settings.title);
129     },
130
131     /**
132      * Adjusts the dialog on resize.
133      *
134      * @param {jQuery.Event} event
135      *   The event triggered.
136      * @param {object} event.data
137      *   Data attached to the event.
138      */
139     handleDialogResize(event) {
140       const $element = event.data.$element;
141       const $container = Drupal.offCanvas.getContainer($element);
142
143       const $offsets = $container.find('> :not(#drupal-off-canvas, .ui-resizable-handle)');
144       let offset = 0;
145
146       // Let scroll element take all the height available.
147       $element.css({ height: 'auto' });
148       const modalHeight = $container.height();
149
150       $offsets.each((i, e) => {
151         offset += $(e).outerHeight();
152       });
153
154       // Take internal padding into account.
155       const scrollOffset = $element.outerHeight() - $element.height();
156       $element.height(modalHeight - offset - scrollOffset);
157     },
158
159     /**
160      * Resets the size of the dialog.
161      *
162      * @param {jQuery.Event} event
163      *   The event triggered.
164      * @param {object} event.data
165      *   Data attached to the event.
166      */
167     resetSize(event) {
168       const offsets = displace.offsets;
169       const $element = event.data.$element;
170       const container = Drupal.offCanvas.getContainer($element);
171
172       const topPosition = (offsets.top !== 0 ? `+${offsets.top}` : '');
173       const adjustedOptions = {
174         // @see http://api.jqueryui.com/position/
175         position: {
176           my: `${Drupal.offCanvas.getEdge()} top`,
177           at: `${Drupal.offCanvas.getEdge()} top${topPosition}`,
178           of: window,
179         },
180       };
181
182       container.css({
183         position: 'fixed',
184         height: `${$(window).height() - (offsets.top + offsets.bottom)}px`,
185       });
186
187       $element
188         .dialog('option', adjustedOptions)
189         .trigger('dialogContentResize.off-canvas');
190     },
191
192     /**
193      * Adjusts the body padding when the dialog is resized.
194      *
195      * @param {jQuery.Event} event
196      *   The event triggered.
197      * @param {object} event.data
198      *   Data attached to the event.
199      */
200     bodyPadding(event) {
201       if ($('body').outerWidth() < Drupal.offCanvas.minDisplaceWidth) {
202         return;
203       }
204       const $element = event.data.$element;
205       const $container = Drupal.offCanvas.getContainer($element);
206       const $mainCanvasWrapper = Drupal.offCanvas.$mainCanvasWrapper;
207
208       const width = $container.outerWidth();
209       const mainCanvasPadding = $mainCanvasWrapper.css(`padding-${Drupal.offCanvas.getEdge()}`);
210       if (width !== mainCanvasPadding) {
211         $mainCanvasWrapper.css(`padding-${Drupal.offCanvas.getEdge()}`, `${width}px`);
212         $container.attr(`data-offset-${Drupal.offCanvas.getEdge()}`, width);
213         displace();
214       }
215     },
216
217     /**
218      * The HTML element that surrounds the dialog.
219      * @param {HTMLElement} $element
220      *   The dialog element.
221      *
222      * @return {HTMLElement}
223      *   The containing element.
224      */
225     getContainer($element) {
226       return $element.dialog('widget');
227     },
228
229     /**
230      * The edge of the screen that the dialog should appear on.
231      *
232      * @return {string}
233      *   The edge the tray will be shown on, left or right.
234      */
235     getEdge() {
236       return document.documentElement.dir === 'rtl' ? 'left' : 'right';
237     },
238   };
239
240   /**
241    * Attaches off-canvas dialog behaviors.
242    *
243    * @type {Drupal~behavior}
244    *
245    * @prop {Drupal~behaviorAttach} attach
246    *   Attaches event listeners for off-canvas dialogs.
247    */
248   Drupal.behaviors.offCanvasEvents = {
249     attach: () => {
250       $(window).once('off-canvas').on({
251         'dialog:beforecreate': (event, dialog, $element, settings) => {
252           if (Drupal.offCanvas.isOffCanvas($element)) {
253             Drupal.offCanvas.beforeCreate({ dialog, $element, settings });
254           }
255         },
256         'dialog:aftercreate': (event, dialog, $element, settings) => {
257           if (Drupal.offCanvas.isOffCanvas($element)) {
258             Drupal.offCanvas.render({ dialog, $element, settings });
259             Drupal.offCanvas.afterCreate({ $element, settings });
260           }
261         },
262         'dialog:beforeclose': (event, dialog, $element) => {
263           if (Drupal.offCanvas.isOffCanvas($element)) {
264             Drupal.offCanvas.beforeClose({ dialog, $element });
265           }
266         },
267       });
268     },
269   };
270 })(jQuery, Drupal, Drupal.debounce, Drupal.displace);