text: /*!
   * Angular Material Design
   * https://github.com/angular/material
   * @license MIT
   * v1.1.0-master-0cd2a59
   */
  (function( window, angular, undefined ){
  "use strict";
  
  /**
   * @ngdoc module
   * @name material.components.menu
   */
  
  angular.module('material.components.menu', [
    'material.core',
    'material.components.backdrop'
  ]);
  
  
  
  MenuController.$inject = ["$mdMenu", "$attrs", "$element", "$scope", "$mdUtil", "$timeout", "$rootScope", "$q"];
  angular
      .module('material.components.menu')
      .controller('mdMenuCtrl', MenuController);
  
  /**
   * ngInject
   */
  function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout, $rootScope, $q) {
  
    var prefixer = $mdUtil.prefixer();
    var menuContainer;
    var self = this;
    var triggerElement;
  
    this.nestLevel = parseInt($attrs.mdNestLevel, 10) || 0;
  
    /**
     * Called by our linking fn to provide access to the menu-content
     * element removed during link
     */
    this.init = function init(setMenuContainer, opts) {
      opts = opts || {};
      menuContainer = setMenuContainer;
  
      // Default element for ARIA attributes has the ngClick or ngMouseenter expression
      triggerElement = $element[0].querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter']));
      triggerElement.setAttribute('aria-expanded', 'false');
  
      this.isInMenuBar = opts.isInMenuBar;
      this.nestedMenus = $mdUtil.nodesToArray(menuContainer[0].querySelectorAll('.md-nested-menu'));
  
      menuContainer.on('$mdInterimElementRemove', function() {
        self.isOpen = false;
        $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
      });
      $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
  
      var menuContainerId = 'menu_container_' + $mdUtil.nextUid();
      menuContainer.attr('id', menuContainerId);
      angular.element(triggerElement).attr({
        'aria-owns': menuContainerId,
        'aria-haspopup': 'true'
      });
  
      $scope.$on('$destroy', angular.bind(this, function() {
        this.disableHoverListener();
        $mdMenu.destroy();
      }));
  
      menuContainer.on('$destroy', function() {
        $mdMenu.destroy();
      });
    };
  
    var openMenuTimeout, menuItems, deregisterScopeListeners = [];
    this.enableHoverListener = function() {
      deregisterScopeListeners.push($rootScope.$on('$mdMenuOpen', function(event, el) {
        if (menuContainer[0].contains(el[0])) {
          self.currentlyOpenMenu = el.controller('mdMenu');
          self.isAlreadyOpening = false;
          self.currentlyOpenMenu.registerContainerProxy(self.triggerContainerProxy.bind(self));
        }
      }));
      deregisterScopeListeners.push($rootScope.$on('$mdMenuClose', function(event, el) {
        if (menuContainer[0].contains(el[0])) {
          self.currentlyOpenMenu = undefined;
        }
      }));
      menuItems = angular.element($mdUtil.nodesToArray(menuContainer[0].children[0].children));
      menuItems.on('mouseenter', self.handleMenuItemHover);
      menuItems.on('mouseleave', self.handleMenuItemMouseLeave);
    };
  
    this.disableHoverListener = function() {
      while (deregisterScopeListeners.length) {
        deregisterScopeListeners.shift()();
      }
      menuItems && menuItems.off('mouseenter', self.handleMenuItemHover);
      menuItems && menuItems.off('mouseleave', self.handleMenuItemMouseLeave);
    };
  
    this.handleMenuItemHover = function(event) {
      if (self.isAlreadyOpening) return;
      var nestedMenu = (
        event.target.querySelector('md-menu')
          || $mdUtil.getClosest(event.target, 'MD-MENU')
      );
      openMenuTimeout = $timeout(function() {
        if (nestedMenu) {
          nestedMenu = angular.element(nestedMenu).controller('mdMenu');
        }
  
        if (self.currentlyOpenMenu && self.currentlyOpenMenu != nestedMenu) {
          var closeTo = self.nestLevel + 1;
          self.currentlyOpenMenu.close(true, { closeTo: closeTo });
          self.isAlreadyOpening = !!nestedMenu;
          nestedMenu && nestedMenu.open();
        } else if (nestedMenu && !nestedMenu.isOpen && nestedMenu.open) {
          self.isAlreadyOpening = !!nestedMenu;
          nestedMenu && nestedMenu.open();
        }
      }, nestedMenu ? 100 : 250);
      var focusableTarget = event.currentTarget.querySelector('.md-button:not([disabled])');
      focusableTarget && focusableTarget.focus();
    };
  
    this.handleMenuItemMouseLeave = function() {
      if (openMenuTimeout) {
        $timeout.cancel(openMenuTimeout);
        openMenuTimeout = undefined;
      }
    };
  
  
    /**
     * Uses the $mdMenu interim element service to open the menu contents
     */
    this.open = function openMenu(ev) {
      ev && ev.stopPropagation();
      ev && ev.preventDefault();
      if (self.isOpen) return;
      self.enableHoverListener();
      self.isOpen = true;
      $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
      triggerElement = triggerElement || (ev ? ev.target : $element[0]);
      triggerElement.setAttribute('aria-expanded', 'true');
      $scope.$emit('$mdMenuOpen', $element);
      $mdMenu.show({
        scope: $scope,
        mdMenuCtrl: self,
        nestLevel: self.nestLevel,
        element: menuContainer,
        target: triggerElement,
        preserveElement: true,
        parent: 'body'
      }).finally(function() {
        triggerElement.setAttribute('aria-expanded', 'false');
        self.disableHoverListener();
      });
    };
  
    // Expose a open function to the child scope for html to use
    $scope.$mdOpenMenu = this.open;
  
    this.onIsOpenChanged = function(isOpen) {
      if (isOpen) {
        menuContainer.attr('aria-hidden', 'false');
        $element[0].classList.add('md-open');
        angular.forEach(self.nestedMenus, function(el) {
          el.classList.remove('md-open');
        });
      } else {
        menuContainer.attr('aria-hidden', 'true');
        $element[0].classList.remove('md-open');
      }
      $scope.$mdMenuIsOpen = self.isOpen;
    };
  
    this.focusMenuContainer = function focusMenuContainer() {
      var focusTarget = menuContainer[0]
        .querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
  
      if (!focusTarget) focusTarget = menuContainer[0].querySelector('.md-button');
      focusTarget.focus();
    };
  
    this.registerContainerProxy = function registerContainerProxy(handler) {
      this.containerProxy = handler;
    };
  
    this.triggerContainerProxy = function triggerContainerProxy(ev) {
      this.containerProxy && this.containerProxy(ev);
    };
  
    this.destroy = function() {
      return self.isOpen ? $mdMenu.destroy() : $q.when(false);
    };
  
    // Use the $mdMenu interim element service to close the menu contents
    this.close = function closeMenu(skipFocus, closeOpts) {
      if ( !self.isOpen ) return;
      self.isOpen = false;
      $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
  
      var eventDetails = angular.extend({}, closeOpts, { skipFocus: skipFocus });
      $scope.$emit('$mdMenuClose', $element, eventDetails);
      $mdMenu.hide(null, closeOpts);
  
      if (!skipFocus) {
        var el = self.restoreFocusTo || $element.find('button')[0];
        if (el instanceof angular.element) el = el[0];
        if (el) el.focus();
      }
    };
  
    /**
     * Build a nice object out of our string attribute which specifies the
     * target mode for left and top positioning
     */
    this.positionMode = function positionMode() {
      var attachment = ($attrs.mdPositionMode || 'target').split(' ');
  
      // If attachment is a single item, duplicate it for our second value.
      // ie. 'target' -> 'target target'
      if (attachment.length == 1) {
        attachment.push(attachment[0]);
      }
  
      return {
        left: attachment[0],
        top: attachment[1]
      };
    };
  
    /**
     * Build a nice object out of our string attribute which specifies
     * the offset of top and left in pixels.
     */
    this.offsets = function offsets() {
      var position = ($attrs.mdOffset || '0 0').split(' ').map(parseFloat);
      if (position.length == 2) {
        return {
          left: position[0],
          top: position[1]
        };
      } else if (position.length == 1) {
        return {
          top: position[0],
          left: position[0]
        };
      } else {
        throw Error('Invalid offsets specified. Please follow format <x, y> or <n>');
      }
    };
  }
  
  /**
   * @ngdoc directive
   * @name mdMenu
   * @module material.components.menu
   * @restrict E
   * @description
   *
   * Menus are elements that open when clicked. They are useful for displaying
   * additional options within the context of an action.
   *
   * Every `md-menu` must specify exactly two child elements. The first element is what is
   * left in the DOM and is used to open the menu. This element is called the trigger element.
   * The trigger element's scope has access to `$mdOpenMenu($event)`
   * which it may call to open the menu. By passing $event as argument, the
   * corresponding event is stopped from propagating up the DOM-tree.
   *
   * The second element is the `md-menu-content` element which represents the
   * contents of the menu when it is open. Typically this will contain `md-menu-item`s,
   * but you can do custom content as well.
   *
   * <hljs lang="html">
   * <md-menu>
   *  <!-- Trigger element is a md-button with an icon -->
   *  <md-button ng-click="$mdOpenMenu($event)" class="md-icon-button" aria-label="Open sample menu">
   *    <md-icon md-svg-icon="call:phone"></md-icon>
   *  </md-button>
   *  <md-menu-content>
   *    <md-menu-item><md-button ng-click="doSomething()">Do Something</md-button></md-menu-item>
   *  </md-menu-content>
   * </md-menu>
   * </hljs>
  
   * ## Sizing Menus
   *
   * The width of the menu when it is open may be specified by specifying a `width`
   * attribute on the `md-menu-content` element.
   * See the [Material Design Spec](https://material.google.com/components/menus.html#menus-simple-menus)
   * for more information.
   *
   *
   * ## Aligning Menus
   *
   * When a menu opens, it is important that the content aligns with the trigger element.
   * Failure to align menus can result in jarring experiences for users as content
   * suddenly shifts. To help with this, `md-menu` provides serveral APIs to help
   * with alignment.
   *
   * ### Target Mode
   *
   * By default, `md-menu` will attempt to align the `md-menu-content` by aligning
   * designated child elements in both the trigger and the menu content.
   *
   * To specify the alignment element in the `trigger` you can use the `md-menu-origin`
   * attribute on a child element. If no `md-menu-origin` is specified, the `md-menu`
   * will be used as the origin element.
   *
   * Similarly, the `md-menu-content` may specify a `md-menu-align-target` for a
   * `md-menu-item` to specify the node that it should try and align with.
   *
   * In this example code, we specify an icon to be our origin element, and an
   * icon in our menu content to be our alignment target. This ensures that both
   * icons are aligned when the menu opens.
   *
   * <hljs lang="html">
   * <md-menu>
   *  <md-button ng-click="$mdOpenMenu($event)" class="md-icon-button" aria-label="Open some menu">
   *    <md-icon md-menu-origin md-svg-icon="call:phone"></md-icon>
   *  </md-button>
   *  <md-menu-content>
   *    <md-menu-item>
   *      <md-button ng-click="doSomething()" aria-label="Do something">
   *        <md-icon md-menu-align-target md-svg-icon="call:phone"></md-icon>
   *        Do Something
   *      </md-button>
   *    </md-menu-item>
   *  </md-menu-content>
   * </md-menu>
   * </hljs>
   *
   * Sometimes we want to specify alignment on the right side of an element, for example
   * if we have a menu on the right side a toolbar, we want to right align our menu content.
   *
   * We can specify the origin by using the `md-position-mode` attribute on both
   * the `x` and `y` axis. Right now only the `x-axis` has more than one option.
   * You may specify the default mode of `target target` or
   * `target-right target` to specify a right-oriented alignment target. See the
   * position section of the demos for more examples.
   *
   * ### Menu Offsets
   *
   * It is sometimes unavoidable to need to have a deeper level of control for
   * the positioning of a menu to ensure perfect alignment. `md-menu` provides
   * the `md-offset` attribute to allow pixel level specificty of adjusting the
   * exact positioning.
   *
   * This offset is provided in the format of `x y` or `n` where `n` will be used
   * in both the `x` and `y` axis.
   *
   * For example, to move a menu by `2px` from the top, we can use:
   * <hljs lang="html">
   * <md-menu md-offset="2 0">
   *   <!-- menu-content -->
   * </md-menu>
   * </hljs>
   *
   * ### Auto Focus
   * By default, when a menu opens, `md-menu` focuses the first button in the menu content.
   * 
   * But sometimes you would like to focus another specific menu item instead of the first.<br/>
   * This can be done by applying the `md-autofocus` directive on the given element.
   *
   * <hljs lang="html">
   * <md-menu-item>
   *   <md-button md-autofocus ng-click="doSomething()">
   *     Auto Focus
   *   </md-button>
   * </md-menu-item>
   * </hljs>
   *
   *
   * ### Preventing close
   *
   * Sometimes you would like to be able to click on a menu item without having the menu
   * close. To do this, ngMaterial exposes the `md-prevent-menu-close` attribute which
   * can be added to a button inside a menu to stop the menu from automatically closing.
   * You can then close the menu programatically by injecting `$mdMenu` and calling 
   * `$mdMenu.hide()`.
   *
   * <hljs lang="html">
   * <md-menu-item>
   *   <md-button ng-click="doSomething()" aria-label="Do something" md-prevent-menu-close="md-prevent-menu-close">
   *     <md-icon md-menu-align-target md-svg-icon="call:phone"></md-icon>
   *     Do Something
   *   </md-button>
   * </md-menu-item>
   * </hljs>
   *
   * @usage
   * <hljs lang="html">
   * <md-menu>
   *  <md-button ng-click="$mdOpenMenu($event)" class="md-icon-button">
   *    <md-icon md-svg-icon="call:phone"></md-icon>
   *  </md-button>
   *  <md-menu-content>
   *    <md-menu-item><md-button ng-click="doSomething()">Do Something</md-button></md-menu-item>
   *  </md-menu-content>
   * </md-menu>
   * </hljs>
   *
   * @param {string} md-position-mode The position mode in the form of
   *           `x`, `y`. Default value is `target`,`target`. Right now the `x` axis
   *           also suppports `target-right`.
   * @param {string} md-offset An offset to apply to the dropdown after positioning
   *           `x`, `y`. Default value is `0`,`0`.
   *
   */
  
  MenuDirective.$inject = ["$mdUtil"];
  angular
      .module('material.components.menu')
      .directive('mdMenu', MenuDirective);
  
  /**
   * ngInject
   */
  function MenuDirective($mdUtil) {
    var INVALID_PREFIX = 'Invalid HTML for md-menu: ';
    return {
      restrict: 'E',
      require: ['mdMenu', '?^mdMenuBar'],
      controller: 'mdMenuCtrl', // empty function to be built by link
      scope: true,
      compile: compile
    };
  
    function compile(templateElement) {
      templateElement.addClass('md-menu');
      var triggerElement = templateElement.children()[0];
      var prefixer = $mdUtil.prefixer();
  
      if (!prefixer.hasAttribute(triggerElement, 'ng-click')) {
        triggerElement = triggerElement
            .querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter'])) || triggerElement;
      }
      if (triggerElement && (
        triggerElement.nodeName == 'MD-BUTTON' ||
        triggerElement.nodeName == 'BUTTON'
      ) && !triggerElement.hasAttribute('type')) {
        triggerElement.setAttribute('type', 'button');
      }
  
      if (templateElement.children().length != 2) {
        throw Error(INVALID_PREFIX + 'Expected two children elements.');
      }
  
      // Default element for ARIA attributes has the ngClick or ngMouseenter expression
      triggerElement && triggerElement.setAttribute('aria-haspopup', 'true');
  
      var nestedMenus = templateElement[0].querySelectorAll('md-menu');
      var nestingDepth = parseInt(templateElement[0].getAttribute('md-nest-level'), 10) || 0;
      if (nestedMenus) {
        angular.forEach($mdUtil.nodesToArray(nestedMenus), function(menuEl) {
          if (!menuEl.hasAttribute('md-position-mode')) {
            menuEl.setAttribute('md-position-mode', 'cascade');
          }
          menuEl.classList.add('_md-nested-menu');
          menuEl.setAttribute('md-nest-level', nestingDepth + 1);
        });
      }
      return link;
    }
  
    function link(scope, element, attr, ctrls) {
      var mdMenuCtrl = ctrls[0];
      var isInMenuBar = ctrls[1] != undefined;
      // Move everything into a md-menu-container and pass it to the controller
      var menuContainer = angular.element( '<div class="_md md-open-menu-container md-whiteframe-z2"></div>');
      var menuContents = element.children()[1];
  
      element.addClass('_md');     // private md component indicator for styling
  
      if (!menuContents.hasAttribute('role')) {
        menuContents.setAttribute('role', 'menu');
      }
      menuContainer.append(menuContents);
  
      element.on('$destroy', function() {
        menuContainer.remove();
      });
  
      element.append(menuContainer);
      menuContainer[0].style.display = 'none';
      mdMenuCtrl.init(menuContainer, { isInMenuBar: isInMenuBar });
  
    }
  }
  
  
  MenuProvider.$inject = ["$$interimElementProvider"];angular
    .module('material.components.menu')
    .provider('$mdMenu', MenuProvider);
  
  /*
   * Interim element provider for the menu.
   * Handles behavior for a menu while it is open, including:
   *    - handling animating the menu opening/closing
   *    - handling key/mouse events on the menu element
   *    - handling enabling/disabling scroll while the menu is open
   *    - handling redrawing during resizes and orientation changes
   *
   */
  
  function MenuProvider($$interimElementProvider) {
    menuDefaultOptions.$inject = ["$mdUtil", "$mdTheming", "$mdConstant", "$document", "$window", "$q", "$$rAF", "$animateCss", "$animate"];
    var MENU_EDGE_MARGIN = 8;
  
    return $$interimElementProvider('$mdMenu')
      .setDefaults({
        methods: ['target'],
        options: menuDefaultOptions
      });
  
    /* ngInject */
    function menuDefaultOptions($mdUtil, $mdTheming, $mdConstant, $document, $window, $q, $$rAF, $animateCss, $animate) {
      var prefixer = $mdUtil.prefixer();
      var animator = $mdUtil.dom.animator;
  
      return {
        parent: 'body',
        onShow: onShow,
        onRemove: onRemove,
        hasBackdrop: true,
        disableParentScroll: true,
        skipCompile: true,
        preserveScope: true,
        skipHide: true,
        themable: true
      };
  
      /**
       * Show modal backdrop element...
       * @returns {function(): void} A function that removes this backdrop
       */
      function showBackdrop(scope, element, options) {
        if (options.nestLevel) return angular.noop;
  
        // If we are not within a dialog...
        if (options.disableParentScroll && !$mdUtil.getClosest(options.target, 'MD-DIALOG')) {
          // !! DO this before creating the backdrop; since disableScrollAround()
          //    configures the scroll offset; which is used by mdBackDrop postLink()
          options.restoreScroll = $mdUtil.disableScrollAround(options.element, options.parent);
        } else {
          options.disableParentScroll = false;
        }
  
        if (options.hasBackdrop) {
          options.backdrop = $mdUtil.createBackdrop(scope, "md-menu-backdrop md-click-catcher");
  
          $animate.enter(options.backdrop, $document[0].body);
        }
  
        /**
         * Hide and destroys the backdrop created by showBackdrop()
         */
        return function hideBackdrop() {
          if (options.backdrop) options.backdrop.remove();
          if (options.disableParentScroll) options.restoreScroll();
        };
      }
  
      /**
       * Removing the menu element from the DOM and remove all associated event listeners
       * and backdrop
       */
      function onRemove(scope, element, opts) {
        opts.cleanupInteraction && opts.cleanupInteraction();
        opts.cleanupResizing();
        opts.hideBackdrop();
  
        // For navigation $destroy events, do a quick, non-animated removal,
        // but for normal closes (from clicks, etc) animate the removal
  
        return (opts.$destroy === true) ? detachAndClean() : animateRemoval().then( detachAndClean );
  
        /**
         * For normal closes, animate the removal.
         * For forced closes (like $destroy events), skip the animations
         */
        function animateRemoval() {
          return $animateCss(element, {addClass: 'md-leave'}).start();
        }
  
        /**
         * Detach the element
         */
        function detachAndClean() {
          element.removeClass('md-active');
          detachElement(element, opts);
          opts.alreadyOpen = false;
        }
  
      }
  
      /**
       * Inserts and configures the staged Menu element into the DOM, positioning it,
       * and wiring up various interaction events
       */
      function onShow(scope, element, opts) {
        sanitizeAndConfigure(opts);
  
        // Wire up theming on our menu element
        $mdTheming.inherit(opts.menuContentEl, opts.target);
  
        // Register various listeners to move menu on resize/orientation change
        opts.cleanupResizing = startRepositioningOnResize();
        opts.hideBackdrop = showBackdrop(scope, element, opts);
  
        // Return the promise for when our menu is done animating in
        return showMenu()
          .then(function(response) {
            opts.alreadyOpen = true;
            opts.cleanupInteraction = activateInteraction();
            return response;
          });
  
        /**
         * Place the menu into the DOM and call positioning related functions
         */
        function showMenu() {
          opts.parent.append(element);
          element[0].style.display = '';
  
          return $q(function(resolve) {
            var position = calculateMenuPosition(element, opts);
  
            element.removeClass('md-leave');
  
            // Animate the menu scaling, and opacity [from its position origin (default == top-left)]
            // to normal scale.
            $animateCss(element, {
              addClass: 'md-active',
              from: animator.toCss(position),
              to: animator.toCss({transform: ''})
            })
            .start()
            .then(resolve);
  
          });
        }
  
        /**
         * Check for valid opts and set some sane defaults
         */
        function sanitizeAndConfigure() {
          if (!opts.target) {
            throw Error(
              '$mdMenu.show() expected a target to animate from in options.target'
            );
          }
          angular.extend(opts, {
            alreadyOpen: false,
            isRemoved: false,
            target: angular.element(opts.target), //make sure it's not a naked dom node
            parent: angular.element(opts.parent),
            menuContentEl: angular.element(element[0].querySelector('md-menu-content'))
          });
        }
  
        /**
         * Configure various resize listeners for screen changes
         */
        function startRepositioningOnResize() {
  
          var repositionMenu = (function(target, options) {
            return $$rAF.throttle(function() {
              if (opts.isRemoved) return;
              var position = calculateMenuPosition(target, options);
  
              target.css(animator.toCss(position));
            });
          })(element, opts);
  
          $window.addEventListener('resize', repositionMenu);
          $window.addEventListener('orientationchange', repositionMenu);
  
          return function stopRepositioningOnResize() {
  
            // Disable resizing handlers
            $window.removeEventListener('resize', repositionMenu);
            $window.removeEventListener('orientationchange', repositionMenu);
  
          }
        }
  
        /**
         * Activate interaction on the menu. Wire up keyboard listerns for
         * clicks, keypresses, backdrop closing, etc.
         */
        function activateInteraction() {
          element.addClass('md-clickable');
  
          // close on backdrop click
          if (opts.backdrop) opts.backdrop.on('click', onBackdropClick);
  
          // Wire up keyboard listeners.
          // - Close on escape,
          // - focus next item on down arrow,
          // - focus prev item on up
          opts.menuContentEl.on('keydown', onMenuKeyDown);
          opts.menuContentEl[0].addEventListener('click', captureClickListener, true);
  
          // kick off initial focus in the menu on the first element
          var focusTarget = opts.menuContentEl[0]
            .querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
  
          if ( !focusTarget ) {
            var firstChild = opts.menuContentEl[0].firstElementChild;
  
            focusTarget = firstChild && (firstChild.querySelector('.md-button:not([disabled])') || firstChild.firstElementChild);
          }
  
          focusTarget && focusTarget.focus();
  
          return function cleanupInteraction() {
            element.removeClass('md-clickable');
            if (opts.backdrop) opts.backdrop.off('click', onBackdropClick);
            opts.menuContentEl.off('keydown', onMenuKeyDown);
            opts.menuContentEl[0].removeEventListener('click', captureClickListener, true);
          };
  
          // ************************************
          // internal functions
          // ************************************
  
          function onMenuKeyDown(ev) {
            var handled;
            switch (ev.keyCode) {
              case $mdConstant.KEY_CODE.ESCAPE:
                opts.mdMenuCtrl.close(false, { closeAll: true });
                handled = true;
                break;
              case $mdConstant.KEY_CODE.UP_ARROW:
                if (!focusMenuItem(ev, opts.menuContentEl, opts, -1) && !opts.nestLevel) {
                  opts.mdMenuCtrl.triggerContainerProxy(ev);
                }
                handled = true;
                break;
              case $mdConstant.KEY_CODE.DOWN_ARROW:
                if (!focusMenuItem(ev, opts.menuContentEl, opts, 1) && !opts.nestLevel) {
                  opts.mdMenuCtrl.triggerContainerProxy(ev);
                }
                handled = true;
                break;
              case $mdConstant.KEY_CODE.LEFT_ARROW:
                if (opts.nestLevel) {
                  opts.mdMenuCtrl.close();
                } else {
                  opts.mdMenuCtrl.triggerContainerProxy(ev);
                }
                handled = true;
                break;
              case $mdConstant.KEY_CODE.RIGHT_ARROW:
                var parentMenu = $mdUtil.getClosest(ev.target, 'MD-MENU');
                if (parentMenu && parentMenu != opts.parent[0]) {
                  ev.target.click();
                } else {
                  opts.mdMenuCtrl.triggerContainerProxy(ev);
                }
                handled = true;
                break;
            }
            if (handled) {
              ev.preventDefault();
              ev.stopImmediatePropagation();
            }
          }
  
          function onBackdropClick(e) {
            e.preventDefault();
            e.stopPropagation();
            scope.$apply(function() {
              opts.mdMenuCtrl.close(true, { closeAll: true });
            });
          }
  
          // Close menu on menu item click, if said menu-item is not disabled
          function captureClickListener(e) {
            var target = e.target;
            // Traverse up the event until we get to the menuContentEl to see if
            // there is an ng-click and that the ng-click is not disabled
            do {
              if (target == opts.menuContentEl[0]) return;
              if ((hasAnyAttribute(target, ['ng-click', 'ng-href', 'ui-sref']) ||
                  target.nodeName == 'BUTTON' || target.nodeName == 'MD-BUTTON') && !hasAnyAttribute(target, ['md-prevent-menu-close'])) {
                var closestMenu = $mdUtil.getClosest(target, 'MD-MENU');
                if (!target.hasAttribute('disabled') && (!closestMenu || closestMenu == opts.parent[0])) {
                  close();
                }
                break;
              }
            } while (target = target.parentNode)
  
            function close() {
              scope.$apply(function() {
                opts.mdMenuCtrl.close(true, { closeAll: true });
              });
            }
  
            function hasAnyAttribute(target, attrs) {
              if (!target) return false;
  
              for (var i = 0, attr; attr = attrs[i]; ++i) {
                if (prefixer.hasAttribute(target, attr)) {
                  return true;
                }
              }
  
              return false;
            }
          }
  
        }
      }
  
      /**
       * Takes a keypress event and focuses the next/previous menu
       * item from the emitting element
       * @param {event} e - The origin keypress event
       * @param {angular.element} menuEl - The menu element
       * @param {object} opts - The interim element options for the mdMenu
       * @param {number} direction - The direction to move in (+1 = next, -1 = prev)
       */
      function focusMenuItem(e, menuEl, opts, direction) {
        var currentItem = $mdUtil.getClosest(e.target, 'MD-MENU-ITEM');
  
        var items = $mdUtil.nodesToArray(menuEl[0].children);
        var currentIndex = items.indexOf(currentItem);
  
        // Traverse through our elements in the specified direction (+/-1) and try to
        // focus them until we find one that accepts focus
        var didFocus;
        for (var i = currentIndex + direction; i >= 0 && i < items.length; i = i + direction) {
          var focusTarget = items[i].querySelector('.md-button');
          didFocus = attemptFocus(focusTarget);
          if (didFocus) {
            break;
          }
        }
        return didFocus;
      }
  
      /**
       * Attempts to focus an element. Checks whether that element is the currently
       * focused element after attempting.
       * @param {HTMLElement} el - the element to attempt focus on
       * @returns {bool} - whether the element was successfully focused
       */
      function attemptFocus(el) {
        if (el && el.getAttribute('tabindex') != -1) {
          el.focus();
          return ($document[0].activeElement == el);
        }
      }
  
      /**
       * Use browser to remove this element without triggering a $destroy event
       */
      function detachElement(element, opts) {
        if (!opts.preserveElement) {
          if (toNode(element).parentNode === toNode(opts.parent)) {
            toNode(opts.parent).removeChild(toNode(element));
          }
        } else {
          toNode(element).style.display = 'none';
        }
      }
  
      /**
       * Computes menu position and sets the style on the menu container
       * @param {HTMLElement} el - the menu container element
       * @param {object} opts - the interim element options object
       */
      function calculateMenuPosition(el, opts) {
  
        var containerNode = el[0],
          openMenuNode = el[0].firstElementChild,
          openMenuNodeRect = openMenuNode.getBoundingClientRect(),
          boundryNode = $document[0].body,
          boundryNodeRect = boundryNode.getBoundingClientRect();
  
        var menuStyle = $window.getComputedStyle(openMenuNode);
  
        var originNode = opts.target[0].querySelector(prefixer.buildSelector('md-menu-origin')) || opts.target[0],
          originNodeRect = originNode.getBoundingClientRect();
  
        var bounds = {
          left: boundryNodeRect.left + MENU_EDGE_MARGIN,
          top: Math.max(boundryNodeRect.top, 0) + MENU_EDGE_MARGIN,
          bottom: Math.max(boundryNodeRect.bottom, Math.max(boundryNodeRect.top, 0) + boundryNodeRect.height) - MENU_EDGE_MARGIN,
          right: boundryNodeRect.right - MENU_EDGE_MARGIN
        };
  
        var alignTarget, alignTargetRect = { top:0, left : 0, right:0, bottom:0 }, existingOffsets  = { top:0, left : 0, right:0, bottom:0  };
        var positionMode = opts.mdMenuCtrl.positionMode();
  
        if (positionMode.top == 'target' || positionMode.left == 'target' || positionMode.left == 'target-right') {
          alignTarget = firstVisibleChild();
          if ( alignTarget ) {
            // TODO: Allow centering on an arbitrary node, for now center on first menu-item's child
            alignTarget = alignTarget.firstElementChild || alignTarget;
            alignTarget = alignTarget.querySelector(prefixer.buildSelector('md-menu-align-target')) || alignTarget;
            alignTargetRect = alignTarget.getBoundingClientRect();
  
            existingOffsets = {
              top: parseFloat(containerNode.style.top || 0),
              left: parseFloat(containerNode.style.left || 0)
            };
          }
        }
  
        var position = {};
        var transformOrigin = 'top ';
  
        switch (positionMode.top) {
          case 'target':
            position.top = existingOffsets.top + originNodeRect.top - alignTargetRect.top;
            break;
          case 'cascade':
            position.top = originNodeRect.top - parseFloat(menuStyle.paddingTop) - originNode.style.top;
            break;
          case 'bottom':
            position.top = originNodeRect.top + originNodeRect.height;
            break;
          default:
            throw new Error('Invalid target mode "' + positionMode.top + '" specified for md-menu on Y axis.');
        }
  
        var rtl = ($mdUtil.bidi() == 'rtl');
  
        switch (positionMode.left) {
          case 'target':
            position.left = existingOffsets.left + originNodeRect.left - alignTargetRect.left;
            transformOrigin += rtl ? 'right'  : 'left';
            break;
          case 'target-left':
            position.left = originNodeRect.left;
            transformOrigin += 'left';
            break;
          case 'target-right':
            position.left = originNodeRect.right - openMenuNodeRect.width + (openMenuNodeRect.right - alignTargetRect.right);
            transformOrigin += 'right';
            break;
          case 'cascade':
            var willFitRight = rtl ? (originNodeRect.left - openMenuNodeRect.width) < bounds.left : (originNodeRect.right + openMenuNodeRect.width) < bounds.right;
            position.left = willFitRight ? originNodeRect.right - originNode.style.left : originNodeRect.left - originNode.style.left - openMenuNodeRect.width;
            transformOrigin += willFitRight ? 'left' : 'right';
            break;
          case 'right':
            if (rtl) {
              position.left = originNodeRect.right - originNodeRect.width;
              transformOrigin += 'left';
            } else {
              position.left = originNodeRect.right - openMenuNodeRect.width;
              transformOrigin += 'right';
            }
            break;
          case 'left':
            if (rtl) {
              position.left = originNodeRect.right - openMenuNodeRect.width;
              transformOrigin += 'right';
            } else {
              position.left = originNodeRect.left;
              transformOrigin += 'left';
            }
            break;
          default:
            throw new Error('Invalid target mode "' + positionMode.left + '" specified for md-menu on X axis.');
        }
  
        var offsets = opts.mdMenuCtrl.offsets();
        position.top += offsets.top;
        position.left += offsets.left;
  
        clamp(position);
  
        var scaleX = Math.round(100 * Math.min(originNodeRect.width / containerNode.offsetWidth, 1.0)) / 100;
        var scaleY = Math.round(100 * Math.min(originNodeRect.height / containerNode.offsetHeight, 1.0)) / 100;
  
        return {
          top: Math.round(position.top),
          left: Math.round(position.left),
          // Animate a scale out if we aren't just repositioning
          transform: !opts.alreadyOpen ? $mdUtil.supplant('scale({0},{1})', [scaleX, scaleY]) : undefined,
          transformOrigin: transformOrigin
        };
  
        /**
         * Clamps the repositioning of the menu within the confines of
         * bounding element (often the screen/body)
         */
        function clamp(pos) {
          pos.top = Math.max(Math.min(pos.top, bounds.bottom - containerNode.offsetHeight), bounds.top);
          pos.left = Math.max(Math.min(pos.left, bounds.right - containerNode.offsetWidth), bounds.left);
        }
  
        /**
         * Gets the first visible child in the openMenuNode
         * Necessary incase menu nodes are being dynamically hidden
         */
        function firstVisibleChild() {
          for (var i = 0; i < openMenuNode.children.length; ++i) {
            if ($window.getComputedStyle(openMenuNode.children[i]).display != 'none') {
              return openMenuNode.children[i];
            }
          }
        }
      }
    }
    function toNode(el) {
      if (el instanceof angular.element) {
        el = el[0];
      }
      return el;
    }
  }
  
  })(window, window.angular);