<nav class="Megamenu Megamenu--default js-megamenu " data-placement="bottom-start">
    <ul class="Megamenu-list">
        <li class="Megamenu-item">
            <a href="#">
        Nulla rerum magni
        <br>Earum rem culpa
      </a>
            <div class="Megamenu-subnav u-jsDisplayNone">
                <ul class="Megamenu-subnavGroup">
                    <li>
                        <a href="/">Earum rem culpa</a>
                        <ul>
                            <li>
                                <a href="/1">Numquam sit veniam</a>
                            </li>
                            <li>
                                <a href="/2">Voluptatem quia cum</a>
                            </li>
                            <li>
                                <a href="/3">Totam et amet</a>
                            </li>
                            <li>
                                <a href="/4">Quo illo molestiae</a>
                            </li>
                        </ul>
                    </li>
                    <li>
                        <a href="">Dolorum quod quia</a>
                    </li>
                </ul>
                <ul class="Megamenu-subnavGroup">
                    <li>
                        <a href="">Aliquid deleniti quae</a>
                        <ul>
                            <li>
                                <a href="/1">Aut illum aliquid</a>
                            </li>
                            <li>
                                <a href="/2">Consequatur eius molestiae</a>
                            </li>
                            <li>
                                <a href="/3">Nulla rerum magni</a>
                            </li>
                            <li>
                                <a href="/4">Earum rem culpa</a>
                            </li>
                        </ul>
                    </li>
                    <li>
                        <a href="">Numquam sit veniam</a>
                    </li>
                </ul>
                <ul class="Megamenu-subnavGroup">
                    <li>
                        <a href="">Voluptatem quia cum</a>
                        <ul>
                            <li>
                                <a href="/1">Totam et amet</a>
                            </li>
                            <li>
                                <a href="/2">Quo illo molestiae</a>
                            </li>
                            <li>
                                <a href="/3">Dolorum quod quia</a>
                            </li>
                            <li>
                                <a href="/4">Aliquid deleniti quae</a>
                            </li>
                        </ul>
                    </li>
                    <li>
                        <a href="">Aut illum aliquid</a>
                    </li>
                </ul>
            </div>
        </li>
        <li class="Megamenu-item">
            <a href="#">Consequatur eius molestiae</a>
            <div class="Megamenu-subnav u-jsDisplayNone">
                <ul class="Megamenu-subnavGroup">
                    <li>
                        <a href="/1">Nulla rerum magni</a>
                    </li>
                    <li>
                        <a href="/2">Earum rem culpa</a>
                    </li>
                </ul>
                <ul class="Megamenu-subnavGroup">
                    <li>
                        <a href="/1">Numquam sit veniam</a>
                    </li>
                    <li>
                        <a href="/2">Voluptatem quia cum</a>
                    </li>
                </ul>
                <ul class="Megamenu-subnavGroup">
                    <li>
                        <a href="/1">Totam et amet</a>
                    </li>
                    <li>
                        <a href="/2">Quo illo molestiae</a>
                    </li>
                </ul>
            </div>
        </li>
        <li class="Megamenu-item">
            <a href="#">Dolorum quod quia</a>
            <div class="Megamenu-subnav u-jsDisplayNone">
                <ul class="Megamenu-subnavGroup">
                    <li>
                        <a href="/1">Aliquid deleniti quae</a>
                    </li>
                    <li>
                        <a href="/2">Aut illum aliquid</a>
                    </li>
                </ul>
                <ul class="Megamenu-subnavGroup">
                    <li>
                        <a href="/1">Consequatur eius molestiae</a>
                    </li>
                    <li>
                        <a href="/2">Nulla rerum magni</a>
                    </li>
                </ul>
                <ul class="Megamenu-subnavGroup">
                    <li>
                        <a href="/1">Earum rem culpa</a>
                    </li>
                    <li>
                        <a href="/2">Numquam sit veniam</a>
                    </li>
                </ul>
            </div>
        </li>
    </ul>
