Skip to content

Instantly share code, notes, and snippets.

@ckrina
Created January 2, 2023 16:29
Show Gist options
  • Save ckrina/225ec50b5b2e0fb163e9ef3a38173f8e to your computer and use it in GitHub Desktop.
Save ckrina/225ec50b5b2e0fb163e9ef3a38173f8e to your computer and use it in GitHub Desktop.
/**
* @file
* Main navigation script.
*/
export default (Drupal, once) => {
let nav;
let dropdowns;
let navButton;
let desktopMenu;
let desktopOnlyMenu;
let desktop2XLMenu;
let tabletMenu;
let submenuToggle;
function expandMenu(element) {
// Relies on the "button.main-navigation__submenu-toggle".
let expanded = element.getAttribute('aria-expanded') === 'true' || false;
submenuToggle = element.querySelector('.main-navigation__submenu-toggle');
element.setAttribute('aria-expanded', !expanded);
let submenu = element.nextElementSibling;
// If it has submenu.
if (typeof submenu !== 'undefined' && submenu !== null) {
dropdowns.forEach(function (dropdown) {
// Close everything if it is not the item being opened.
if (dropdown !== element) {
dropdown.parentNode.classList.remove('opened');
dropdown.setAttribute('aria-expanded', false);
submenu.classList.remove('opened');
submenuToggle.setAttribute('aria-expanded', false);
}
});
// Opening.
if (!expanded) {
element.parentNode.classList.add('opened');
submenu.classList.add('opened');
submenuToggle.setAttribute('aria-expanded', true);
// Submenu placement.
let submenuX = submenu.getBoundingClientRect().x;
let submenuRight = submenu.getBoundingClientRect().right;
let elementX = element.getBoundingClientRect().x;
let elementRight = element.getBoundingClientRect().right;
// Tablet.
if (tabletMenu.matches) {
// Further right position a small element can take on Tablet.
let maxRight = window.innerWidth - submenuRight;
// Check if the submenu is misaligned with the Main menu item: if the
// submenu left is further in the left than the Menu item and if the right
// point is more in the left than parent it's outside, so align.
if ((submenuX < elementX) && (submenuRight < elementRight)) {
// If submenuRight falls outside the window edge use the maxRight.
if (elementX > maxRight) {
submenu.style.left = `${maxRight}px`;
} else {
// Use the left item value minus 16px per design.
let leftPlacement = elementX - 16;
submenu.style.left = `${leftPlacement}px`;
}
}
}
// From LG / Desktop.
if (desktopOnlyMenu.matches && submenuX > elementX) {
// Use the left item value minus 16px per design.
let leftPlacement = elementX - 16;
submenu.style.left = `${leftPlacement}px`;
}
// From LG / Desktop.
if (desktop2XLMenu.matches && submenuX > elementX) {
// Use the left item value minus 16px per design.
submenu.style.left = `-1rem`;
}
// Closing.
} else {
element.parentNode.classList.remove('opened');
submenuToggle.setAttribute('aria-expanded', false);
element.setAttribute('aria-expanded', false);
submenu.classList.remove('opened');
}
}
}
function init(el) {
nav = el;
dropdowns = document.querySelectorAll('.main-navigation__level-0-content');
navButton = document.querySelector('button.main-navigation__control');
desktopMenu = window.matchMedia('(min-width: 48rem)');
desktopOnlyMenu = window.matchMedia('(min-width: 60rem)');
desktop2XLMenu = window.matchMedia('(min-width: 87.5rem)');
tabletMenu = window.matchMedia('(min-width:48rem) and (max-width: 59.99rem)');
submenuToggle = el.querySelector('.main-navigation__submenu-toggle');
// Resets.
if (typeof navButton !== 'undefined' && navButton !== null) {
// Reset all menus when click happens outside the menu.
navButton.addEventListener('click', function() {
dropdowns.forEach(function(dropdown) {
// Remove the .open class from all list wrappers.
dropdown.parentNode.classList.remove('opened');
// Remove the aria-expanded=true from all dropdowns.
dropdown.setAttribute('aria-expanded', false);
// Remove the .open class from all submenus.
dropdown.nextElementSibling.classList.remove('opened');
// Remove the aria-expanded=true from all toggles.
submenuToggle.setAttribute('aria-expanded', false);
});
});
if (typeof nav !== 'undefined' && nav !== null && typeof ResizeObserver === 'function') {
// Reset all menus when navigation closes or the window is resized to desktop.
let resizeObserver = new ResizeObserver(function () {
if ((navButton.offsetParent !== null && nav.classList.contains('main-navigation--closed')) || desktopMenu.matches) {
dropdowns.forEach(function(dropdown) {
// Remove the .open class from all list wrappers.
dropdown.parentNode.classList.remove('opened');
// Remove the aria-expanded=true from all dropdowns.
dropdown.setAttribute('aria-expanded', false);
if (typeof dropdown.nextElementSibling !== 'undefined' && dropdown.nextElementSibling !== null) {
// Remove the aria-expanded=true from all toggles.
submenuToggle.setAttribute('aria-expanded', false);
// Remove the .open class from all submenus.
dropdown.nextElementSibling.classList.remove('opened');
}
});
}
});
resizeObserver.observe(nav);
}
}
dropdowns.forEach(function(dropdown) {
dropdown.addEventListener('click', function(event) {
if (desktopMenu.matches) {
if (typeof nav !== 'undefined' && nav !== null && nav.classList.contains('main-navigation--closed')) {
expandMenu(dropdown);
}
}
else {
if (typeof nav !== 'undefined' && nav !== null && !nav.classList.contains('main-navigation--closed')) {
expandMenu(dropdown);
}
}
});
dropdown.parentNode.addEventListener('mouseenter', function(o) {
if (typeof nav !== 'undefined' && nav !== null && nav.classList.contains('main-navigation--closed')) {
expandMenu(dropdown);
}
});
dropdown.parentNode.addEventListener('mouseleave', function(event) {
if (typeof nav !== 'undefined' && nav !== null && nav.classList.contains('main-navigation--closed')) {
expandMenu(dropdown);
}
});
});
}
Drupal.behaviors.mainNavigation = {
attach(context) {
once("mainNavigation", ".main-navigation", context).forEach(init);
},
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment