228 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			228 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @file
							 | 
						||
| 
								 | 
							
								 * Controls the visibility of desktop navigation.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Shows and hides the desktop navigation based on scroll position and controls
							 | 
						||
| 
								 | 
							
								 * the functionality of the button that shows/hides the navigation.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/* eslint-disable no-inner-declarations */
							 | 
						||
| 
								 | 
							
								((Drupal) => {
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Olivero helper functions.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @namespace
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  Drupal.olivero = {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Checks if the mobile navigation button is visible.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @return {boolean}
							 | 
						||
| 
								 | 
							
								   *   True if navButtons is hidden, false if not.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function isDesktopNav() {
							 | 
						||
| 
								 | 
							
								    const navButtons = document.querySelector(
							 | 
						||
| 
								 | 
							
								      '[data-drupal-selector="mobile-buttons"]',
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    return navButtons
							 | 
						||
| 
								 | 
							
								      ? window.getComputedStyle(navButtons).getPropertyValue('display') ===
							 | 
						||
| 
								 | 
							
								          'none'
							 | 
						||
| 
								 | 
							
								      : false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  Drupal.olivero.isDesktopNav = isDesktopNav;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const stickyHeaderToggleButton = document.querySelector(
							 | 
						||
| 
								 | 
							
								    '[data-drupal-selector="sticky-header-toggle"]',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  const siteHeaderFixable = document.querySelector(
							 | 
						||
| 
								 | 
							
								    '[data-drupal-selector="site-header-fixable"]',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Checks if the sticky header is enabled.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @return {boolean}
							 | 
						||
| 
								 | 
							
								   *   True if sticky header is enabled, false if not.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function stickyHeaderIsEnabled() {
							 | 
						||
| 
								 | 
							
								    return stickyHeaderToggleButton.getAttribute('aria-checked') === 'true';
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Save the current sticky header expanded state to localStorage, and set
							 | 
						||
| 
								 | 
							
								   * it to expire after two weeks.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {boolean} expandedState
							 | 
						||
| 
								 | 
							
								   *   Current state of the sticky header button.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function setStickyHeaderStorage(expandedState) {
							 | 
						||
| 
								 | 
							
								    if (!expandedState) {
							 | 
						||
| 
								 | 
							
								      localStorage.removeItem('Drupal.olivero.stickyHeaderState');
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const now = new Date();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const item = {
							 | 
						||
| 
								 | 
							
								      value: expandedState,
							 | 
						||
| 
								 | 
							
								      expiry: now.getTime() + 20160000, // 2 weeks from now.
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								    localStorage.setItem(
							 | 
						||
| 
								 | 
							
								      'Drupal.olivero.stickyHeaderState',
							 | 
						||
| 
								 | 
							
								      JSON.stringify(item),
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Update the expiration date if the sticky header expanded state is set.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {boolean} expandedState
							 | 
						||
| 
								 | 
							
								   *   Current state of the sticky header button.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function updateStickyHeaderStorage(expandedState) {
							 | 
						||
| 
								 | 
							
								    const stickyHeaderState = localStorage.getItem(
							 | 
						||
| 
								 | 
							
								      'Drupal.olivero.stickyHeaderState',
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (stickyHeaderState !== null) {
							 | 
						||
| 
								 | 
							
								      setStickyHeaderStorage(expandedState);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Toggle the state of the sticky header between always pinned and
							 | 
						||
| 
								 | 
							
								   * only pinned when scrolled to the top of the viewport.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {boolean} pinnedState
							 | 
						||
| 
								 | 
							
								   *   State to change the sticky header to.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function toggleStickyHeaderState(pinnedState) {
							 | 
						||
| 
								 | 
							
								    if (isDesktopNav()) {
							 | 
						||
| 
								 | 
							
								      siteHeaderFixable.classList.toggle('is-expanded', pinnedState);
							 | 
						||
| 
								 | 
							
								      stickyHeaderToggleButton.setAttribute('aria-checked', pinnedState);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Return the sticky header's stored state from localStorage.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @return {boolean}
							 | 
						||
| 
								 | 
							
								   *   Stored state of the sticky header.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function getStickyHeaderStorage() {
							 | 
						||
| 
								 | 
							
								    const stickyHeaderState = localStorage.getItem(
							 | 
						||
| 
								 | 
							
								      'Drupal.olivero.stickyHeaderState',
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!stickyHeaderState) return false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const item = JSON.parse(stickyHeaderState);
							 | 
						||
| 
								 | 
							
								    const now = new Date();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Compare the expiry time of the item with the current time.
							 | 
						||
| 
								 | 
							
								    if (now.getTime() > item.expiry) {
							 | 
						||
| 
								 | 
							
								      // If the item is expired, delete the item from storage and return null.
							 | 
						||
| 
								 | 
							
								      localStorage.removeItem('Drupal.olivero.stickyHeaderState');
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return item.value;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const fixableElements = document.querySelectorAll(
							 | 
						||
| 
								 | 
							
								    '[data-drupal-selector="site-header-fixable"], [data-drupal-selector="social-bar-inner"]',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function toggleDesktopNavVisibility(entries) {
							 | 
						||
| 
								 | 
							
								    if (!isDesktopNav()) return;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    entries.forEach((entry) => {
							 | 
						||
| 
								 | 
							
								      // Firefox doesn't seem to support entry.isIntersecting properly,
							 | 
						||
| 
								 | 
							
								      // so we check the intersectionRatio.
							 | 
						||
| 
								 | 
							
								      fixableElements.forEach((el) =>
							 | 
						||
| 
								 | 
							
								        el.classList.toggle('is-fixed', entry.intersectionRatio < 1),
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Gets the root margin by checking for various toolbar classes.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @return {string}
							 | 
						||
| 
								 | 
							
								   *   Root margin for the Intersection Observer options object.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function getRootMargin() {
							 | 
						||
| 
								 | 
							
								    let rootMarginTop = 72;
							 | 
						||
| 
								 | 
							
								    const { body } = document;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (body.classList.contains('toolbar-fixed')) {
							 | 
						||
| 
								 | 
							
								      rootMarginTop -= 39;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      body.classList.contains('toolbar-horizontal') &&
							 | 
						||
| 
								 | 
							
								      body.classList.contains('toolbar-tray-open')
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      rootMarginTop -= 40;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return `${rootMarginTop}px 0px 0px 0px`;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Monitor the navigation position.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function monitorNavPosition() {
							 | 
						||
| 
								 | 
							
								    const primaryNav = document.querySelector(
							 | 
						||
| 
								 | 
							
								      '[data-drupal-selector="site-header"]',
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    const options = {
							 | 
						||
| 
								 | 
							
								      rootMargin: getRootMargin(),
							 | 
						||
| 
								 | 
							
								      threshold: [0.999, 1],
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const observer = new IntersectionObserver(
							 | 
						||
| 
								 | 
							
								      toggleDesktopNavVisibility,
							 | 
						||
| 
								 | 
							
								      options,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (primaryNav) {
							 | 
						||
| 
								 | 
							
								      observer.observe(primaryNav);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (stickyHeaderToggleButton) {
							 | 
						||
| 
								 | 
							
								    stickyHeaderToggleButton.addEventListener('click', () => {
							 | 
						||
| 
								 | 
							
								      const pinnedState = !stickyHeaderIsEnabled();
							 | 
						||
| 
								 | 
							
								      toggleStickyHeaderState(pinnedState);
							 | 
						||
| 
								 | 
							
								      setStickyHeaderStorage(pinnedState);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // If header is pinned open and a header element gains focus, scroll to the
							 | 
						||
| 
								 | 
							
								  // top of the page to ensure that the header elements can be seen.
							 | 
						||
| 
								 | 
							
								  const siteHeaderInner = document.querySelector(
							 | 
						||
| 
								 | 
							
								    '[data-drupal-selector="site-header-inner"]',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  if (siteHeaderInner) {
							 | 
						||
| 
								 | 
							
								    siteHeaderInner.addEventListener('focusin', () => {
							 | 
						||
| 
								 | 
							
								      if (isDesktopNav() && !stickyHeaderIsEnabled()) {
							 | 
						||
| 
								 | 
							
								        const header = document.querySelector(
							 | 
						||
| 
								 | 
							
								          '[data-drupal-selector="site-header"]',
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        const headerNav = header.querySelector(
							 | 
						||
| 
								 | 
							
								          '[data-drupal-selector="header-nav"]',
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        const headerMargin = header.clientHeight - headerNav.clientHeight;
							 | 
						||
| 
								 | 
							
								        if (window.scrollY > headerMargin) {
							 | 
						||
| 
								 | 
							
								          window.scrollTo(0, headerMargin);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  monitorNavPosition();
							 | 
						||
| 
								 | 
							
								  updateStickyHeaderStorage(getStickyHeaderStorage());
							 | 
						||
| 
								 | 
							
								  toggleStickyHeaderState(getStickyHeaderStorage());
							 | 
						||
| 
								 | 
							
								})(Drupal);
							 |