1/** 2 * @file 3 * Controls the visibility of desktop navigation. 4 * 5 * Shows and hides the desktop navigation based on scroll position and controls 6 * the functionality of the button that shows/hides the navigation. 7 */ 8 9/* eslint-disable no-inner-declarations */ 10((Drupal) => { 11 /** 12 * Olivero helper functions. 13 * 14 * @namespace 15 */ 16 Drupal.olivero = {}; 17 18 /** 19 * Checks if the mobile navigation button is visible. 20 * 21 * @return {boolean} 22 * True if navButtons is hidden, false if not. 23 */ 24 function isDesktopNav() { 25 const navButtons = document.querySelector( 26 '[data-drupal-selector="mobile-buttons"]', 27 ); 28 return navButtons 29 ? window.getComputedStyle(navButtons).getPropertyValue('display') === 30 'none' 31 : false; 32 } 33 34 Drupal.olivero.isDesktopNav = isDesktopNav; 35 36 const stickyHeaderToggleButton = document.querySelector( 37 '[data-drupal-selector="sticky-header-toggle"]', 38 ); 39 const siteHeaderFixable = document.querySelector( 40 '[data-drupal-selector="site-header-fixable"]', 41 ); 42 43 /** 44 * Checks if the sticky header is enabled. 45 * 46 * @return {boolean} 47 * True if sticky header is enabled, false if not. 48 */ 49 function stickyHeaderIsEnabled() { 50 return stickyHeaderToggleButton.getAttribute('aria-checked') === 'true'; 51 } 52 53 /** 54 * Save the current sticky header expanded state to localStorage, and set 55 * it to expire after two weeks. 56 * 57 * @param {boolean} expandedState 58 * Current state of the sticky header button. 59 */ 60 function setStickyHeaderStorage(expandedState) { 61 const now = new Date(); 62 63 const item = { 64 value: expandedState, 65 expiry: now.getTime() + 20160000, // 2 weeks from now. 66 }; 67 localStorage.setItem( 68 'Drupal.olivero.stickyHeaderState', 69 JSON.stringify(item), 70 ); 71 } 72 73 /** 74 * Toggle the state of the sticky header between always pinned and 75 * only pinned when scrolled to the top of the viewport. 76 * 77 * @param {boolean} pinnedState 78 * State to change the sticky header to. 79 */ 80 function toggleStickyHeaderState(pinnedState) { 81 if (isDesktopNav()) { 82 siteHeaderFixable.classList.toggle('is-expanded', pinnedState); 83 stickyHeaderToggleButton.setAttribute('aria-checked', pinnedState); 84 setStickyHeaderStorage(pinnedState); 85 } 86 } 87 88 /** 89 * Return the sticky header's stored state from localStorage. 90 * 91 * @return {boolean} 92 * Stored state of the sticky header. 93 */ 94 function getStickyHeaderStorage() { 95 const stickyHeaderState = localStorage.getItem( 96 'Drupal.olivero.stickyHeaderState', 97 ); 98 99 if (!stickyHeaderState) return false; 100 101 const item = JSON.parse(stickyHeaderState); 102 const now = new Date(); 103 104 // Compare the expiry time of the item with the current time. 105 if (now.getTime() > item.expiry) { 106 // If the item is expired, delete the item from storage and return null. 107 localStorage.removeItem('Drupal.olivero.stickyHeaderState'); 108 return false; 109 } 110 return item.value; 111 } 112 113 // Only enable scroll interactivity if the browser supports Intersection 114 // Observer. 115 // @see https://github.com/w3c/IntersectionObserver/blob/master/polyfill/intersection-observer.js#L19-L21 116 if ( 117 'IntersectionObserver' in window && 118 'IntersectionObserverEntry' in window && 119 'intersectionRatio' in window.IntersectionObserverEntry.prototype 120 ) { 121 const fixableElements = document.querySelectorAll( 122 '[data-drupal-selector="site-header-fixable"], [data-drupal-selector="social-bar-inner"]', 123 ); 124 125 function toggleDesktopNavVisibility(entries) { 126 if (!isDesktopNav()) return; 127 128 entries.forEach((entry) => { 129 // Firefox doesn't seem to support entry.isIntersecting properly, 130 // so we check the intersectionRatio. 131 fixableElements.forEach((el) => 132 el.classList.toggle('is-fixed', entry.intersectionRatio < 1), 133 ); 134 }); 135 } 136 137 /** 138 * Gets the root margin by checking for various toolbar classes. 139 * 140 * @return {string} 141 * Root margin for the Intersection Observer options object. 142 */ 143 function getRootMargin() { 144 let rootMarginTop = 72; 145 const { body } = document; 146 147 if (body.classList.contains('toolbar-fixed')) { 148 rootMarginTop -= 39; 149 } 150 151 if ( 152 body.classList.contains('toolbar-horizontal') && 153 body.classList.contains('toolbar-tray-open') 154 ) { 155 rootMarginTop -= 40; 156 } 157 158 return `${rootMarginTop}px 0px 0px 0px`; 159 } 160 161 /** 162 * Monitor the navigation position. 163 */ 164 function monitorNavPosition() { 165 const primaryNav = document.querySelector( 166 '[data-drupal-selector="site-header"]', 167 ); 168 const options = { 169 rootMargin: getRootMargin(), 170 threshold: [0.999, 1], 171 }; 172 173 const observer = new IntersectionObserver( 174 toggleDesktopNavVisibility, 175 options, 176 ); 177 178 if (primaryNav) { 179 observer.observe(primaryNav); 180 } 181 } 182 183 if (stickyHeaderToggleButton) { 184 stickyHeaderToggleButton.addEventListener('click', () => { 185 toggleStickyHeaderState(!stickyHeaderIsEnabled()); 186 }); 187 } 188 189 // If header is pinned open and a header element gains focus, scroll to the 190 // top of the page to ensure that the header elements can be seen. 191 const siteHeaderInner = document.querySelector( 192 '[data-drupal-selector="site-header-inner"]', 193 ); 194 if (siteHeaderInner) { 195 siteHeaderInner.addEventListener('focusin', () => { 196 if (isDesktopNav() && !stickyHeaderIsEnabled()) { 197 const header = document.querySelector( 198 '[data-drupal-selector="site-header"]', 199 ); 200 const headerNav = header.querySelector( 201 '[data-drupal-selector="header-nav"]', 202 ); 203 const headerMargin = header.clientHeight - headerNav.clientHeight; 204 if (window.scrollY > headerMargin) { 205 window.scrollTo(0, headerMargin); 206 } 207 } 208 }); 209 } 210 211 monitorNavPosition(); 212 setStickyHeaderStorage(getStickyHeaderStorage()); 213 toggleStickyHeaderState(getStickyHeaderStorage()); 214 } 215})(Drupal);
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.