text: angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
  
  .constant('uibDropdownConfig', {
    appendToOpenClass: 'uib-dropdown-open',
    openClass: 'open'
  })
  
  .service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
    var openScope = null;
  
    this.open = function(dropdownScope, element) {
      if (!openScope) {
        $document.on('click', closeDropdown);
      }
  
      if (openScope && openScope !== dropdownScope) {
        openScope.isOpen = false;
      }
  
      openScope = dropdownScope;
    };
  
    this.close = function(dropdownScope, element) {
      if (openScope === dropdownScope) {
        openScope = null;
        $document.off('click', closeDropdown);
        $document.off('keydown', this.keybindFilter);
      }
    };
  
    var closeDropdown = function(evt) {
      // This method may still be called during the same mouse event that
      // unbound this event handler. So check openScope before proceeding.
      if (!openScope) { return; }
  
      if (evt && openScope.getAutoClose() === 'disabled') { return; }
  
      if (evt && evt.which === 3) { return; }
  
      var toggleElement = openScope.getToggleElement();
      if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
        return;
      }
  
      var dropdownElement = openScope.getDropdownElement();
      if (evt && openScope.getAutoClose() === 'outsideClick' &&
        dropdownElement && dropdownElement[0].contains(evt.target)) {
        return;
      }
  
      openScope.focusToggleElement();
      openScope.isOpen = false;
  
      if (!$rootScope.$$phase) {
        openScope.$apply();
      }
    };
  
    this.keybindFilter = function(evt) {
      var dropdownElement = openScope.getDropdownElement();
      var toggleElement = openScope.getToggleElement();
      var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);
      var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);
      if (evt.which === 27) {
        evt.stopPropagation();
        openScope.focusToggleElement();
        closeDropdown();
      } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
        evt.preventDefault();
        evt.stopPropagation();
        openScope.focusDropdownEntry(evt.which);
      }
    };
  }])
  
  .controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
    var self = this,
      scope = $scope.$new(), // create a child scope so we are not polluting original one
      templateScope,
      appendToOpenClass = dropdownConfig.appendToOpenClass,
      openClass = dropdownConfig.openClass,
      getIsOpen,
      setIsOpen = angular.noop,
      toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
      appendToBody = false,
      appendTo = null,
      keynavEnabled = false,
      selectedOption = null,
      body = $document.find('body');
  
    $element.addClass('dropdown');
  
    this.init = function() {
      if ($attrs.isOpen) {
        getIsOpen = $parse($attrs.isOpen);
        setIsOpen = getIsOpen.assign;
  
        $scope.$watch(getIsOpen, function(value) {
          scope.isOpen = !!value;
        });
      }
  
      if (angular.isDefined($attrs.dropdownAppendTo)) {
        var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
        if (appendToEl) {
          appendTo = angular.element(appendToEl);
        }
      }
  
      appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
      keynavEnabled = angular.isDefined($attrs.keyboardNav);
  
      if (appendToBody && !appendTo) {
        appendTo = body;
      }
  
      if (appendTo && self.dropdownMenu) {
        appendTo.append(self.dropdownMenu);
        $element.on('$destroy', function handleDestroyEvent() {
          self.dropdownMenu.remove();
        });
      }
    };
  
    this.toggle = function(open) {
      scope.isOpen = arguments.length ? !!open : !scope.isOpen;
      if (angular.isFunction(setIsOpen)) {
        setIsOpen(scope, scope.isOpen);
      }
  
      return scope.isOpen;
    };
  
    // Allow other directives to watch status
    this.isOpen = function() {
      return scope.isOpen;
    };
  
    scope.getToggleElement = function() {
      return self.toggleElement;
    };
  
    scope.getAutoClose = function() {
      return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
    };
  
    scope.getElement = function() {
      return $element;
    };
  
    scope.isKeynavEnabled = function() {
      return keynavEnabled;
    };
  
    scope.focusDropdownEntry = function(keyCode) {
      var elems = self.dropdownMenu ? //If append to body is used.
        angular.element(self.dropdownMenu).find('a') :
        $element.find('ul').eq(0).find('a');
  
      switch (keyCode) {
        case 40: {
          if (!angular.isNumber(self.selectedOption)) {
            self.selectedOption = 0;
          } else {
            self.selectedOption = self.selectedOption === elems.length - 1 ?
              self.selectedOption :
              self.selectedOption + 1;
          }
          break;
        }
        case 38: {
          if (!angular.isNumber(self.selectedOption)) {
            self.selectedOption = elems.length - 1;
          } else {
            self.selectedOption = self.selectedOption === 0 ?
              0 : self.selectedOption - 1;
          }
          break;
        }
      }
      elems[self.selectedOption].focus();
    };
  
    scope.getDropdownElement = function() {
      return self.dropdownMenu;
    };
  
    scope.focusToggleElement = function() {
      if (self.toggleElement) {
        self.toggleElement[0].focus();
      }
    };
  
    scope.$watch('isOpen', function(isOpen, wasOpen) {
      if (appendTo && self.dropdownMenu) {
        var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
          css,
          rightalign,
          scrollbarPadding,
          scrollbarWidth = 0;
  
        css = {
          top: pos.top + 'px',
          display: isOpen ? 'block' : 'none'
        };
  
        rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
        if (!rightalign) {
          css.left = pos.left + 'px';
          css.right = 'auto';
        } else {
          css.left = 'auto';
          scrollbarPadding = $position.scrollbarPadding(appendTo);
  
          if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
            scrollbarWidth = scrollbarPadding.scrollbarWidth;
          }
  
          css.right = window.innerWidth - scrollbarWidth -
            (pos.left + $element.prop('offsetWidth')) + 'px';
        }
  
        // Need to adjust our positioning to be relative to the appendTo container
        // if it's not the body element
        if (!appendToBody) {
          var appendOffset = $position.offset(appendTo);
  
          css.top = pos.top - appendOffset.top + 'px';
  
          if (!rightalign) {
            css.left = pos.left - appendOffset.left + 'px';
          } else {
            css.right = window.innerWidth -
              (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
          }
        }
  
        self.dropdownMenu.css(css);
      }
  
      var openContainer = appendTo ? appendTo : $element;
      var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);
  
      if (hasOpenClass === !isOpen) {
        $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
          if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
            toggleInvoker($scope, { open: !!isOpen });
          }
        });
      }
  
      if (isOpen) {
        if (self.dropdownMenuTemplateUrl) {
          $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
            templateScope = scope.$new();
            $compile(tplContent.trim())(templateScope, function(dropdownElement) {
              var newEl = dropdownElement;
              self.dropdownMenu.replaceWith(newEl);
              self.dropdownMenu = newEl;
              $document.on('keydown', uibDropdownService.keybindFilter);
            });
          });
        } else {
          $document.on('keydown', uibDropdownService.keybindFilter);
        }
  
        scope.focusToggleElement();
        uibDropdownService.open(scope, $element);
      } else {
        uibDropdownService.close(scope, $element);
        if (self.dropdownMenuTemplateUrl) {
          if (templateScope) {
            templateScope.$destroy();
          }
          var newEl = angular.element('<ul class="dropdown-menu"></ul>');
          self.dropdownMenu.replaceWith(newEl);
          self.dropdownMenu = newEl;
        }
  
        self.selectedOption = null;
      }
  
      if (angular.isFunction(setIsOpen)) {
        setIsOpen($scope, isOpen);
      }
    });
  }])
  
  .directive('uibDropdown', function() {
    return {
      controller: 'UibDropdownController',
      link: function(scope, element, attrs, dropdownCtrl) {
        dropdownCtrl.init();
      }
    };
  })
  
  .directive('uibDropdownMenu', function() {
    return {
      restrict: 'A',
      require: '?^uibDropdown',
      link: function(scope, element, attrs, dropdownCtrl) {
        if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
          return;
        }
  
        element.addClass('dropdown-menu');
  
        var tplUrl = attrs.templateUrl;
        if (tplUrl) {
          dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
        }
  
        if (!dropdownCtrl.dropdownMenu) {
          dropdownCtrl.dropdownMenu = element;
        }
      }
    };
  })
  
  .directive('uibDropdownToggle', function() {
    return {
      require: '?^uibDropdown',
      link: function(scope, element, attrs, dropdownCtrl) {
        if (!dropdownCtrl) {
          return;
        }
  
        element.addClass('dropdown-toggle');
  
        dropdownCtrl.toggleElement = element;
  
        var toggleDropdown = function(event) {
          event.preventDefault();
  
          if (!element.hasClass('disabled') && !attrs.disabled) {
            scope.$apply(function() {
              dropdownCtrl.toggle();
            });
          }
        };
  
        element.bind('click', toggleDropdown);
  
        // WAI-ARIA
        element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
        scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
          element.attr('aria-expanded', !!isOpen);
        });
  
        scope.$on('$destroy', function() {
          element.unbind('click', toggleDropdown);
        });
      }
    };
  });
  