PageSourceSearch

https://www.gjpi.org/wp-content/themes/umctheme3/js/menu-accessibility.js?ver=1.32.15

js gjpi.org collected 2026-10-02 07:57:09 UTC 8,143 bytes, 245 lines download raw bytes

1(function () {
2    'use strict';
3
4    /**
5     * Lightweight polyfills to support older browsers that still appear in some campus environments.
6     * WordPress core ships similar fallbacks, but we add them locally to avoid ordering issues.
7     */
8    if (!Element.prototype.matches) {
9        Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
10    }
11
12    if (!Element.prototype.closest) {
13        Element.prototype.closest = function (selector) {
14            var element = this;
15            while (element && element.nodeType === 1) {
16                if (element.matches(selector)) {
17                    return element;
18                }
19                element = element.parentElement || element.parentNode;
20            }
21            return null;
22        };
23    }
24
25    document.addEventListener('DOMContentLoaded', function () {
26        var navigation = document.getElementById('site-navigation');
27        var sideNav = document.getElementById('sidenav');
28        var sideNavToggle = document.getElementById('sidenav_toggle_btn');
29        var pageWrapper = document.getElementById('page');
30        var htmlElement = document.documentElement;
31        var searchToggle = document.querySelector('.search-btn');
32        var mainSearch = document.getElementById('main_search');
33
34        /**
35         * Primary navigation focus handling (desktop mega menu).
36         * Adds/removes a `.focus` class so CSS can present sub-menus while keyboard tabbing.
37         */
38        if (navigation) {
39            var menu = navigation.querySelector('ul');
40
41            if (menu) {
42                setupPrimaryMenu(navigation);
43            }
44        }
45
46        /**
47         * Side navigation focus handling (hamburger / drawer menu).
48         * Opens the drawer when focus enters and closes it once focus leaves or Esc is pressed.
49         */
50        if (sideNav && pageWrapper && htmlElement) {
51            setupSideNavAccessibility();
52        }
53
54        /**
55         * Initialise keyboard support for the primary navigation.
56         *
57         * @param {HTMLElement} navElement
58         */
59        function setupPrimaryMenu(navElement) {
60            var FOCUS_CLASS = 'focus';
61
62            initialiseAriaStates();
63
64            navElement.addEventListener('focusin', function (event) {
65                var link = event.target.closest('a');
66
67                if (!link || !navElement.contains(link)) {
68                    return;
69                }
70
71                var menuItem = link.closest('li');
72
73                if (!menuItem) {
74                    return;
75                }
76
77                setFocusState(menuItem);
78            });
79
80            navElement.addEventListener('focusout', function (event) {
81                var nextElement = event.relatedTarget;
82
83                if (!nextElement || !navElement.contains(nextElement)) {
84                    clearAllFocus();
85                }
86            });
87
88            navElement.addEventListener('keydown', function (event) {
89                if (event.key === 'Escape' || event.key === 'Esc') {
90                    clearAllFocus();
91                }
92            });
93
94            function setFocusState(menuItem) {
95                removeSiblingFocus(menuItem);
96                addFocusToAncestors(menuItem);
97                syncAriaExpanded();
98            }
99
100            function removeSiblingFocus(menuItem) {
101                var parentList = menuItem.parentElement;
102
103                if (!parentList) {
104                    return;
105                }
106
107                Array.prototype.forEach.call(parentList.children, function (sibling) {
108                    if (sibling !== menuItem) {
109                        removeFocusRecursive(sibling);
110                    }
111                });
112            }
113
114            function addFocusToAncestors(menuItem) {
115                var current = menuItem;
116
117                while (current && current !== navElement) {
118                    if (current.tagName && current.tagName.toLowerCase() === 'li') {
119                        current.classList.add(FOCUS_CLASS);
120                    }
121
122                    current = current.parentElement;
123
124                    if (current && current.tagName && current.tagName.toLowerCase() === 'ul') {
125                        current = current.parentElement;
126                    }
127                }
128            }
129
130            function removeFocusRecursive(element) {
131                element.classList.remove(FOCUS_CLASS);
132
133                var descendants = element.querySelectorAll('.' + FOCUS_CLASS);
134
135                Array.prototype.forEach.call(descendants, function (descendant) {
136                    descendant.classList.remove(FOCUS_CLASS);
137                });
138            }
139
140            function clearAllFocus() {
141                var focusedItems = navElement.querySelectorAll('.' + FOCUS_CLASS);
142
143                Array.prototype.forEach.call(focusedItems, function (item) {
144                    item.classList.remove(FOCUS_CLASS);
145                });
146
147                syncAriaExpanded();
148            }
149
150            function initialiseAriaStates() {
151                var toggleLinks = navElement.querySelectorAll('.menu-item-has-children > a, .page_item_has_children > a');
152
153                Array.prototype.forEach.call(toggleLinks, function (link) {
154                    if (!link.hasAttribute('aria-expanded')) {
155                        link.setAttribute('aria-expanded', 'false');
156                    }
157                });
158            }
159
160            function syncAriaExpanded() {
161                var parentItems = navElement.querySelectorAll('.menu-item-has-children, .page_item_has_children');
162
163                Array.prototype.forEach.call(parentItems, function (item) {
164                    var trigger = item.querySelector('a');
165
166                    if (!trigger) {
167                        return;
168                    }
169
170                    if (item.classList.contains(FOCUS_CLASS)) {
171                        trigger.setAttribute('aria-expanded', 'true');
172                    } else {
173                        trigger.setAttribute('aria-expanded', 'false');
174                    }
175                });
176            }
177        }
178
179        /**
180         * Attach focus management to the sidenav drawer.
181         */
182        function setupSideNavAccessibility() {
183            sideNav.addEventListener('focusin', function () {
184                openSideNav();
185            });
186
187            sideNav.addEventListener('focusout', function (event) {
188                var next = event.relatedTarget;
189
190                if (!next || !sideNav.contains(next)) {
191                    closeSideNav();
192                }
193            });
194
195            sideNav.addEventListener('keydown', function (event) {
196                if (event.key === 'Escape' || event.key === 'Esc') {
197                    closeSideNav();
198
199                    if (sideNavToggle) {
200                        sideNavToggle.focus();
201                    }
202                }
203            });
204        }
205
206        function openSideNav() {
207            if (pageWrapper.classList.contains('sidenav-open')) {
208                return;
209            }
210
211            if (searchToggle) {
212                searchToggle.classList.remove('search-open');
213            }
214
215            if (mainSearch) {
216                mainSearch.classList.remove('search-open');
217            }
218
219            pageWrapper.classList.remove('search-open');
220            htmlElement.classList.remove('search-open');
221
222            pageWrapper.classList.add('sidenav-open');
223            htmlElement.classList.add('sidenav-open');
224            sideNav.classList.add('sidenav-open');
225
226            if (sideNavToggle) {
227                sideNavToggle.classList.add('sidenav-open');
228            }
229        }
230
231        function closeSideNav() {
232            pageWrapper.classList.remove('sidenav-open');
233            htmlElement.classList.remove('sidenav-open');
234
235            if (sideNav) {
236                sideNav.classList.remove('sidenav-open');
237            }
238
239            if (sideNavToggle) {
240                sideNavToggle.classList.remove('sidenav-open');
241            }
242        }
243    });
244}());
245

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.