You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
216 lines
6.0 KiB
216 lines
6.0 KiB
12 months ago
|
/**
|
||
|
* @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) => {
|
||
|
/**
|
||
|
* Olives helper functions.
|
||
|
*
|
||
|
* @namespace
|
||
|
*/
|
||
|
Drupal.olives = {};
|
||
|
|
||
|
/**
|
||
|
* 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.olives.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) {
|
||
|
const now = new Date();
|
||
|
|
||
|
const item = {
|
||
|
value: expandedState,
|
||
|
expiry: now.getTime() + 20160000, // 2 weeks from now.
|
||
|
};
|
||
|
localStorage.setItem(
|
||
|
'Drupal.olives.stickyHeaderState',
|
||
|
JSON.stringify(item),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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);
|
||
|
setStickyHeaderStorage(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.olives.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.olives.stickyHeaderState');
|
||
|
return false;
|
||
|
}
|
||
|
return item.value;
|
||
|
}
|
||
|
|
||
|
// Only enable scroll interactivity if the browser supports Intersection
|
||
|
// Observer.
|
||
|
// @see https://github.com/w3c/IntersectionObserver/blob/master/polyfill/intersection-observer.js#L19-L21
|
||
|
if (
|
||
|
'IntersectionObserver' in window &&
|
||
|
'IntersectionObserverEntry' in window &&
|
||
|
'intersectionRatio' in window.IntersectionObserverEntry.prototype
|
||
|
) {
|
||
|
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', () => {
|
||
|
toggleStickyHeaderState(!stickyHeaderIsEnabled());
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// 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();
|
||
|
setStickyHeaderStorage(getStickyHeaderStorage());
|
||
|
toggleStickyHeaderState(getStickyHeaderStorage());
|
||
|
}
|
||
|
})(Drupal);
|