priority-menu.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. (function() {
  2. /**
  3. * Debounce
  4. *
  5. * @param {Function} func
  6. * @param {number} wait
  7. * @param {boolean} immediate
  8. */
  9. function debounce(func, wait, immediate) {
  10. 'use strict';
  11. var timeout;
  12. wait = (typeof wait !== 'undefined') ? wait : 20;
  13. immediate = (typeof immediate !== 'undefined') ? immediate : true;
  14. return function() {
  15. var context = this, args = arguments;
  16. var later = function() {
  17. timeout = null;
  18. if (!immediate) {
  19. func.apply(context, args);
  20. }
  21. };
  22. var callNow = immediate && !timeout;
  23. clearTimeout(timeout);
  24. timeout = setTimeout(later, wait);
  25. if (callNow) {
  26. func.apply(context, args);
  27. }
  28. };
  29. }
  30. /**
  31. * Prepends an element to a container.
  32. *
  33. * @param {Element} container
  34. * @param {Element} element
  35. */
  36. function prependElement(container, element) {
  37. if (container.firstChild.nextSibling) {
  38. return container.insertBefore(element, container.firstChild.nextSibling);
  39. } else {
  40. return container.appendChild(element);
  41. }
  42. }
  43. /**
  44. * Shows an element by adding a hidden className.
  45. *
  46. * @param {Element} element
  47. */
  48. function showButton(element) {
  49. // classList.remove is not supported in IE11
  50. element.className = element.className.replace('is-empty', '');
  51. }
  52. /**
  53. * Hides an element by removing the hidden className.
  54. *
  55. * @param {Element} element
  56. */
  57. function hideButton(element) {
  58. // classList.add is not supported in IE11
  59. if (!element.classList.contains('is-empty')) {
  60. element.className += ' is-empty';
  61. }
  62. }
  63. /**
  64. * Returns the currently available space in the menu container.
  65. *
  66. * @returns {number} Available space
  67. */
  68. function getAvailableSpace( button, container ) {
  69. return container.offsetWidth - button.offsetWidth - 22;
  70. }
  71. /**
  72. * Returns whether the current menu is overflowing or not.
  73. *
  74. * @returns {boolean} Is overflowing
  75. */
  76. function isOverflowingNavivation( list, button, container ) {
  77. return list.offsetWidth > getAvailableSpace( button, container );
  78. }
  79. /**
  80. * Set menu container variable
  81. */
  82. var navContainer = document.querySelector('.main-navigation');
  83. var breaks = [];
  84. /**
  85. * Let’s bail if we our menu doesn't exist
  86. */
  87. if ( ! navContainer ) {
  88. return;
  89. }
  90. /**
  91. * Refreshes the list item from the menu depending on the menu size
  92. */
  93. function updateNavigationMenu( container ) {
  94. /**
  95. * Let’s bail if our menu is empty
  96. */
  97. if ( ! container.parentNode.querySelector('.main-menu[id]') ) {
  98. return;
  99. }
  100. // Adds the necessary UI to operate the menu.
  101. var visibleList = container.parentNode.querySelector('.main-menu[id]');
  102. var hiddenList = visibleList.parentNode.nextElementSibling.querySelector('.hidden-links');
  103. var toggleButton = visibleList.parentNode.nextElementSibling.querySelector('.main-menu-more-toggle');
  104. if ( isOverflowingNavivation( visibleList, toggleButton, container ) ) {
  105. // Record the width of the list
  106. breaks.push( visibleList.offsetWidth );
  107. // Move last item to the hidden list
  108. prependElement( hiddenList, ! visibleList.lastChild || null === visibleList.lastChild ? visibleList.previousElementSibling : visibleList.lastChild );
  109. // Show the toggle button
  110. showButton( toggleButton );
  111. } else {
  112. // There is space for another item in the nav
  113. if ( getAvailableSpace( toggleButton, container ) > breaks[breaks.length - 1] ) {
  114. // Move the item to the visible list
  115. visibleList.appendChild( hiddenList.firstChild.nextSibling );
  116. breaks.pop();
  117. }
  118. // Hide the dropdown btn if hidden list is empty
  119. if (breaks.length < 2) {
  120. hideButton( toggleButton );
  121. }
  122. }
  123. // Recur if the visible list is still overflowing the nav
  124. if ( isOverflowingNavivation( visibleList, toggleButton, container ) ) {
  125. updateNavigationMenu( container );
  126. }
  127. }
  128. /**
  129. * Run our priority+ function as soon as the document is `ready`
  130. */
  131. document.addEventListener( 'DOMContentLoaded', function() {
  132. updateNavigationMenu( navContainer );
  133. // Also, run our priority+ function on selective refresh in the customizer
  134. var hasSelectiveRefresh = (
  135. 'undefined' !== typeof wp &&
  136. wp.customize &&
  137. wp.customize.selectiveRefresh &&
  138. wp.customize.navMenusPreview.NavMenuInstancePartial
  139. );
  140. if ( hasSelectiveRefresh ) {
  141. // Re-run our priority+ function on Nav Menu partial refreshes
  142. wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function ( placement ) {
  143. var isNewNavMenu = (
  144. placement &&
  145. placement.partial.id.includes( 'nav_menu_instance' ) &&
  146. 'null' !== placement.container[0].parentNode &&
  147. placement.container[0].parentNode.classList.contains( 'main-navigation' )
  148. );
  149. if ( isNewNavMenu ) {
  150. updateNavigationMenu( placement.container[0].parentNode );
  151. }
  152. });
  153. }
  154. });
  155. /**
  156. * Run our priority+ function on load
  157. */
  158. window.addEventListener( 'load', function() {
  159. updateNavigationMenu( navContainer );
  160. });
  161. /**
  162. * Run our priority+ function every time the window resizes
  163. */
  164. var isResizing = false;
  165. window.addEventListener( 'resize',
  166. debounce( function() {
  167. if ( isResizing ) {
  168. return;
  169. }
  170. isResizing = true;
  171. setTimeout( function() {
  172. updateNavigationMenu( navContainer );
  173. isResizing = false;
  174. }, 150 );
  175. } )
  176. );
  177. /**
  178. * Run our priority+ function
  179. */
  180. updateNavigationMenu( navContainer );
  181. })();