PageSourceSearch

https://www.whittiercity.net/apps/js/common/jquery-accessibleMegaMenu.js

js whittiercity.net collected 2026-09-28 10:30:51 UTC 46,799 bytes, 1,069 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 */
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            openDelay: 0 // default open delay when opening menu via mouseover
60        },
61        Keyboard = {
62            BACKSPACE: 8,
63            COMMA: 188,
64            DELETE: 46,
65            DOWN: 40,
66            END: 35,
67            ENTER: 13,
68            ESCAPE: 27,
69            HOME: 36,
70            LEFT: 37,
71            PAGE_DOWN: 34,
72            PAGE_UP: 33,
73            PERIOD: 190,
74            RIGHT: 39,
75            SPACE: 32,
76            TAB: 9,
77            UP: 38,
78            keyMap: {
79                48: "0",
80                49: "1",
81                50: "2",
82                51: "3",
83                52: "4",
84                53: "5",
85                54: "6",
86                55: "7",
87                56: "8",
88                57: "9",
89                59: ";",
90                65: "a",
91                66: "b",
92                67: "c",
93                68: "d",
94                69: "e",
95                70: "f",
96                71: "g",
97                72: "h",
98                73: "i",
99                74: "j",
100                75: "k",
101                76: "l",
102                77: "m",
103                78: "n",
104                79: "o",
105                80: "p",
106                81: "q",
107                82: "r",
108                83: "s",
109                84: "t",
110                85: "u",
111                86: "v",
112                87: "w",
113                88: "x",
114                89: "y",
115                90: "z",
116                96: "0",
117                97: "1",
118                98: "2",
119                99: "3",
120                100: "4",
121                101: "5",
122                102: "6",
123                103: "7",
124                104: "8",
125                105: "9",
126                190: "."
127            }
128        };
129    /**
130     * @desc Creates a new accessible mega menu instance.
131     * @param {jquery} element
132     * @param {object} [options] Mega Menu options
133     * @param {string} [options.uuidPrefix=accessible-megamenu] - Prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby
134     * @param {string} [options.menuClass=accessible-megamenu] - CSS class used to define the megamenu styling
135     * @param {string} [options.topNavItemClass=accessible-megamenu-top-nav-item] - CSS class for a top-level navigation item in the megamenu
136     * @param {string} [options.panelClass=accessible-megamenu-panel] - CSS class for a megamenu panel
137     * @param {string} [options.panelGroupClass=accessible-megamenu-panel-group] - CSS class for a group of items within a megamenu panel
138     * @param {string} [options.hoverClass=hover] - CSS class for the hover state
139     * @param {string} [options.focusClass=focus] - CSS class for the focus state
140     * @param {string} [options.openClass=open] - CSS class for the open state
141     * @constructor
142     */
143    function AccessibleMegaMenu(element, options) {
144        this.element = element;
145
146        // merge optional settings and defaults into settings
147        this.settings = $.extend({}, defaults, options);
148
149        this._defaults = defaults;
150        this._name = pluginName;
151
152        this.mouseTimeoutID = null;
153        this.focusTimeoutID = null;
154        this.mouseFocused = false;
155        this.justFocused = false;
156
157        this.init();
158    }
159
160    AccessibleMegaMenu.prototype = (function () {
161
162        /* private attributes and methods ------------------------ */
163        var uuid = 0,
164            keydownTimeoutDuration = 1000,
165            keydownSearchString = "",
166            isTouch = typeof window.hasOwnProperty === "function" && !!window.hasOwnProperty("ontouchstart"),
167            _getPlugin,
168            _addUniqueId,
169            _togglePanel,
170            _clickHandler,
171            _clickOutsideHandler,
172            _DOMAttrModifiedHandler,
173            _focusInHandler,
174            _focusOutHandler,
175            _keyDownHandler,
176            _mouseDownHandler,
177            _mouseOverHandler,
178            _mouseOutHandler,
179            _toggleExpandedEventHandlers;
180
181        /**
182         * @name jQuery.fn.accessibleMegaMenu~_getPlugin
183         * @desc Returns the parent accessibleMegaMenu instance for a given element
184         * @param {jQuery} element
185         * @memberof jQuery.fn.accessibleMegaMenu
186         * @inner
187         * @private
188         */
189        _getPlugin = function (element) {
190            return $(element).closest(':data(plugin_' + pluginName + ')').data("plugin_" + pluginName);
191        };
192
193        /**
194         * @name jQuery.fn.accessibleMegaMenu~_addUniqueId
195         * @desc Adds a unique id and element.
196         * The id string starts with the
197         * string defined in settings.uuidPrefix.
198         * @param {jQuery} element
199         * @memberof jQuery.fn.accessibleMegaMenu
200         * @inner
201         * @private
202         */
203        _addUniqueId = function (element) {
204            element = $(element);
205            var settings = this.settings;
206            if (!element.attr("id")) {
207                element.attr("id", settings.uuidPrefix + "-" + new Date().getTime() + "-" + (++uuid));
208            }
209        };
210
211        /**
212         * @name jQuery.fn.accessibleMegaMenu~_togglePanel
213         * @desc Toggle the display of mega menu panels in response to an event.
214         * The optional boolean value 'hide' forces all panels to hide.
215         * @param {event} event
216         * @param {Boolean} [hide] Hide all mega menu panels when true
217         * @memberof jQuery.fn.accessibleMegaMenu
218         * @inner
219         * @private
220         */
221        _togglePanel = function (event, hide) {
222            var target = $(event.target),
223                that = this,
224                settings = this.settings,
225                menu = this.menu,
226                topli = target.closest('.' + settings.topNavItemClass),
227                panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass),
228                newfocus;
229
230            _toggleExpandedEventHandlers.call(this, true);
231
232            if (hide) {
233                topli = menu.find('.' + settings.topNavItemClass + ' .' + settings.openClass + ':first').closest('.' + settings.topNavItemClass);
234                if (!(topli.is(event.relatedTarget) || topli.has(event.relatedTarget).length > 0)) {
235                    if ((event.type === 'mouseout' || event.type === 'focusout') && topli.has(document.activeElement).length > 0) {
236                        return;
237                    }
238                    topli.find('[aria-expanded]')
239                        .attr('aria-expanded', 'false')
240                        .removeClass(settings.openClass)
241                        .filter('.' + settings.panelClass)
242                        .attr('aria-hidden', 'true');
243                    if ((event.type === 'keydown' && event.keyCode === Keyboard.ESCAPE) || event.type === 'DOMAttrModified') {
244                        newfocus = topli.find(':tabbable:first');
245                        setTimeout(function () {
246                            menu.find('[aria-expanded].' + that.settings.panelClass).off('DOMAttrModified.accessible-megamenu');
247                            newfocus.focus();
248                            that.justFocused = false;
249                        }, 99);
250                    }
251                } else if (topli.length === 0) {
252                    menu.find('[aria-expanded=true]')
253                        .attr('aria-expanded', 'false')
254                        .removeClass(settings.openClass)
255                        .filter('.' + settings.panelClass)
256                        .attr('aria-hidden', 'true');
257                }
258            } else {
259                clearTimeout(that.focusTimeoutID);
260                topli.siblings()
261                    .find('[aria-expanded]')
262                    .attr('aria-expanded', 'false')
263                    .removeClass(settings.openClass)
264                    .filter('.' + settings.panelClass)
265                    .attr('aria-hidden', 'true');
266                topli.find('[aria-expanded]')
267                    .attr('aria-expanded', 'true')
268                    .addClass(settings.openClass)
269                    .filter('.' + settings.panelClass)
270                    .attr('aria-hidden', 'false');
271                if (event.type === 'mouseover' && target.is(':tabbable') && topli.length === 1 && panel.length === 0 && menu.has(document.activeElement).length > 0) {
272                    target.focus();
273                    that.justFocused = false;
274                }
275
276                _toggleExpandedEventHandlers.call(that);
277            }
278        };
279
280        /**
281         * @name jQuery.fn.accessibleMegaMenu~_clickHandler
282         * @desc Handle click event on mega menu item
283         * @param {event} Event object
284         * @memberof jQuery.fn.accessibleMegaMenu
285         * @inner
286         * @private
287         */
288        _clickHandler = function (event) {
289            var target = $(event.target).closest(':tabbable'),
290                topli = target.closest('.' + this.settings.topNavItemClass),
291                panel = target.closest('.' + this.settings.panelClass);
292            if (topli.length === 1
293                    && panel.length === 0
294                    && topli.find('.' + this.settings.panelClass).length === 1) {
295                if (!target.hasClass(this.settings.openClass)) {
296                    event.preventDefault();
297                    event.stopPropagation();
298                    _togglePanel.call(this, event);
299                    this.justFocused = false;
300                } else {
301                    if (this.justFocused) {
302                        event.preventDefault();
303                        event.stopPropagation();
304                        this.justFocused = false;
305                    } else if (isTouch) {
306                        event.preventDefault();
307                        event.stopPropagation();
308                        _togglePanel.call(this, event, target.hasClass(this.settings.openClass));
309                    }
310                }
311            }
312        };
313
314        /**
315         * @name jQuery.fn.accessibleMegaMenu~_clickOutsideHandler
316         * @desc Handle click event outside of a the megamenu
317         * @param {event} Event object
318         * @memberof jQuery.fn.accessibleMegaMenu
319         * @inner
320         * @private
321         */
322        _clickOutsideHandler = function (event) {
323            if ($(event.target).closest(this.menu).length === 0) {
324                event.preventDefault();
325                event.stopPropagation();
326                _togglePanel.call(this, event, true);
327            }
328        };
329
330        /**
331         * @name jQuery.fn.accessibleMegaMenu~_DOMAttrModifiedHandler
332         * @desc Handle DOMAttrModified event on panel to respond to Windows 8 Narrator ExpandCollapse pattern
333         * @param {event} Event object
334         * @memberof jQuery.fn.accessibleMegaMenu
335         * @inner
336         * @private
337         */
338        _DOMAttrModifiedHandler = function (event) {
339            if (event.originalEvent.attrName === 'aria-expanded'
340                    && event.originalEvent.newValue === 'false'
341                    && $(event.target).hasClass(this.settings.openClass)) {
342                event.preventDefault();
343                event.stopPropagation();
344                _togglePanel.call(this, event, true);
345            }
346        };
347
348        /**
349         * @name jQuery.fn.accessibleMegaMenu~_focusInHandler
350         * @desc Handle focusin event on mega menu item.
351         * @param {event} Event object
352         * @memberof jQuery.fn.accessibleMegaMenu
353         * @inner
354         * @private
355         */
356        _focusInHandler = function (event) {
357            clearTimeout(this.focusTimeoutID);
358            var target = $(event.target),
359                panel = target.closest('.' + this.settings.panelClass);
360            target
361                .addClass(this.settings.focusClass)
362                .on('click.accessible-megamenu', $.proxy(_clickHandler, this));
363            this.justFocused = !this.mouseFocused;
364            this.mouseFocused = false;
365            if (this.panels.not(panel).filter('.' + this.settings.openClass).length) {
366                _togglePanel.call(this, event);
367            }
368        };
369
370        /**
371         * @name jQuery.fn.accessibleMegaMenu~_focusOutHandler
372         * @desc Handle focusout event on mega menu item.
373         * @param {event} Event object
374         * @memberof jQuery.fn.accessibleMegaMenu
375         * @inner
376         * @private
377         */
378        _focusOutHandler = function (event) {
379            this.justFocused = false;
380            var that = this,
381                target = $(event.target),
382                topli = target.closest('.' + this.settings.topNavItemClass),
383                keepOpen = false;
384            target
385                .removeClass(this.settings.focusClass)
386                .off('click.accessible-megamenu');
387
388            if (window.cvox) {
389                // If ChromeVox is running...
390                that.focusTimeoutID = setTimeout(function () {
391                    window.cvox.Api.getCurrentNode(function (node) {
392                        if (topli.has(node).length) {
393                            // and the current node being voiced is in
394                            // the mega menu, clearTimeout,
395                            // so the panel stays open.
396                            clearTimeout(that.focusTimeoutID);
397                        } else {
398                            that.focusTimeoutID = setTimeout(function (scope, event, hide) {
399                                _togglePanel.call(scope, event, hide);
400                            }, 275, that, event, true);
401                        }
402                    });
403                }, 25);
404            } else {
405                that.focusTimeoutID = setTimeout(function () {
406                    _togglePanel.call(that, event, true);
407                }, 300);
408            }
409        };
410
411        /**
412         * @name jQuery.fn.accessibleMegaMenu~_keyDownHandler
413         * @desc Handle keydown event on mega menu.
414         * @param {event} Event object
415         * @memberof jQuery.fn.accessibleMegaMenu
416         * @inner
417         * @private
418         */
419        _keyDownHandler = function (event) {
420            var that = (this.constructor === AccessibleMegaMenu) ? this : _getPlugin(this), // determine the AccessibleMegaMenu plugin instance
421                settings = that.settings,
422                target = $($(this).is('.' + settings.hoverClass + ':tabbable') ? this : event.target), // if the element is hovered the target is this, otherwise, its the focused element
423                menu = that.menu,
424                topnavitems = that.topnavitems,
425                topli = target.closest('.' + settings.topNavItemClass),
426                tabbables = menu.find(':tabbable'),
427                panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass),
428                panelGroups = panel.find('.' + settings.panelGroupClass),
429                currentPanelGroup = target.closest('.' + settings.panelGroupClass),
430                next,
431                keycode = event.keyCode || event.which,
432                start,
433                i,
434                o,
435                label,
436                found = false,
437                newString = Keyboard.keyMap[event.keyCode] || '',
438                regex,
439                isTopNavItem = (topli.length === 1 && panel.length === 0);
440
441            if (target.is("input:focus, select:focus, textarea:focus, button:focus")) {
442                // if the event target is a form element we should handle keydown normally
443                return;
444            }
445
446            if (target.is('.' + settings.hoverClass + ':tabbable')) {
447                $('html').off('keydown.accessible-megamenu');
448            }
449
450            switch (keycode) {
451            case Keyboard.ESCAPE:
452                _togglePanel.call(that, event, true);
453                break;
454            case Keyboard.DOWN:
455                event.preventDefault();
456                if (isTopNavItem) {
457                    _togglePanel.call(that, event);
458                    found = (topli.find('.' + settings.panelClass + ' :tabbable:first').focus().length === 1);
459                } else {
460                    found = (tabbables.filter(':gt(' + tabbables.index(target) + '):first').focus().length === 1);
461                }
462
463                if (!found && window.opera && opera.toString() === "[object Opera]" && (event.ctrlKey || event.metaKey)) {
464                    tabbables = $(':tabbable');
465                    i = tabbables.index(target);
466                    found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1);
467                }
468                break;
469            case Keyboard.UP:
470                event.preventDefault();
471                if (isTopNavItem && target.hasClass(settings.openClass)) {
472                    _togglePanel.call(that, event, true);
473                    next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last');
474                    if (next.children('.' + settings.panelClass).length) {
475                        found = (next.children()
476                            .attr('aria-expanded', 'true')
477                            .addClass(settings.openClass)
478                            .filter('.' + settings.panelClass)
479                            .attr('aria-hidden', 'false')
480                            .find(':tabbable:last')
481                            .focus() === 1);
482                    }
483                } else if (!isTopNavItem) {
484                    found = (tabbables.filter(':lt(' + tabbables.index(target) + '):last').focus().length === 1);
485                }
486
487                if (!found && window.opera && opera.toString() === "[object Opera]" && (event.ctrlKey || event.metaKey)) {
488                    tabbables = $(':tabbable');
489                    i = tabbables.index(target);
490                    found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):first').focus().length === 1);
491                }
492                break;
493            case Keyboard.RIGHT:
494                event.preventDefault();
495                if (isTopNavItem) {
496                    found = (topnavitems.filter(':gt(' + topnavitems.index(topli) + '):first').find(':tabbable:first').focus().length === 1);
497                } else {
498                    if (panelGroups.length && currentPanelGroup.length) {
499                        // if the current panel contains panel groups, and we are able to focus the first tabbable element of the next panel group
500                        found = (panelGroups.filter(':gt(' + panelGroups.index(currentPanelGroup) + '):first').find(':tabbable:first').focus().length === 1);
501                    }
502
503                    if (!found) {
504                        found = (topli.find(':tabbable:first').focus().length === 1);
505                    }
506                }
507                break;
508            case Keyboard.LEFT:
509                event.preventDefault();
510                if (isTopNavItem) {
511                    found = (topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last').find(':tabbable:first').focus().length === 1);
512                } else {
513                    if (panelGroups.length && currentPanelGroup.length) {
514                        // if the current panel contains panel groups, and we are able to focus the first tabbable element of the previous panel group
515                        found = (panelGroups.filter(':lt(' + panelGroups.index(currentPanelGroup) + '):last').find(':tabbable:first').focus().length === 1);
516                    }
517
518                    if (!found) {
519                        found = (topli.find(':tabbable:first').focus().length === 1);
520                    }
521                }
522                break;
523            case Keyboard.TAB:
524                i = tabbables.index(target);
525                if (event.shiftKey && isTopNavItem && target.hasClass(settings.openClass)) {
526                    _togglePanel.call(that, event, true);
527                    next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last');
528                    if (next.children('.' + settings.panelClass).length) {
529                        found = next.children()
530                            .attr('aria-expanded', 'true')
531                            .addClass(settings.openClass)
532                            .filter('.' + settings.panelClass)
533                            .attr('aria-hidden', 'false')
534                            .find(':tabbable:last')
535                            .focus();
536                    }
537                } else if (event.shiftKey && i > 0) {
538                    found = (tabbables.filter(':lt(' + i + '):last').focus().length === 1);
539                } else if (!event.shiftKey && i < tabbables.length - 1) {
540                    found = (tabbables.filter(':gt(' + i + '):first').focus().length === 1);
541                } else if (window.opera && opera.toString() === "[object Opera]") {
542                    tabbables = $(':tabbable');
543                    i = tabbables.index(target);
544                    if (event.shiftKey) {
545                        found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):last').focus().length === 1);
546                    } else {
547                        found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1);
548                    }
549                }
550
551                if (found) {
552                    event.preventDefault();
553                }
554                break;
555            case Keyboard.SPACE:
556                if (isTopNavItem) {
557                    event.preventDefault();
558                    _clickHandler.call(that, event);
559                } else {
560                    return true;
561                }
562                break;
563            case Keyboard.ENTER:
564                return true;
565                break;
566            default:
567                // alphanumeric filter
568                clearTimeout(this.keydownTimeoutID);
569
570                keydownSearchString += newString !== keydownSearchString ? newString : '';
571
572                if (keydownSearchString.length === 0) {
573                    return;
574                }
575
576                this.keydownTimeoutID = setTimeout(function () {
577                    keydownSearchString = '';
578                }, keydownTimeoutDuration);
579
580                if (isTopNavItem && !target.hasClass(settings.openClass)) {
581                    tabbables = tabbables.filter(':not(.' + settings.panelClass + ' :tabbable)');
582                } else {
583                    tabbables = topli.find(':tabbable');
584                }
585
586                if (event.shiftKey) {
587                    tabbables = $(tabbables.get()
588                        .reverse());
589                }
590
591                for (i = 0; i < tabbables.length; i++) {
592                    o = tabbables.eq(i);
593                    if (o.is(target)) {
594                        start = (keydownSearchString.length === 1) ? i + 1 : i;
595                        break;
596                    }
597                }
598
599                regex = new RegExp('^' + keydownSearchString.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'), 'i');
600
601                for (i = start; i < tabbables.length; i++) {
602                    o = tabbables.eq(i);
603                    label = $.trim(o.text());
604                    if (regex.test(label)) {
605                        found = true;
606                        o.focus();
607                        break;
608                    }
609                }
610                if (!found) {
611                    for (i = 0; i < start; i++) {
612                        o = tabbables.eq(i);
613                        label = $.trim(o.text());
614                        if (regex.test(label)) {
615                            o.focus();
616                            break;
617                        }
618                    }
619                }
620                break;
621            }
622            that.justFocused = false;
623        };
624
625        /**
626         * @name jQuery.fn.accessibleMegaMenu~_mouseDownHandler
627         * @desc Handle mousedown event on mega menu.
628         * @param {event} Event object
629         * @memberof accessibleMegaMenu
630         * @inner
631         * @private
632         */
633        _mouseDownHandler = function (event) {
634            if ($(event.target).is(this.settings.panelClass) || $(event.target).closest(":focusable").length) {
635                this.mouseFocused = true;
636            }
637            clearTimeout(this.mouseTimeoutID);
638            this.mouseTimeoutID = setTimeout(function () {
639                clearTimeout(this.focusTimeoutID);
640            }, 1);
641        };
642
643        /**
644         * @name jQuery.fn.accessibleMegaMenu~_mouseOverHandler
645         * @desc Handle mouseover event on mega menu.
646         * @param {event} Event object
647         * @memberof jQuery.fn.accessibleMegaMenu
648         * @inner
649         * @private
650         */
651        _mouseOverHandler = function (event) {
652            clearTimeout(this.mouseTimeoutID);
653            var that = this;
654            this.mouseTimeoutID = setTimeout(function () {
655                $(event.target).addClass(that.settings.hoverClass);
656                _togglePanel.call(that, event);
657                if ($(event.target).is(':tabbable')) {
658                    $('html').on('keydown.accessible-megamenu', $.proxy(_keyDownHandler, event.target));
659                }
660            }, this.settings.openDelay);
661        };
662
663        /**
664         * @name jQuery.fn.accessibleMegaMenu~_mouseOutHandler
665         * @desc Handle mouseout event on mega menu.
666         * @param {event} Event object
667         * @memberof jQuery.fn.accessibleMegaMenu
668         * @inner
669         * @private
670         */
671        _mouseOutHandler = function (event) {
672            clearTimeout(this.mouseTimeoutID);
673            var that = this;
674            $(event.target)
675                .removeClass(that.settings.hoverClass);
676
677            that.mouseTimeoutID = setTimeout(function () {
678                _togglePanel.call(that, event, true);
679            }, 250);
680            if ($(event.target).is(':tabbable')) {
681                $('html').off('keydown.accessible-megamenu');
682            }
683        };
684
685        _toggleExpandedEventHandlers = function (hide) {
686            var menu = this.menu;
687            if (hide) {
688                $('html').off('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu,  pointerup.outside-accessible-megamenu');
689
690                menu.find('[aria-expanded].' + this.settings.panelClass).off('DOMAttrModified.accessible-megamenu');
691            } else {
692                $('html').on('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu,  pointerup.outside-accessible-megamenu', $.proxy(_clickOutsideHandler, this));
693
694                /* Narrator in Windows 8 automatically toggles the aria-expanded property on double tap or click.
695                   To respond to the change to collapse the panel, we must add a listener for a DOMAttrModified event. */
696                menu.find('[aria-expanded=true].' + this.settings.panelClass).on('DOMAttrModified.accessible-megamenu', $.proxy(_DOMAttrModifiedHandler, this));
697            }
698        };
699
700        /* public attributes and methods ------------------------- */
701        return {
702            constructor: AccessibleMegaMenu,
703
704            /**
705             * @lends jQuery.fn.accessibleMegaMenu
706             * @desc Initializes an instance of the accessibleMegaMenu plugins
707             * @memberof jQuery.fn.accessibleMegaMenu
708             * @instance
709             */
710            init: function () {
711                var settings = this.settings,
712                    nav = $(this.element),
713                    menu = nav.children().first(),
714                    topnavitems = menu.children();
715                this.start(settings, nav, menu, topnavitems);
716            },
717
718            start: function(settings, nav, menu, topnavitems) {
719                var that = this;
720                this.settings = settings;
721                this.menu = menu;
722                this.topnavitems = topnavitems;
723
724                // Removing the following line, as since we use it exclusively in NAV elements, the aria-role is redundant and unnecessary.
725                // nav.attr("role", "navigation");
726                menu.addClass(settings.menuClass);
727                topnavitems.each(function (i, topnavitem) {
728                    var topnavitemlink, topnavitempanel;
729                    topnavitem = $(topnavitem);
730                    topnavitem.addClass(settings.topNavItemClass);
731                    topnavitemlink = topnavitem.find(":tabbable:first");
732                    topnavitempanel = topnavitem.children(":not(:tabbable):last");
733                    _addUniqueId.call(that, topnavitemlink);
734                    if (topnavitempanel.length) {
735                        _addUniqueId.call(that, topnavitempanel);
736                        topnavitemlink.attr({
737                            // "aria-haspopup": true,
738                            "aria-controls": topnavitempanel.attr("id"),
739                            "aria-expanded": false
740                        });
741
742                        topnavitempanel.attr({
743                            "role": "region",
744                            "aria-expanded": false,
745                            "aria-hidden": true
746                        })
747                            .addClass(settings.panelClass)
748                            .not("[aria-labelledby]")
749                            .attr("aria-labelledby", topnavitemlink.attr("id"));
750                    }
751                });
752
753                this.panels = menu.find("." + settings.panelClass);
754
755                menu.on("focusin.accessible-megamenu", ":focusable, ." + settings.panelClass, $.proxy(_focusInHandler, this))
756                    .on("focusout.accessible-megamenu", ":focusable, ." + settings.panelClass, $.proxy(_focusOutHandler, this))
757                    .on("keydown.accessible-megamenu", $.proxy(_keyDownHandler, this))
758                    .on("mouseover.accessible-megamenu", $.proxy(_mouseOverHandler, this))
759                    .on("mouseout.accessible-megamenu", $.proxy(_mouseOutHandler, this))
760                    .on("mousedown.accessible-megamenu", $.proxy(_mouseDownHandler, this));
761
762                if (isTouch) {
763                    menu.on("touchstart.accessible-megamenu",  $.proxy(_clickHandler, this));
764                }
765
766                menu.find("hr").attr("role", "separator");
767
768                if ($(document.activeElement).closest(menu).length) {
769                  $(document.activeElement).trigger("focusin.accessible-megamenu");
770                }
771            },
772
773            /**
774             * @desc Get default values
775             * @example $(selector).accessibleMegaMenu("getDefaults");
776             * @return {object}
777             * @memberof jQuery.fn.accessibleMegaMenu
778             * @instance
779             */
780            getDefaults: function () {
781                return this._defaults;
782            },
783
784            /**
785             * @desc Get any option set to plugin using its name (as string)
786             * @example $(selector).accessibleMegaMenu("getOption", some_option);
787             * @param {string} opt
788             * @return {string}
789             * @memberof jQuery.fn.accessibleMegaMenu
790             * @instance
791             */
792            getOption: function (opt) {
793                return this.settings[opt];
794            },
795
796            /**
797             * @desc Get all options
798             * @example $(selector).accessibleMegaMenu("getAllOptions");
799             * @return {object}
800             * @memberof jQuery.fn.accessibleMegaMenu
801             * @instance
802             */
803            getAllOptions: function () {
804                return this.settings;
805            },
806
807            /**
808             * @desc Set option
809             * @example $(selector).accessibleMegaMenu("setOption", "option_name",  "option_value",  reinitialize);
810             * @param {string} opt - Option name
811             * @param {string} val - Option value
812             * @param {boolean} [reinitialize] - boolean to re-initialize the menu.
813             * @memberof jQuery.fn.accessibleMegaMenu
814             * @instance
815             */
816            setOption: function (opt, value, reinitialize) {
817                this.settings[opt] = value;
818                if (reinitialize) {
819                    this.init();
820                }
821            }
822        };
823    }());
824
825    /* lightweight plugin wrapper around the constructor,
826       to prevent against multiple instantiations */
827
828    /**
829     * @class accessibleMegaMenu
830     * @memberOf jQuery.fn
831     * @classdesc Implements an accessible mega menu as a jQuery plugin.
832     * <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>
833     * <h3>Keyboard Accessibility</h3>
834     * <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>
835     * <h3>Screen Reader Accessibility</h3>
836     * <p>The accessible mega menu models its use of WAI-ARIA Roles, States, and Properties after those described 
836in 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>
837     * @example <h4>HTML</h4><hr/>
838&lt;nav&gt;
839    &lt;ul class=&quot;nav-menu&quot;&gt;
840        &lt;li class=&quot;nav-item&quot;&gt;
841            &lt;a href=&quot;?movie&quot;&gt;Movies&lt;/a&gt;
842            &lt;div class=&quot;sub-nav&quot;&gt;
843                &lt;ul class=&quot;sub-nav-group&quot;&gt;
844                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=0&quot;&gt;Action &amp;amp; Adventure&lt;/a&gt;&lt;/li&gt;
845                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=2&quot;&gt;Children &amp;amp; Family&lt;/a&gt;&lt;/li&gt;
846                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
847                &lt;/ul&gt;
848                &lt;ul class=&quot;sub-nav-group&quot;&gt;
849                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=7&quot;&gt;Dramas&lt;/a&gt;&lt;/li&gt;
850                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=9&quot;&gt;Foreign&lt;/a&gt;&lt;/li&gt;
851                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
852                &lt;/ul&gt;
853                &lt;ul class=&quot;sub-nav-group&quot;&gt;
854                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=14&quot;&gt;Musicals&lt;/a&gt;&lt;/li&gt;
855                    &lt;li&gt;&lt;a href=&quot;?movie&amp;genre=15&quot;&gt;Romance&lt;/a&gt;&lt;/li&gt;
856                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
857                &lt;/ul&gt;
858            &lt;/div&gt;
859        &lt;/li&gt;
860        &lt;li class=&quot;nav-item&quot;&gt;
861            &lt;a href=&quot;?tv&quot;&gt;TV Shows&lt;/a&gt;
862            &lt;div class=&quot;sub-nav&quot;&gt;
863                &lt;ul class=&quot;sub-nav-group&quot;&gt;
864                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=20&quot;&gt;Classic TV&lt;/a&gt;&lt;/li&gt;
865                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=21&quot;&gt;Crime TV&lt;/a&gt;&lt;/li&gt;
866                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
867                &lt;/ul&gt;
868                &lt;ul class=&quot;sub-nav-group&quot;&gt;
869                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=27&quot;&gt;Reality TV&lt;/a&gt;&lt;/li&gt;
870                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=30&quot;&gt;TV Action&lt;/a&gt;&lt;/li&gt;
871                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
872                &lt;/ul&gt;
873                &lt;ul class=&quot;sub-nav-group&quot;&gt;
874                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=33&quot;&gt;TV Dramas&lt;/a&gt;&lt;/li&gt;
875                    &lt;li&gt;&lt;a href=&quot;?tv&amp;genre=34&quot;&gt;TV Horror&lt;/a&gt;&lt;/li&gt;
876                    &lt;li&gt;&amp;#8230;&lt;/li&gt;
877                &lt;/ul&gt;
878            &lt;/div&gt;
879        &lt;/li&gt;
880    &lt;/ul&gt;
881&lt;/nav&gt;
882     * @example <h4>CSS</h4><hr/>
883&#47;* Rudimentary mega menu CSS for demonstration *&#47;
884
885&#47;* mega menu list *&#47;
886.nav-menu {
887    display: block;
888    position: relative;
889    list-style: none;
890    margin: 0;
891    padding: 0;
892    z-index: 15;
893}
894
895&#47;* a top level navigation item in the mega menu *&#47;
896.nav-item {
897    list-style: none;
898    display: inline-block;
899    padding: 0;
900    margin: 0;
901}
902
903&#47;* first descendant link within a top level navigation item *&#47;
904.nav-item &gt; a {
905    position: relative;
906    display: inline-block;
907    padding: 0.5em 1em;
908    margin: 0 0 -1px 0;
909    border: 1px solid transparent;
910}
911
912&#47;* focus/open states of first descendant link within a top level
913   navigation item *&#47;
914.nav-item &gt; a:focus,
915.nav-item &gt; a.open {
916    border: 1px solid #dedede;
917}
918
919&#47;* open state of first descendant link within a top level
920   navigation item *&#47;
921.nav-item &gt; a.open {
922    background-color: #fff;
923    border-bottom: none;
924    z-index: 1;
925}
926
927&#47;* sub-navigation panel *&#47;
928.sub-nav {
929    position: absolute;
930    display: none;
931    top: 2.2em;
932    margin-top: -1px;
933    padding: 0.5em 1em;
934    border: 1px solid #dedede;
935    background-color: #fff;
936}
937
938&#47;* sub-navigation panel open state *&#47;
939.sub-nav.open {
940    display: block;
941}
942
943&#47;* list of items within sub-navigation panel *&#47;
944.sub-nav ul {
945    display: inline-block;
946    vertical-align: top;
947    margin: 0 1em 0 0;
948    padding: 0;
949}
950
951&#47;* list item within sub-navigation panel *&#47;
952.sub-nav li {
953    display: block;
954    list-style-type: none;
955    margin: 0;
956    padding: 0;
957}
958     * @example <h4>JavaScript</h4><hr/>
959&lt;!-- include jquery --&gt;
960&lt;script src=&quot;http://code.jquery.com/jquery-1.10.1.min.js&quot;&gt;&lt;/script&gt;
961
962&lt;!-- include the jquery-accessibleMegaMenu plugin script --&gt;
963&lt;script src=&quot;js/jquery-accessibleMegaMenu.js&quot;&gt;&lt;/script&gt;
964
965&lt;!-- initialize a selector as an accessibleMegaMenu --&gt;
966&lt;script&gt;
967    $(&quot;nav:first&quot;).accessibleMegaMenu({
968        &#47;* prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby *&#47;
969        uuidPrefix: &quot;accessible-megamenu&quot;,
970
971        &#47;* css class used to define the megamenu styling *&#47;
972        menuClass: &quot;nav-menu&quot;,
973
974        &#47;* css class for a top-level navigation item in the megamenu *&#47;
975        topNavItemClass: &quot;nav-item&quot;,
976
977        &#47;* css class for a megamenu panel *&#47;
978        panelClass: &quot;sub-nav&quot;,
979
980        &#47;* css class for a group of items within a megamenu panel *&#47;
981        panelGroupClass: &quot;sub-nav-group&quot;,
982
983        &#47;* css class for the hover state *&#47;
984        hoverClass: &quot;hover&quot;,
985
986        &#47;* css class for the focus state *&#47;
987        focusClass: &quot;focus&quot;,
988
989        &#47;* css class for the open state *&#47;
990        openClass: &quot;open&quot;
991    });
992&lt;/script&gt;
993     * @param {object} [options] Mega Menu options
994     * @param {string} [options.uuidPrefix=accessible-megamenu] - Prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby
995     * @param {string} [options.menuClass=accessible-megamenu] - CSS class used to define the megamenu styling
996     * @param {string} [options.topNavItemClass=accessible-megamenu-top-nav-item] - CSS class for a top-level navigation item in the megamenu
997     * @param {string} [options.panelClass=accessible-megamenu-panel] - CSS class for a megamenu panel
998     * @param {string} [options.panelGroupClass=accessible-megamenu-panel-group] - CSS class for a group of items within a megamenu panel
999     * @param {string} [options.hoverClass=hover] - CSS class for the hover state
1000     * @param {string} [options.focusClass=focus] - CSS class for the focus state
1001     * @param {string} [options.openClass=open] - CSS class for the open state
1002     * @param {string} [options.openDelay=0] - Open delay when opening menu via mouseover
1003     */
1004    $.fn[pluginName] = function (options) {
1005        return this.each(function () {
1006            if (!$.data(this, "plugin_" + pluginName)) {
1007                $.data(this, "plugin_" + pluginName, new $.fn[pluginName].Acce
vendor: 2,158 bytes, lines 1007-1069
1007ssibleMegaMenu(this, options));
1008            }
1009        });
1010    };
1011
1012    $.fn[pluginName].AccessibleMegaMenu = AccessibleMegaMenu;
1013
1014    /* :focusable and :tabbable selectors from
1015       https://raw.github.com/jquery/jquery-ui/master/ui/jquery.ui.core.js */
1016
1017    /**
1018     * @private
1019     */
1020    function visible(element) {
1021        return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function () {
1022            return $.css(this, "visibility") === "hidden";
1023        }).length;
1024    }
1025
1026    /**
1027     * @private
1028     */
1029    function focusable(element, isTabIndexNotNaN) {
1030        var map, mapName, img,
1031            nodeName = element.nodeName.toLowerCase();
1032        if ("area" === nodeName) {
1033            map = element.parentNode;
1034            mapName = map.name;
1035            if (!element.href || !mapName || map.nodeName.toLowerCase() !== "map") {
1036                return false;
1037            }
1038            img = $("img[usemap=#" + mapName + "]")[0];
1039            return !!img && visible(img);
1040        }
1041        return (/input|select|textarea|button|object/.test(nodeName) ? !element.disabled :
1042                "a" === nodeName ?
1043                        element.href || isTabIndexNotNaN :
1044                        isTabIndexNotNaN) &&
1045                            // the element and all of its ancestors must be visible
1046                            visible(element);
1047    }
1048
1049    $.extend($.expr[":"], {
1050        data: $.expr.createPseudo ? $.expr.createPseudo(function (dataName) {
1051            return function (elem) {
1052                return !!$.data(elem, dataName);
1053            };
1054        }) : // support: jQuery <1.8
1055                function (elem, i, match) {
1056                    return !!$.data(elem, match[3]);
1057                },
1058
1059        focusable: function (element) {
1060            return focusable(element, !isNaN($.attr(element, "tabindex")));
1061        },
1062
1063        tabbable: function (element) {
1064            var tabIndex = $.attr(element, "tabindex"),
1065                isTabIndexNaN = isNaN(tabIndex);
1066            return (isTabIndexNaN || tabIndex >= 0) && focusable(element, !isTabIndexNaN);
1067        }
1068    });
1069}(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.