off-canvas.es6.js 8.0 KB

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