antora-ui/src/js/01-nav.js (126 lines of code) (raw):

;(function () { 'use strict' var SECT_CLASS_RX = /^sect(\d)$/ var navContainer = document.querySelector('.nav-container') var navToggle = document.querySelector('.nav-toggle') navToggle.addEventListener('click', showNav) // NOTE don't let click events propagate outside of nav container navContainer.addEventListener('click', concealEvent) var menuPanel = navContainer.querySelector('[data-panel=menu]') if (!menuPanel) return var nav = navContainer.querySelector('.nav') var currentPageItem = menuPanel.querySelector('.is-current-page') var originalPageItem = currentPageItem if (currentPageItem) { activateCurrentPath(currentPageItem) scrollItemToMidpoint(menuPanel, currentPageItem.querySelector('.nav-link')) } else { menuPanel.scrollTop = 0 } find(menuPanel, '.nav-item-toggle').forEach(function (btn) { var li = btn.parentElement btn.addEventListener('click', toggleActive.bind(li)) var navItemSpan = findNextElement(btn, '.nav-text') if (navItemSpan) { navItemSpan.style.cursor = 'pointer' navItemSpan.addEventListener('click', toggleActive.bind(li)) } }) nav.querySelector('.context').addEventListener('click', function () { var currentPanel = nav.querySelector('.is-active[data-panel]') var activatePanel = currentPanel.dataset.panel === 'menu' ? 'explore' : 'menu' currentPanel.classList.toggle('is-active') nav.querySelector('[data-panel=' + activatePanel + ']').classList.toggle('is-active') }) // NOTE prevent text from being selected by double click menuPanel.addEventListener('mousedown', function (e) { if (e.detail > 1) e.preventDefault() }) function onHashChange () { var navLink var hash = window.location.hash if (hash) { if (hash.indexOf('%')) hash = decodeURIComponent(hash) navLink = menuPanel.querySelector('.nav-link[href="' + hash + '"]') if (!navLink) { var targetNode = document.getElementById(hash.slice(1)) if (targetNode) { var current = targetNode var ceiling = document.querySelector('article.doc') while ((current = current.parentNode) && current !== ceiling) { var id = current.id // NOTE: look for section heading if (!id && (id = current.className.match(SECT_CLASS_RX))) id = (current.firstElementChild || {}).id if (id && (navLink = menuPanel.querySelector('.nav-link[href="#' + id + '"]'))) break } } } } var navItem if (navLink) { navItem = navLink.parentNode } else if (originalPageItem) { navLink = (navItem = originalPageItem).querySelector('.nav-link') } else { return } if (navItem === currentPageItem) return find(menuPanel, '.nav-item.is-active').forEach(function (el) { el.classList.remove('is-active', 'is-current-path', 'is-current-page') }) navItem.classList.add('is-current-page') currentPageItem = navItem activateCurrentPath(navItem) scrollItemToMidpoint(menuPanel, navLink) } if (menuPanel.querySelector('.nav-link[href^="#"]')) { if (window.location.hash) onHashChange() window.addEventListener('hashchange', onHashChange) } function activateCurrentPath (navItem) { var ancestorClasses var ancestor = navItem.parentNode while (!(ancestorClasses = ancestor.classList).contains('nav-menu')) { if (ancestor.tagName === 'LI' && ancestorClasses.contains('nav-item')) { ancestorClasses.add('is-active', 'is-current-path') } ancestor = ancestor.parentNode } navItem.classList.add('is-active') } function toggleActive () { this.classList.toggle('is-active') } function showNav (e) { if (navToggle.classList.contains('is-active')) return hideNav(e) var html = document.documentElement html.classList.add('is-clipped--nav') navToggle.classList.add('is-active') navContainer.classList.add('is-active') html.addEventListener('click', hideNav) concealEvent(e) } function hideNav (e) { var html = document.documentElement html.classList.remove('is-clipped--nav') navToggle.classList.remove('is-active') navContainer.classList.remove('is-active') html.removeEventListener('click', hideNav) concealEvent(e) } // NOTE don't let event get picked up by window click listener function concealEvent (e) { e.stopPropagation() } function scrollItemToMidpoint (panel, el) { var rect = panel.getBoundingClientRect() var effectiveHeight = rect.height var navStyle = window.getComputedStyle(nav) if (navStyle.position === 'sticky') effectiveHeight -= rect.top - parseFloat(navStyle.top) panel.scrollTop = Math.max(0, (el.getBoundingClientRect().height - effectiveHeight) * 0.5 + el.offsetTop) } function find (from, selector) { return [].slice.call(from.querySelectorAll(selector)) } function findNextElement (from, selector) { var el = from.nextElementSibling if (!el) return return selector ? el[el.matches ? 'matches' : 'msMatchesSelector'](selector) && el : el } })()