</nav>
  • Content:
    /** @define Megamenu; weak; */
    
    :root {
      --Megamenu-arrow-width: 0.5em;
      --Megamenu-subnav-width: 14em;
    }
    
    /*
     *  1. IE hack
     */
    
    .Megamenu-list {
      @extend .u-block; /* 1 */
      @extend .u-flex;
      @extend .u-flexJustifyStart;
      @extend .u-flexAlignContentCenter;
      @extend .u-posRelative;
      @extend .u-zindex-40;
      @extend .u-alignMiddle;
      @extend .u-textLeft;
    }
    
    /* a top level navigation item in the mega menu */
    
    /*
     *  1. IE hack
     */
    
    .Megamenu-item {
      @extend .u-inlineBlock; /* 1 */
      @extend .u-flex;
      @extend .u-sizeFit;
      @extend .u-alignBottom;
      @extend .u-borderHideFocus;
    }
    
    .Megamenu-item > a:hover,
    .Megamenu-item:hover {
      cursor: pointer !important;
    }
    
    /* first descendant link within a top level navigation item */
    
    .Megamenu-item > a {
      @extend .u-alignBottom;
      @extend .u-inlineBlock;
      @extend .u-flex;
      @extend .u-flexCol;
      @extend .u-flexJustifyCenter;
      @extend .u-flexAlignSelfStretch;
      @extend .u-padding-top-s;
      @extend .u-padding-bottom-s;
      @extend .u-padding-right-xl;
      @extend .u-padding-left-xl;
    }
    
    .Megamenu-item:first-child > a {
      padding-left: 0 !important;
    }
    
    .Megamenu-item > a:focus {
      @extend .u-backround-none;
    }
    
    /* focus/open states of first descendant link within a top level
          navigation item
          .Megamenu-subnavGroup > li > ul > li > a.is-focus {
        }
    */
    
    /* open state of first descendant link within a top level
          navigation item */
    
    .Megamenu-item > a.is-open {
      @extend .u-zindex-min;
    }
    
    /* sub-navigation panel */
    
    .Megamenu.is-ready .Megamenu-subnav {
      @extend .u-posAbsolute;
      @extend .u-zindex-max;
      @extend .u-inlineBlock;
    
      left: 0;
      top: 100%;
      visibility: hidden;
    }
    
    /* sub-navigation panel open state */
    
    .Megamenu.is-ready .Megamenu-subnav.is-open {
      visibility: visible;
    }
    
    /* list of items within sub-navigation panel */
    
    .Megamenu-subnavGroup {
      @extend .u-inlineBlock;
      @extend .u-alignTop;
    }
    
    .Megamenu-subnavGroup:nth-child(3n+0) {
      @extend .u-cf;
    }
    
    /* list item within sub-navigation panel */
    
    .Megamenu-subnavGroup > li {
      @extend .u-block;
    
      width: var(--Megamenu-subnav-width);
    }
    
    /* Some custom style */
    
    .Megamenu--default {
      @extend .u-layout-r-withGutter;
    
      background-color: color(var(--Color-primary) l(+5%) s(-15%));
    }
    
    .Megamenu--default > .Megamenu-list {
      @extend .u-layout-wide;
      @extend .u-layoutCenter;
    
      @media (--lg-viewport) {
        padding-left: 0 !important;
      }
    }
    
    .Megamenu--default .Megamenu-item > a,
    .Megamenu--default .Megamenu-subnavGroup > li > a {
      @extend .u-textClean;
      @extend .u-posRelative;
      @extend .u-textWeight-600;
    }
    
    .Megamenu--default .Megamenu-subnavGroup > li > a {
      @extend .u-color-95;
      @extend .u-text-r-s;
      @extend .u-textUppercase;
      @extend .u-textWeight-700;
    }
    
    .Megamenu--default .Megamenu-item > a {
      @extend .u-color-white;
      @extend .u-text-r-xs;
    }
    
    /*
     *  @FIXME: hack to insert expand icon
     *
     *  1. Cannot use padding-r-* because of pseudoselector.
     */
    
    .Megamenu--default .Megamenu-list > li > a[aria-expanded=true]::after,
    .Megamenu--default .Megamenu-list > li > a[aria-expanded=false]::after {
      @extend .u-padding-left-xs; /* 1 */
      @extend .u-text-r-xxs;
      @extend .u-posAbsolute;
    
      @extend .Icon;
    
      bottom: 35%;
      right: 0.5em;
    }
    
    .Megamenu--default .Megamenu-list > li > a[aria-expanded=false]::after {
      content: "\23f6";
    }
    
    .Megamenu--default .Megamenu-list > li > a[aria-expanded=true]::after {
      content: "\23f7";
    }
    
    .Megamenu--default .Megamenu-list > li > a[aria-expanded=true] {
      opacity: 0.7;
    }
    
    .Megamenu--default .Megamenu-subnav {
      @extend .u-background-white;
      @extend .u-padding-top-l;
      @extend .u-padding-bottom-l;
      @extend .u-padding-left-xl;
      @extend .u-padding-right-xl;
      @extend .u-borderRadius-m;
      @extend .u-borderShadow-l;
    }
    
    .Megamenu--default .Megamenu-subnavGroup > li {
      @extend .u-color-grey-50;
      @extend .u-padding-top-s;
      @extend .u-padding-bottom-s;
      @extend .u-margin-r-right;
    }
    
    .Megamenu--default .Megamenu-subnavGroup > li:last-child {
      border-bottom: none !important;
    }
    
    /*
     *  Do not put padding and margin here.
     */
    
    .Megamenu--default .Megamenu-subnavGroup {
      @extend .u-textLeft;
      @extend .u-color-grey-40;
    }
    
    .Megamenu--default .Megamenu-subnavGroup:last-child {
      border-right: none !important;
    }
    
    .Megamenu--default .Megamenu-subnavGroup > li > ul {
      @extend .u-padding-top-xs;
      @extend .u-margin-top-s;
      @extend .u-padding-bottom-xxs;
    }
    
    /*
     *	Max depth = 3 levels
     */
    
    .Megamenu--default .Megamenu-subnavGroup > li > ul li {
      @extend .u-padding-top-xxs;
      @extend .u-padding-bottom-xs;
    }
    
    .Megamenu--default .Megamenu-subnavGroup > li > ul > li a {
      @extend .u-text-r-xs;
      @extend .u-color-grey-80;
      @extend .u-textClean;
    }
    
    .Megamenu--default .Megamenu-subnavGroup > li > ul > li > ul > li > a {
      @extend .u-text-r-xxs;
      @extend .u-color-grey-80;
      @extend .u-textClean;
    }
    
    .Megamenu--default .Megamenu-subnavGroup > li > ul > li > ul {
      @extend .u-margin-top-xs;
      @extend .u-margin-bottom-xs;
    }
    
    /*
     *  1. does not work in IE(11)
     */
    
    .Megamenu--default .Megamenu-area {
      @extend .u-flex;
      @extend .u-flexAlignItemsCenter;
      @extend .u-padding-r-left;
      @extend .u-posAbsolute;
    
      /* [1] margin-left: auto; */
    
      height: 100%;
      right: 0;
      top: 0;
    }
    
    .Megamenu--default .Megamenu-close {
      @extend .u-posAbsolute;
      @extend .u-text-r-m;
    
      cursor: pointer;
      opacity: 0.7;
      right: 2em;
      top: 2em;
    }
    
  • URL: /components/raw/megamenu/index.css
  • Filesystem Path: src/components/megamenu/index.css
  • Size: 5.4 KB
  • Content:
    import $ from 'jquery'
    import Popper from 'popper.js'
    import Megamenu from './megamenu'
    
    // ASAP hide megamenu panels
    $('.js-megamenu').addClass('is-ready')
    
    const opts = {
      /* add a close button to every subnav */
      addCloseButton: false,
    
      closeButtonClass: 'js-Megamenu-close',
    
      closeButtonTemplate: `<button title="chiudi il menu" class="Megamenu-close js-Megamenu-close">
        <span class="Icon Icon-close"></span><span class="u-hiddenVisually">chiudi</span></button>`,
    
      /* if false open menu on hover */
      openOnClick: true,
    
      /* prefix for generated unique id attributes, which are required
         to indicate aria-owns, aria-controls and aria-labelledby */
      uuidPrefix: 'megamenu',
    
      /* css class used to define the megamenu styling */
      menuClass: 'Megamenu',
    
      menuListClass: 'Megamenu-list',
    
      /* css class for a top-level navigation item in the megamenu */
      topNavItemClass: 'Megamenu-item',
    
      /* css class for a megamenu panel */
      panelClass: 'Megamenu-subnav',
    
      /* css class for a group of items within a megamenu panel */
      panelGroupClass: 'Megamenu-subnavGroup',
    
      /* css class for the hover state */
      hoverClass: 'is-hover',
    
      /* css class for the focus state */
      focusClass: 'is-focus',
    
      /* css class for the open state */
      openClass: 'is-open'
    }
    
    /*
     *	Takes a Treeview and makes it work with Megamenu
     */
    const listToMegaMenu = ($ul, _opts) => {
      const attrs = 'class aria-expanded aria-hidden role tabindex'
      return $ul
        .clone()
        .removeAttr(attrs)
        .addClass(_opts.menuListClass)
        .find('*')
        .removeAttr(attrs)
        .end()
        .find('> li')
        .each(function (i, li) {
          $(li)
            .addClass(function () {
              let className = $(this).data('megamenu-class')
              return className ? className : _opts.topNavItemClass
            })
            .find('[data-megamenu-class]')
            .addClass(function () {
              return $(this).data('megamenu-class')
            })
            .end()
            .find('a')
            // make item tabbable, this is required !
            // .attr('href', '#')
            .end()
            .find('> ul > li')
            .unwrap()
            .wrap(`<ul class="${_opts.panelGroupClass}" />`)
            .end()
            .find('> ul')
            .wrapAll(`<div class="${_opts.panelClass}" />`)
        })
        .end()
    }
    
    $(document).ready(function () {
      $('.js-megamenu').each((i, el) => {
        const $el = $(el)
        const rel = $(el).data('rel')
    
        if ($el.find('ul').length === 0 && rel && $(rel).length > 0) {
          let $menu = listToMegaMenu($(rel), opts)
          $el.append($menu)
          // @FIXME: make space for javascript rendered megamenu
          if ($('header').css('position') === 'fixed') {
            $('body').css({
              paddingTop: '+=' + $el.height() + 'px'
            })
          }
        }
    
        $el
          .find('.' + opts.panelClass)
          .append(
            '<span class="Icon-drop-down Dropdown-arrow u-color-white"></span>'
          )
    
        $el.accessibleMegaMenu(opts)
    
        if (opts.addCloseButton) {
          $(opts.closeButtonTemplate).appendTo($('.' + opts.panelClass))
        }
    
        $('.' + opts.closeButtonClass).on('click', function () {
          const e = $.Event('keydown')
          e.which = 27
          $('.' + opts.menuClass).trigger(e)
          return false
        })
      })
    
      $('.' + opts.topNavItemClass + ' > a').each((i, el) => {
        const $el = $(el)
        const placement = $el.closest('.js-megamenu').attr('data-placement')
        const $target = $(el).parent().find('.' + opts.panelClass).not(el)
    
        if (el && $target.length > 0) {
          new Popper(el, $target, {
            placement: placement || 'bottom',
            modifiers: {
              arrow: {
                element: '.Dropdown-arrow'
              },
              flip: {
                enabled: false
              },
              preventOverflow: {
                padding: 8,
                priority: ['right', 'left'],
                boundariesElement: 'scrollParent'
              }
            }
          })
          $el.click(function( event ) {
            event.preventDefault()
          })
        }
      })
    })
    
    export default {
      opts,
      listToMegaMenu,
      Megamenu
    }
    
  • URL: /components/raw/megamenu/index.js
  • Filesystem Path: src/components/megamenu/index.js
  • Size: 4 KB
  • Content:
    /*
    Copyright © 2013 Adobe Systems Incorporated.
    
    Licensed under the Apache License, Version 2.0 (the “License”)
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    
    http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an “AS IS” BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
    */
    
    /**
     * @fileOverview accessibleMegaMenu plugin
     *
     *<p>Licensed under the Apache License, Version 2.0 (the “License”)
     *<br />Copyright © 2013 Adobe Systems Incorporated.
     *<br />Project page <a href='https://github.com/adobe-accessibility/Accessible-Mega-Menu'>https://github.com/adobe-accessibility/Accessible-Mega-Menu</a>
     * @version 0.1
     * @author Michael Jordan
     * @requires jquery
     */
    
    /*jslint browser: true, devel: true, plusplus: true, nomen: true */
    /*global jQuery */
    (function($, window, document) {
      var pluginName = 'accessibleMegaMenu',
        defaults = {
          openOnClick: false,
          uuidPrefix: 'accessible-megamenu', // unique ID's are required to indicate aria-owns, aria-controls and aria-labelledby
          menuClass: 'accessible-megamenu', // default css class used to define the megamenu styling
          topNavItemClass: 'accessible-megamenu-top-nav-item', // default css class for a top-level navigation item in the megamenu
          panelClass: 'accessible-megamenu-panel', // default css class for a megamenu panel
          panelGroupClass: 'accessible-megamenu-panel-group', // default css class for a group of items within a megamenu panel
          hoverClass: 'hover', // default css class for the hover state
          focusClass: 'focus', // default css class for the focus state
          openClass: 'open' // default css class for the open state
        },
        Keyboard = {
          BACKSPACE: 8,
          COMMA: 188,
          DELETE: 46,
          DOWN: 40,
          END: 35,
          ENTER: 13,
          ESCAPE: 27,
          HOME: 36,
          LEFT: 37,
          PAGE_DOWN: 34,
          PAGE_UP: 33,
          PERIOD: 190,
          RIGHT: 39,
          SPACE: 32,
          TAB: 9,
          UP: 38,
          keyMap: {
            48: '0',
            49: '1',
            50: '2',
            51: '3',
            52: '4',
            53: '5',
            54: '6',
            55: '7',
            56: '8',
            57: '9',
            59: '',
            65: 'a',
            66: 'b',
            67: 'c',
            68: 'd',
            69: 'e',
            70: 'f',
            71: 'g',
            72: 'h',
            73: 'i',
            74: 'j',
            75: 'k',
            76: 'l',
            77: 'm',
            78: 'n',
            79: 'o',
            80: 'p',
            81: 'q',
            82: 'r',
            83: 's',
            84: 't',
            85: 'u',
            86: 'v',
            87: 'w',
            88: 'x',
            89: 'y',
            90: 'z',
            96: '0',
            97: '1',
            98: '2',
            99: '3',
            100: '4',
            101: '5',
            102: '6',
            103: '7',
            104: '8',
            105: '9',
            190: '.'
          }
        }
        /**
         * @desc Creates a new accessible mega menu instance.
         * @param {jquery} element
         * @param {object} [options] Mega Menu options
         * @param {string} [options.uuidPrefix=accessible-megamenu] - Prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby
         * @param {string} [options.menuClass=accessible-megamenu] - CSS class used to define the megamenu styling
         * @param {string} [options.topNavItemClass=accessible-megamenu-top-nav-item] - CSS class for a top-level navigation item in the megamenu
         * @param {string} [options.panelClass=accessible-megamenu-panel] - CSS class for a megamenu panel
         * @param {string} [options.panelGroupClass=accessible-megamenu-panel-group] - CSS class for a group of items within a megamenu panel
         * @param {string} [options.hoverClass=hover] - CSS class for the hover state
         * @param {string} [options.focusClass=focus] - CSS class for the focus state
         * @param {string} [options.openClass=open] - CSS class for the open state
         * @constructor
         */
      function AccessibleMegaMenu(element, options) {
        this.element = element
    
        // merge optional settings and defaults into settings
        this.settings = $.extend({}, defaults, options)
    
        this._defaults = defaults
        this._name = pluginName
    
        this.mouseTimeoutID = null
        this.focusTimeoutID = null
        this.mouseFocused = false
        this.justFocused = false
    
        this.init()
      }
    
      AccessibleMegaMenu.prototype = (function() {
    
        /* private attributes and methods ------------------------ */
        var uuid = 0,
          keydownTimeoutDuration = 1000,
          keydownSearchString = '',
          isTouch = typeof window.hasOwnProperty === 'function' && !!window.hasOwnProperty('ontouchstart'),
          _getPlugin,
          _addUniqueId,
          _togglePanel,
          _clickHandler,
          _clickOutsideHandler,
          _DOMAttrModifiedHandler,
          _focusInHandler,
          _focusOutHandler,
          _keyDownHandler,
          _mouseDownHandler,
          _mouseOverHandler,
          _mouseOutHandler,
          _toggleExpandedEventHandlers
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_getPlugin
         * @desc Returns the parent accessibleMegaMenu instance for a given element
         * @param {jQuery} element
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _getPlugin = function(element) {
          return $(element).closest(':data(plugin_' + pluginName + ')').data('plugin_' + pluginName)
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_addUniqueId
         * @desc Adds a unique id and element.
         * The id string starts with the
         * string defined in settings.uuidPrefix.
         * @param {jQuery} element
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _addUniqueId = function(element) {
          element = $(element)
          var settings = this.settings
          if (!element.attr('id')) {
            element.attr('id', settings.uuidPrefix + '-' + new Date().getTime() + '-' + (++uuid))
          }
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_togglePanel
         * @desc Toggle the display of mega menu panels in response to an event.
         * The optional boolean value 'hide' forces all panels to hide.
         * @param {event} event
         * @param {Boolean} [hide] Hide all mega menu panels when true
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _togglePanel = function(event, hide) {
          var target = $(event.target),
            that = this,
            settings = this.settings,
            menu = this.menu,
            topli = target.closest('.' + settings.topNavItemClass),
            panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass),
            newfocus
    
          _toggleExpandedEventHandlers.call(this, true)
    
          if (hide) {
            topli = menu.find('.' + settings.topNavItemClass + ' .' + settings.openClass + ':first').closest('.' + settings.topNavItemClass)
            if (!(topli.is(event.relatedTarget) || topli.has(event.relatedTarget).length > 0)) {
              if ((event.type === 'mouseout' || event.type === 'focusout') && topli.has(document.activeElement).length > 0) {
                return
              }
              topli.find('[aria-expanded]')
                .attr('aria-expanded', 'false')
                .removeClass(settings.openClass)
                .filter('.' + settings.panelClass)
                .attr('aria-hidden', 'true')
              if ((event.type === 'keydown' && event.keyCode === Keyboard.ESCAPE) || event.type === 'DOMAttrModified') {
                newfocus = topli.find(':tabbable:first')
                setTimeout(function() {
                  menu.find('[aria-expanded].' + that.settings.panelClass).off('DOMAttrModified.accessible-megamenu')
                  newfocus.focus()
                  that.justFocused = false
                }, 99)
              }
            } else if (topli.length === 0) {
              menu.find('[aria-expanded=true]')
                .attr('aria-expanded', 'false')
                .removeClass(settings.openClass)
                .filter('.' + settings.panelClass)
                .attr('aria-hidden', 'true')
            }
          } else {
            clearTimeout(that.focusTimeoutID)
            topli.siblings()
              .find('[aria-expanded]')
              .attr('aria-expanded', 'false')
              .removeClass(settings.openClass)
              .filter('.' + settings.panelClass)
              .attr('aria-hidden', 'true')
            topli.find('[aria-expanded]')
              .attr('aria-expanded', 'true')
              .addClass(settings.openClass)
              .filter('.' + settings.panelClass)
              .attr('aria-hidden', 'false')
            if (event.type === 'mouseover' && target.is(':tabbable') && topli.length === 1 && panel.length === 0 && menu.has(document.activeElement).length > 0) {
              target.focus()
              that.justFocused = false
            }
    
            _toggleExpandedEventHandlers.call(that)
          }
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_clickHandler
         * @desc Handle click event on mega menu item
         * @param {event} Event object
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _clickHandler = function(event) {
          var target = $(event.currentTarget),
            topli = target.closest('.' + this.settings.topNavItemClass),
            panel = target.closest('.' + this.settings.panelClass)
          if (topli.length === 1 &&
            panel.length === 0 &&
            topli.find('.' + this.settings.panelClass).length === 1) {
            if (!target.hasClass(this.settings.openClass)) {
              event.preventDefault()
              event.stopPropagation()
              _togglePanel.call(this, event)
              this.justFocused = false
            } else {
              if (this.justFocused) {
                event.preventDefault()
                event.stopPropagation()
                this.justFocused = false
              } else if (isTouch || this.mouseFocused) {
                event.preventDefault()
                event.stopPropagation()
                _togglePanel.call(this, event, target.hasClass(this.settings.openClass))
              }
            }
          }
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_clickOutsideHandler
         * @desc Handle click event outside of a the megamenu
         * @param {event} Event object
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _clickOutsideHandler = function(event) {
          if ($(event.target).closest(this.menu).length === 0) {
            event.preventDefault()
            event.stopPropagation()
            _togglePanel.call(this, event, true)
          }
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_DOMAttrModifiedHandler
         * @desc Handle DOMAttrModified event on panel to respond to Windows 8 Narrator ExpandCollapse pattern
         * @param {event} Event object
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _DOMAttrModifiedHandler = function(event) {
          if (event.originalEvent.attrName === 'aria-expanded' &&
            event.originalEvent.newValue === 'false' &&
            $(event.target).hasClass(this.settings.openClass)) {
            event.preventDefault()
            event.stopPropagation()
            _togglePanel.call(this, event, true)
          }
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_focusInHandler
         * @desc Handle focusin event on mega menu item.
         * @param {event} Event object
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _focusInHandler = function(event) {
          clearTimeout(this.focusTimeoutID)
          var target = $(event.target),
            panel = target.closest('.' + this.settings.panelClass)
          target
            .addClass(this.settings.focusClass)
            .on('click.accessible-megamenu', $.proxy(_clickHandler, this))
          this.justFocused = !this.mouseFocused
          this.mouseFocused = false
          if (this.panels.not(panel).filter('.' + this.settings.openClass).length) {
            _togglePanel.call(this, event)
          }
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_focusOutHandler
         * @desc Handle focusout event on mega menu item.
         * @param {event} Event object
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _focusOutHandler = function(event) {
          this.justFocused = false
          var that = this,
            target = $(event.target),
            topli = target.closest('.' + this.settings.topNavItemClass)
    
          target
          // .removeClass(this.settings.focusClass)
            .off('click.accessible-megamenu')
    
          if (window.cvox) {
            // If ChromeVox is running...
            that.focusTimeoutID = setTimeout(function() {
              window.cvox.Api.getCurrentNode(function(node) {
                if (topli.has(node).length) {
                  // and the current node being voiced is in
                  // the mega menu, clearTimeout,
                  // so the panel stays open.
                  clearTimeout(that.focusTimeoutID)
                } else {
                  that.focusTimeoutID = setTimeout(function(scope, _event, hide) {
                    _togglePanel.call(scope, _event, hide)
                  }, 275, that, event, true)
                }
              })
            }, 25)
          } else {
            that.focusTimeoutID = setTimeout(function() {
              _togglePanel.call(that, event, true)
            }, 300)
          }
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_keyDownHandler
         * @desc Handle keydown event on mega menu.
         * @param {event} Event object
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _keyDownHandler = function(event) {
          var that = (this.constructor === AccessibleMegaMenu) ? this : _getPlugin(this), // determine the AccessibleMegaMenu plugin instance
            settings = that.settings,
            target = $($(this).is('.' + settings.hoverClass + ':tabbable') ? this : event.target), // if the element is hovered the target is this, otherwise, its the focused element
            menu = that.menu,
            topnavitems = that.topnavitems,
            topli = target.closest('.' + settings.topNavItemClass),
            tabbables = menu.find(':tabbable'),
            panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass),
            panelGroups = panel.find('.' + settings.panelGroupClass),
            currentPanelGroup = target.closest('.' + settings.panelGroupClass),
            next,
            keycode = event.keyCode || event.which,
            start,
            i,
            o,
            label,
            found = false,
            newString = Keyboard.keyMap[event.keyCode] || '',
            regex,
            isTopNavItem = (topli.length === 1 && panel.length === 0)
    
          if (target.is('input:focus, select:focus, textarea:focus, button:focus')) {
            // if the event target is a form element we should handle keydown normally
            return
          }
    
          if (target.is('.' + settings.hoverClass + ':tabbable')) {
            $('html').off('keydown.accessible-megamenu')
          }
    
          switch (keycode) {
            case Keyboard.ESCAPE:
              _togglePanel.call(that, event, true)
              break
            case Keyboard.DOWN:
              event.preventDefault()
              if (isTopNavItem) {
                _togglePanel.call(that, event)
                found = (topli.find('.' + settings.panelClass + ' :tabbable:first').focus().length === 1)
              } else {
                found = (tabbables.filter(':gt(' + tabbables.index(target) + '):first').focus().length === 1)
              }
    
              if (!found && window.opera && opera.toString() === '[object Opera]' && (event.ctrlKey || event.metaKey)) {
                tabbables = $(':tabbable')
                i = tabbables.index(target)
                found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1)
              }
              break
            case Keyboard.UP:
              event.preventDefault()
              if (isTopNavItem && target.hasClass(settings.openClass)) {
                _togglePanel.call(that, event, true)
                next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last')
                if (next.children('.' + settings.panelClass).length) {
                  found = (next.children()
                    .attr('aria-expanded', 'true')
                    .addClass(settings.openClass)
                    .filter('.' + settings.panelClass)
                    .attr('aria-hidden', 'false')
                    .find(':tabbable:last')
                    .focus() === 1)
                }
              } else if (!isTopNavItem) {
                found = (tabbables.filter(':lt(' + tabbables.index(target) + '):last').focus().length === 1)
              }
    
              if (!found && window.opera && opera.toString() === '[object Opera]' && (event.ctrlKey || event.metaKey)) {
                tabbables = $(':tabbable')
                i = tabbables.index(target)
                found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):first').focus().length === 1)
              }
              break
            case Keyboard.RIGHT:
              event.preventDefault()
              if (isTopNavItem) {
                found = (topnavitems.filter(':gt(' + topnavitems.index(topli) + '):first').find(':tabbable:first').focus().length === 1)
              } else {
                if (panelGroups.length && currentPanelGroup.length) {
                  // if the current panel contains panel groups, and we are able to focus the first tabbable element of the next panel group
                  found = (panelGroups.filter(':gt(' + panelGroups.index(currentPanelGroup) + '):first').find(':tabbable:first').focus().length === 1)
                }
    
                if (!found) {
                  found = (topli.find(':tabbable:first').focus().length === 1)
                }
              }
              break
            case Keyboard.LEFT:
              event.preventDefault()
              if (isTopNavItem) {
                found = (topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last').find(':tabbable:first').focus().length === 1)
              } else {
                if (panelGroups.length && currentPanelGroup.length) {
                  // if the current panel contains panel groups, and we are able to focus the first tabbable element of the previous panel group
                  found = (panelGroups.filter(':lt(' + panelGroups.index(currentPanelGroup) + '):last').find(':tabbable:first').focus().length === 1)
                }
    
                if (!found) {
                  found = (topli.find(':tabbable:first').focus().length === 1)
                }
              }
              break
            case Keyboard.TAB:
              i = tabbables.index(target)
              if (event.shiftKey && isTopNavItem && target.hasClass(settings.openClass)) {
                _togglePanel.call(that, event, true)
                next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last')
                if (next.children('.' + settings.panelClass).length) {
                  found = next.children()
                    .attr('aria-expanded', 'true')
                    .addClass(settings.openClass)
                    .filter('.' + settings.panelClass)
                    .attr('aria-hidden', 'false')
                    .find(':tabbable:last')
                    .focus()
                }
              } else if (event.shiftKey && i > 0) {
                found = (tabbables.filter(':lt(' + i + '):last').focus().length === 1)
              } else if (!event.shiftKey && i < tabbables.length - 1) {
                found = (tabbables.filter(':gt(' + i + '):first').focus().length === 1)
              } else if (window.opera && opera.toString() === '[object Opera]') {
                tabbables = $(':tabbable')
                i = tabbables.index(target)
                if (event.shiftKey) {
                  found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):last').focus().length === 1)
                } else {
                  found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1)
                }
              }
    
              if (found) {
                event.preventDefault()
              }
              break
            case Keyboard.SPACE:
              if (isTopNavItem) {
                event.preventDefault()
                _clickHandler.call(that, event)
              } else {
                return
              }
              break
            case Keyboard.ENTER:
              return
              break
            default:
              // alphanumeric filter
              clearTimeout(this.keydownTimeoutID)
    
              keydownSearchString += newString !== keydownSearchString ? newString : ''
    
              if (keydownSearchString.length === 0) {
                return
              }
    
              this.keydownTimeoutID = setTimeout(function() {
                keydownSearchString = ''
              }, keydownTimeoutDuration)
    
              if (isTopNavItem && !target.hasClass(settings.openClass)) {
                tabbables = tabbables.filter(':not(.' + settings.panelClass + ' :tabbable)')
              } else {
                tabbables = topli.find(':tabbable')
              }
    
              if (event.shiftKey) {
                tabbables = $(tabbables.get()
                  .reverse())
              }
    
              for (i = 0; i < tabbables.length; i++) {
                o = tabbables.eq(i)
                if (o.is(target)) {
                  start = (keydownSearchString.length === 1) ? i + 1 : i
                  break
                }
              }
    
              regex = new RegExp('^' + keydownSearchString.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'), 'i')
    
              for (i = start; i < tabbables.length; i++) {
                o = tabbables.eq(i)
                label = $.trim(o.text())
                if (regex.test(label)) {
                  found = true
                  o.focus()
                  break
                }
              }
              if (!found) {
                for (i = 0; i < start; i++) {
                  o = tabbables.eq(i)
                  label = $.trim(o.text())
                  if (regex.test(label)) {
                    o.focus()
                    break
                  }
                }
              }
              break
          }
          that.justFocused = false
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_mouseDownHandler
         * @desc Handle mousedown event on mega menu.
         * @param {event} Event object
         * @memberof accessibleMegaMenu
         * @inner
         * @private
         */
        _mouseDownHandler = function(event) {
          if ($(event.target).is(this.settings.panelClass) || $(event.target).closest(':focusable').length) {
            this.mouseFocused = true
          }
          this.mouseTimeoutID = setTimeout(function() {
            clearTimeout(this.focusTimeoutID)
          }, 1)
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_mouseOverHandler
         * @desc Handle mouseover event on mega menu.
         * @param {event} Event object
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _mouseOverHandler = function(event) {
          clearTimeout(this.mouseTimeoutID)
          $(event.target)
            .addClass(this.settings.hoverClass)
          _togglePanel.call(this, event)
          if ($(event.target).is(':tabbable')) {
            $('html').on('keydown.accessible-megamenu', $.proxy(_keyDownHandler, event.target))
          }
        }
    
        /**
         * @name jQuery.fn.accessibleMegaMenu~_mouseOutHandler
         * @desc Handle mouseout event on mega menu.
         * @param {event} Event object
         * @memberof jQuery.fn.accessibleMegaMenu
         * @inner
         * @private
         */
        _mouseOutHandler = function(event) {
          var that = this
          $(event.target)
            .removeClass(that.settings.hoverClass)
    
          that.mouseTimeoutID = setTimeout(function() {
            _togglePanel.call(that, event, true)
          }, 250)
          if ($(event.target).is(':tabbable')) {
            $('html').off('keydown.accessible-megamenu')
          }
        }
    
        _toggleExpandedEventHandlers = function(hide) {
          var menu = this.menu
          if (hide) {
            $('html').off('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu,  pointerup.outside-accessible-megamenu')
    
            menu.find('[aria-expanded].' + this.settings.panelClass).off('DOMAttrModified.accessible-megamenu')
          } else {
            $('html').on('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu,  pointerup.outside-accessible-megamenu', $.proxy(_clickOutsideHandler, this))
    
            /* Narrator in Windows 8 automatically toggles the aria-expanded property on double tap or click.
               To respond to the change to collapse the panel, we must add a listener for a DOMAttrModified event. */
            menu.find('[aria-expanded=true].' + this.settings.panelClass).on('DOMAttrModified.accessible-megamenu', $.proxy(_DOMAttrModifiedHandler, this))
          }
        }
    
        /* public attributes and methods ------------------------- */
        return {
          constructor: AccessibleMegaMenu,
    
          /**
           * @lends jQuery.fn.accessibleMegaMenu
           * @desc Initializes an instance of the accessibleMegaMenu plugins
           * @memberof jQuery.fn.accessibleMegaMenu
           * @instance
           */
          init: function() {
            var settings = this.settings,
              nav = $(this.element),
              menu = nav.children().first(),
              // Only filter to those items with the selector class
              topnavitems = this.settings.topNavItems ?
              menu.children(this.settings.topNavItems) :
              menu.children()
            this.start(settings, nav, menu, topnavitems)
          },
    
          start: function(settings, nav, menu, topnavitems) {
            var that = this
            this.settings = settings
            this.menu = menu
            this.topnavitems = topnavitems
    
            nav.attr('role', 'navigation')
            menu.addClass(settings.menuClass)
            topnavitems.each(function(i, topnavitem) {
              var topnavitemlink, topnavitempanel
              topnavitem = $(topnavitem)
              // @FIXME: Add classed manually !!!
              // topnavitem.addClass(settings.topNavItemClass)
              topnavitemlink = topnavitem.find(':tabbable:first')
              topnavitempanel = topnavitem.children(':not(:tabbable):last')
              _addUniqueId.call(that, topnavitemlink)
              if (topnavitempanel.length) {
                _addUniqueId.call(that, topnavitempanel)
                topnavitemlink.attr({
                  'aria-haspopup': true,
                  'aria-controls': topnavitempanel.attr('id'),
                  'aria-expanded': false
                })
    
                topnavitempanel.attr({
                    'role': 'group',
                    'aria-expanded': false,
                    'aria-hidden': true
                  })
                  .addClass(settings.panelClass)
                  .not('[aria-labelledby]')
                  .attr('aria-labelledby', topnavitemlink.attr('id'))
              }
            })
    
            this.panels = menu.find('.' + settings.panelClass)
    
            menu.on('focusin.accessible-megamenu', ':focusable, .' + settings.panelClass, $.proxy(_focusInHandler, this))
              .on('focusout.accessible-megamenu', ':focusable, .' + settings.panelClass, $.proxy(_focusOutHandler, this))
              .on('keydown.accessible-megamenu', $.proxy(_keyDownHandler, this))
              .on('mousedown.accessible-megamenu', $.proxy(_mouseDownHandler, this))
    
            if (!settings.openOnClick) {
              menu.on('mouseover.accessible-megamenu', $.proxy(_mouseOverHandler, this))
                .on('mouseout.accessible-megamenu', $.proxy(_mouseOutHandler, this))
            }
            else {
              menu.on('click.accessible-megamenu', $.proxy(_mouseOverHandler, this))
            }
    
            if (isTouch) {
              menu.on('touchstart.accessible-megamenu', $.proxy(_clickHandler, this))
            }
    
            menu.find('hr').attr('role', 'separator')
    
            if ($(document.activeElement).closest(menu).length) {
              $(document.activeElement).trigger('focusin.accessible-megamenu')
            }
          },
    
          /**
           * @desc Get default values
           * @example $(selector).accessibleMegaMenu('getDefaults')
           * @return {object}
           * @memberof jQuery.fn.accessibleMegaMenu
           * @instance
           */
          getDefaults: function() {
            return this._defaults
          },
    
          /**
           * @desc Get any option set to plugin using its name (as string)
           * @example $(selector).accessibleMegaMenu('getOption', some_option)
           * @param {string} opt
           * @return {string}
           * @memberof jQuery.fn.accessibleMegaMenu
           * @instance
           */
          getOption: function(opt) {
            return this.settings[opt]
          },
    
          /**
           * @desc Get all options
           * @example $(selector).accessibleMegaMenu('getAllOptions')
           * @return {object}
           * @memberof jQuery.fn.accessibleMegaMenu
           * @instance
           */
          getAllOptions: function() {
            return this.settings
          },
    
          /**
           * @desc Set option
           * @example $(selector).accessibleMegaMenu('setOption', 'option_name',  'option_value',  reinitialize)
           * @param {string} opt - Option name
           * @param {string} val - Option value
           * @param {boolean} [reinitialize] - boolean to re-initialize the menu.
           * @memberof jQuery.fn.accessibleMegaMenu
           * @instance
           */
          setOption: function(opt, value, reinitialize) {
            this.settings[opt] = value
            if (reinitialize) {
              this.init()
            }
          }
        }
      }())
    
      /*
       * @param {object} [options] Mega Menu options
       * @param {string} [options.uuidPrefix=accessible-megamenu] - Prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby
       * @param {string} [options.menuClass=accessible-megamenu] - CSS class used to define the megamenu styling
       * @param {string} [options.topNavItemClass=accessible-megamenu-top-nav-item] - CSS class for a top-level navigation item in the megamenu
       * @param {string} [options.panelClass=accessible-megamenu-panel] - CSS class for a megamenu panel
       * @param {string} [options.panelGroupClass=accessible-megamenu-panel-group] - CSS class for a group of items within a megamenu panel
       * @param {string} [options.hoverClass=hover] - CSS class for the hover state
       * @param {string} [options.focusClass=focus] - CSS class for the focus state
       * @param {string} [options.openClass=open] - CSS class for the open state
       */
      $.fn[pluginName] = function(options) {
        return this.each(function() {
          if (!$.data(this, 'plugin_' + pluginName)) {
            $.data(this, 'plugin_' + pluginName, new $.fn[pluginName].AccessibleMegaMenu(this, options))
          }
        })
      }
    
      $.fn[pluginName].AccessibleMegaMenu = AccessibleMegaMenu
    
      /* :focusable and :tabbable selectors from
         https://raw.github.com/jquery/jquery-ui/master/ui/jquery.ui.core.js */
    
      /**
       * @private
       */
      function visible(element) {
        return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function() {
          return $.css(this, 'visibility') === 'hidden'
        }).length
      }
    
      /**
       * @private
       */
      function focusable(element, isTabIndexNotNaN) {
        var map, mapName, img,
          nodeName = element.nodeName.toLowerCase()
        if ('area' === nodeName) {
          map = element.parentNode
          mapName = map.name
          if (!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
            return false
          }
          img = $('img[usemap=#' + mapName + ']')[0]
          return !!img && visible(img)
        }
        return (/input|select|textarea|button|object/.test(nodeName) ? !element.disabled :
            'a' === nodeName ?
            element.href || isTabIndexNotNaN :
            isTabIndexNotNaN) &&
          // the element and all of its ancestors must be visible
          visible(element)
      }
    
      $.extend($.expr[':'], {
        data: $.expr.createPseudo ? $.expr.createPseudo(function(dataName) {
            return function(elem) {
              return !!$.data(elem, dataName)
            }
          }) : // support: jQuery <1.8
          function(elem, i, match) {
            return !!$.data(elem, match[3])
          },
    
        focusable: function(element) {
          return focusable(element, !isNaN($.attr(element, 'tabindex')))
        },
    
        tabbable: function(element) {
          var tabIndex = $.attr(element, 'tabindex'),
            isTabIndexNaN = isNaN(tabIndex)
          return (isTabIndexNaN || tabIndex >= 0) && focusable(element, !isTabIndexNaN)
        }
      })
    }(jQuery, window, document))
    
  • URL: /components/raw/megamenu/megamenu.js
  • Filesystem Path: src/components/megamenu/megamenu.js
  • Size: 32.2 KB

There are no notes for this item.