/** * Table of Contents JavaScript * * Provides smooth scrolling and active link highlighting for the TOC. * Pure vanilla JavaScript - no jQuery dependency. * * @package Apus_Theme * @since 1.0.0 */ (function() { 'use strict'; /** * Initialize TOC functionality when DOM is ready */ function initTOC() { const toc = document.querySelector('.apus-toc'); if (!toc) { return; // No TOC on this page } initToggleButton(); initSmoothScroll(); initActiveHighlight(); } /** * Initialize toggle button functionality */ function initToggleButton() { const toggleButton = document.querySelector('.apus-toc-toggle'); const tocList = document.querySelector('.apus-toc-list'); if (!toggleButton || !tocList) { return; } toggleButton.addEventListener('click', function() { const isExpanded = this.getAttribute('aria-expanded') === 'true'; this.setAttribute('aria-expanded', !isExpanded); // Save state to localStorage try { localStorage.setItem('apus-toc-collapsed', isExpanded ? 'true' : 'false'); } catch (e) { // localStorage might not be available } }); // Restore saved state try { const isCollapsed = localStorage.getItem('apus-toc-collapsed') === 'true'; if (isCollapsed) { toggleButton.setAttribute('aria-expanded', 'false'); } } catch (e) { // localStorage might not be available } } /** * Initialize smooth scrolling for TOC links */ function initSmoothScroll() { const tocLinks = document.querySelectorAll('.apus-toc-link'); tocLinks.forEach(function(link) { link.addEventListener('click', function(e) { e.preventDefault(); const targetId = this.getAttribute('href').substring(1); const targetElement = document.getElementById(targetId); if (!targetElement) { return; } // Smooth scroll to target targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Update URL without jumping if (history.pushState) { history.pushState(null, null, '#' + targetId); } else { window.location.hash = targetId; } // Update active state updateActiveLink(this); // Focus the target heading for accessibility targetElement.setAttribute('tabindex', '-1'); targetElement.focus(); }); }); } /** * Initialize active link highlighting based on scroll position */ function initActiveHighlight() { const tocLinks = document.querySelectorAll('.apus-toc-link'); const headings = Array.from(document.querySelectorAll('h2[id], h3[id]')); if (headings.length === 0) { return; } let ticking = false; // Debounced scroll handler function onScroll() { if (!ticking) { window.requestAnimationFrame(function() { updateActiveOnScroll(headings, tocLinks); ticking = false; }); ticking = true; } } window.addEventListener('scroll', onScroll, { passive: true }); // Initial update updateActiveOnScroll(headings, tocLinks); } /** * Update active link based on scroll position * * @param {Array} headings Array of heading elements * @param {NodeList} tocLinks TOC link elements */ function updateActiveOnScroll(headings, tocLinks) { const scrollPosition = window.scrollY + 100; // Offset for better UX // Find the current heading let currentHeading = null; for (let i = headings.length - 1; i >= 0; i--) { if (headings[i].offsetTop <= scrollPosition) { currentHeading = headings[i]; break; } } // If we're at the top, use the first heading if (!currentHeading && scrollPosition < headings[0].offsetTop) { currentHeading = headings[0]; } // Update active class tocLinks.forEach(function(link) { if (currentHeading && link.getAttribute('href') === '#' + currentHeading.id) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } /** * Update active link when clicked * * @param {Element} clickedLink The clicked TOC link */ function updateActiveLink(clickedLink) { const tocLinks = document.querySelectorAll('.apus-toc-link'); tocLinks.forEach(function(link) { link.classList.remove('active'); }); clickedLink.classList.add('active'); } /** * Handle hash navigation on page load */ function handleHashOnLoad() { if (!window.location.hash) { return; } const targetId = window.location.hash.substring(1); const targetElement = document.getElementById(targetId); const tocLink = document.querySelector('.apus-toc-link[href="#' + targetId + '"]'); if (targetElement && tocLink) { // Small delay to ensure page is fully loaded setTimeout(function() { targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); updateActiveLink(tocLink); }, 100); } } /** * Initialize when DOM is ready */ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { initTOC(); handleHashOnLoad(); }); } else { // DOM is already ready initTOC(); handleHashOnLoad(); } })();