PageSourceSearch

https://manifestfreedom.com/wp-content/themes/vast/blocks/menu-a/frontend.js?ver=1.0.0

js manifestfreedom.com collected 2026-10-09 21:36:17 UTC 12,309 bytes, 344 lines download raw bytes

1/**
2 * Frontend JavaScript for Menu A block
3 * Handles mega menu interactions, mobile menu, and responsive behavior
4 */
5
6document.addEventListener('DOMContentLoaded', function() {
7    const menuContainer = document.querySelector('.vast-menu-a');
8    if (!menuContainer) return;
9
10    const mobileToggle = menuContainer.querySelector('.vast-menu-a__mobile-toggle');
11    const menuItems = menuContainer.querySelector('.vast-menu-a__items');
12    const dropdownToggles = menuContainer.querySelectorAll('.vast-menu-a__toggle');
13    const mobileDropdownToggles = menuContainer.querySelectorAll('.vast-menu-a__mobile-toggle-button');
14    const panels = menuContainer.querySelectorAll('.vast-menu-a__panel');
15
16    // Create overlay element
17    const overlay = document.createElement('div');
18    overlay.className = 'vast-menu-a__overlay';
19    document.body.appendChild(overlay);
20
21    let activePanel = null;
22    let isMobile = window.innerWidth < 1200;
23    let isScrolled = false;
24    let isMenuOpen = false;
25
26    // Scroll detection at 80vh
27    function handleScroll() {
28        const scrollPosition = window.scrollY;
29        const triggerHeight = window.innerHeight * 0.8; // 80vh
30        const shouldBeScrolled = scrollPosition >= triggerHeight;
31        
32        if (shouldBeScrolled !== isScrolled) {
33            isScrolled = shouldBeScrolled;
34            updateNavigationState();
35        }
36    }
37
38    // Update navigation classes based on state
39    function updateNavigationState() {
40        if (isScrolled || isMenuOpen) {
41            document.body.classList.add('scrolled');
42            if (isMenuOpen) {
43                document.body.classList.add('menu-open');
44            }
45        } else {
46            document.body.classList.remove('scrolled');
47            document.body.classList.remove('menu-open');
48        }
49        
50        // Force mobile to always show scrolled state for readability
51        if (isMobile) {
52            document.body.classList.add('scrolled');
53        }
54    }
55
56    // Initialize scroll listener
57    window.addEventListener('scroll', handleScroll);
58    handleScroll(); // Initial check
59    updateNavigationState(); // Initial state setup for mobile
60
61    // Update mobile state on resize
62    window.addEventListener('resize', function() {
63        const newIsMobile = window.innerWidth < 1200;
64        if (newIsMobile !== isMobile) {
65            isMobile = newIsMobile;
66            closeAllPanels();
67            closeMobileMenu();
68            updateNavigationState(); // Ensure mobile gets scrolled class
69        }
70    });
71
72    // Mobile menu toggle
73    if (mobileToggle && menuItems) {
74        mobileToggle.addEventListener('click', function(e) {
75            e.preventDefault();
76            e.stopPropagation();
77            toggleMobileMenu();
78        });
79    }
80
81    // Desktop dropdown toggles
82    dropdownToggles.forEach(function(toggle) {
83        const item = toggle.closest('.vast-menu-a__item');
84        const panel = item.querySelector('.vast-menu-a__panel--desktop');
85        
86        if (!panel) return;
87
88        // Click handler
89        toggle.addEventListener('click', function(e) {
90            e.preventDefault();
91            e.stopPropagation();
92            
93            if (!isMobile) {
94                togglePanel(panel, toggle);
95            }
96        });
97
98        // Remove hover handlers - click-only interaction
99    });
100
101    // Mobile dropdown toggles (including mobile-only items)
102    function setupMobileDropdowns() {
103        const allMobileToggles = menuContainer.querySelectorAll('.vast-menu-a__mobile-toggle-button');
104        
105        allMobileToggles.forEach(function(toggle) {
106            const item = toggle.closest('.vast-menu-a__item');
107            const panel = item.querySelector('.vast-menu-a__panel:not(.vast-menu-a__panel--desktop)');
108            
109            if (!panel) return;
110
111            toggle.addEventListener('click', function(e) {
112                e.preventDefault();
113                e.stopPropagation();
114                
115                if (isMobile) {
116                    toggleMobilePanel(panel, toggle);
117                }
118            });
119        });
120    }
121    
122    // Setup mobile dropdowns
123    setupMobileDropdowns();
124
125    // Close panels when clicking outside
126    document.addEventListener('click', function(e) {
127        if (!menuContainer.contains(e.target)) {
128            closeAllPanels();
129            closeMobileMenu();
130        }
131    });
132
133    // Close menu when clicking the overlay
134    overlay.addEventListener('click', function(e) {
135        e.preventDefault();
136        closeAllPanels();
137        closeMobileMenu();
138    });
139
140    // Close on escape key
141    document.addEventListener('keydown', function(e) {
142        if (e.key === 'Escape') {
143            closeAllPanels();
144            closeMobileMenu();
145        }
146    });
147
148    // Functions
149    function toggleMobileMenu() {
150        const isOpen = menuItems.classList.contains('is-active');
151        
152        if (isOpen) {
153            closeMobileMenu();
154        } else {
155            openMobileMenu();
156        }
157    }
158
159    function openMobileMenu() {
160        menuItems.classList.add('is-active');
161        mobileToggle.setAttribute('aria-expanded', 'true');
162        document.body.classList.add('vast-menu-open');
163    }
164
165    function closeMobileMenu() {
166        menuItems.classList.remove('is-active');
167        if (mobileToggle) {
168            mobileToggle.setAttribute('aria-expanded', 'false');
169        }
170        document.body.classList.remove('vast-menu-open');
171        
172        // Close all mobile panels (including mobile-only items)
173        const allMobileToggles = menuContainer.querySelectorAll('.vast-menu-a__mobile-toggle-button');
174        allMobileToggles.forEach(function(toggle) {
175            const item = toggle.closest('.vast-menu-a__item');
176            const panel = item.querySelector('.vast-menu-a__panel:not(.vast-menu-a__panel--desktop)');
177            if (panel) {
178                panel.classList.remove('is-active');
179                toggle.setAttribute('aria-expanded', 'false');
180            }
181        });
182    }
183
184    function togglePanel(panel, toggle) {
185        if (activePanel === panel) {
186            hidePanel(panel, toggle);
187        } else {
188            closeAllPanels();
189            showPanel(panel, toggle);
190        }
191    }
192
193    function showPanel(panel, toggle) {
194        closeAllPanels();
195        panel.classList.add('is-active');
196        toggle.setAttribute('aria-expanded', 'true');
197        panel.setAttribute('aria-hidden', 'false');
198        activePanel = panel;
199        isMenuOpen = true;
200        overlay.classList.add('is-active');
201        updateNavigationState();
202    }
203
204    function hidePanel(panel, toggle) {
205        panel.classList.remove('is-active');
206        toggle.setAttribute('aria-expanded', 'false');
207        panel.setAttribute('aria-hidden', 'true');
208        if (activePanel === panel) {
209            activePanel = null;
210        }
211        isMenuOpen = false;
212        overlay.classList.remove('is-active');
213        updateNavigationState();
214    }
215
216    function toggleMobilePanel(panel, toggle) {
217        const isOpen = panel.classList.contains('is-active');
218        
219        if (isOpen) {
220            panel.classList.remove('is-active');
221            toggle.setAttribute('aria-expanded', 'false');
222            panel.setAttribute('aria-hidden', 'true');
223        } else {
224            panel.classList.add('is-active');
225            toggle.setAttribute('aria-expanded', 'true');
226            panel.setAttribute('aria-hidden', 'false');
227        }
228    }
229
230    function closeAllPanels() {
231        panels.forEach(function(panel) {
232            panel.classList.remove('is-active');
233            panel.setAttribute('aria-hidden', 'true');
234        });
235        
236        dropdownToggles.forEach(function(toggle) {
237            toggle.setAttribute('aria-expanded', 'false');
238        });
239        
240        activePanel = null;
241        isMenuOpen = false;
242        overlay.classList.remove('is-active');
243        updateNavigationState();
244    }
245
246    // Enhanced keyboard navigation support
247    menuContainer.addEventListener('keydown', function(e) {
248        const focusedElement = document.activeElement;
249        const menuItems = Array.from(menuContainer.querySelectorAll('.vast-menu-a__link, .vast-menu-a__toggle'));
250        const currentIndex = menuItems.indexOf(focusedElement);
251        
252        switch(e.key) {
253            case 'Enter':
254            case ' ':
255                if (focusedElement.classList.contains('vast-menu-a__toggle')) {
256                    e.preventDefault();
257                    focusedElement.click();
258                } else if (focusedElement.classList.contains('vast-menu-a__mobile-toggle')) {
259                    e.preventDefault();
260                    focusedElement.click();
261                } else if (focusedElement.classList.contains('vast-menu-a__mobile-toggle-button')) {
262                    e.preventDefault();
263                    focusedElement.click();
264                }
265                break;
266                
267            case 'ArrowRight':
268                e.preventDefault();
269                if (currentIndex < menuItems.length - 1) {
270                    menuItems[currentIndex + 1].focus();
271                } else {
272                    menuItems[0].focus(); // Wrap to first item
273                }
274                break;
275                
276            case 'ArrowLeft':
277                e.preventDefault();
278                if (currentIndex > 0) {
279                    menuItems[currentIndex - 1].focus();
280                } else {
281                    menuItems[menuItems.length - 1].focus(); // Wrap to last item
282                }
283                break;
284                
285            case 'ArrowDown':
286                e.preventDefault();
287                // If focused on toggle with dropdown, open it and focus first link
288                if (focusedElement.classList.contains('vast-menu-a__toggle')) {
289                    const item = focusedElement.closest('.vast-menu-a__item');
290                    const panel = item.querySelector('.vast-menu-a__panel');
291                    if (panel) {
292                        showPanel(panel, focusedElement);
293                        setTimeout(() => {
294                            const firstLink = panel.querySelector('a, button');
295                            if (firstLink) firstLink.focus();
296                        }, 100);
297                    }
298                }
299                break;
300                
301            case 'Escape':
302                closeAllPanels();
303                closeMobileMenu();
304                // Return focus to the toggle that opened the panel
305                if (focusedElement.closest('.vast-menu-a__panel')) {
306                    const panel = focusedElement.closest('.vast-menu-a__panel');
307                    const item = panel.closest('.vast-menu-a__item');
308                    const toggle = item.querySelector('.vast-menu-a__toggle');
309                    if (toggle) toggle.focus();
310                }
311                break;
312        }
313    });
314
315    // Focus management for accessibility
316    dropdownToggles.forEach(function(toggle) {
317        const item = toggle.closest('.vast-menu-a__item');
318        const panel = item.querySelector('.vast-menu-a__panel');
319        
320        if (!panel) return;
321
322        panel.addEventListener('keydown', function(e) {
323            if (e.key === 'Tab') {
324                const focusableElements = panel.querySelectorAll('a, button, [tabindex]:not([tabindex="-1"])');
325                const firstElement = focusableElements[0];
326                const lastElement = focusableElements[focusableElements.length - 1];
327
328                if (e.shiftKey && document.activeElement === firstElement) {
329                    e.preventDefault();
330                    toggle.focus();
331                } else if (!e.shiftKey && document.activeElement === lastElement) {
332                    e.preventDefault();
333                    // Move to next menu item or close panel
334                    hidePanel(panel, toggle);
335                    const nextItem = item.nextElementSibling;
336                    if (nextItem) {
337                        const nextLink = nextItem.querySelector('.vast-menu-a__link, .vast-menu-a__toggle');
338                        if (nextLink) nextLink.focus();
339                    }
340                }
341            }
342        });
343    });
344});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.