navigation.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. (function( $ ) {
  2. var $body = $( document.body ),
  3. $window = $( window ),
  4. bodyWrapper = $( '#body-wrapper' ),
  5. header = $( '#masthead' ),
  6. slideMenu = $( '#slide-menu' ),
  7. menuToggle = $( '.menu-toggle' ),
  8. menuWrapper = $( '.menu-wrapper' ),
  9. resizeTimer;
  10. if ( ! slideMenu.length ) {
  11. return;
  12. }
  13. function menuSlide() {
  14. menuToggle.attr( {
  15. 'role': 'button',
  16. 'aria-controls': 'menu-wrapper',
  17. 'aria-expanded': 'false'
  18. } );
  19. // Do this on open.
  20. var openMenu = function() {
  21. menuToggle.attr( {
  22. 'aria-expanded': 'true'
  23. } );
  24. menuWrapper.attr( {
  25. 'aria-expanded': 'true',
  26. 'tabindex': '-1'
  27. } );
  28. };
  29. // Do this on close.
  30. var closeMenu = function() {
  31. menuToggle.attr( {
  32. 'aria-expanded': 'false'
  33. } );
  34. menuWrapper.attr( {
  35. 'aria-expanded': 'false'
  36. } );
  37. menuWrapper.removeAttr(
  38. 'tabindex'
  39. );
  40. bodyWrapper.removeAttr(
  41. 'style'
  42. );
  43. };
  44. // Toggle all the things on click.
  45. menuToggle.on( 'click', function( e ) {
  46. e.preventDefault();
  47. $body.toggleClass( 'menu-open' );
  48. $( this ).toggleClass( 'toggled' );
  49. $( this ).attr( 'aria-expanded' ) == 'true' ? closeMenu() : openMenu();
  50. } );
  51. // Close with escape key.
  52. $( document ).keyup( function( e ) {
  53. if ( e.keyCode === 27 && $body.hasClass( 'menu-open' ) ) {
  54. $body.removeClass( 'menu-open' );
  55. menuToggle.toggleClass( 'toggled' );
  56. closeMenu();
  57. }
  58. } );
  59. }
  60. function menuFocus() {
  61. // Get all the elements in there.
  62. var o = slideMenu.find( '*' );
  63. // Store only the elements that would be focusable.
  64. var focuasable = '#slide-menu a[href], #slide-menu area[href], #slide-menu input:not([disabled]), #slide-menu select:not([disabled]), #slide-menu textarea:not([disabled]), #slide-menu button:not([disabled]), #slide-menu iframe, #slide-menu object, #slide-menu embed, #slide-menu *[tabindex], #slide-menu *[contenteditable]';
  65. // Now, filter through all the elements and get the first focusable item that's visisble.
  66. var firstFocusable = o.filter( focuasable ).filter(':visible').first();
  67. // Now, filter through all the elements and get the last focusable item that's visisble.
  68. var lastFocusable = o.filter( focuasable ).filter(':visible').last();
  69. // At end of slide menu block, return focus to navigation menu toggle.
  70. $( lastFocusable ).on( 'keydown', function( e ) {
  71. if ( e.keyCode === 9 ) {
  72. if ( ! e.shiftKey ) {
  73. e.preventDefault();
  74. menuToggle.focus();
  75. }
  76. }
  77. } );
  78. // At start of slide menu block, refocus navigation menu toggle on SHIFT+TAB.
  79. $( '#site-navigation li:first a' ).on( 'keydown', function( e ) {
  80. if ( e.keyCode === 9 ) {
  81. if ( e.shiftKey ) {
  82. e.preventDefault();
  83. menuToggle.focus();
  84. }
  85. }
  86. } );
  87. // If the menu/sidebar is visible, always TAB into it from the menu button.
  88. $( '.menu-toggle[aria-expanded]' ).on( 'keydown', function( e ) {
  89. if ( e.keyCode === 9 ) {
  90. if ( $( this ).attr( 'aria-expanded' ) == 'true' ) {
  91. if ( ! e.shiftKey ) {
  92. e.preventDefault();
  93. $( firstFocusable ).focus();
  94. } else {
  95. if ( e.shiftKey ) {
  96. e.preventDefault();
  97. $( '#masthead' ).attr( 'tabindex', '-1' ).focus();
  98. }
  99. }
  100. }
  101. }
  102. } );
  103. // Shift focus to slide container, if open.
  104. menuToggle.on( 'click', function() {
  105. if ( $( this ).attr( 'aria-expanded' ) == 'true' ) {
  106. $( '#slide-menu' ).focus();
  107. }
  108. } );
  109. }
  110. function menuResponsive() {
  111. var bodyWrapperHeight = function() {
  112. bodyWrapper.css( {
  113. 'height': slideMenu.outerHeight() + 'px',
  114. } );
  115. };
  116. if ( $body.hasClass( 'menu-open' ) ) {
  117. bodyWrapperHeight();
  118. }
  119. // Toggle #body-wrapper height
  120. menuToggle.on( 'click', function( e ) {
  121. e.preventDefault();
  122. if ( $body.hasClass( 'menu-open' ) ) {
  123. bodyWrapperHeight();
  124. }
  125. } );
  126. var closeMenu = function() {
  127. menuToggle.attr( {
  128. 'aria-expanded': 'false'
  129. } );
  130. menuWrapper.attr( {
  131. 'aria-expanded': 'false'
  132. } );
  133. menuWrapper.removeAttr(
  134. 'tabindex'
  135. );
  136. bodyWrapper.removeAttr(
  137. 'style'
  138. );
  139. };
  140. // Make sure #slide-menu is closed when menus are missing
  141. if ( $window.width() > 1056 && $body.hasClass( 'no-menu' ) ) {
  142. $body.removeClass( 'menu-open' );
  143. menuToggle.removeClass( 'toggled' );
  144. closeMenu();
  145. }
  146. }
  147. $window.load( function() {
  148. $window.on( 'resize.publication', function() {
  149. clearTimeout( resizeTimer );
  150. resizeTimer = setTimeout( function() {
  151. menuResponsive();
  152. }, 300 );
  153. } );
  154. menuSlide();
  155. menuFocus();
  156. menuResponsive();
  157. for ( var i = 1; i < 4; i++ ) {
  158. setTimeout( menuResponsive, 100 * i );
  159. }
  160. } );
  161. } )( jQuery );