PageSourceSearch

https://www.trustindex.io/wp-content/plugins/megamenu/js/maxmegamenu.js?ver=3.10.6

js trustindex.io collected 2026-09-24 07:20:06 UTC 48,333 bytes, 1,105 lines download raw bytes

1/*jslint browser: true, white: true, this: true, long: true */
2/*global console,jQuery,megamenu,window,navigator*/
3
4/*! Max Mega Menu jQuery Plugin */
5(function ( $ ) {
6    "use strict";
7
8    let instanceCounter = 0;
9
10    $.maxmegamenu = function(menu, options) {
11
12        // --- DOM references ---
13        const plugin       = this;
14        const $menu        = $(menu);
15        const $wrap        = $menu.parent();
16        const $toggle_bar  = $menu.siblings(".mega-menu-toggle");
17
18        // --- Instance identifiers ---
19        const menuId            = $menu.attr("id");
20        const instanceId        = menuId + '-' + (++instanceCounter);
21        const docEventNamespace = '.megamenu-' + instanceId;
22
23        // --- Cached element sets (captured at init) ---
24        const items_with_submenus = $([
25            "li.mega-menu-megamenu.mega-menu-item-has-children",
26            "li.mega-menu-flyout.mega-menu-item-has-children",
27            "li.mega-menu-tabbed > ul.mega-sub-menu > li.mega-menu-item-has-children",
28            "li.mega-menu-flyout li.mega-menu-item-has-children"
29        ].join(","), $menu);
30
31        const collapse_children_parents = $("li.mega-menu-megamenu li.mega-menu-item-has-children.mega-collapse-children > a.mega-menu-link", $menu);
32
33        // Focusable link elements inside a menu item, shared by the keyboard
34        // navigation helpers so the matched set cannot drift between them.
35        const focusable_selector = "a.mega-menu-link, button.mega-menu-link, .mega-search span[role=button]";
36
37        // --- Keyboard key identifiers (KeyboardEvent.key values) ---
38        const tab_key         = "Tab";
39        const escape_key      = "Escape";
40        const enter_key       = "Enter";
41        const space_key       = " ";
42        const left_arrow_key  = "ArrowLeft";
43        const up_arrow_key    = "ArrowUp";
44        const right_arrow_key = "ArrowRight";
45        const down_arrow_key  = "ArrowDown";
46
47        // --- Settings defaults (read from data attributes) ---
48        const defaults = {
49            event:                $menu.attr("data-event"),
50            effect:               $menu.attr("data-effect"),
51            effect_speed:         parseInt($menu.attr("data-effect-speed")),
52            effect_mobile:        $menu.attr("data-effect-mobile"),
53            effect_speed_mobile:  parseInt($menu.attr("data-effect-speed-mobile")),
54            panel_width:          $menu.attr("data-panel-width"),
55            panel_inner_width:    $menu.attr("data-panel-inner-width"),
56            mobile_force_width:   $menu.attr("data-mobile-force-width"),
57            mobile_overlay:       $menu.attr("data-mobile-overlay"),
58            mobile_state:         $menu.attr("data-mobile-state"),
59            mobile_direction:     $menu.attr("data-mobile-direction"),
60            second_click:         $menu.attr("data-second-click"),
61            vertical_behaviour:   $menu.attr("data-vertical-behaviour"),
62            document_click:       $menu.attr("data-document-click"),
63            breakpoint:           $menu.attr("data-breakpoint"),
64            unbind_events:        $menu.attr("data-unbind"),
65            hover_intent_timeout: $menu.attr("data-hover-intent-timeout") ?? 300,
66            hover_intent_interval: $menu.attr("data-hover-intent-interval") ?? 100
67        };
68
69        // --- Mutable state ---
70        plugin.settings = {};
71        let html_body_class_timeout;
72
73        plugin.addAnimatingClass = function(element) {
74            if (plugin.settings.effect === "disabled") {
75                return;
76            }
77
78            $(".mega-animating", $wrap).removeClass("mega-animating");
79
80            const timeout = plugin.settings.effect_speed + parseInt(plugin.settings.hover_intent_timeout, 10);
81
82            element.addClass("mega-animating");
83
84            setTimeout(function() {
85               element.removeClass("mega-animating");
86            }, timeout );
87        };
88
89        plugin.hideAllPanels = function() {
90            $(".mega-toggle-on > a.mega-menu-link", $menu).each(function() {
91                plugin.hidePanel($(this), false);
92            });
93        };
94
95        plugin.expandMobileSubMenus = function() {
96            if (plugin.settings.mobile_direction !== 'vertical') {
97                return;
98            }
99            
100            $(".mega-menu-item-has-children.mega-expand-on-mobile > a.mega-menu-link", $menu).each(function() {
101                plugin.showPanel($(this), true);
102            });
103
104            if ( plugin.settings.mobile_state === 'expand_all' ) {
105                $(".mega-menu-item-has-children:not(.mega-toggle-on) > a.mega-menu-link", $menu).each(function() {
106                    plugin.showPanel($(this), true);
107                });
108            }
109
110            if ( plugin.settings.mobile_state === 'expand_active' ) {
111                const activeItemSelectors = [
112                    "li.mega-current-menu-ancestor.mega-menu-item-has-children > a.mega-menu-link",
113                    "li.mega-current-menu-item.mega-menu-item-has-children > a.mega-menu-link",
114                    "li.mega-current-menu-parent.mega-menu-item-has-children > a.mega-menu-link",
115                    "li.mega-current_page_ancestor.mega-menu-item-has-children > a.mega-menu-link",
116                    "li.mega-current_page_item.mega-menu-item-has-children > a.mega-menu-link"
117                ];
118
119                $menu.find(activeItemSelectors.join(', ')).each(function() {
120                    plugin.showPanel($(this), true);
121                });
122            }
123        };
124
125        plugin.hideSiblingPanels = function(anchor, immediate) {
126            anchor.parent().parent().find(".mega-toggle-on").children("a.mega-menu-link").each(function() { // all open children of open siblings
127                plugin.hidePanel($(this), immediate);
128            });
129        };
130
131        plugin.isDesktopView = function() {
132            const width = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
133            return width > plugin.settings.breakpoint;
134        };
135
136        plugin.isMobileView = function() {
137            return !plugin.isDesktopView();
138        };
139
140        plugin.isHorizontalMobileSubmenuMode = function() {
141            return plugin.isMobileView() && plugin.isMobileOffCanvas() && plugin.settings.mobile_direction === "horizontal";
142        };
143
144        const getBackLink = function($submenu) {
145            return $submenu.find("> li.mega-mobile-back:visible > button.mega-mobile-back-link").first();
146        };
147
148        // The open submenu of the most deeply nested toggled-on menu item.
149        const getDeepestOpenPanel = function() {
150            return $("li.mega-toggle-on:not(.mega-collapse-children) > ul.mega-sub-menu", $menu).last();
151        };
152
153        plugin.getFocusableItemsInSubmenu = function($submenu) {
154            // jQuery :visible checks layout dimensions but ignores CSS visibility:hidden.
155            // Sliding sub-panels use visibility:hidden (not display:none) for the closed
156            // state, so we must also check computed visibility to exclude their items.
157            return $submenu
158                .find(focusable_selector)
159                .not(".mega-mobile-back-link")
160                .filter(function() {
161                    return $(this).is(":visible") && window.getComputedStyle(this).visibility !== 'hidden';
162                });
163        };
164
165        plugin.focusFirstItemInOpenedSubmenu = function($item) {
166            if ( ! plugin.isHorizontalMobileSubmenuMode() || ! $wrap.hasClass("mega-keyboard-navigation")) {
167                return;
168            }
169
170            const $submenu = $item.children("ul.mega-sub-menu");
171
172            if (!$submenu.length) {
173                return;
174            }
175
176            // Search all descendants, not just direct li.mega-menu-item children, so that
177            // megamenu panels (where links are nested inside li.mega-menu-row > li.mega-menu-column)
178            // are handled the same as flat flyout panels.
179            const $firstFocusable = $submenu
180                .find(focusable_selector)
181                .not(".mega-mobile-back-link")
182                .filter(":visible")
183                .first();
184
185            if ($firstFocusable.length) {
186                $firstFocusable.trigger("focus");
187            } else {
188                getBackLink($submenu).trigger("focus");
189            }
190        };
191
192        plugin.deferFocusFirstItemInOpenedSubmenu = function($item) {
193            const delay = Math.min(120, parseInt(plugin.settings.effect_speed_mobile, 10) || 0);
194
195            setTimeout(function() {
196                plugin.focusFirstItemInOpenedSubmenu($item);
197
198                // Retry once after the first paint in case CSS visibility/transform has not applied yet.
199                setTimeout(function() {
200                    const focusedInSubmenu = $item.find("ul.mega-sub-menu").has(document.activeElement).length !== 0;
201
202                    if (!focusedInSubmenu) {
203                        plugin.focusFirstItemInOpenedSubmenu($item);
204                    }
205                }, 40);
206            }, delay);
207        };
208
209        // Accepts a menu item ID (number or numeric string), a li.mega-menu-item or an
210        // anchor, and returns the item's anchor.
211        const resolveAnchor = function(anchor) {
212            if ( typeof anchor === 'number' || ( typeof anchor === 'string' && anchor.trim() !== '' && !isNaN(anchor) ) ) {
213                return $("li.mega-menu-item-" + anchor, $menu).find("a.mega-menu-link").first();
214            }
215
216            if ( anchor.is("li.mega-menu-item") ) {
217                return anchor.find("a.mega-menu-link").first();
218            }
219
220            return anchor;
221        };
222
223        plugin.showPanel = function(anchor, immediate) {
224            anchor = resolveAnchor(anchor);
225
226            const $item = anchor.parent();
227            const isDesktop = plugin.isDesktopView();
228            const isMobile = !isDesktop;
229
230            $item.triggerHandler("before_open_panel");
231
232            $item.find("[aria-expanded]").first().attr("aria-expanded", "true");
233
234            $(".mega-animating", $wrap).removeClass("mega-animating");
235
236            if (isMobile && $item.hasClass("mega-hide-sub-menu-on-mobile")) {
237                return;
238            }
239
240            if (isDesktop && ( $menu.hasClass("mega-menu-horizontal") || $menu.hasClass("mega-menu-vertical") ) && !$item.hasClass("mega-collapse-children")) {
241                plugin.hideSiblingPanels(anchor, true);
242            }
243
244            if ((isMobile && $wrap.hasClass("mega-keyboard-navigation")) || plugin.settings.vertical_behaviour === "accordion") {
245                plugin.hideSiblingPanels(anchor, false);
246            }
247
248            plugin.calculateDynamicSubmenuWidths(anchor);
249
250            // apply jQuery transition (only if the effect is set to "slide", other transitions are CSS based)
251            if ( plugin.shouldUseSlideAnimation(anchor, immediate) ) {
252                const speed = isMobile ? plugin.settings.effect_speed_mobile : plugin.settings.effect_speed;
253
254                anchor.siblings(".mega-sub-menu").css("display", "none").animate({"height":"show", "paddingTop":"show", "paddingBottom":"show", "minHeight":"show"}, speed, function() {
255                    $(this).css("display", "");
256                });
257            }
258
259            $item.addClass("mega-toggle-on").triggerHandler("open_panel");
260            plugin.deferFocusFirstItemInOpenedSubmenu($item);
261        };
262        
263
264        plugin.shouldUseSlideAnimation = function(anchor, immediate) {
265
266            if (immediate === true) {
267                return false;
268            }
269
270            if (anchor.parent().hasClass("mega-collapse-children")) {
271                return true;
272            }
273
274            const isDesktop = plugin.isDesktopView();
275
276            if (isDesktop && plugin.settings.effect === "slide") {
277                return true;
278            }
279
280            if (!isDesktop) {
281                if (plugin.settings.effect_mobile === "slide") {
282                    return true;
283                }
284
285                if ( plugin.isMobileOffCanvas() ) {
286                    return plugin.settings.mobile_direction !== "horizontal";
287                }
288            }
289
290            return false;
291        };
292
293
294        plugin.hidePanel = function(anchor, immediate) {
295            anchor = resolveAnchor(anchor);
296
297            const $item = anchor.parent();
298            const $submenu = anchor.siblings(".mega-sub-menu");
299            const isMobile = plugin.isMobileView();
300
301            $item.triggerHandler("before_close_panel");
302
303            $item.find("[aria-expanded]").first().attr("aria-expanded", "false");
304
305            if ( plugin.shouldUseSlideAnimation(anchor) ) {
306                const speed = isMobile ? plugin.settings.effect_speed_mobile : plugin.settings.effect_speed;
307
308                $submenu.animate({"height":"hide", "paddingTop":"hide", "paddingBottom":"hide", "minHeight":"hide"}, speed, function() {
309                    $submenu.css("display", "");
310                    $item.removeClass("mega-toggle-on").triggerHandler("close_panel");
311                });
312
313                return;
314            }
315
316            if (immediate) {
317                $submenu.css("display", "none").delay(plugin.settings.effect_speed).queue(function(){
318                    $(this).css("display", "").dequeue();
319                });
320            }
321
322            // pause video widget videos
323            $submenu.find(".widget_media_video video").each(function() {
324                if ( this.player ) {
325                    this.player.pause();
326                }
327            });
328
329            $item.removeClass("mega-toggle-on").triggerHandler("close_panel");
330            plugin.addAnimatingClass($item);
331        };
332
333        // Resolve a panel width setting ('$menu', '$wrap' or a CSS selector) to elements.
334        const resolveWidthEl = function(val) {
335            return val === '$menu' ? $menu : val === '$wrap' ? $wrap : $(val);
336        };
337
338        plugin.calculateDynamicSubmenuWidths = function(anchor) {
339            const $item = anchor.parent();
340            const $submenu = anchor.siblings(".mega-sub-menu");
341            const isDesktop = plugin.isDesktopView();
342            const isTopLevelMegamenu = $item.hasClass("mega-menu-megamenu") && $item.parent().hasClass("max-mega-menu");
343
344            // apply dynamic width and sub menu position (only to top level mega menus)
345            if (isTopLevelMegamenu && plugin.settings.panel_width) {
346                if (isDesktop) {
347                    const submenu_offset = $menu.offset();
348
349                    if ( plugin.settings.panel_width === '100vw' ) {
350                        const target_offset = $('body').offset();
351
352                        $submenu.css({
353                            left: (target_offset.left - submenu_offset.left) + "px"
354                        });
355                    } else {
356                        const $panel_width_el = resolveWidthEl(plugin.settings.panel_width);
357
358                        if ( $panel_width_el.length > 0 ) {
359                            $submenu.css({
360                                width: $panel_width_el.outerWidth(),
361                                left: ($panel_width_el.offset().left - submenu_offset.left) + "px"
362                            });
363                        }
364                    }
365                } else {
366                    $submenu.css({
367                        width: "",
368                        left: ""
369                    });
370                }
371            }
372
373            // apply inner width to sub menu by adding padding to the left and right of the mega menu
374            if (isTopLevelMegamenu && plugin.settings.panel_inner_width) {
375                const $panel_inner_width_el = resolveWidthEl(plugin.settings.panel_inner_width);
376
377                if ($panel_inner_width_el.length > 0) {
378                    const target_width = parseInt($panel_inner_width_el.width(), 10);
379
380                    $submenu.css({
381                        "paddingLeft": "",
382                        "paddingRight": ""
383                    });
384
385                    const submenu_width = parseInt($submenu.innerWidth(), 10);
386
387                    if (isDesktop && target_width > 0 && target_width < submenu_width) {
388                        $submenu.css({
389                            "paddingLeft": (submenu_width - target_width) / 2 + "px",
390                            "paddingRight": (submenu_width - target_width) / 2 + "px"
391                        });
392                    }
393                }
394            }
395        };
396
397        const recalculateSubmenuWidths = function() {
398            plugin.calculateDynamicSubmenuWidths($("> li.mega-menu-megamenu > a.mega-menu-link", $menu));
399        };
400
401        plugin.bindClickEvents = function() {
402
403            plugin.unbindClickEvents();
404
405            let dragging = false;
406
407            $(document).on({
408                ["touchmove" + docEventNamespace]: function() { dragging = true; },
409                ["touchstart" + docEventNamespace]: function() { dragging = false; }
410            });
411
412            $(document).on("click" + docEventNamespace + " touchend" + docEventNamespace, function(e) { // hide menu when clicked away from
413                if (!dragging && plugin.settings.document_click === "collapse" && ! $(e.target).closest(".mega-menu-wrap").length ) {
414                    plugin.hideAllPanels();
415                    plugin.hideMobileMenu();
416                }
417                dragging = false;
418            });
419
420            collapse_children_parents.off("click.megamenu touchend.megamenu");
421            const clickable_parents = $("> a.mega-menu-link", items_with_submenus).add(collapse_children_parents);
422
423            clickable_parents.on("touchend.megamenu", function(e) {
424                if (plugin.settings.event === "hover_intent") {
425                    plugin.unbindHoverIntentEvents();
426                }
427
428                if (plugin.settings.event === "hover") {
429                    plugin.unbindHoverEvents();
430                }
431            });
432
433            clickable_parents.on("click.megamenu", function(e) {
434                if ( $(e.target).hasClass('mega-indicator') ) {
435                    return;
436                }
437
438                if (plugin.isDesktopView() && $(this).parent().hasClass("mega-toggle-on") && $(this).closest("ul.mega-sub-menu").parent().hasClass("mega-menu-tabbed") ) {
439                    if (plugin.settings.second_click === "go") {
440                        return;
441                    } else {
442                        e.preventDefault();
443                        return;
444                    }
445                }
446                if (dragging) {
447                    return;
448                }
449                if (plugin.isMobileView() && $(this).parent().hasClass("mega-hide-sub-menu-on-mobile")) {
450                    return; // allow all clicks on parent items when sub menu is hidden on mobile
451                }
452                if ((plugin.settings.second_click === "go" || $(this).parent().hasClass("mega-click-click-go")) && $(this).attr("href") !== undefined) { // check for second click
453                    if (!$(this).parent().hasClass("mega-toggle-on")) {
454                        e.preventDefault();
455                        plugin.showPanel($(this));
456                    }
457                } else {
458                    e.preventDefault();
459
460                    if ($(this).parent().hasClass("mega-toggle-on")) {
461                        plugin.hidePanel($(this), false);
462                    } else {
463                        plugin.showPanel($(this));
464                    }
465                }
466            });
467
468            if ( plugin.settings.second_click === "disabled" ) {
469                clickable_parents.off("click.megamenu");
470            }
471
472            $(".mega-close-after-click:not(.mega-menu-item-has-children) > a.mega-menu-link", $menu).on("click.megamenu", function() {
473                plugin.hideAllPanels();
474                plugin.hideMobileMenu();
475            });
476
477            $("button.mega-close", $wrap).on("click.megamenu", function(e) {
478                plugin.hideMobileMenu();
479            });
480        };
481
482        plugin.bindHoverEvents = function() {
483            items_with_submenus.on({
484                "mouseenter.megamenu" : function() {
485                    plugin.unbindClickEvents();
486                    if (! $(this).hasClass("mega-toggle-on")) {
487                        plugin.showPanel($(this).children("a.mega-menu-link"));
488                    }
489                },
490                "mouseleave.megamenu" : function() {
491                    if ($(this).hasClass("mega-toggle-on") && ! $(this).hasClass("mega-disable-collapse") && ! $(this).parent().parent().hasClass("mega-menu-tabbed")) {
492                        plugin.hidePanel($(this).children("a.mega-menu-link"), false);
493                    }
494                }
495            });
496        };
497
498        plugin.bindHoverIntentEvents = function() {
499            items_with_submenus.hoverIntent({
500                over: function () {
501                    plugin.unbindClickEvents();
502                    if (! $(this).hasClass("mega-toggle-on")) {
503                        plugin.showPanel($(this).children("a.mega-menu-link"));
504                    }
505                },
506                out: function () {
507                    if ($(this).hasClass("mega-toggle-on") && ! $(this).hasClass("mega-disable-collapse") && ! $(this).parent().parent().hasClass("mega-menu-tabbed")) {
508                        plugin.hidePanel($(this).children("a.mega-menu-link"), false);
509                    }
510                },
511                timeout: plugin.settings.hover_intent_timeout,
512                interval: plugin.settings.hover_intent_interval
513            });
514        };
515
516        plugin.isMobileOffCanvas = function() {
517            return plugin.settings.effect_mobile === 'slide_left' || plugin.settings.effect_mobile === 'slide_right';
518        };
519
520        plugin.shouldGoToNextTopLevelItem = function(key) {
521            return ( ( key === right_arrow_key && plugin.isDesktopView() ) || ( key === down_arrow_key && plugin.isMobileView() ) ) && $menu.hasClass("mega-menu-horizontal");
522        };
523
524        plugin.shouldGoToPreviousTopLevelItem = function(key) {
525            return ( ( key === left_arrow_key && plugin.isDesktopView() ) || ( key === up_arrow_key && plugin.isMobileView() ) ) && $menu.hasClass("mega-menu-horizontal");
526        };
527
528        plugin.bindKeyboardEvents = function() {
529            const $firstFocusable = $menu.find("a.mega-menu-link").first();
530            const $lastFocusable  = $wrap.find("button.mega-close").first();
531            // Direct-child variant of focusable_selector: matches only the link element
532            // belonging to the item itself, not links inside nested submenus.
533            const focusableLinkSelector = focusable_selector.split(", ").map(s => "> " + s).join(", ");
534
535            const togglePanelForAnchor = function(anchor) {
536                if ( !anchor || !anchor.length ) return;
537                if ( anchor.parent().hasClass("mega-toggle-on") && !anchor.closest("ul.mega-sub-menu").parent().hasClass("mega-menu-tabbed") ) {
538                    plugin.hidePanel(anchor);
539                } else {
540                    plugin.showPanel(anchor);
541                }
542            };
543
544            const closeNearestOpenPanelAndRefocus = function() {
545                const $focused = $menu[0].contains(document.activeElement) ? $(document.activeElement) : $();
546                const $parentAnchor = $("> a.mega-menu-link", $focused.closest(".mega-toggle-on"));
547                if ( $parentAnchor.length ) {
548                    plugin.hidePanel($parentAnchor);
549                    $parentAnchor.trigger("focus");
550                    return true;
551                }
552                return false;
553            };
554
555            // ── Key Handlers ─────────────────────────────────────────────────────────
556
557            const handleTabKey = function(e, $active, isOffCanvasHorizontal, isMobileOffCanvas) {
558                // Close button: hand focus off to the menu, or receive it back from the menu
559                if ( $active.is($lastFocusable) && isMobileOffCanvas ) {
560                    if ( !e.shiftKey ) {
561                        // Tab → forward to back link (if a flyout is open) or first menu item
562                        e.preventDefault();
563                        const $backLink = isOffCanvasHorizontal ? getBackLink(getDeepestOpenPanel()) : $();
564                        ( $backLink.length ? $backLink : $firstFocusable ).trigger('focus');
565                    } else if ( isOffCanvasHorizontal ) {
566                        // Shift+Tab → last item in the active submenu
567                        const $last = plugin.getFocusableItemsInSubmenu(getDeepestOpenPanel()).last();
568                        if ( $last.length ) { e.preventDefault(); $last.trigger('focus'); }
569                    }
570                    return;
571                }
572
573                // First top-level item: Shift+Tab wraps around to the close button
574                if ( isMobileOffCanvas && e.shiftKey && $active.is($firstFocusable) ) {
575                    e.preventDefault();
576                    $lastFocusable.trigger('focus');
577                    return;
578                }
579
580                // Inside an offcanvas horizontal submenu: keep focus trapped within the panel
581                if ( isOffCanvasHorizontal ) {
582                    const $deepestPanel = getDeepestOpenPanel();
583
584                    // Ghost-item guard: a panel is open but focus escaped to an item outside it
585                    // (top-level links, or parent-panel items covered by a child panel).
586                    if ( $deepestPanel.length && !$deepestPanel.has($active).length ) {
587                        e.preventDefault();
588                        if ( !e.shiftKey ) {
589                            const $backLink = getBackLink($deepestPanel);
590                            ( $backLink.length ? $backLink : plugin.getFocusableItemsInSubmenu($deepestPanel).first() ).trigger('focus');
591                        } else {
592                            $lastFocusable.trigger('focus');
593                        }
594                        return;
595                    }
596
597                    // Walk up to the nearest ul.mega-sub-menu whose parent li has mega-toggle-on.
598                    // For mega menus, $active may be inside a column ul rather than the panel ul itself.
599                    const $submenu = $active.parentsUntil($menu, "ul.mega-sub-menu")
600                        .filter(function() {
601                            const $p = $(this).parent();
602                            return $p.hasClass("mega-toggle-on") && !$p.hasClass("mega-collapse-children");
603                        })
604                        .first();
605                    if ( $submenu.length ) {
606                        if ( !e.shiftKey ) {
607                            // Tab forward: when on the last item, hand off to the close button
608                            const $items = plugin.getFocusableItemsInSubmenu($submenu);
609                            if ( $items.length && $active.is($items.last(
609)) ) {
610                                e.preventDefault();
611                                $lastFocusable.trigger('focus');
612                            }
613                        } else if ( $active.hasClass("mega-mobile-back-link") ) {
614                            // Shift+Tab on back link → close button
615                            e.preventDefault();
616                            $lastFocusable.trigger('focus');
617                        } else {
618                            // Shift+Tab on first item → back link
619                            const $items   = plugin.getFocusableItemsInSubmenu($submenu);
620                            const $backLink = getBackLink($submenu);
621                            if ( $items.length && $backLink.length && $active.is($items.first()) ) {
622                                e.preventDefault();
623                                $backLink.trigger('focus');
624                            }
625                        }
626                    }
627                }
628            };
629
630            const handleToggleBarTrigger = function(e) {
631                e.preventDefault();
632                if ( $toggle_bar.hasClass("mega-menu-open") ) {
633                    plugin.hideMobileMenu();
634                } else {
635                    plugin.showMobileMenu();
636                    html_body_class_timeout = setTimeout(function() {
637                        $menu.find("a.mega-menu-link").first().trigger('focus');
638                    }, plugin.settings.effect_speed_mobile);
639                }
640            };
641
642            const handleSpaceKey = function(e, $active) {
643                if ( $active.is("a.mega-menu-link") ) {
644                    e.preventDefault(); // prevent page scroll on any menu link
645                    if ( $active.parent().is(items_with_submenus) ) togglePanelForAnchor($active);
646                } else if ( $active.is(".mega-indicator") ) {
647                    e.preventDefault();
648                    togglePanelForAnchor($active.parent());
649                }
650            };
651
652            const handleEscapeKey = function() {
653                const submenu_open = $(".mega-toggle-on", $menu).length !== 0;
654                if ( submenu_open && closeNearestOpenPanelAndRefocus() ) return;
655                if ( plugin.isMobileView() && !submenu_open ) plugin.hideMobileMenu();
656            };
657
658            const handleEnterKey = function(e, $active) {
659                if ( $active.is(".mega-indicator") ) {
660                    togglePanelForAnchor($active.parent());
661                    return;
662                }
663                if ( $active.parent().is(items_with_submenus) ) {
664                    if ( plugin.isMobileView() && $active.parent().is(".mega-hide-sub-menu-on-mobile") ) return;
665                    if ( !$active.is("[href]") ) { togglePanelForAnchor($active); return; }
666                    if ( $active.parent().hasClass("mega-toggle-on") && !$active.closest("ul.mega-sub-menu").parent().hasClass("mega-menu-tabbed") ) return;
667                    e.preventDefault();
668                    plugin.showPanel($active);
669                }
670            };
671
672            const handleArrowUpDown = function(e, goingDown) {
673                e.preventDefault();
674                const $activeSubmenu = getDeepestOpenPanel();
675
676                if ( $activeSubmenu.length ) {
677                    // Inside a flyout panel: cycle through the same sequence as Tab/Shift+Tab —
678                    // back-link → items → close-button — using an explicit ordered array so
679                    // the back button (li.mega-mobile-back, not li.mega-menu-item) is included.
680                    const $backLink  = getBackLink($activeSubmenu);
681                    const $items     = plugin.getFocusableItemsInSubmenu($activeSubmenu);
682                    const focusOrder = [];
683                    if ( $backLink.length ) focusOrder.push($backLink[0]);
684                    $items.each(function() { focusOrder.push(this); });
685                    if ( $lastFocusable.length ) focusOrder.push($lastFocusable[0]);
686
687                    const idx     = focusOrder.indexOf(document.activeElement);
688                    const nextIdx = (idx < 0)
689                        ? (goingDown ? 0 : focusOrder.length - 1)
690                        : (idx + (goingDown ? 1 : -1) + focusOrder.length) % focusOrder.length;
691                    $(focusOrder[nextIdx]).trigger('focus');
692                } else {
693                    // Top level: cycle through the visible top-level links.
694                    const $topLinks = $menu.children("li.mega-menu-item:visible").find(focusableLinkSelector);
695                    const idx       = $topLinks.index(document.activeElement);
696                    if ( idx >= 0 ) {
697                        const nextIdx = (idx + (goingDown ? 1 : -1) + $topLinks.length) % $topLinks.length;
698                        $topLinks.eq(nextIdx).trigger('focus');
699                    }
700                }
701            };
702
703            const handleArrowLeftRight = function(e, goingToNext) {
704                e.preventDefault();
705                // Use find(focusableLinkSelector) with > so only direct-child links are
706                // matched — prevents picking up nested submenu links as top-level items.
707                const $topLinks = $menu.children("li.mega-menu-item:visible").find(focusableLinkSelector);
708                const $topLink  = $(document.activeElement)
709                    .closest($menu.children("li.mega-menu-item"))
710                    .find(focusableLinkSelector).first();
711                const idx     = $topLinks.index($topLink);
712                const nextIdx = goingToNext ? idx + 1 : idx - 1;
713                // Guard: nextIdx < 0 would let jQuery's eq(-1) silently wrap to the last item
714                if ( idx >= 0 && nextIdx >= 0 ) {
715                    plugin.hideAllPanels();
716                    $topLinks.eq(nextIdx).trigger('focus'); // eq(length) = empty set = no-op at boundary
717                }
718            };
719
720            // ── Event Bindings ───────────────────────────────────────────────────────
721
722            $wrap.on("keyup.megamenu", ".max-mega-menu, .mega-menu-toggle", function(e) {
723                if ( e.key !== tab_key ) return;
724                $wrap.addClass("mega-keyboard-navigation");
725                plugin.bindClickEvents(); // Windows Narrator ignores Enter, so ensure click events are bound on tab
726                const $target = $(e.target);
727                if ( plugin.isDesktopView() && $target.is(".mega-menu-link") && $target.parent().parent().hasClass('max-mega-menu') ) {
728                    plugin.hideAllPanels();
729                }
730            });
731
732            $wrap.on("keydown.megamenu", "a.mega-menu-link, button.mega-menu-link, button.mega-mobile-back-link, .mega-indicator, .mega-menu-toggle-block, .mega-menu-toggle-animated-block button, button.mega-close", function(e) {
733                if ( !$wrap.hasClass("mega-keyboard-navigation") ) return;
734
735                const key = e.key;
736                const $active = $(e.target);
737                const isOffCanvasHorizontal = plugin.isHorizontalMobileSubmenuMode();
738                const isMobileOffCanvas = plugin.isMobileView() && plugin.isMobileOffCanvas();
739
740                switch (key) {
741                    case tab_key:
742                        handleTabKey(e, $active, isOffCanvasHorizontal, isMobileOffCanvas);
743                        break;
744
745                    case space_key:
746                        if ( $active.is(".mega-menu-toggle-block button, .mega-menu-toggle-animated-block button") ) {
747                            handleToggleBarTrigger(e);
748                        } else {
749                            handleSpaceKey(e, $active);
750                        }
751                        break;
752
753                    case enter_key:
754                        if ( $active.is(".mega-menu-toggle-block button, .mega-menu-toggle-animated-block button") ) {
755                            handleToggleBarTrigger(e);
756                        } else {
757                            handleEnterKey(e, $active);
758                        }
759                        break;
760
761                    case escape_key:
762                        handleEscapeKey();
763                        break;
764
765                    case up_arrow_key:
766                    case down_arrow_key:
767                        if ( isOffCanvasHorizontal ) {
768                            handleArrowUpDown(e, key === down_arrow_key);
769                        }
770                        break;
771
772                    case left_arrow_key:
773                    case right_arrow_key:
774                        const goingToNext = plugin.shouldGoToNextTopLevelItem(key);
775                        const goingToPrev = plugin.shouldGoToPreviousTopLevelItem(key);
776                        if ( goingToNext || goingToPrev ) {
777                            handleArrowLeftRight(e, goingToNext);
778                        }
779                        break;
780                }
781            });
782
783            $wrap.on("focusout.megamenu", function(e) {
784                if ( $wrap.hasClass("mega-keyboard-navigation") ) {
785                    setTimeout(function() {
786                        if ( !$wrap[0].contains(document.activeElement) ) {
787                            $wrap.removeClass("mega-keyboard-navigation");
788                            plugin.hideAllPanels();
789                            plugin.hideMobileMenu();
790                        }
791                    }, 10);
792                }
793            });
794        };
795
796        plugin.unbindAllEvents = function() {
797            $(document).off(docEventNamespace);
798            $("ul.mega-sub-menu, li.mega-menu-item, li.mega-menu-row, li.mega-menu-column, a.mega-menu-link, .mega-indicator", $menu).off();
799        };
800
801        plugin.unbindClickEvents = function() {
802            if ( $wrap.hasClass('mega-keyboard-navigation') ) {
803                return;
804            }
805
806            $(document).off(docEventNamespace);
807
808            // collapsable parents always have a click event
809            $("> a.mega-menu-link", items_with_submenus).not(collapse_children_parents).off("click.megamenu touchend.megamenu");
810        };
811
812        plugin.unbindHoverEvents = function() {
813            items_with_submenus.off("mouseenter.megamenu mouseleave.megamenu");
814        };
815
816        plugin.unbindHoverIntentEvents = function() {
817            items_with_submenus.off("mouseenter mouseleave").removeProp("hoverIntent_t").removeProp("hoverIntent_s"); // hoverintent does not allow namespaced events
818        };
819
820        plugin.unbindKeyboardEvents = function() {
821            $wrap.off("keyup.megamenu keydown.megamenu focusout.megamenu");
822        };
823
824        plugin.unbindMegaMenuEvents = function() {
825            if (plugin.settings.event === "hover_intent") {
826                plugin.unbindHoverIntentEvents();
827            }
828
829            if (plugin.settings.event === "hover") {
830                plugin.unbindHoverEvents();
831            }
832
833            plugin.unbindClickEvents();
834            plugin.unbindKeyboardEvents();
835        };
836
837        plugin.bindMegaMenuEvents = function() {
838            plugin.unbindMegaMenuEvents();
839
840            const isDesktop = plugin.isDesktopView();
841
842            if (isDesktop && plugin.settings.event === "hover_intent") {
843                plugin.bindHoverIntentEvents();
844            }
845
846            if (isDesktop && plugin.settings.event === "hover") {
847                plugin.bindHoverEvents();
848            }
849
850            plugin.bindClickEvents(); // always bind click events for touch screen devices
851            plugin.bindKeyboardEvents();
852        };
853
854        plugin.checkWidth = function() {
855            if ( plugin.isMobileView() && $menu.data("view") === "desktop" ) {
856                plugin.switchToMobile();
857            }
858
859            if ( plugin.isDesktopView() && $menu.data("view") === "mobile" ) {
860                plugin.switchToDesktop();
861            }
862
863            recalculateSubmenuWidths();
864        };
865
866        plugin.reverseRightAlignedItems = function() {
867            if ( ! $("body").hasClass("rtl") && $menu.hasClass("mega-menu-horizontal") && $menu.css("display") !== 'flex' ) {
868                $menu.append($menu.children("li.mega-item-align-right").get().reverse());
869            }
870        };
871
872        plugin.addClearClassesToMobileItems = function() {
873            $(".mega-menu-row", $menu).each(function() {
874                $("> .mega-sub-menu > .mega-menu-column:not(.mega-hide-on-mobile)", $(this)).filter(":even").addClass("mega-menu-clear"); // :even is 0 based
875            });
876        };
877
878        plugin.initDesktop = function() {
879            $menu.data("view", "desktop");
880            plugin.bindMegaMenuEvents();
881            plugin.initIndicators();
882            $menu.trigger("mmm:switchToDesktop");
883        };
884
885        plugin.initMobile = function() {
886            plugin.switchToMobile();
887        };
888
889        plugin.switchToDesktop = function() {
890            $menu.data("view", "desktop");
891            plugin.bindMegaMenuEvents();
892            plugin.reverseRightAlignedItems();
893            plugin.hideAllPanels();
894            plugin.hideMobileMenu(true);
895            $menu.removeAttr('role');
896            $menu.removeAttr('aria-modal');
897            $menu.removeAttr('aria-hidden');
898            $menu.trigger("mmm:switchToDesktop");
899        };
900
901        plugin.switchToMobile = function() {
902            $menu.data("view", "mobile");
903
904            if (plugin.isMobileOffCanvas() && $toggle_bar.is(":visible") ) {
905                $menu.attr('role', 'dialog');
906                $menu.attr('aria-modal', 'true');
907                $menu.attr('aria-hidden', 'true');
908            }
909
910            plugin.bindMegaMenuEvents();
911            plugin.initIndicators();
912            plugin.reverseRightAlignedItems();
913            plugin.addClearClassesToMobileItems();
914            plugin.hideAllPanels();
915            plugin.expandMobileSubMenus();
916
917            $menu.trigger("mmm:switchToMobile");
918
919        };
920
921        plugin.initToggleBar = function() {
922            $toggle_bar.on("click", function(e) {
923                const isToggleTrigger = $(e.target).closest(".mega-menu-toggle-block, button.mega-toggle-animated, .mega-menu-toggle-custom-block", this).length;
924
925                if ( isToggleTrigger ) {
926                    e.preventDefault();
927                    if ($(this).hasClass("mega-menu-open")) {
928                        plugin.hideMobileMenu();
929                    } else {
930                        plugin.showMobileMenu();
931                    }
932                } else if ( e.target === this && plugin.isMobileOffCanvas() ) {
933                    plugin.hideMobileMenu();
934                }
935            });
936        };
937
938        plugin.initIndicators = function() {
939             $menu.off('click.megamenu', '.mega-indicator');
940
941             $menu.on('click.megamenu', '.mega-indicator', function(e) {
942                e.preventDefault();
943                e.stopPropagation();
944
945                if ( $(this).closest(".mega-menu-item").hasClass("mega-toggle-on") ) {
946                    if ( ! $(this).closest("ul.mega-sub-menu").parent().hasClass("mega-menu-tabbed") || plugin.isMobileView() ) {
947                        plugin.hidePanel($(this).parent(), false);
948                    }
949                } else {
950                    plugin.showPanel($(this).parent(), false);
951                }
952             });
953        };
954
955        plugin.hideMobileMenu = function(force = false) {
956
957            if ( ! $toggle_bar.is(":visible") && ! force ) {
958                return;
959            }
960
961            $menu.attr("aria-hidden", "true");
962
963            clearTimeout(html_body_class_timeout);
964            html_body_class_timeout = setTimeout(function() {
965                $("body").removeClass(menuId + "-mobile-open");
966                $("html").removeClass(menuId + "-off-canvas-open");
967            }, plugin.settings.effect_speed_mobile);
968
969            if ($wrap.hasClass("mega-keyboard-navigation")) {
970                $(".mega-menu-toggle-block button, button.mega-toggle-animated", $toggle_bar).first().trigger('focus');
971            }
972
973            $(".mega-toggle-label, .mega-toggle-animated", $toggle_bar).attr("aria-expanded", "false");
974
975            const resetMenuStyles = function() {
976                $menu.css({
977                    width: "",
978                    left: "",
979                    display: ""
980                });
981
982                $toggle_bar.removeClass("mega-menu-open");
983            };
984
985            if (plugin.settings.effect_mobile === "slide" && ! force ) {
986                $menu.animate({"height":"hide"}, plugin.settings.effect_speed_mobile, resetMenuStyles);
987            } else {
988                resetMenuStyles();
989            }
990
991            $menu.triggerHandler("mmm:hideMobileMenu");
992        };
993
994        plugin.showMobileMenu = function() {
995            if ( ! $toggle_bar.is(":visible") ) {
996                return;
997            }
998
999            clearTimeout(html_body_class_timeout);
1000
1001            $("body").addClass(menuId + "-mobile-open");
1002
1003            plugin.expandMobileSubMenus();
1004
1005            if ( plugin.isMobileOffCanvas() ) {
1006                $("html").addClass(menuId + "-off-canvas-open");
1007            }
1008
1009            if (plugin.settings.effect_mobile === "slide") {
1010                $menu.animate({"height":"show"}, plugin.settings.effect_speed_mobile, function() {
1011                    $(this).css("display", "");
1012                });
1013            }
1014
1015            $(".mega-toggle-label, .mega-toggle-animated", $toggle_bar).attr("aria-expanded", "true");
1016
1017            $toggle_bar.addClass("mega-menu-open");
1018
1019            plugin.toggleBarForceWidth();
1020
1021            $menu.attr("aria-hidden", "false");
1022            $menu.triggerHandler("mmm:showMobileMenu");
1023        };
1024
1025        plugin.toggleBarForceWidth = function() {
1026            const $force_width_el = $(plugin.settings.mobile_force_width);
1027
1028            if ($force_width_el.length && ( plugin.settings.effect_mobile === "slide" || plugin.settings.effect_mobile === "disabled" ) ) {
1029                const submenu_offset = $toggle_bar.offset();
1030                const target_offset = $force_width_el.offset();
1031
1032                $menu.css({
1033                    width: $force_width_el.outerWidth(),
1034                    left: (target_offset.left - submenu_offset.left) + "px"
1035                });
1036            }
1037        };
1038
1039        plugin.doConsoleChecks = function() {
1040            if (plugin.settings.mobile_force_width !== "false" && ! $(plugin.settings.mobile_force_width).length && ( plugin.settings.effect_mobile === "slide" || plugin.settings.effect_mobile === "disabled" ) ) {
1041                console.warn('Max Mega Menu #' + $wrap.attr('id') + ': Mobile Force Width element (' + plugin.settings.mobile_force_width + ') not found');
1042            }
1043
1044            const cssWidthRegex = /^((\d+(\.\d+)?(px|%|em|rem|vw|vh|ch|ex|cm|mm|in|pt|pc))|auto)$/i;
1045
1046            if (plugin.settings.panel_width !== undefined && ! cssWidthRegex.test(plugin.settings.panel_width) && ! resolveWidthEl(plugin.settings.panel_width).length ) {
1047                console.warn('Max Mega Menu #' + $wrap.attr('id') + ': Panel Width (Outer) element (' + plugin.settings.panel_width + ') not found');
1048            }
1049
1050            if (plugin.settings.panel_inner_width !== undefined && ! cssWidthRegex.test(plugin.settings.panel_inner_width) && ! resolveWidthEl(plugin.settings.panel_inner_width).length ) {
1051                console.warn('Max Mega Menu #' + $wrap.attr('id') + ': Panel Width (Inner) element (' + plugin.settings.panel_inner_width + ') not found');
1052            }
1053        };
1054
1055        plugin.init = function() {
1056            $menu.triggerHandler("before_mega_menu_init");
1057            plugin.settings = $.extend({}, defaults, options);
1058
1059            if (window.console) {
1060                plugin.doConsoleChecks();
1061            }
1062
1063            $menu.removeClass("mega-no-js");
1064
1065            plugin.initToggleBar();
1066            
1067            if (plugin.settings.unbind_events === "true") {
1068                plugin.unbindAllEvents();
1069            }
1070
1071            if ( document.readyState === 'complete' ) {
1072                recalculateSubmenuWidths();
1073            } else {
1074                $(window).on("load", recalculateSubmenuWidths);
1075            }
1076
1077            if ( plugin.isDesktopView() ) {
1078                plugin.initDesktop();
1079            } else {
1080                plugin.initMobile();
1081            }
1082
1083            $(window).on("resize", function() {
1084                plugin.checkWidth();
1085            });
1086
1087            $menu.triggerHandler("after_mega_menu_init");
1088        };
1089
1090        plugin.init();
1091    };
1092
1093    $.fn.maxmegamenu = function(options) {
1094        return this.each(function() {
1095            if (undefined === $(this).data("maxmegamenu")) {
1096                const plugin = new $.maxmegamenu(this, options);
1097                $(this).data("maxmegamenu", plugin);
1098            }
1099        });
1100    };
1101
1102    $(function() {
1103        $(".max-mega-menu").maxmegamenu();
1104    });
1105}( jQuery ));

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.