PageSourceSearch

https://www.canton.edu/media/wwwcantonedu/scripts/menu/jquery-accessibleMegaMenu.js

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

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.