PageSourceSearch

https://iabeurope.eu/wp-content/plugins/oxyextras/components/assets/accessible-megamenu.js?ver=1.0.0

js iabeurope.eu collected 2026-09-24 06:50:02 UTC 52,349 bytes, 1,201 lines download raw bytes

1/*
2Copyright © 2013 Adobe Systems Incorporated.
3
4Licensed under the Apache License, Version 2.0 (the “License”);
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an “AS IS” BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17/**
18 * See <a href="http://jquery.com">http://jquery.com</a>.
19 * @name jquery
20 * @class
21 * See the jQuery Library  (<a href="http://jquery.com">http://jquery.com</a>) for full details.  This just
22 * documents the function and classes that are added to jQuery by this plug-in.
23 */
24
25/**
26 * See <a href="http://jquery.com">http://jquery.com</a>
27 * @name fn
28 * @class
29 * See the jQuery Library  (<a href="http://jquery.com">http://jquery.com</a>) for full details.  This just
30 * documents the function and classes that are added to jQuery by this plug-in.
31 * @memberOf jquery
32 */
33
34/**
35 * @fileOverview accessibleMegaMenu plugin
36 *
37 *<p>Licensed under the Apache License, Version 2.0 (the “License”)
38 *<br />Copyright © 2013 Adobe Systems Incorporated.
39 *<br />Project page <a href="https://github.com/adobe-accessibility/Accessible-Mega-Menu">https://github.com/adobe-accessibility/Accessible-Mega-Menu</a>
40 * @version 0.1
41 * @author Michael Jordan
42 * @requires jquery
43 */
44
45/*jslint browser: true, devel: true, plusplus: true, nomen: true */
46/*global jQuery, window, document */
47(function ($, window, document) {
48    "use strict";
49    var pluginName = "accessibleMegaMenu",
50        defaults = {
51            uuidPrefix: "accessible-megamenu", // unique ID's are required to indicate aria-owns, aria-controls and aria-labelledby
52            menuClass: "accessible-megamenu", // default css class used to define the megamenu styling
53            topNavItemClass: "accessible-megamenu-top-nav-item", // default css class for a top-level navigation item in the megamenu
54            panelClass: "accessible-megamenu-panel", // default css class for a megamenu panel
55            panelGroupClass: "accessible-megamenu-panel-group", // default css class for a group of items within a megamenu panel
56            hoverClass: "hover", // default css class for the hover state
57            focusClass: "focus", // default css class for the focus state
58            openClass: "open", // default css class for the open state,
59            toggleButtonClass: "accessible-megamenu-toggle", // default css class responsive toggle button
60            openDelay: 0, // default open delay when opening menu via mouseover
61            closeDelay: 250, // default open delay when opening menu via mouseover
62            openOnMouseover: false // default setting for whether menu should open on mouseover
63        },
64        Keyboard = {
65            BACKSPACE: 8,
66            COMMA: 188,
67            DELETE: 46,
68            DOWN: 40,
69            END: 35,
70            ENTER: 13,
71            ESCAPE: 27,
72            HOME: 36,
73            LEFT: 37,
74            PAGE_DOWN: 34,
75            PAGE_UP: 33,
76            PERIOD: 190,
77            RIGHT: 39,
78            SPACE: 32,
79            TAB: 9,
80            UP: 38,
81            keyMap: {
82                48: "0",
83                49: "1",
84                50: "2",
85                51: "3",
86                52: "4",
87                53: "5",
88                54: "6",
89                55: "7",
90                56: "8",
91                57: "9",
92                59: ";",
93                65: "a",
94                66: "b",
95                67: "c",
96                68: "d",
97                69: "e",
98                70: "f",
99                71: "g",
100                72: "h",
101                73: "i",
102                74: "j",
103                75: "k",
104                76: "l",
105                77: "m",
106                78: "n",
107                79: "o",
108                80: "p",
109                81: "q",
110                82: "r",
111                83: "s",
112                84: "t",
113                85: "u",
114                86: "v",
115                87: "w",
116                88: "x",
117                89: "y",
118                90: "z",
119                96: "0",
120                97: "1",
121                98: "2",
122                99: "3",
123                100: "4",
124                101: "5",
125                102: "6",
126                103: "7",
127                104: "8",
128                105: "9",
129                190: "."
130            }
131        },
132        clearTimeout = window.clearTimeout,
133        setTimeout = window.setTimeout,
134        isOpera = window.opera && window.opera.toString() === '[object Opera]';
135    /**
136     * @desc Creates a new accessible mega menu instance.
137     * @param {jquery} element
138     * @param {object} [options] Mega Menu options
139     * @param {string} [options.uuidPrefix=accessible-megamenu] - Prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby
140     * @param {string} [options.menuClass=accessible-megamenu] - CSS class used to define the megamenu styling
141     * @param {string} [options.topNavItemClass=accessible-megamenu-top-nav-item] - CSS class for a top-level navigation item in the megamenu
142     * @param {string} [options.panelClass=accessible-megamenu-panel] - CSS class for a megamenu panel
143     * @param {string} [options.panelGroupClass=accessible-megamenu-panel-group] - CSS class for a group of items within a megamenu panel
144     * @param {string} [options.hoverClass=hover] - CSS class for the hover state
145     * @param {string} [options.focusClass=focus] - CSS class for the focus state
146     * @param {string} [options.openClass=open] - CSS class for the open state
147     * @constructor
148     */
149    function AccessibleMegaMenu(element, options) {
150        this.element = element;
151
152        // merge optional settings and defaults into settings
153        this.settings = $.extend({}, defaults, options);
154
155        this._defaults = defaults;
156        this._name = pluginName;
157
158        this.mouseTimeoutID = null;
159        this.focusTimeoutID = null;
160        this.mouseFocused = false;
161        this.justFocused = false;
162
163        this.init();
164    }
165
166    AccessibleMegaMenu.prototype = (function () {
167
168        /* private attributes and methods ------------------------ */
169        var uuid = 0,
170            keydownTimeoutDuration = 1000,
171            keydownSearchString = "",
172            isTouch = 'ontouchstart' in window || window.navigator.msMaxTouchPoints,
173            _getPlugin,
174            _addUniqueId,
175            _togglePanel,
176            _clickHandler,
177            _touchmoveHandler,
178            _clickOutsideHandler,
179            _DOMAttrModifiedHandler,
180            _focusInHandler,
181            _focusOutHandler,
182            _keyDownHandler,
183            _mouseDownHandler,
184            _mouseOverHandler,
185            _mouseOutHandler,
186            _clickToggleHandler,
187            _toggleExpandedEventHandlers,
188            _addEventHandlers,
189            _removeEventHandlers;
190
191        /**
192         * @name jQuery.fn.accessibleMegaMenu~_getPlugin
193         * @desc Returns the parent accessibleMegaMenu instance for a given element
194         * @param {jQuery} element
195         * @memberof jQuery.fn.accessibleMegaMenu
196         * @inner
197         * @private
198         */
199        _getPlugin = function (element) {
200            return $(element).closest(':data(plugin_' + pluginName + ')').data("plugin_" + pluginName);
201        };
202
203        /**
204         * @name jQuery.fn.accessibleMegaMenu~_addUniqueId
205         * @desc Adds a unique id and element.
206         * The id string starts with the
207         * string defined in settings.uuidPrefix.
208         * @param {jQuery} element
209         * @memberof jQuery.fn.accessibleMegaMenu
210         * @inner
211         * @private
212         */
213        _addUniqueId = function (element) {
214            element = $(element);
215            var settings = this.settings;
216            if (!element.attr("id")) {
217                element.attr("id", settings.uuidPrefix + "-" + new Date().getTime() + "-" + (++uuid));
218            }
219        };
220
221        /**
222         * @name jQuery.fn.accessibleMegaMenu~_togglePanel
223         * @desc Toggle the display of mega menu panels in response to an event.
224         * The optional boolean value 'hide' forces all panels to hide.
225         * @param {event} event
226         * @param {Boolean} [hide] Hide all mega menu panels when true
227         * @memberof jQuery.fn.accessibleMegaMenu
228         * @inner
229         * @private
230         */
231        _togglePanel = function (event, hide) {
232            var target = $(event.target),
233                that = this,
234                settings = this.settings,
235                menu = this.menu,
236                topli = target.closest('.' + settings.topNavItemClass),
237                panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass),
238                newfocus;
239
240            _toggleExpandedEventHandlers.call(this, true);
241
242            // Hide all panels.
243            if (hide) {
244                // Get the first top level menu item.
245                topli = menu.find('.' + settings.topNavItemClass + ' .' + settings.openClass + ':first').closest('.' + settings.topNavItemClass);
246                // Validate event.
247                if (!(topli.is(event.relatedTarget) || topli.has(event.relatedTarget).length > 0)) {
248                    if ((event.type === 'mouseout' || event.type === 'focusout') && topli.has(document.activeElement).length > 0) {
249                        return;
250                    }
251
252                    // Close top level link.
253                    topli.find('[aria-expanded]')
254                        .attr('aria-expanded', 'false')
255                        .removeClass(settings.openClass)
256                    // Close panel.
257                    topli.find('.' + settings.panelClass)
258                        .removeClass(settings.openClass)
259                        .attr('aria-hidden', 'true');
260
261                    if ((event.type === 'keydown' && event.keyCode === Keyboard.ESCAPE) || event.type === 'DOMAttrModified') {
262                        newfocus = topli.find(':tabbable:first');
263                        setTimeout(function () {
264                            menu.find('[aria-expanded].' + that.settings.panelClass).off('DOMAttrModified.accessible-megamenu');
265                            newfocus.focus();
266                            that.justFocused = false;
267                        }, 99);
268                    }
269                } else if (topli.length === 0) {
270                    menu.find('[aria-expanded=true]')
271                        .attr('aria-expanded', 'false')
272                        .removeClass(settings.openClass)
273                        .closest('.' + settings.panelClass)
274                        .removeClass(settings.openClass)
275                        .attr('aria-hidden', 'true');
276                }
277            }
278            // Toggle panels.
279            else {
280                clearTimeout(that.focusTimeoutID);
281                // Close previously open top level link and its panel.
282                var openli = menu.find('[aria-expanded=true]').parent();
283                if (!openli.is(topli)) {
284                    openli.find('[aria-expanded]')
285                        .attr('aria-expanded', 'false')
286                        .removeClass(settings.openClass)
287                        .siblings('.' + settings.panelClass)
288                        .removeClass(settings.openClass)
289                        .attr('aria-hidden', 'true');
290                }
291                // Open current top level link and its panel.
292                topli.find('[aria-expanded]')
293                    .attr('aria-expanded', 'true')
294                    .addClass(settings.openClass)
295                    .siblings('.' + settings.panelClass)
296                    .addClass(settings.openClass)
297                    .attr('aria-hidden', 'false');
298
299                if (event.type === 'mouseover' && target.is(':tabbable') && topli.length === 1 && panel.length === 0 && menu.has(document.activeElement).length > 0) {
300                    target.focus();
301                    that.justFocused = false;
302                }
303
304                _toggleExpandedEventHandlers.call(that);
305            }
306        };
307
308        /**
309         * @name jQuery.fn.accessibleMegaMenu~_clickHandler
310         * @desc Handle click event on mega menu item
311         * @param {event} Event object
312         * @memberof jQuery.fn.accessibleMegaMenu
313         * @inner
314         * @private
315         */
316        _clickHandler = function (event) {
317            var target = $(event.target).closest(':tabbable'),
318                topli = target.closest('.' + this.settings.topNavItemClass),
319                panel = target.closest('.' + this.settings.panelClass);
320            // With panel.
321            if (topli.length === 1 && panel.length === 0 && topli.find('.' + this.settings.panelClass).length === 1) {
322                // Handle click event.
323                if (!target.hasClass(this.settings.openClass)) {
324                    event.preventDefault();
325                    event.stopPropagation();
326                    _togglePanel.call(this, event);
327                    this.justFocused = false;
328                }
329                else {
330                    // Handle focus event.
331                    if (this.justFocused) {
332                        event.preventDefault();
333                        event.stopPropagation();
334                        this.justFocused = false;
335                    }
336                    // Handle touch/click event.
337                    else if (isTouch || !isTouch && !this.settings.openOnMouseover) {
338                        event.preventDefault();
339                        event.stopPropagation();
340                        _togglePanel.call(this, event, target.hasClass(this.settings.openClass));
341                    }
342                }
343            }
344            // Without panel on enter event.
345            else if (topli.length === 1 && panel.length === 0 && event.type == "keydown") {
346                window.location.href = target.attr("href");
347            }
348        };
349
350        /**
351         * @name jQuery.fn.accessibleMegaMenu~_touchmoveHandler
352         * @desc Handle touch move event on menu
353         * @memberof jQuery.fn.accessibleMegaMenu
354         * @inner
355         * @private
356         */
357        _touchmoveHandler = function () {
358            this.justMoved = true;
359        };
360
361        /**
362         * @name jQuery.fn.accessibleMegaMenu~_clickOutsideHandler
363         * @desc Handle click event outside of a the megamenu
364         * @param {event} Event object
365         * @memberof jQuery.fn.accessibleMegaMenu
366         * @inner
367         * @private
368         */
369        _clickOutsideHandler = function (event) {
370            if ($(event.target).closest(this.menu).length === 0) {
371                event.preventDefault();
372                event.stopPropagation();
373                _togglePanel.call(this, event, true);
374            }
375        };
376
377        /**
378         * @name jQuery.fn.accessibleMegaMenu~_DOMAttrModifiedHandler
379         * @desc Handle DOMAttrModified event on panel to respond to Windows 8 Narrator ExpandCollapse pattern
380         * @param {event} Event object
381         * @memberof jQuery.fn.accessibleMegaMenu
382         * @inner
383         * @private
384         */
385        _DOMAttrModifiedHandler = function (event) {
386            if (event.originalEvent.attrName === 'aria-expanded'
387                    && event.originalEvent.newValue === 'false'
388                    && $(event.target).hasClass(this.settings.openClass)) {
389                event.preventDefault();
390                event.stopPropagation();
391                _togglePanel.call(this, event, true);
392            }
393        };
394
395        /**
396         * @name jQuery.fn.accessibleMegaMenu~_focusInHandler
397         * @desc Handle focusin event on mega menu item.
398         * @param {event} Event object
399         * @memberof jQuery.fn.accessibleMegaMenu
400         * @inner
401         * @private
402         */
403        _focusInHandler = function (event) {
404            clearTimeout(this.focusTimeoutID);
405            var target = $(event.target),
406                panel = target.closest('.' + this.settings.panelClass);
407            target
408                .addClass(this.settings.focusClass);
409            this.justFocused = !this.mouseFocused || (!this.settings.openOnMouseover && this.mouseFocused);
410            this.mouseFocused = false;
411            if (this.justFocused && this.panels.not(panel).filter('.' + this.settings.openClass).length) {
412                _togglePanel.call(this, event);
413            }
414        };
415
416        /**
417         * @name jQuery.fn.accessibleMegaMenu~_focusOutHandler
418         * @desc Handle focusout event on mega menu item.
419         * @param {event} Event object
420         * @memberof jQuery.fn.accessibleMegaMenu
421         * @inner
422         * @private
423         */
424        _focusOutHandler = function (event) {
425            this.justFocused = false;
426            var that = this,
427                target = $(event.target),
428                topli = target.closest('.' + this.settings.topNavItemClass);
429            target
430                .removeClass(this.settings.focusClass);
431
432            if (typeof window.cvox === 'object' && typeof window.cvox.Api !== 'object') {
433                // If ChromeVox is running...
434                that.focusTimeoutID = setTimeout(function () {
435                    window.cvox.Api.getCurrentNode(function (node) {
436                        if (topli.has(node).length) {
437                            // and the current node being voiced is in
438                            // the mega menu, clearTimeout,
439                            // so the panel stays open.
440                            clearTimeout(that.focusTimeoutID);
441                        } else {
442                            that.focusTimeoutID = setTimeout(function (scope, event, hide) {
443                                _togglePanel.call(scope, event, hide);
444                            }, 275, that, event, true);
445                        }
446                    });
447                }, 25);
448            } else {
449                that.focusTimeoutID = setTimeout(function () {
450                    if (that.mouseFocused && event.relatedTarget === null) {
451                        return;
452                    }
453                    _togglePanel.call(that, event, true);
454                }, 300);
455            }
456        };
457
458        /**
459         * @name jQuery.fn.accessibleMegaMenu~_keyDownHandler
460         * @desc Handle keydown event on mega menu.
461         * @param {event} Event object
462         * @memberof jQuery.fn.accessibleMegaMenu
463         * @inner
464         * @private
465         */
466        _keyDownHandler = function (event) {
467            var that = (this.constructor === AccessibleMegaMenu) ? this : _getPlugin(this), // determine the AccessibleMegaMenu plugin instance
468                settings = that.settings,
469                target = $($(this).is('.' + settings.hoverClass + ':tabbable') ? this : event.target), // if the element is hovered the target is this, otherwise, its the focused element
470                menu = that.menu,
471                topnavitems = that.topnavitems,
472                topli = target.closest('.' + settings.topNavItemClass),
473                tabbables = menu.find(':tabbable'),
474                panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass),
475                panelGroups = panel.find('.' + settings.panelGroupClass),
476                currentPanelGroup = target.closest('.' + settings.panelGroupClass),
477                next,
478                keycode = event.keyCode || event.which,
479                start,
480                i,
481                o,
482                label,
483                found = false,
484                newString = Keyboard.keyMap[event.keyCode] || '',
485                regex,
486                isTopNavItem = (topli.length === 1 && panel.length === 0);
487
488            if (target.is("input:focus, select:focus, textarea:focus, button:focus")) {
489                // if the event target is a form element we should handle keydown normally
490                return;
491            }
492
493            if (target.is('.' + settings.hoverClass + ':tabbable')) {
494                $('html').off('keydown.accessible-megamenu');
495            }
496
497            switch (keycode) {
498            case Keyboard.ESCAPE:
499                this.mouseFocused = false;
500                _togglePanel.call(that, event, true);
501                break;
502            case Keyboard.DOWN:
503                event.preventDefault();
504                this.mouseFocused = false;
505                if (isTopNavItem) {
506                    _togglePanel.call(that, event);
507                    found = (topli.find('.' + settings.panelClass + ' :tabbable:first').focus().length === 1);
508                } else {
509                    found = (tabbables.filter(':gt(' + tabbables.index(target) + '):first').focus().length === 1);
510                }
511
512                if (!found && isOpera && (event.ctrlKey || event.metaKey)) {
513                    tabbables = $(':tabbable');
514                    i = tabbables.index(target);
515                    found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1);
516                }
517                break;
518            case Keyboard.UP:
519                event.preventDefault();
520                this.mouseFocused = false;
521                if (isTopNavItem && target.hasClass(settings.openClass)) {
522                    _togglePanel.call(that, event, true);
523                    next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last');
524                    if (next.children('.' + settings.panelClass).length) {
525                        found = (next.find('[aria-expanded]')
526                            .attr('aria-expanded', 'true')
527                            .addClass(settings.openClass)
528                            .filter('.' + settings.panelClass)
529                            .attr('aria-hidden', 'false')
530                            .find(':tabbable:last')
531                            .focus() === 1);
532                    }
533                } else if (!isTopNavItem) {
534                    found = (tabbables.filter(':lt(' + tabbables.index(target) + '):last').focus().length === 1);
535                }
536
537                if (!found && isOpera && (event.ctrlKey || event.metaKey)) {
538                    tabbables = $(':tabbable');
539                    i = tabbables.index(target);
540                    found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):first').focus().length === 1);
541                }
542                break;
543            case Keyboard.RIGHT:
544                event.preventDefault();
545                this.mouseFocused = false;
546                if (isTopNavItem) {
547                    found = (topnavitems.filter(':gt(' + topnavitems.index(topli) + '):first').find(':tabbable:first').focus().length === 1);
548                } else {
549                    if (panelGroups.length && currentPanelGroup.length) {
550                        // if the current panel contains panel groups, and we are able to focus the first tabbable element of the next panel group
551                        found = (panelGroups.filter(':gt(' + panelGroups.index(currentPanelGroup) + '):first').find(':tabbable:first').focus().length === 1);
552                    }
553
554                    if (!found) {
555                        found = (topli.find(':tabbable:first').focus().length === 1);
556                    }
557                }
558                break;
559            case Keyboard.LEFT:
560                event.preventDefault();
561                this.mouseFocused = false;
562                if (isTopNavItem) {
563                    found = (topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last').find(':tabbable:first').focus().length === 1);
564                } else {
565                    if (panelGroups.length && currentPanelGroup.length) {
566                        // if the current panel contains panel groups, and we are able to focus the first tabbable element of the previous panel group
567                        found = (panelGroups.filter(':lt(' + panelGroups.index(currentPanelGroup) + '):last').find(':tabbable:first').focus().length === 1);
568                    }
569
570                    if (!found) {
571                        found = (topli.find(':tabbable:first').focus().length === 1);
572                    }
573                }
574                break;
575            case Keyboard.TAB:
576                this.mouseFocused = false;
577                i = tabbables.index(target);
578                if (event.shiftKey && isTopNavItem && target.hasClass(settings.openClass)) {
579                    _togglePanel.call(that, event, true);
580                    next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last');
581                    if (next.children('.' + settings.panelClass).length) {
582                        found = next.children()
583                            .attr('aria-expanded', 'true')
584                            .addClass(settings.openClass)
585                            .filter('.' + settings.panelClass)
586                            .attr('aria-hidden', 'false')
587                            .find(':tabbable:last')
588                            .focus();
589                    }
590                } else if (event.shiftKey && i > 0) {
591                    found = (tabbables.filter(':lt(' + i + '):last').focus().length === 1);
592                } else if (!event.shiftKey && i < tabbables.length - 1) {
593                    found = (tabbables.filter(':gt(' + i + '):first').focus().length === 1);
594                } else if (isOpera) {
595                    tabbables = $(':tabbable');
596                    i = tabbables.index(target);
597                    if (event.shiftKey) {
598                        found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):last').focus().length === 1);
599                    } else {
600                        found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1);
601                    }
602                }
603
604                if (found) {
605                    event.preventDefault();
606                }
607                break;
608            case Keyboard.SPACE:
609            case Keyboard.ENTER:
610                // Top level links.
611                if (isTopNavItem) {
612                    event.preventDefault();
613                    // Handle enter event on open top level link as escape event.
614                    if (target.hasClass("open")) {
615                        this.mouseFocused = false;
616                        _togglePanel.call(that, event, true);
617                    }
618                    // Handle enter event on top level link as a click.
619                    else {
620                        _clickHandler.call(that, event);
621                    }
622                }
623                // Sub level links.
624                else {
625                  return true;
626                }
627                break;
628            default:
629                // alphanumeric filter
630                clearTimeout(this.keydownTimeoutID);
631
632                keydownSearchString += newString !== keydownSearchString ? newString : '';
633
634                if (keydownSearchString.length === 0) {
635                    return;
636                }
637
638                this.keydownTimeoutID = setTimeout(function () {
639                    keydownSearchString = '';
640                }, keydownTimeoutDuration);
641
642                if (isTopNavItem && !target.hasClass(settings.openClass)) {
643                    tabbables = tabbables.filter(':not(.' + settings.panelClass + ' :tabbable)');
644                } else {
645                    tabbables = topli.find(':tabbable');
646                }
647
648                if (event.shiftKey) {
649                    tabbables = $(tabbables.get()
650                        .reverse());
651                }
652
653                for (i = 0; i < tabbables.length; i++) {
654                    o = tabbables.eq(i);
655                    if (o.is(target)) {
656                        start = (keydownSearchString.length === 1) ? i + 1 : i;
657                        break;
658                    }
659                }
660
661                regex = new RegExp('^' + keydownSearchString.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'), 'i');
662
663                for (i = start; i < tabbables.length; i++) {
664                    o = tabbables.eq(i);
665                    label = $.trim(o.text());
666                    if (regex.test(label)) {
667                        found = true;
668                        o.focus();
669                        break;
670                    }
671                }
672                if (!found) {
673                    for (i = 0; i < start; i++) {
674                        o = tabbables.eq(i);
675                        label = $.trim(o.text());
676                        if (regex.test(label)) {
677                            o.focus();
678                            break;
679                        }
680                    }
681                }
682                break;
683            }
684            that.justFocused = false;
685        };
686
687        /**
688         * @name jQuery.fn.accessibleMegaMenu~_mouseDownHandler
689         * @desc Handle mousedown event on mega menu.
690         * @param {event} Event object
691         * @memberof accessibleMegaMenu
692         * @inner
693         * @private
694         */
695        _mouseDownHandler = function (event) {
696            if ($(event.target).closest(this.settings.panelClass) || $(event.target).closest(":focusable").length) {
697                this.mouseFocused = true;
698                if ($(event.target).closest(this.settings.menuClass)) {
699                    $('html').on('keydown.accessible-megamenu', $.proxy(_keyDownHandler, event.target));
700                }
701            }
702            clearTimeout(this.mouseTimeoutID);
703            this.mouseTimeoutID = setTimeout(function () {
704                clearTimeout(this.focusTimeoutID);
705            }, 1);
706        };
707
708        /**
709         * @name jQuery.fn.accessibleMegaMenu~_mouseOverHandler
710         * @desc Handle mouseover event on mega menu.
711         * @param {event} Event object
712         * @memberof jQuery.fn.accessibleMegaMenu
713         * @inner
714         * @private
715         */
716        _mouseOverHandler = function (event) {
717            clearTimeout(this.mouseTimeoutID);
718            var that = this;
719
720            if (!that.settings.openOnMouseover) {
721                return;
722            }
723
724            this.mouseTimeoutID = setTimeout(function () {
725                $(event.target).addClass(that.settings.hoverClass);
726                _togglePanel.call(that, event);
727                if ($(event.target).closest(that.settings.menuClass)) {
728                    $('html').on('keydown.accessible-megamenu', $.proxy(_keyDownHandler, event.target));
729                }
730            }, this.settings.openDelay);
731        };
732
733        /**
734         * @name jQuery.fn.accessibleMegaMenu~_mouseOutHandler
735         * @desc Handle mouseout event on mega menu.
736         * @param {event} Event object
737         * @memberof jQuery.fn.accessibleMegaMenu
738         * @inner
739         * @private
740         */
741        _mouseOutHandler = function (event) {
742            clearTimeout(this.mouseTimeoutID);
743            var that = this;
744
745            if (!that.settings.openOnMouseover) {
746                return;
747            }
748
749            $(event.target)
750                .removeClass(that.settings.hoverClass);
751
752            that.mouseTimeoutID = setTimeout(function () {
753                _togglePanel.call(that, event, true);
754            }, this.settings.closeDelay);
755            if ($(event.target).is(':tabbable')) {
756                $('html').off('keydown.accessible-megamenu');
757            }
758        };
759
760        /**
761         * @name jQuery.fn.accessibleMegaMenu~_clickToggleHandler
762         * @desc Handle click event on menu toggle button.
763         * @memberof jQuery.fn.accessibleMegaMenu
764         * @inner
765         * @private
766         */
767        _clickToggleHandler = function () {
768            var isExpanded = this.toggleButton.attr('aria-expanded') === 'true';
769            this.toggleButton.attr({'aria-expanded': !isExpanded});
770        };
771
772        _toggleExpandedEventHandlers = function (hide) {
773            var menu = this.menu;
774            if (hide) {
775                $('html').off('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu,  pointerup.outside-accessible-megamenu');
776
777                menu.find('[aria-expanded].' + this.settings.panelClass).off('DOMAttrModified.accessible-megamenu');
778            } else {
779                $('html').on('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu,  pointerup.outside-accessible-megamenu', $.proxy(_clickOutsideHandler, this));
780
781                /* Narrator in Windows 8 automatically toggles the aria-expanded property on double tap or click.
782                   To respond to the change to collapse the panel, we must add a listener for a DOMAttrModified event. */
783                menu.find('[aria-expanded=true].' + this.settings.panelClass).on('DOMAttrModified.accessible-megamenu', $.proxy(_DOMAttrModifiedHandler, this));
784            }
785        };
786
787        _addEventHandlers = function() {
788            var menu = this.menu,
789                toggleButton = this.toggleButton;
790            menu.on("focusin.accessible-megamenu", ":focusable, ." + this.settings.panelClass, $.proxy(_focusInHandler, this))
791                .on("focusout.accessible-megamenu", ":focusable, ." + this.settings.panelClass, $.proxy(_focusOutHandler, this))
792                .on("keydown.accessible-megamenu", $.proxy(_keyDownHandler, this))
793                .on("mouseover.accessible-megamenu", $.proxy(_mouseOverHandler, this))
794                .on("mouseout.accessible-megamenu", $.proxy(_mouseOutHandler, this))
795                .on("mousedown.accessible-megamenu", $.proxy(_mouseDownHandler, this))
796                .on("click.accessible-megamenu", $.proxy(_clickHandler, this));
797
798            toggleButton.on('click.accessible-megamenu', $.proxy(_clickToggleHandler, this));
799
800            if (isTouch) {
801                menu.on("touchmove.accessible-megamenu", $.proxy(_touchmoveHandler, this));
802            }
803
804            if ($(document.activeElement).closest(menu).length) {
805              $(document.activeElement).trigger("focusin.accessible-megamenu");
806            }
807        };
808
809        _removeEventHandlers = function () {
810            var menu = this.menu,
811                toggleButton = this.toggleButton;
812            menu.off('.accessible-megamenu');
813            if (menu.find('[aria-expanded=true].' + this.settings.panelClass).length) {
814                _toggleExpandedEventHandlers.call(this, true);
815            }
816
817            toggleButton.off('.accessible-megamenu');
818        };
819
820        /* public attributes and methods ------------------------- */
821        return {
822            constructor: AccessibleMegaMenu,
823
824            /**
825             * @lends jQuery.fn.accessibleMegaMenu
826             * @desc Initializes an instance of the accessibleMegaMenu plugins
827             * @memberof jQuery.fn.accessibleMegaMenu
828             * @instance
829             */
830            init: function () {
831                var settings = this.settings,
832                    nav = $(this.element),
833                    menu = nav.children('ol,ul').first(),
834                    topnavitems = menu.children(),
835                    toggleButton = nav.children('button').first();
836                this.start(settings, nav, menu, topnavitems, toggleButton);
837            },
838
839            start: function(settings, nav, menu, topnavitems, toggleButton) {
840                var that = this;
841                this.settings = settings;
842                this.menu = menu;
843                this.topnavitems = topnavitems;
844                this.toggleButton = toggleButton;
845
846                nav.attr("role", "navigation");
847                _addUniqueId.call(that, menu);
848                menu.addClass(settings.menuClass);
849                menu.addClass(['js', settings.menuClass].join('-'));
850                topnavitems.each(function (i, topnavitem) {
851                    var topnavitemlink, topnavitempanel;
852                    topnavitem = $(topnavitem);
853                    topnavitem.addClass(settings.topNavItemClass);
854                    topnavitemlink = topnavitem.find("a").first();
855                    topnavitempanel = topnavitem.find('.' + settings.panelClass);
856                    _addUniqueId.call(that, topnavitemlink);
857                    // When sub nav exists.
858                    if (topnavitempanel.length) {
859                        _addUniqueId.call(that, topnavitempanel);
860                        // Add attributes to top level link.
861                        topnavitemlink.attr({
862                            "role": "button",
863                            "aria-controls": topnavitempanel.attr("id"),
864                            "aria-expanded": false,
865                            "tabindex": 0
866                        });
867                        // Add attributes to sub nav.
868                        topnavitempanel.attr({
869                            "role": "region",
870                            "aria-hidden": true
871                        })
872                        .addClass(settings.panelClass)
873                        .not("[aria-labelledby]")
874                        .attr("aria-labelledby", topnavitemlink.attr("id"));
875                    }
876                });
877
878                this.panels = menu.find("." + settings.panelClass);
879
880                menu.find("hr").attr("role", "separator");
881
882                toggleButton.addClass(settings.toggleButtonClass);
883                toggleButton.attr({'aria-expanded': false, 'aria-controls': menu.attr('id')});
884
885                _addEventHandlers.call(this);
886            },
887
888            /**
889             * @desc Removes maga menu javascript behavior
890             * @example $(selector).accessibleMegaMenu("destroy");
891             * @return {object}
892             * @memberof jQuery.fn.accessibleMegaMenu
893             * @instance
894             */
895            destroy: function () {
896                this.menu.removeClass(['js', this.settings.menuClass].join('-'));
897                _removeEventHandlers.call(this, true);
898            },
899
900            /**
901             * @desc Get default values
902             * @example $(selector).accessibleMegaMenu("getDefaults");
903             * @return {object}
904             * @memberof jQuery.fn.accessibleMegaMenu
905             * @instance
906             */
907            getDefaults: function () {
908                return this._defaults;
909            },
910
911            /**
912             * @desc Get any option set to plugin using its name (as string)
913             * @example $(selector).accessibleMegaMenu("getOption", some_option);
914             * @param {string} opt
915             * @return {string}
916             * @memberof jQuery.fn.accessibleMegaMenu
917             * @instance
918             */
919            getOption: function (opt) {
920                return this.settings[opt];
921            },
922
923            /**
924             * @desc Get all options
925             * @example $(selector).accessibleMegaMenu("getAllOptions");
926             * @return {object}
927             * @memberof jQuery.fn.accessibleMegaMenu
928             * @instance
929             */
930            getAllOptions: function () {
931                return this.settings;
932            },
933
934            /**
935             * @desc Set option
936             * @example $(selector).accessibleMegaMenu("setOption", "option_name",  "option_value",  reinitialize);
937             * @param {string} opt - Option name
938             * @param {string} val - Option value
939             * @param {boolean} [reinitialize] - boolean to re-initialize the menu.
940             * @memberof jQuery.fn.accessibleMegaMenu
941             * @instance
942             */
943            setOption: function (opt, value, reinitialize) {
944                this.settings[opt] = value;
945                if (reinitialize) {
946                    this.init();
947                }
948            }
949        };
950    }());
951
952    /* lightweight plugin wrapper around the constructor,
953       to prevent against multiple instantiations */
954
955    /**
956     * @class accessibleMegaMenu
957     * @memberOf jQuery.fn
958     * @classdesc Implements an accessible mega menu as a jQuery plugin.
959     * <p>The mega-menu It is modeled after the mega menu on {@link http://adobe.com|adobe.com} but has been simplified for use by others. A brief description of the interaction design choices can be found in a blog post at {@link http://blogs.adobe.com/accessibility/2013/05/adobe-com.html|Mega menu accessibility on adobe.com}.</p>
960     * <h3>Keyboard Accessibility</h3>
961     * <p>The accessible mega menu supports keyboard interaction modeled after the behavior described in the {@link http://www.w3.org/TR/wai-aria-practices/#menu|WAI-ARIA Menu or Menu bar (widget) design pattern}, however we also try to respect users' general expectations for the behavior of links in a global navigation. To this end, the accessible mega menu implementation permits tab focus on each of the six top-level menu items. When one of the menu items has focus, pressing the Enter key, Spacebar or Down arrow will open the submenu panel, and pressing the Left or Right arrow key will shift focus to the adjacent menu item. Links within the submenu panels are included in the tab order when the panel is open. They can also be navigated with the arrow keys or by typing the first character in the link name, which speeds up keyboard navigation considerably. Pressing the Escape key closes the submenu and restores focus to the parent menu item.</p>
962     * <h3>Screen Reader Accessibility</h3>
963     * <p>The accessible mega menu models its use of WAI-ARIA Roles, States, and Properties after those described in the {@link http://www.w3.org/TR/wai-aria-practices/#menu|WAI-ARIA Menu or Menu bar (widget) design pattern} with some notable exceptions, so that it behaves better with screen reader user expectations for global navigation. We don't use <code class="prettyprint prettyprinted" style=""><span class="pln">role</span><span class="pun">=</span><span class="str">"menu"</span></code> for the menu container and <code class="prettyprint prettyprinted" style=""><span class="pln">role</span><span class="pun">=</span><span class="str">"menuitem"</span></code> for each of the links therein, because if we do, assistive technology will no longer interpret the links as links, but instead, as menu items, and the links in our global navigation will no longer show up when a screen reader user executes a shortcut command to bring up a list of links in the page.</p>
964     * @example <h4>HTML</h4><hr/>
965&lt;nav&gt;
966    &lt;ul class=&quot;nav-menu&quot;&gt;
967        &lt;li class=&quot;nav-item&quot;&gt;
968            &lt;a href=&quot;?movie&quot;&gt;Movies&lt;/a&gt;
969            &lt;div class=&quot;sub-nav&quot;&gt;
970                &lt;ul class=&quot;sub-nav-group&quot;&gt;
971                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=0&quot;&gt;Action &amp;amp; Adventure&lt;/a&gt;&lt;/li&gt;
972                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=2&quot;&gt;Children &amp;amp; Family&lt;/a&gt;&lt;/li&gt;
973                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
974                &lt;/ul&gt;
975                &lt;ul class=&quot;sub-nav-group&quot;&gt;
976                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=7&quot;&gt;Dramas&lt;/a&gt;&lt;/li&gt;
977                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=9&quot;&gt;Foreign&lt;/a&gt;&lt;/li&gt;
978                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
979                &lt;/ul&gt;
980                &lt;ul class=&quot;sub-nav-group&quot;&gt;
981                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=14&quot;&gt;Musicals&lt;/a&gt;&lt;/li&gt;
982                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=15&quot;&gt;Romance&lt;/a&gt;&lt;/li&gt;
983                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
984                &lt;/ul&gt;
985            &lt;/div&gt;
986        &lt;/li&gt;
987        &lt;li class=&quot;nav-item&quot;&gt;
988            &lt;a href=&quot;?tv&quot;&gt;TV Shows&lt;/a&gt;
989            &lt;div class=&quot;sub-nav&quot;&gt;
990                &lt;ul class=&quot;sub-nav-group&quot;&gt;
991                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=20&quot;&gt;Classic TV&lt;/a&gt;&lt;/li&gt;
992                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=21&quot;&gt;Crime TV&lt;/a&gt;&lt;/li&gt;
993                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
994                &lt;/ul&gt;
995                &lt;ul class=&quot;sub-nav-group&quot;&gt;
996                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=27&quot;&gt;Reality TV&lt;/a&gt;&lt;/li&gt;
997                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=30&quot;&gt;TV Action&lt;/a&gt;&lt;/li&gt;
998                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
999                &lt;/ul&gt;
1000                &lt;ul class=&quot;sub-nav-group&quot;&gt;
1001                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=33&quot;&gt;TV Dramas&lt;/a&gt;&lt;/li&gt;
1002                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=34&quot;&gt;TV Horror&lt;/a&gt;&lt;/li&gt;
1003                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
1004                &lt;/ul&gt;
1005            &lt;/div&gt;
1006        &lt;/li&gt;
1007    &lt;/ul&gt;
1008&lt;/nav&gt;
1009     * @example <h4>CSS</h4><hr/>
1010&#47;* Rudimentary mega menu CSS for demonstration *&#47;
1011
1012&#47;* mega menu list *&#47;
1013.nav-menu {
1014    display: block;
1015    position: relative;
1016    list-style: none;
1017    margin: 0;
1018    padding: 0;
1019    z-index: 15;
1020}
1021
1022&#47;* a top level navigation item in the mega menu *&#47;
1023.nav-item {
1024    list-style: none;
1025    display: inline-block;
1026    padding: 0;
1027    margin: 0;
1028}
1029
1030&#47;* first descendant link within a top level navigation item *&#47;
1031.nav-item &gt; a {
1032    position: relative;
1033    display: inline-block;
1034    padding: 0.5em 1em;
1035    margin: 0 0 -1px 0;
1036    border: 1px solid transparent;
1037}
1038
1039&#47;* focus/open states of first descendant link within a top level
1040   navigation item *&#47;
1041.nav-item &gt; a:focus,
1042.nav-item &gt; a.open {
1043    border: 1px solid #dedede;
1044}
1045
1046&#47;* open state of first descendant link within a top level
1047   navigation item *&#47;
1048.nav-item &gt; a.open {
1049    background-color: #fff;
1050    border-bottom: none;
1051    z-index: 1;
1052}
1053
1054&#47;* sub-navigation panel *&#47;
1055.sub-nav {
1056    position: absolute;
1057    display: none;
1058    top: 2.2em;
1059    margin-top: -1px;
1060    padding: 0.5em 1em;
1061    border: 1px solid #dedede;
1062    background-color: #fff;
1063}
1064
1065&#47;* sub-navigation panel open state *&#47;
1066.sub-nav.open {
1067    display: block;
1068}
1069
1070&#47;* list of items within sub-navigation panel *&#47;
1071.sub-nav ul {
1072    display: inline-block;
1073    vertical-align: top;
1074    margin: 0 1em 0 0;
1075    padding: 0;
1076}
1077
1078&#47;* list item within sub-navigation panel *&#47;
1079.sub-nav li {
1080    display: block;
1081    list-style-type: none;
1082    margin: 0;
1083    padding: 0;
1084}
1085     * @example <h4>JavaScript</h4><hr/>
1086&lt;!-- include jquery --&gt;
1087&lt;script src=&quot;http://code.jquery.com/jquery-1.10.1.min.js&quot;&gt;&lt;/script&gt;
1088
1089&lt;!-- include the jquery-accessibleMegaMenu plugin script --&gt;
1090&lt;script src=&quot;js/jquery-accessibleMegaMenu.js&quot;&gt;&lt;/script&gt;
1091
1092&lt;!-- initialize a selector as an accessibleMegaMenu --&gt;
1093&lt;script&gt;
1094    $(&quot;nav:first&quot;).accessibleMegaMenu({
1095        &#47;* prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby *&#47;
1096        uuidPrefix: &quot;accessible-megamenu&quot;,
1097
1098        &#47;* css class used to define the megamenu styling *&#47;
1099        menuClass: &quot;nav-menu&quot;,
1100
1101        &#47;* css class for a top-level navigation item in the megamenu *&#47;
1102        topNavItemClass: &quot;nav-item&quot;,
1103
1104        &#47;* css class for a megamenu panel *&#47;
1105        panelClass: &quot;sub-nav&quot;,
1106
1107        &#47;* css class for a group of items within a megamenu panel *&#47;
1108        panelGroupClass: &quot;sub-nav-group&quot;,
1109
1110        &#47;* css class for the hover state *&#47;
1111        hoverClass: &quot;hover&quot;,
1112
1113        &#47;* css class for the focus state *&#47;
1114        focusClass: &quot;focus&quot;,
1115
1116        &#47;* css class for the open state *&#47;
1117        openClass: &quot;open&quot;
1118    });
1119&lt;/script&gt;
1120     * @param {object} [options] Mega Menu options
1121     * @param {string} [options.uuidPrefix=accessible-megamenu] - Prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby
1122     * @param {string} [options.menuClass=accessible-megamenu] - CSS class used to define the megamenu styling
1123     * @param {string} [options.topNavItemClass=accessible-megamenu-top-nav-item] - CSS class for a top-level navigation item in the megamenu
1124     * @param {string} [options.panelClass=accessible-megamenu-panel] - CSS class for a megamenu panel
1125     * @param {string} [options.panelGroupClass=accessible-megamenu-panel-group] - CSS class for a group of items within a megamenu panel
1126     * @param {string} [options.hoverClass=hover] - CSS class for the hover state
1127     * @param {string} [options.focusClass=focus] - CSS class for the focus state
1128     * @param {string} [options.openClass=open] - CSS class for the open state
1129     * @param {string} [options.openDelay=0] - Open delay when opening menu via mouseover
1130     * @param {string} [options.closeDelay=250] - Open delay when opening menu via mouseover
1131     * @param {boolean} [options.openOnMouseover=false] - Should menu open on mouseover
1132     */
1133    $.fn[pluginName] = function (options) {
1134        return this.each(function () {
1135            var pluginInstance = $.data(this, "plugin_" + pluginName);
1136            if (!pluginInstance) {
1137                $.data(this, "plugin_" + pluginName, new $.fn[pluginName].Acce
1137ssibleMegaMenu(this, options));
1138            } else if (typeof pluginInstance[options] === 'function') {
1139                pluginInstance[options].apply(pluginInstance, Array.prototype.slice.call(arguments, 1));
1140            }
1141        });
1142    };
1143
1144    $.fn[pluginName].AccessibleMegaMenu = AccessibleMegaMenu;
1145
1146    /* :focusable and :tabbable selectors from
1147       https://raw.github.com/jquery/jquery-ui/master/ui/jquery.ui.core.js */
1148
1149    /**
1150     * @private
1151     */
1152    function visible(element) {
1153        return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function () {
1154            return $.css(this, "visibility") === "hidden";
1155        }).length;
1156    }
1157
1158    /**
1159     * @private
1160     */
1161    function focusable(element, isTabIndexNotNaN) {
1162        var map, mapName, img,
1163            nodeName = element.nodeName.toLowerCase();
1164        if ("area" === nodeName) {
1165            map = element.parentNode;
1166            mapName = map.name;
1167            if (!element.href || !mapName || map.nodeName.toLowerCase() !== "map") {
1168                return false;
1169            }
1170            img = $("img[usemap=#" + mapName + "]")[0];
1171            return !!img && visible(img);
1172        }
1173        return (/input|select|textarea|button|object/.test(nodeName) ? !element.disabled :
1174                "a" === nodeName ?
1175                        element.href || isTabIndexNotNaN :
1176                        isTabIndexNotNaN) &&
1177                            // the element and all of its ancestors must be visible
1178                            visible(element);
1179    }
1180
1181    $.extend($.expr[":"], {
1182        data: $.expr.createPseudo ? $.expr.createPseudo(function (dataName) {
1183            return function (elem) {
1184                return !!$.data(elem, dataName);
1185            };
1186        }) : // support: jQuery <1.8
1187                function (elem, i, match) {
1188                    return !!$.data(elem, match[3]);
1189                },
1190
1191        focusable: function (element) {
1192            return focusable(element, !isNaN($.attr(element, "tabindex")));
1193        },
1194
1195        tabbable: function (element) {
1196            var tabIndex = $.attr(element, "tabindex"),
1197                isTabIndexNaN = isNaN(tabIndex);
1198            return (isTabIndexNaN || tabIndex >= 0) && focusable(element, !isTabIndexNaN);
1199        }
1200    });
1201}(jQuery, window, document));

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.