1/* Mobile navigation. 2 Builds a real hamburger menu from the markup already on the page, so the 3 header stays short on a phone and every link is a proper tap target. 4 5 Progressive enhancement: without JavaScript the nav renders as a plain list 6 of links, which still works. The toggle only appears once this runs. */ 7 8(function () { 9 'use strict'; 10 11 var BREAKPOINT = 860; 12 var head = document.querySelector('.site-head'); 13 if (!head) return; 14 15 var nav = head.querySelector('.nav'); 16 if (!nav) return; 17 18 var subnav = document.querySelector('.subnav'); 19 var panel, toggle, lastFocus; 20 21 function buildToggle() { 22 toggle = document.createElement('button'); 23 toggle.className = 'navtoggle'; 24 toggle.type = 'button'; 25 toggle.setAttribute('aria-expanded', 'false'); 26 toggle.setAttribute('aria-controls', 'primary-nav'); 27 toggle.setAttribute('aria-label', 'Open menu'); 28 toggle.innerHTML = '<span class="navtoggle-bars" aria-hidden="true"><i></i><i></i><i></i></span>' + 29 '<span class="navtoggle-text">Menu</span>'; 30 var brand = head.querySelector('.brand'); 31 var row = head.querySelector('.head-in') || head; 32 if (brand && brand.parentNode === row) { row.insertBefore(toggle, brand.nextSibling); } 33 else { row.appendChild(toggle); } 34 } 35 36 function buildPanel() { 37 panel = document.createElement('div'); 38 panel.className = 'navpanel'; 39 panel.id = 'primary-nav'; 40 41 // the primary links, moved (not copied) so there is one set in the DOM 42 var primary = document.createElement('div'); 43 primary.className = 'navpanel-primary'; 44 primary.appendChild(nav); 45 panel.appendChild(primary); 46 47 // the category rail, mirrored into the panel so everything is in one place 48 if (subnav) { 49 var links = subnav.querySelectorAll('a'); 50 if (links.length) { 51 var wrap = document.createElement('div'); 52 wrap.className = 'navpanel-cats'; 53 var heading = document.createElement('p'); 54 heading.className = 'navpanel-heading'; 55 heading.textContent = 'Sections'; 56 wrap.appendChild(heading); 57 var list = document.createElement('ul'); 58 Array.prototype.forEach.call(links, function (a) { 59 var li = document.createElement('li'); 60 var copy = document.createElement('a'); 61 copy.href = a.getAttribute('href'); 62 copy.textContent = a.textContent; 63 li.appendChild(copy); 64 list.appendChild(li); 65 }); 66 wrap.appendChild(list); 67 panel.appendChild(wrap); 68 } 69 } 70 71 head.appendChild(panel); 72 } 73 74 function open() { 75 lastFocus = document.activeElement; 76 head.classList.add('nav-open'); 77 document.body.classList.add('nav-locked'); 78 toggle.setAttribute('aria-expanded', 'true'); 79 toggle.setAttribute('aria-label', 'Close menu'); 80 var first = panel.querySelector('a'); 81 if (first) first.focus(); 82 } 83 84 function close(returnFocus) { 85 head.classList.remove('nav-open'); 86 document.body.classList.remove('nav-locked'); 87 toggle.setAttribute('aria-expanded', 'false'); 88 toggle.setAttribute('aria-label', 'Open menu'); 89 if (returnFocus && lastFocus && lastFocus.focus) lastFocus.focus(); 90 } 91 92 function isOpen() { return head.classList.contains('nav-open'); } 93 94 function wire() { 95 toggle.addEventListener('click', function () { 96 isOpen() ? close(true) : open(); 97 }); 98 99 // a link inside the panel navigates, so close behind it 100 panel.addEventListener('click', function (ev) { 101 if (ev.target.closest('a')) close(false); 102 }); 103 104 document.addEventListener('keydown', function (ev) { 105 if (ev.key === 'Escape' && isOpen()) close(true); 106 }); 107 108 // keep focus inside the panel while it is open 109 panel.addEventListener('keydown', function (ev) { 110 if (ev.key !== 'Tab' || !isOpen()) return; 111 var items = panel.querySelectorAll('a, button'); 112 if (!items.length) return; 113 var first = items[0], last = items[items.length - 1]; 114 if (ev.shiftKey && document.activeElement === first) { ev.preventDefault(); last.focus(); } 115 else if (!ev.shiftKey && document.activeElement === last) { ev.preventDefault(); toggle.focus(); } 116 }); 117 118 // a rotation back to desktop must not leave the page stuck behind a panel 119 var timer; 120 window.addEventListener('resize', function () { 121 clearTimeout(timer); 122 timer = setTimeout(function () { 123 if (window.innerWidth > BREAKPOINT && isOpen()) close(false); 124 }, 150); 125 }); 126 } 127
128 buildToggle(); 129 buildPanel(); 130 wire(); 131 head.classList.add('has-toggle'); 132})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.