1/* JS for preset "Menu V2" */ 2(function() { 3 $(function() { 4 $('.menu-wrapper').each(function() { 5 initMenu($(this)) 6 }); 7 }); 8 9 // Make :active pseudo classes work on iOS 10 document.addEventListener("touchstart", function() {}, false); 11 12 const initMenu = function($menuWrapper) { 13 const $body = $('body'); 14 const $menu = $('.ed-menu', $menuWrapper); 15 const $menuLinks = $('a', $menu); 16 const $menuTrigger = $('.menu-trigger', $menuWrapper); 17 const $banner = $('.banner').first(); 18 19 const smoothScrollOffset = 20; 20 21 // Set aria attributes 22 $menuTrigger.attr({ 23 'aria-expanded': 'false', 24 'aria-controls': $menu.attr('id'), 25 }); 26 27 toggleClassOnClick($body.add($menu), $menuTrigger, null, 'open open-menu'); // Keep open on $menu for backward compatibility 28 activateSmoothScroll($menuLinks.add($('.scroll a')), smoothScrollOffset); 29 addClassOnVisibleLinkTargets($menuLinks, 'active', 2 / 3); 30 handleSticky($menuWrapper, 'sticky', $banner); 31 }; 32 33 /** 34 * Observe element's height changes and reload the initMenu() function 35 * 36 * @param {HTMLElement} elm Element to observe 37 * @param {function} callback to call when elmement's height changed 38 */ 39 const observeHeightChange = function(elm, callback) { 40 if (!('ResizeObserver' in window) || elm == null) return; 41 42 const ro = new ResizeObserver(callback); 43 ro.observe(elm); 44 } 45 46 /** 47 * Toggles class on a target when a trigger is clicked 48 * 49 * @param {jQuery} $target The target to apply the CSS class to 50 * @param {jQuery} $trigger The Trigger 51 * @param {jQuery} $closeTrigger Optional close trigger 52 * @param {string} cssClass CSS Class to toggle on the target 53 */ 54 const toggleClassOnClick = function($target, $trigger, $closeTrigger, cssClass) { 55 56 // Reset in case class "open" was saved accidentally 57 $target.removeClass(cssClass); 58 $trigger.removeClass(cssClass).attr('aria-expanded', 'false'); 59 60 // Click on trigger toggles class "open" 61 $trigger.off('.toggle').on('click.toggle', function() { 62 const isExpanded = $(this).attr('aria-expanded') === 'true'; 63 const $menu = $target.filter('.ed-menu'); 64 $(this).toggleClass(cssClass).attr('aria-expanded', !isExpanded); 65 $target.toggleClass(cssClass); 66 67 // Set focus to menu on open 68 if (!isExpanded) { 69 $menu.attr('tabindex', '0'); 70 const focusMenu = () => { 71 $menu.focus(); 72 $menu.off('transitionend', focusMenu); 73 }; 74 $menu.on('transitionend', focusMenu); 75 } 76 77 $menu[0].addEventListener( 78 'blur', 79 function() { 80 $menu[0].removeAttribute('tabindex'); 81 }, { 82 once: true 83 } 84 ); 85 }); 86 87 // Close target when link inside is clicked 88 $target.find('a').click(function() { 89 $target.removeClass(cssClass); 90 $trigger.removeClass(cssClass).attr('aria-expanded', 'false'); 91 }); 92 93 if (!$closeTrigger || !$closeTrigger.length) { 94 return; 95 } 96 97 $closeTrigger.click(function() { 98 $target.removeClass(cssClass); 99 $trigger.removeClass(cssClass).attr('aria-expanded', 'false'); 100 }); 101 }; 102 103 /** 104 * Smooth scroll to link targets 105 * 106 * @param {jQuery} $scrollLinks The links 107 * @param {jQuery} scrollOffset Offset to subtract from the scroll target position (e.g. for fixed positioned elements like a menu) 108 */ 109 const activateSmoothScroll = function($scrollLinks, scrollOffset) { 110 if (typeof scrollOffset === 'undefined') { 111 scrollOffset = 0; 112 } 113 114 const determineTarget = function($trigger, hash) { 115 if (hash == '#!next') { 116 return $trigger.closest('.ed-element').next(); 117 } 118 // Prevent invalid selectors 119 try { 120 return $(hash); 121 } catch (e) { 122 return $(); 123 } 124 } 125 126 $scrollLinks.click(function(e) { 127 const $target = determineTarget($(this), this.hash); 128 if (!$target.length) return; 129 e.preventDefault(); 130 131 viewport.scrollTo($target, 'top', 500, 0); 132 133 }); 134 }; 135 136 /** 137 * We are using the fill property on an element to pass user's choices from CSS to JavaScript 138 * 139 * @param {jQuery} $element 140 */ 141 const getStickyMode = function($element) { 142 const fillValue = getComputedStyle($element[0]).fill; 143 144 return fillValue === 'rgb(255, 0, 0)' ? 145 'sticky_banner' : 146 fillValue === 'rgb(0, 255, 0)' ? 147 'sticky_menu' : 148 fillValue === 'rgb(0, 0, 255)' ? 149 'sticky_instant' : 150 fillValue === 'rgb(255, 255, 255)' ? 151 'sticky_reverse' : 152 'sticky_none'; 153 }; 154 155 /** 156 * Adds a class to an element when not currently visible 157 * 158 * @param {jQuery} $element The element to handle stickyness for 159 * @param {string} cssClass The actual CSS class to be applied to the element when it's above a certain scroll position 160 * @param {jQuery} $banner A banner to reference the scroll position to 161 */ 162 const handleSticky = function($element, cssClass, $banner) { 163 let triggerPos = 0, 164 offset = 0; 165 let menuWrapperHeight = $element.outerHeight(); 166 let mode; 167 let prevScroll = 0; 168 $element.removeClass(cssClass); 169 170 const toggleSpacer = function(toggle) { 171 document.body.style.setProperty('--spacer-height', toggle ? menuWrapperHeight + 'px' : ''); 172 }; 173 174 const handleScroll = function() { 175 if (!$element.length || mode === 'sticky_none') return; 176 //if (!$element.length || mode === 'sticky_none' || mode === 'sticky_instant') return; 177 178 const isReverse = mode === 'sticky_reverse', 179 curScroll = viewport.getScrollTop(); 180 181 if (triggerPos <= curScroll && (!isReverse || prevScroll > curScroll)) { 182 $element.addClass(cssClass); 183 toggleSpacer(true); 184 } else { 185 $element.removeClass(cssClass); 186 toggleSpacer(false); 187 } 188 189 prevScroll = curScroll; 190 }; 191
192 const updateOffset = function() { 193 mode = getStickyMode($element); 194 menuWrapperHeight = $element.outerHeight(); 195 if (!$element.hasClass(cssClass)) { 196 offset = $element.offset().top; 197 } 198 if (mode === 'sticky_banner' && !$banner.length) { 199 mode = 'sticky_menu'; 200 } 201 if (mode === 'sticky_banner') { 202 triggerPos = $banner.offset().top + ($banner.length ? $banner.outerHeight() : $element.outerHeight()); 203 } 204 if (mode === 'sticky_menu' || mode === 'sticky_reverse') { 205 triggerPos = offset + $element.outerHeight(); 206 } 207 if (mode === 'sticky_instant') { 208 triggerPos = offset; 209 } 210 211 handleScroll(); 212 } 213 214 viewport.observe('resize', updateOffset); 215 viewport.observe('animation.end', updateOffset); 216 observeHeightChange($element[0], updateOffset); 217 updateOffset(); 218 219 viewport.observe('scroll', handleScroll); 220 handleScroll(); 221 }; 222 223 /** 224 * Adds a class to links whose target is currently inside the viewport 225 * 226 * @param {jQuery} $links Link(s) to be observed 227 * @param {string} cssClass CSS Class to be applied 228 * @param {float} sectionViewportRatio Ratio by which the target should be within the viewport 229 */ 230 const addClassOnVisibleLinkTargets = function($links, cssClass, sectionViewportRatio) { 231 if (typeof sectionViewportRatio === 'undefined') { 232 sectionViewportRatio = 1 / 2; 233 } 234 235 const menuTargets = []; 236 const activeLink = $links.filter('.active'); 237 238 const links = $links.filter(function() { 239 // Prevent invalid selectors 240 let $target; 241 try { 242 $target = $(this.hash); 243 } catch (e) { 244 return false; 245 } 246 if (!this.hash || !$target.length) { 247 return false; 248 } 249 250 // Cache offset position to improve performance (update on resize) 251 const updateOffset = function() { 252 $target.data('offset', $target.offset().top); 253 }; 254 255 viewport.observe('resize', updateOffset); 256 viewport.observe('animation.end', updateOffset); 257 updateOffset(); 258 259 menuTargets.push($target); 260 return true; 261 }); 262 263 // No hash links found, so don't handle it at all 264 if (!links.length) { 265 return; 266 } 267 268 const checkVisibility = function() { 269 $links.removeClass('active'); 270 271 // Check section position reversely 272 for (let i = menuTargets.length - 1; i >= 0; i--) { 273 const desiredScrollPosition = menuTargets[i].data('offset') - viewport.getHeight() * (1 - sectionViewportRatio); 274 if (viewport.getScrollTop() >= desiredScrollPosition && menuTargets[i][0].offsetParent !== null) { 275 links.eq(i).addClass(cssClass); 276 return; 277 } 278 } 279 280 // Fallback to originally active item 281 activeLink.addClass(cssClass); 282 }; 283 284 viewport.observe('scroll', checkVisibility); 285 checkVisibility(); 286 }; 287})(); 288/* End JS for preset "Menu V2" */
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.