arnaucube
arnaucube/socketioMEANseedPublic Self-hosted repository
socketioMEANseed/www/bower_components/angular-material/angular-material.js
www/bower_components/angular-material/angular-material.jsRaw
    1/*!
    2 * Angular Material Design
    3 * https://github.com/angular/material
    4 * @license MIT
    5 * v1.1.3
    6 */
    7(function( window, angular, undefined ){
    8"use strict";
    9
   10(function(){
   11"use strict";
   12
   13angular.module('ngMaterial', ["ng","ngAnimate","ngAria","material.core","material.core.gestures","material.core.interaction","material.core.layout","material.core.meta","material.core.theming.palette","material.core.theming","material.core.animate","material.components.autocomplete","material.components.backdrop","material.components.bottomSheet","material.components.button","material.components.card","material.components.checkbox","material.components.chips","material.components.colors","material.components.content","material.components.datepicker","material.components.dialog","material.components.divider","material.components.fabActions","material.components.fabShared","material.components.fabSpeedDial","material.components.fabToolbar","material.components.gridList","material.components.icon","material.components.input","material.components.list","material.components.menu","material.components.menuBar","material.components.panel","material.components.navBar","material.components.progressCircular","material.components.progressLinear","material.components.radioButton","material.components.select","material.components.showHide","material.components.sidenav","material.components.slider","material.components.sticky","material.components.subheader","material.components.swipe","material.components.switch","material.components.toast","material.components.tabs","material.components.toolbar","material.components.tooltip","material.components.truncate","material.components.virtualRepeat","material.components.whiteframe"]);
   14})();
   15(function(){
   16"use strict";
   17
   18/**
   19 * Initialization function that validates environment
   20 * requirements.
   21 */
   22DetectNgTouch.$inject = ["$log", "$injector"];
   23MdCoreConfigure.$inject = ["$provide", "$mdThemingProvider"];
   24rAFDecorator.$inject = ["$delegate"];
   25qDecorator.$inject = ["$delegate"];
   26angular
   27  .module('material.core', [
   28    'ngAnimate',
   29    'material.core.animate',
   30    'material.core.layout',
   31    'material.core.interaction',
   32    'material.core.gestures',
   33    'material.core.theming'
   34  ])
   35  .config(MdCoreConfigure)
   36  .run(DetectNgTouch);
   37
   38
   39/**
   40 * Detect if the ng-Touch module is also being used.
   41 * Warn if detected.
   42 * @ngInject
   43 */
   44function DetectNgTouch($log, $injector) {
   45  if ( $injector.has('$swipe') ) {
   46    var msg = "" +
   47      "You are using the ngTouch module. \n" +
   48      "Angular Material already has mobile click, tap, and swipe support... \n" +
   49      "ngTouch is not supported with Angular Material!";
   50    $log.warn(msg);
   51  }
   52}
   53
   54/**
   55 * @ngInject
   56 */
   57function MdCoreConfigure($provide, $mdThemingProvider) {
   58
   59  $provide.decorator('$$rAF', ['$delegate', rAFDecorator]);
   60  $provide.decorator('$q', ['$delegate', qDecorator]);
   61
   62  $mdThemingProvider.theme('default')
   63    .primaryPalette('indigo')
   64    .accentPalette('pink')
   65    .warnPalette('deep-orange')
   66    .backgroundPalette('grey');
   67}
   68
   69/**
   70 * @ngInject
   71 */
   72function rAFDecorator($delegate) {
   73  /**
   74   * Use this to throttle events that come in often.
   75   * The throttled function will always use the *last* invocation before the
   76   * coming frame.
   77   *
   78   * For example, window resize events that fire many times a second:
   79   * If we set to use an raf-throttled callback on window resize, then
   80   * our callback will only be fired once per frame, with the last resize
   81   * event that happened before that frame.
   82   *
   83   * @param {function} callback function to debounce
   84   */
   85  $delegate.throttle = function(cb) {
   86    var queuedArgs, alreadyQueued, queueCb, context;
   87    return function debounced() {
   88      queuedArgs = arguments;
   89      context = this;
   90      queueCb = cb;
   91      if (!alreadyQueued) {
   92        alreadyQueued = true;
   93        $delegate(function() {
   94          queueCb.apply(context, Array.prototype.slice.call(queuedArgs));
   95          alreadyQueued = false;
   96        });
   97      }
   98    };
   99  };
  100  return $delegate;
  101}
  102
  103/**
  104 * @ngInject
  105 */
  106function qDecorator($delegate) {
  107  /**
  108   * Adds a shim for $q.resolve for Angular version that don't have it,
  109   * so we don't have to think about it.
  110   *
  111   * via https://github.com/angular/angular.js/pull/11987
  112   */
  113
  114  // TODO(crisbeto): this won't be necessary once we drop Angular 1.3
  115  if (!$delegate.resolve) {
  116    $delegate.resolve = $delegate.when;
  117  }
  118  return $delegate;
  119}
  120
  121})();
  122(function(){
  123"use strict";
  124
  125
  126MdAutofocusDirective.$inject = ["$parse"];angular.module('material.core')
  127  .directive('mdAutofocus', MdAutofocusDirective)
  128
  129  // Support the deprecated md-auto-focus and md-sidenav-focus as well
  130  .directive('mdAutoFocus', MdAutofocusDirective)
  131  .directive('mdSidenavFocus', MdAutofocusDirective);
  132
  133/**
  134 * @ngdoc directive
  135 * @name mdAutofocus
  136 * @module material.core.util
  137 *
  138 * @description
  139 *
  140 * `[md-autofocus]` provides an optional way to identify the focused element when a `$mdDialog`,
  141 * `$mdBottomSheet`, `$mdMenu` or `$mdSidenav` opens or upon page load for input-like elements.
  142 *
  143 * When one of these opens, it will find the first nested element with the `[md-autofocus]`
  144 * attribute directive and optional expression. An expression may be specified as the directive
  145 * value to enable conditional activation of the autofocus.
  146 *
  147 * @usage
  148 *
  149 * ### Dialog
  150 * <hljs lang="html">
  151 * <md-dialog>
  152 *   <form>
  153 *     <md-input-container>
  154 *       <label for="testInput">Label</label>
  155 *       <input id="testInput" type="text" md-autofocus>
  156 *     </md-input-container>
  157 *   </form>
  158 * </md-dialog>
  159 * </hljs>
  160 *
  161 * ### Bottomsheet
  162 * <hljs lang="html">
  163 * <md-bottom-sheet class="md-list md-has-header">
  164 *  <md-subheader>Comment Actions</md-subheader>
  165 *  <md-list>
  166 *    <md-list-item ng-repeat="item in items">
  167 *
  168 *      <md-button md-autofocus="$index == 2">
  169 *        <md-icon md-svg-src="{{item.icon}}"></md-icon>
  170 *        <span class="md-inline-list-icon-label">{{ item.name }}</span>
  171 *      </md-button>
  172 *
  173 *    </md-list-item>
  174 *  </md-list>
  175 * </md-bottom-sheet>
  176 * </hljs>
  177 *
  178 * ### Autocomplete
  179 * <hljs lang="html">
  180 *   <md-autocomplete
  181 *       md-autofocus
  182 *       md-selected-item="selectedItem"
  183 *       md-search-text="searchText"
  184 *       md-items="item in getMatches(searchText)"
  185 *       md-item-text="item.display">
  186 *     <span md-highlight-text="searchText">{{item.display}}</span>
  187 *   </md-autocomplete>
  188 * </hljs>
  189 *
  190 * ### Sidenav
  191 * <hljs lang="html">
  192 * <div layout="row" ng-controller="MyController">
  193 *   <md-sidenav md-component-id="left" class="md-sidenav-left">
  194 *     Left Nav!
  195 *   </md-sidenav>
  196 *
  197 *   <md-content>
  198 *     Center Content
  199 *     <md-button ng-click="openLeftMenu()">
  200 *       Open Left Menu
  201 *     </md-button>
  202 *   </md-content>
  203 *
  204 *   <md-sidenav md-component-id="right"
  205 *     md-is-locked-open="$mdMedia('min-width: 333px')"
  206 *     class="md-sidenav-right">
  207 *     <form>
  208 *       <md-input-container>
  209 *         <label for="testInput">Test input</label>
  210 *         <input id="testInput" type="text"
  211 *                ng-model="data" md-autofocus>
  212 *       </md-input-container>
  213 *     </form>
  214 *   </md-sidenav>
  215 * </div>
  216 * </hljs>
  217 **/
  218function MdAutofocusDirective($parse) {
  219  return {
  220    restrict: 'A',
  221    link: {
  222      pre: preLink
  223    }
  224  };
  225
  226  function preLink(scope, element, attr) {
  227    var attrExp = attr.mdAutoFocus || attr.mdAutofocus || attr.mdSidenavFocus;
  228
  229    // Initially update the expression by manually parsing the expression as per $watch source.
  230    updateExpression($parse(attrExp)(scope));
  231
  232    // Only watch the expression if it is not empty.
  233    if (attrExp) {
  234      scope.$watch(attrExp, updateExpression);
  235    }
  236
  237    /**
  238     * Updates the autofocus class which is used to determine whether the attribute
  239     * expression evaluates to true or false.
  240     * @param {string|boolean} value Attribute Value
  241     */
  242    function updateExpression(value) {
  243
  244      // Rather than passing undefined to the jqLite toggle class function we explicitly set the
  245      // value to true. Otherwise the class will be just toggled instead of being forced.
  246      if (angular.isUndefined(value)) {
  247        value = true;
  248      }
  249
  250      element.toggleClass('md-autofocus', !!value);
  251    }
  252  }
  253
  254}
  255
  256})();
  257(function(){
  258"use strict";
  259
  260/**
  261 * @ngdoc module
  262 * @name material.core.colorUtil
  263 * @description
  264 * Color Util
  265 */
  266angular
  267  .module('material.core')
  268  .factory('$mdColorUtil', ColorUtilFactory);
  269
  270function ColorUtilFactory() {
  271  /**
  272   * Converts hex value to RGBA string
  273   * @param color {string}
  274   * @returns {string}
  275   */
  276  function hexToRgba (color) {
  277    var hex   = color[ 0 ] === '#' ? color.substr(1) : color,
  278      dig   = hex.length / 3,
  279      red   = hex.substr(0, dig),
  280      green = hex.substr(dig, dig),
  281      blue  = hex.substr(dig * 2);
  282    if (dig === 1) {
  283      red += red;
  284      green += green;
  285      blue += blue;
  286    }
  287    return 'rgba(' + parseInt(red, 16) + ',' + parseInt(green, 16) + ',' + parseInt(blue, 16) + ',0.1)';
  288  }
  289
  290  /**
  291   * Converts rgba value to hex string
  292   * @param color {string}
  293   * @returns {string}
  294   */
  295  function rgbaToHex(color) {
  296    color = color.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
  297
  298    var hex = (color && color.length === 4) ? "#" +
  299    ("0" + parseInt(color[1],10).toString(16)).slice(-2) +
  300    ("0" + parseInt(color[2],10).toString(16)).slice(-2) +
  301    ("0" + parseInt(color[3],10).toString(16)).slice(-2) : '';
  302
  303    return hex.toUpperCase();
  304  }
  305
  306  /**
  307   * Converts an RGB color to RGBA
  308   * @param color {string}
  309   * @returns {string}
  310   */
  311  function rgbToRgba (color) {
  312    return color.replace(')', ', 0.1)').replace('(', 'a(');
  313  }
  314
  315  /**
  316   * Converts an RGBA color to RGB
  317   * @param color {string}
  318   * @returns {string}
  319   */
  320  function rgbaToRgb (color) {
  321    return color
  322      ? color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')')
  323      : 'rgb(0,0,0)';
  324  }
  325
  326  return {
  327    rgbaToHex: rgbaToHex,
  328    hexToRgba: hexToRgba,
  329    rgbToRgba: rgbToRgba,
  330    rgbaToRgb: rgbaToRgb
  331  };
  332}
  333
  334})();
  335(function(){
  336"use strict";
  337
  338angular.module('material.core')
  339.factory('$mdConstant', MdConstantFactory);
  340
  341/**
  342 * Factory function that creates the grab-bag $mdConstant service.
  343 * @ngInject
  344 */
  345function MdConstantFactory() {
  346
  347  var prefixTestEl = document.createElement('div');
  348  var vendorPrefix = getVendorPrefix(prefixTestEl);
  349  var isWebkit = /webkit/i.test(vendorPrefix);
  350  var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g;
  351
  352  function vendorProperty(name) {
  353    // Add a dash between the prefix and name, to be able to transform the string into camelcase.
  354    var prefixedName = vendorPrefix + '-' + name;
  355    var ucPrefix = camelCase(prefixedName);
  356    var lcPrefix = ucPrefix.charAt(0).toLowerCase() + ucPrefix.substring(1);
  357
  358    return hasStyleProperty(prefixTestEl, name)     ? name     :       // The current browser supports the un-prefixed property
  359           hasStyleProperty(prefixTestEl, ucPrefix) ? ucPrefix :       // The current browser only supports the prefixed property.
  360           hasStyleProperty(prefixTestEl, lcPrefix) ? lcPrefix : name; // Some browsers are only supporting the prefix in lowercase.
  361  }
  362
  363  function hasStyleProperty(testElement, property) {
  364    return angular.isDefined(testElement.style[property]);
  365  }
  366
  367  function camelCase(input) {
  368    return input.replace(SPECIAL_CHARS_REGEXP, function(matches, separator, letter, offset) {
  369      return offset ? letter.toUpperCase() : letter;
  370    });
  371  }
  372
  373  function getVendorPrefix(testElement) {
  374    var prop, match;
  375    var vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/;
  376
  377    for (prop in testElement.style) {
  378      if (match = vendorRegex.exec(prop)) {
  379        return match[0];
  380      }
  381    }
  382  }
  383
  384  var self = {
  385    isInputKey : function(e) { return (e.keyCode >= 31 && e.keyCode <= 90); },
  386    isNumPadKey : function (e){ return (3 === e.location && e.keyCode >= 97 && e.keyCode <= 105); },
  387    isNavigationKey : function(e) {
  388      var kc = self.KEY_CODE, NAVIGATION_KEYS =  [kc.SPACE, kc.ENTER, kc.UP_ARROW, kc.DOWN_ARROW];
  389      return (NAVIGATION_KEYS.indexOf(e.keyCode) != -1);
  390    },
  391
  392    /**
  393     * Maximum size, in pixels, that can be explicitly set to an element. The actual value varies
  394     * between browsers, but IE11 has the very lowest size at a mere 1,533,917px. Ideally we could
  395     * compute this value, but Firefox always reports an element to have a size of zero if it
  396     * goes over the max, meaning that we'd have to binary search for the value.
  397     */
  398    ELEMENT_MAX_PIXELS: 1533917,
  399
  400    /**
  401     * Priority for a directive that should run before the directives from ngAria.
  402     */
  403    BEFORE_NG_ARIA: 210,
  404
  405    /**
  406     * Common Keyboard actions and their associated keycode.
  407     */
  408    KEY_CODE: {
  409      COMMA: 188,
  410      SEMICOLON : 186,
  411      ENTER: 13,
  412      ESCAPE: 27,
  413      SPACE: 32,
  414      PAGE_UP: 33,
  415      PAGE_DOWN: 34,
  416      END: 35,
  417      HOME: 36,
  418      LEFT_ARROW : 37,
  419      UP_ARROW : 38,
  420      RIGHT_ARROW : 39,
  421      DOWN_ARROW : 40,
  422      TAB : 9,
  423      BACKSPACE: 8,
  424      DELETE: 46
  425    },
  426
  427    /**
  428     * Vendor prefixed CSS properties to be used to support the given functionality in older browsers
  429     * as well.
  430     */
  431    CSS: {
  432      /* Constants */
  433      TRANSITIONEND: 'transitionend' + (isWebkit ? ' webkitTransitionEnd' : ''),
  434      ANIMATIONEND: 'animationend' + (isWebkit ? ' webkitAnimationEnd' : ''),
  435
  436      TRANSFORM: vendorProperty('transform'),
  437      TRANSFORM_ORIGIN: vendorProperty('transformOrigin'),
  438      TRANSITION: vendorProperty('transition'),
  439      TRANSITION_DURATION: vendorProperty('transitionDuration'),
  440      ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'),
  441      ANIMATION_DURATION: vendorProperty('animationDuration'),
  442      ANIMATION_NAME: vendorProperty('animationName'),
  443      ANIMATION_TIMING: vendorProperty('animationTimingFunction'),
  444      ANIMATION_DIRECTION: vendorProperty('animationDirection')
  445    },
  446
  447    /**
  448     * As defined in core/style/variables.scss
  449     *
  450     * $layout-breakpoint-xs:     600px !default;
  451     * $layout-breakpoint-sm:     960px !default;
  452     * $layout-breakpoint-md:     1280px !default;
  453     * $layout-breakpoint-lg:     1920px !default;
  454     *
  455     */
  456    MEDIA: {
  457      'xs'        : '(max-width: 599px)'                         ,
  458      'gt-xs'     : '(min-width: 600px)'                         ,
  459      'sm'        : '(min-width: 600px) and (max-width: 959px)'  ,
  460      'gt-sm'     : '(min-width: 960px)'                         ,
  461      'md'        : '(min-width: 960px) and (max-width: 1279px)' ,
  462      'gt-md'     : '(min-width: 1280px)'                        ,
  463      'lg'        : '(min-width: 1280px) and (max-width: 1919px)',
  464      'gt-lg'     : '(min-width: 1920px)'                        ,
  465      'xl'        : '(min-width: 1920px)'                        ,
  466      'landscape' : '(orientation: landscape)'                   ,
  467      'portrait'  : '(orientation: portrait)'                    ,
  468      'print' : 'print'
  469    },
  470
  471    MEDIA_PRIORITY: [
  472      'xl',
  473      'gt-lg',
  474      'lg',
  475      'gt-md',
  476      'md',
  477      'gt-sm',
  478      'sm',
  479      'gt-xs',
  480      'xs',
  481      'landscape',
  482      'portrait',
  483      'print'
  484    ]
  485  };
  486
  487  return self;
  488}
  489
  490})();
  491(function(){
  492"use strict";
  493
  494  angular
  495    .module('material.core')
  496    .config( ["$provide", function($provide){
  497       $provide.decorator('$mdUtil', ['$delegate', function ($delegate){
  498           /**
  499            * Inject the iterator facade to easily support iteration and accessors
  500            * @see iterator below
  501            */
  502           $delegate.iterator = MdIterator;
  503
  504           return $delegate;
  505         }
  506       ]);
  507     }]);
  508
  509  /**
  510   * iterator is a list facade to easily support iteration and accessors
  511   *
  512   * @param items Array list which this iterator will enumerate
  513   * @param reloop Boolean enables iterator to consider the list as an endless reloop
  514   */
  515  function MdIterator(items, reloop) {
  516    var trueFn = function() { return true; };
  517
  518    if (items && !angular.isArray(items)) {
  519      items = Array.prototype.slice.call(items);
  520    }
  521
  522    reloop = !!reloop;
  523    var _items = items || [ ];
  524
  525    // Published API
  526    return {
  527      items: getItems,
  528      count: count,
  529
  530      inRange: inRange,
  531      contains: contains,
  532      indexOf: indexOf,
  533      itemAt: itemAt,
  534
  535      findBy: findBy,
  536
  537      add: add,
  538      remove: remove,
  539
  540      first: first,
  541      last: last,
  542      next: angular.bind(null, findSubsequentItem, false),
  543      previous: angular.bind(null, findSubsequentItem, true),
  544
  545      hasPrevious: hasPrevious,
  546      hasNext: hasNext
  547
  548    };
  549
  550    /**
  551     * Publish copy of the enumerable set
  552     * @returns {Array|*}
  553     */
  554    function getItems() {
  555      return [].concat(_items);
  556    }
  557
  558    /**
  559     * Determine length of the list
  560     * @returns {Array.length|*|number}
  561     */
  562    function count() {
  563      return _items.length;
  564    }
  565
  566    /**
  567     * Is the index specified valid
  568     * @param index
  569     * @returns {Array.length|*|number|boolean}
  570     */
  571    function inRange(index) {
  572      return _items.length && ( index > -1 ) && (index < _items.length );
  573    }
  574
  575    /**
  576     * Can the iterator proceed to the next item in the list; relative to
  577     * the specified item.
  578     *
  579     * @param item
  580     * @returns {Array.length|*|number|boolean}
  581     */
  582    function hasNext(item) {
  583      return item ? inRange(indexOf(item) + 1) : false;
  584    }
  585
  586    /**
  587     * Can the iterator proceed to the previous item in the list; relative to
  588     * the specified item.
  589     *
  590     * @param item
  591     * @returns {Array.length|*|number|boolean}
  592     */
  593    function hasPrevious(item) {
  594      return item ? inRange(indexOf(item) - 1) : false;
  595    }
  596
  597    /**
  598     * Get item at specified index/position
  599     * @param index
  600     * @returns {*}
  601     */
  602    function itemAt(index) {
  603      return inRange(index) ? _items[index] : null;
  604    }
  605
  606    /**
  607     * Find all elements matching the key/value pair
  608     * otherwise return null
  609     *
  610     * @param val
  611     * @param key
  612     *
  613     * @return array
  614     */
  615    function findBy(key, val) {
  616      return _items.filter(function(item) {
  617        return item[key] === val;
  618      });
  619    }
  620
  621    /**
  622     * Add item to list
  623     * @param item
  624     * @param index
  625     * @returns {*}
  626     */
  627    function add(item, index) {
  628      if ( !item ) return -1;
  629
  630      if (!angular.isNumber(index)) {
  631        index = _items.length;
  632      }
  633
  634      _items.splice(index, 0, item);
  635
  636      return indexOf(item);
  637    }
  638
  639    /**
  640     * Remove item from list...
  641     * @param item
  642     */
  643    function remove(item) {
  644      if ( contains(item) ){
  645        _items.splice(indexOf(item), 1);
  646      }
  647    }
  648
  649    /**
  650     * Get the zero-based index of the target item
  651     * @param item
  652     * @returns {*}
  653     */
  654    function indexOf(item) {
  655      return _items.indexOf(item);
  656    }
  657
  658    /**
  659     * Boolean existence check
  660     * @param item
  661     * @returns {boolean}
  662     */
  663    function contains(item) {
  664      return item && (indexOf(item) > -1);
  665    }
  666
  667    /**
  668     * Return first item in the list
  669     * @returns {*}
  670     */
  671    function first() {
  672      return _items.length ? _items[0] : null;
  673    }
  674
  675    /**
  676     * Return last item in the list...
  677     * @returns {*}
  678     */
  679    function last() {
  680      return _items.length ? _items[_items.length - 1] : null;
  681    }
  682
  683    /**
  684     * Find the next item. If reloop is true and at the end of the list, it will go back to the
  685     * first item. If given, the `validate` callback will be used to determine whether the next item
  686     * is valid. If not valid, it will try to find the next item again.
  687     *
  688     * @param {boolean} backwards Specifies the direction of searching (forwards/backwards)
  689     * @param {*} item The item whose subsequent item we are looking for
  690     * @param {Function=} validate The `validate` function
  691     * @param {integer=} limit The recursion limit
  692     *
  693     * @returns {*} The subsequent item or null
  694     */
  695    function findSubsequentItem(backwards, item, validate, limit) {
  696      validate = validate || trueFn;
  697
  698      var curIndex = indexOf(item);
  699      while (true) {
  700        if (!inRange(curIndex)) return null;
  701
  702        var nextIndex = curIndex + (backwards ? -1 : 1);
  703        var foundItem = null;
  704        if (inRange(nextIndex)) {
  705          foundItem = _items[nextIndex];
  706        } else if (reloop) {
  707          foundItem = backwards ? last() : first();
  708          nextIndex = indexOf(foundItem);
  709        }
  710
  711        if ((foundItem === null) || (nextIndex === limit)) return null;
  712        if (validate(foundItem)) return foundItem;
  713
  714        if (angular.isUndefined(limit)) limit = nextIndex;
  715
  716        curIndex = nextIndex;
  717      }
  718    }
  719  }
  720
  721
  722})();
  723(function(){
  724"use strict";
  725
  726
  727mdMediaFactory.$inject = ["$mdConstant", "$rootScope", "$window"];angular.module('material.core')
  728.factory('$mdMedia', mdMediaFactory);
  729
  730/**
  731 * @ngdoc service
  732 * @name $mdMedia
  733 * @module material.core
  734 *
  735 * @description
  736 * `$mdMedia` is used to evaluate whether a given media query is true or false given the
  737 * current device's screen / window size. The media query will be re-evaluated on resize, allowing
  738 * you to register a watch.
  739 *
  740 * `$mdMedia` also has pre-programmed support for media queries that match the layout breakpoints:
  741 *
  742 *  <table class="md-api-table">
  743 *    <thead>
  744 *    <tr>
  745 *      <th>Breakpoint</th>
  746 *      <th>mediaQuery</th>
  747 *    </tr>
  748 *    </thead>
  749 *    <tbody>
  750 *    <tr>
  751 *      <td>xs</td>
  752 *      <td>(max-width: 599px)</td>
  753 *    </tr>
  754 *    <tr>
  755 *      <td>gt-xs</td>
  756 *      <td>(min-width: 600px)</td>
  757 *    </tr>
  758 *    <tr>
  759 *      <td>sm</td>
  760 *      <td>(min-width: 600px) and (max-width: 959px)</td>
  761 *    </tr>
  762 *    <tr>
  763 *      <td>gt-sm</td>
  764 *      <td>(min-width: 960px)</td>
  765 *    </tr>
  766 *    <tr>
  767 *      <td>md</td>
  768 *      <td>(min-width: 960px) and (max-width: 1279px)</td>
  769 *    </tr>
  770 *    <tr>
  771 *      <td>gt-md</td>
  772 *      <td>(min-width: 1280px)</td>
  773 *    </tr>
  774 *    <tr>
  775 *      <td>lg</td>
  776 *      <td>(min-width: 1280px) and (max-width: 1919px)</td>
  777 *    </tr>
  778 *    <tr>
  779 *      <td>gt-lg</td>
  780 *      <td>(min-width: 1920px)</td>
  781 *    </tr>
  782 *    <tr>
  783 *      <td>xl</td>
  784 *      <td>(min-width: 1920px)</td>
  785 *    </tr>
  786 *    <tr>
  787 *      <td>landscape</td>
  788 *      <td>landscape</td>
  789 *    </tr>
  790 *    <tr>
  791 *      <td>portrait</td>
  792 *      <td>portrait</td>
  793 *    </tr>
  794 *    <tr>
  795 *      <td>print</td>
  796 *      <td>print</td>
  797 *    </tr>
  798 *    </tbody>
  799 *  </table>
  800 *
  801 *  See Material Design's <a href="https://material.google.com/layout/responsive-ui.html">Layout - Adaptive UI</a> for more details.
  802 *
  803 *  <a href="https://www.google.com/design/spec/layout/adaptive-ui.html">
  804 *  <img src="https://material-design.storage.googleapis.com/publish/material_v_4/material_ext_publish/0B8olV15J7abPSGFxemFiQVRtb1k/layout_adaptive_breakpoints_01.png" width="100%" height="100%"></img>
  805 *  </a>
  806 *
  807 * @returns {boolean} a boolean representing whether or not the given media query is true or false.
  808 *
  809 * @usage
  810 * <hljs lang="js">
  811 * app.controller('MyController', function($mdMedia, $scope) {
  812 *   $scope.$watch(function() { return $mdMedia('lg'); }, function(big) {
  813 *     $scope.bigScreen = big;
  814 *   });
  815 *
  816 *   $scope.screenIsSmall = $mdMedia('sm');
  817 *   $scope.customQuery = $mdMedia('(min-width: 1234px)');
  818 *   $scope.anotherCustom = $mdMedia('max-width: 300px');
  819 * });
  820 * </hljs>
  821 */
  822
  823/* @ngInject */
  824function mdMediaFactory($mdConstant, $rootScope, $window) {
  825  var queries = {};
  826  var mqls = {};
  827  var results = {};
  828  var normalizeCache = {};
  829
  830  $mdMedia.getResponsiveAttribute = getResponsiveAttribute;
  831  $mdMedia.getQuery = getQuery;
  832  $mdMedia.watchResponsiveAttributes = watchResponsiveAttributes;
  833
  834  return $mdMedia;
  835
  836  function $mdMedia(query) {
  837    var validated = queries[query];
  838    if (angular.isUndefined(validated)) {
  839      validated = queries[query] = validate(query);
  840    }
  841
  842    var result = results[validated];
  843    if (angular.isUndefined(result)) {
  844      result = add(validated);
  845    }
  846
  847    return result;
  848  }
  849
  850  function validate(query) {
  851    return $mdConstant.MEDIA[query] ||
  852           ((query.charAt(0) !== '(') ? ('(' + query + ')') : query);
  853  }
  854
  855  function add(query) {
  856    var result = mqls[query];
  857    if ( !result ) {
  858      result = mqls[query] = $window.matchMedia(query);
  859    }
  860
  861    result.addListener(onQueryChange);
  862    return (results[result.media] = !!result.matches);
  863  }
  864
  865  function onQueryChange(query) {
  866    $rootScope.$evalAsync(function() {
  867      results[query.media] = !!query.matches;
  868    });
  869  }
  870
  871  function getQuery(name) {
  872    return mqls[name];
  873  }
  874
  875  function getResponsiveAttribute(attrs, attrName) {
  876    for (var i = 0; i < $mdConstant.MEDIA_PRIORITY.length; i++) {
  877      var mediaName = $mdConstant.MEDIA_PRIORITY[i];
  878      if (!mqls[queries[mediaName]].matches) {
  879        continue;
  880      }
  881
  882      var normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
  883      if (attrs[normalizedName]) {
  884        return attrs[normalizedName];
  885      }
  886    }
  887
  888    // fallback on unprefixed
  889    return attrs[getNormalizedName(attrs, attrName)];
  890  }
  891
  892  function watchResponsiveAttributes(attrNames, attrs, watchFn) {
  893    var unwatchFns = [];
  894    attrNames.forEach(function(attrName) {
  895      var normalizedName = getNormalizedName(attrs, attrName);
  896      if (angular.isDefined(attrs[normalizedName])) {
  897        unwatchFns.push(
  898            attrs.$observe(normalizedName, angular.bind(void 0, watchFn, null)));
  899      }
  900
  901      for (var mediaName in $mdConstant.MEDIA) {
  902        normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
  903        if (angular.isDefined(attrs[normalizedName])) {
  904          unwatchFns.push(
  905              attrs.$observe(normalizedName, angular.bind(void 0, watchFn, mediaName)));
  906        }
  907      }
  908    });
  909
  910    return function unwatch() {
  911      unwatchFns.forEach(function(fn) { fn(); });
  912    };
  913  }
  914
  915  // Improves performance dramatically
  916  function getNormalizedName(attrs, attrName) {
  917    return normalizeCache[attrName] ||
  918        (normalizeCache[attrName] = attrs.$normalize(attrName));
  919  }
  920}
  921
  922})();
  923(function(){
  924"use strict";
  925
  926angular
  927  .module('material.core')
  928  .config( ["$provide", function($provide) {
  929    $provide.decorator('$mdUtil', ['$delegate', function ($delegate) {
  930
  931      // Inject the prefixer into our original $mdUtil service.
  932      $delegate.prefixer = MdPrefixer;
  933
  934      return $delegate;
  935    }]);
  936  }]);
  937
  938function MdPrefixer(initialAttributes, buildSelector) {
  939  var PREFIXES = ['data', 'x'];
  940
  941  if (initialAttributes) {
  942    // The prefixer also accepts attributes as a parameter, and immediately builds a list or selector for
  943    // the specified attributes.
  944    return buildSelector ? _buildSelector(initialAttributes) : _buildList(initialAttributes);
  945  }
  946
  947  return {
  948    buildList: _buildList,
  949    buildSelector: _buildSelector,
  950    hasAttribute: _hasAttribute,
  951    removeAttribute: _removeAttribute
  952  };
  953
  954  function _buildList(attributes) {
  955    attributes = angular.isArray(attributes) ? attributes : [attributes];
  956
  957    attributes.forEach(function(item) {
  958      PREFIXES.forEach(function(prefix) {
  959        attributes.push(prefix + '-' + item);
  960      });
  961    });
  962
  963    return attributes;
  964  }
  965
  966  function _buildSelector(attributes) {
  967    attributes = angular.isArray(attributes) ? attributes : [attributes];
  968
  969    return _buildList(attributes)
  970      .map(function(item) {
  971        return '[' + item + ']';
  972      })
  973      .join(',');
  974  }
  975
  976  function _hasAttribute(element, attribute) {
  977    element = _getNativeElement(element);
  978
  979    if (!element) {
  980      return false;
  981    }
  982
  983    var prefixedAttrs = _buildList(attribute);
  984
  985    for (var i = 0; i < prefixedAttrs.length; i++) {
  986      if (element.hasAttribute(prefixedAttrs[i])) {
  987        return true;
  988      }
  989    }
  990
  991    return false;
  992  }
  993
  994  function _removeAttribute(element, attribute) {
  995    element = _getNativeElement(element);
  996
  997    if (!element) {
  998      return;
  999    }
 1000
 1001    _buildList(attribute).forEach(function(prefixedAttribute) {
 1002      element.removeAttribute(prefixedAttribute);
 1003    });
 1004  }
 1005
 1006  /**
 1007   * Transforms a jqLite or DOM element into a HTML element.
 1008   * This is useful when supporting jqLite elements and DOM elements at
 1009   * same time.
 1010   * @param element {JQLite|Element} Element to be parsed
 1011   * @returns {HTMLElement} Parsed HTMLElement
 1012   */
 1013  function _getNativeElement(element) {
 1014    element =  element[0] || element;
 1015
 1016    if (element.nodeType) {
 1017      return element;
 1018    }
 1019  }
 1020
 1021}
 1022
 1023})();
 1024(function(){
 1025"use strict";
 1026
 1027/*
 1028 * This var has to be outside the angular factory, otherwise when
 1029 * there are multiple material apps on the same page, each app
 1030 * will create its own instance of this array and the app's IDs
 1031 * will not be unique.
 1032 */
 1033UtilFactory.$inject = ["$document", "$timeout", "$compile", "$rootScope", "$$mdAnimate", "$interpolate", "$log", "$rootElement", "$window", "$$rAF"];
 1034var nextUniqueId = 0;
 1035
 1036/**
 1037 * @ngdoc module
 1038 * @name material.core.util
 1039 * @description
 1040 * Util
 1041 */
 1042angular
 1043  .module('material.core')
 1044  .factory('$mdUtil', UtilFactory);
 1045
 1046/**
 1047 * @ngInject
 1048 */
 1049function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $interpolate, $log, $rootElement, $window, $$rAF) {
 1050  // Setup some core variables for the processTemplate method
 1051  var startSymbol = $interpolate.startSymbol(),
 1052    endSymbol = $interpolate.endSymbol(),
 1053    usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}'));
 1054
 1055  /**
 1056   * Checks if the target element has the requested style by key
 1057   * @param {DOMElement|JQLite} target Target element
 1058   * @param {string} key Style key
 1059   * @param {string=} expectedVal Optional expected value
 1060   * @returns {boolean} Whether the target element has the style or not
 1061   */
 1062  var hasComputedStyle = function (target, key, expectedVal) {
 1063    var hasValue = false;
 1064
 1065    if ( target && target.length  ) {
 1066      var computedStyles = $window.getComputedStyle(target[0]);
 1067      hasValue = angular.isDefined(computedStyles[key]) && (expectedVal ? computedStyles[key] == expectedVal : true);
 1068    }
 1069
 1070    return hasValue;
 1071  };
 1072
 1073  function validateCssValue(value) {
 1074    return !value       ? '0'   :
 1075      hasPx(value) || hasPercent(value) ? value : value + 'px';
 1076  }
 1077
 1078  function hasPx(value) {
 1079    return String(value).indexOf('px') > -1;
 1080  }
 1081
 1082  function hasPercent(value) {
 1083    return String(value).indexOf('%') > -1;
 1084
 1085  }
 1086
 1087  var $mdUtil = {
 1088    dom: {},
 1089    now: window.performance && window.performance.now ?
 1090      angular.bind(window.performance, window.performance.now) : Date.now || function() {
 1091      return new Date().getTime();
 1092    },
 1093
 1094    /**
 1095     * Cross-version compatibility method to retrieve an option of a ngModel controller,
 1096     * which supports the breaking changes in the AngularJS snapshot (SHA 87a2ff76af5d0a9268d8eb84db5755077d27c84c).
 1097     * @param {!angular.ngModelCtrl} ngModelCtrl
 1098     * @param {!string} optionName
 1099     * @returns {Object|undefined}
 1100     */
 1101    getModelOption: function (ngModelCtrl, optionName) {
 1102      if (!ngModelCtrl.$options) {
 1103        return;
 1104      }
 1105
 1106      var $options = ngModelCtrl.$options;
 1107
 1108      // The newer versions of Angular introduced a `getOption function and made the option values no longer
 1109      // visible on the $options object.
 1110      return $options.getOption ? $options.getOption(optionName) : $options[optionName]
 1111    },
 1112
 1113    /**
 1114     * Bi-directional accessor/mutator used to easily update an element's
 1115     * property based on the current 'dir'ectional value.
 1116     */
 1117    bidi : function(element, property, lValue, rValue) {
 1118      var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl');
 1119
 1120      // If accessor
 1121      if ( arguments.length == 0 ) return ltr ? 'ltr' : 'rtl';
 1122
 1123      // If mutator
 1124      var elem = angular.element(element);
 1125
 1126      if ( ltr && angular.isDefined(lValue)) {
 1127        elem.css(property, validateCssValue(lValue));
 1128      }
 1129      else if ( !ltr && angular.isDefined(rValue)) {
 1130        elem.css(property, validateCssValue(rValue) );
 1131      }
 1132    },
 1133
 1134    bidiProperty: function (element, lProperty, rProperty, value) {
 1135      var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl');
 1136
 1137      var elem = angular.element(element);
 1138
 1139      if ( ltr && angular.isDefined(lProperty)) {
 1140        elem.css(lProperty, validateCssValue(value));
 1141        elem.css(rProperty, '');
 1142      }
 1143      else if ( !ltr && angular.isDefined(rProperty)) {
 1144        elem.css(rProperty, validateCssValue(value) );
 1145        elem.css(lProperty, '');
 1146      }
 1147    },
 1148
 1149    clientRect: function(element, offsetParent, isOffsetRect) {
 1150      var node = getNode(element);
 1151      offsetParent = getNode(offsetParent || node.offsetParent || document.body);
 1152      var nodeRect = node.getBoundingClientRect();
 1153
 1154      // The user can ask for an offsetRect: a rect relative to the offsetParent,
 1155      // or a clientRect: a rect relative to the page
 1156      var offsetRect = isOffsetRect ?
 1157        offsetParent.getBoundingClientRect() :
 1158      {left: 0, top: 0, width: 0, height: 0};
 1159      return {
 1160        left: nodeRect.left - offsetRect.left,
 1161        top: nodeRect.top - offsetRect.top,
 1162        width: nodeRect.width,
 1163        height: nodeRect.height
 1164      };
 1165    },
 1166    offsetRect: function(element, offsetParent) {
 1167      return $mdUtil.clientRect(element, offsetParent, true);
 1168    },
 1169
 1170    // Annoying method to copy nodes to an array, thanks to IE
 1171    nodesToArray: function(nodes) {
 1172      nodes = nodes || [];
 1173
 1174      var results = [];
 1175      for (var i = 0; i < nodes.length; ++i) {
 1176        results.push(nodes.item(i));
 1177      }
 1178      return results;
 1179    },
 1180
 1181    /**
 1182     * Determines the absolute position of the viewport.
 1183     * Useful when making client rectangles absolute.
 1184     * @returns {number}
 1185     */
 1186    getViewportTop: function() {
 1187      return window.scrollY || window.pageYOffset || 0;
 1188    },
 1189
 1190    /**
 1191     * Finds the proper focus target by searching the DOM.
 1192     *
 1193     * @param containerEl
 1194     * @param attributeVal
 1195     * @returns {*}
 1196     */
 1197    findFocusTarget: function(containerEl, attributeVal) {
 1198      var AUTO_FOCUS = this.prefixer('md-autofocus', true);
 1199      var elToFocus;
 1200
 1201      elToFocus = scanForFocusable(containerEl, attributeVal || AUTO_FOCUS);
 1202
 1203      if ( !elToFocus && attributeVal != AUTO_FOCUS) {
 1204        // Scan for deprecated attribute
 1205        elToFocus = scanForFocusable(containerEl, this.prefixer('md-auto-focus', true));
 1206
 1207        if ( !elToFocus ) {
 1208          // Scan for fallback to 'universal' API
 1209          elToFocus = scanForFocusable(containerEl, AUTO_FOCUS);
 1210        }
 1211      }
 1212
 1213      return elToFocus;
 1214
 1215      /**
 1216       * Can target and nested children for specified Selector (attribute)
 1217       * whose value may be an expression that evaluates to True/False.
 1218       */
 1219      function scanForFocusable(target, selector) {
 1220        var elFound, items = target[0].querySelectorAll(selector);
 1221
 1222        // Find the last child element with the focus attribute
 1223        if ( items && items.length ){
 1224          items.length && angular.forEach(items, function(it) {
 1225            it = angular.element(it);
 1226
 1227            // Check the element for the md-autofocus class to ensure any associated expression
 1228            // evaluated to true.
 1229            var isFocusable = it.hasClass('md-autofocus');
 1230            if (isFocusable) elFound = it;
 1231          });
 1232        }
 1233        return elFound;
 1234      }
 1235    },
 1236
 1237    /**
 1238     * Disables scroll around the passed parent element.
 1239     * @param element Unused
 1240     * @param {!Element|!angular.JQLite} parent Element to disable scrolling within.
 1241     *   Defaults to body if none supplied.
 1242     * @param options Object of options to modify functionality
 1243     *   - disableScrollMask Boolean of whether or not to create a scroll mask element or
 1244     *     use the passed parent element.
 1245     */
 1246    disableScrollAround: function(element, parent, options) {
 1247      options = options || {};
 1248
 1249      $mdUtil.disableScrollAround._count = Math.max(0, $mdUtil.disableScrollAround._count || 0);
 1250      $mdUtil.disableScrollAround._count++;
 1251
 1252      if ($mdUtil.disableScrollAround._restoreScroll) {
 1253        return $mdUtil.disableScrollAround._restoreScroll;
 1254      }
 1255
 1256      var body = $document[0].body;
 1257      var restoreBody = disableBodyScroll();
 1258      var restoreElement = disableElementScroll(parent);
 1259
 1260      return $mdUtil.disableScrollAround._restoreScroll = function() {
 1261        if (--$mdUtil.disableScrollAround._count <= 0) {
 1262          restoreBody();
 1263          restoreElement();
 1264          delete $mdUtil.disableScrollAround._restoreScroll;
 1265        }
 1266      };
 1267
 1268      /**
 1269       * Creates a virtual scrolling mask to prevent touchmove, keyboard, scrollbar clicking,
 1270       * and wheel events
 1271       */
 1272      function disableElementScroll(element) {
 1273        element = angular.element(element || body);
 1274
 1275        var scrollMask;
 1276
 1277        if (options.disableScrollMask) {
 1278          scrollMask = element;
 1279        } else {
 1280          scrollMask = angular.element(
 1281            '<div class="md-scroll-mask">' +
 1282            '  <div class="md-scroll-mask-bar"></div>' +
 1283            '</div>');
 1284          element.append(scrollMask);
 1285        }
 1286
 1287        scrollMask.on('wheel', preventDefault);
 1288        scrollMask.on('touchmove', preventDefault);
 1289
 1290        return function restoreElementScroll() {
 1291          scrollMask.off('wheel');
 1292          scrollMask.off('touchmove');
 1293
 1294          if (!options.disableScrollMask) {
 1295            scrollMask[0].parentNode.removeChild(scrollMask[0]);
 1296          }
 1297        };
 1298
 1299        function preventDefault(e) {
 1300          e.preventDefault();
 1301        }
 1302      }
 1303
 1304      // Converts the body to a position fixed block and translate it to the proper scroll position
 1305      function disableBodyScroll() {
 1306        var documentElement = $document[0].documentElement;
 1307
 1308        var prevDocumentStyle = documentElement.style.cssText || '';
 1309        var prevBodyStyle = body.style.cssText || '';
 1310
 1311        var viewportTop = $mdUtil.getViewportTop();
 1312        var clientWidth = body.clientWidth;
 1313        var hasVerticalScrollbar = body.scrollHeight > body.clientHeight + 1;
 1314
 1315        if (hasVerticalScrollbar) {
 1316          angular.element(body).css({
 1317            position: 'fixed',
 1318            width: '100%',
 1319            top: -viewportTop + 'px'
 1320          });
 1321        }
 1322
 1323        if (body.clientWidth < clientWidth) {
 1324          body.style.overflow = 'hidden';
 1325        }
 1326
 1327        // This should be applied after the manipulation to the body, because
 1328        // adding a scrollbar can potentially resize it, causing the measurement
 1329        // to change.
 1330        if (hasVerticalScrollbar) {
 1331          documentElement.style.overflowY = 'scroll';
 1332        }
 1333
 1334        return function restoreScroll() {
 1335          // Reset the inline style CSS to the previous.
 1336          body.style.cssText = prevBodyStyle;
 1337          documentElement.style.cssText = prevDocumentStyle;
 1338
 1339          // The body loses its scroll position while being fixed.
 1340          body.scrollTop = viewportTop;
 1341        };
 1342      }
 1343
 1344    },
 1345
 1346    enableScrolling: function() {
 1347      var restoreFn = this.disableScrollAround._restoreScroll;
 1348      restoreFn && restoreFn();
 1349    },
 1350
 1351    floatingScrollbars: function() {
 1352      if (this.floatingScrollbars.cached === undefined) {
 1353        var tempNode = angular.element('<div><div></div></div>').css({
 1354          width: '100%',
 1355          'z-index': -1,
 1356          position: 'absolute',
 1357          height: '35px',
 1358          'overflow-y': 'scroll'
 1359        });
 1360        tempNode.children().css('height', '60px');
 1361
 1362        $document[0].body.appendChild(tempNode[0]);
 1363        this.floatingScrollbars.cached = (tempNode[0].offsetWidth == tempNode[0].childNodes[0].offsetWidth);
 1364        tempNode.remove();
 1365      }
 1366      return this.floatingScrollbars.cached;
 1367    },
 1368
 1369    // Mobile safari only allows you to set focus in click event listeners...
 1370    forceFocus: function(element) {
 1371      var node = element[0] || element;
 1372
 1373      document.addEventListener('click', function focusOnClick(ev) {
 1374        if (ev.target === node && ev.$focus) {
 1375          node.focus();
 1376          ev.stopImmediatePropagation();
 1377          ev.preventDefault();
 1378          node.removeEventListener('click', focusOnClick);
 1379        }
 1380      }, true);
 1381
 1382      var newEvent = document.createEvent('MouseEvents');
 1383      newEvent.initMouseEvent('click', false, true, window, {}, 0, 0, 0, 0,
 1384        false, false, false, false, 0, null);
 1385      newEvent.$material = true;
 1386      newEvent.$focus = true;
 1387      node.dispatchEvent(newEvent);
 1388    },
 1389
 1390    /**
 1391     * facade to build md-backdrop element with desired styles
 1392     * NOTE: Use $compile to trigger backdrop postLink function
 1393     */
 1394    createBackdrop: function(scope, addClass) {
 1395      return $compile($mdUtil.supplant('<md-backdrop class="{0}">', [addClass]))(scope);
 1396    },
 1397
 1398    /**
 1399     * supplant() method from Crockford's `Remedial Javascript`
 1400     * Equivalent to use of $interpolate; without dependency on
 1401     * interpolation symbols and scope. Note: the '{<token>}' can
 1402     * be property names, property chains, or array indices.
 1403     */
 1404    supplant: function(template, values, pattern) {
 1405      pattern = pattern || /\{([^\{\}]*)\}/g;
 1406      return template.replace(pattern, function(a, b) {
 1407        var p = b.split('.'),
 1408          r = values;
 1409        try {
 1410          for (var s in p) {
 1411            if (p.hasOwnProperty(s) ) {
 1412              r = r[p[s]];
 1413            }
 1414          }
 1415        } catch (e) {
 1416          r = a;
 1417        }
 1418        return (typeof r === 'string' || typeof r === 'number') ? r : a;
 1419      });
 1420    },
 1421
 1422    fakeNgModel: function() {
 1423      return {
 1424        $fake: true,
 1425        $setTouched: angular.noop,
 1426        $setViewValue: function(value) {
 1427          this.$viewValue = value;
 1428          this.$render(value);
 1429          this.$viewChangeListeners.forEach(function(cb) {
 1430            cb();
 1431          });
 1432        },
 1433        $isEmpty: function(value) {
 1434          return ('' + value).length === 0;
 1435        },
 1436        $parsers: [],
 1437        $formatters: [],
 1438        $viewChangeListeners: [],
 1439        $render: angular.noop
 1440      };
 1441    },
 1442
 1443    // Returns a function, that, as long as it continues to be invoked, will not
 1444    // be triggered. The function will be called after it stops being called for
 1445    // N milliseconds.
 1446    // @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs
 1447    // @param invokeApply should the $timeout trigger $digest() dirty checking
 1448    debounce: function(func, wait, scope, invokeApply) {
 1449      var timer;
 1450
 1451      return function debounced() {
 1452        var context = scope,
 1453          args = Array.prototype.slice.call(arguments);
 1454
 1455        $timeout.cancel(timer);
 1456        timer = $timeout(function() {
 1457
 1458          timer = undefined;
 1459          func.apply(context, args);
 1460
 1461        }, wait || 10, invokeApply);
 1462      };
 1463    },
 1464
 1465    // Returns a function that can only be triggered every `delay` milliseconds.
 1466    // In other words, the function will not be called unless it has been more
 1467    // than `delay` milliseconds since the last call.
 1468    throttle: function throttle(func, delay) {
 1469      var recent;
 1470      return function throttled() {
 1471        var context = this;
 1472        var args = arguments;
 1473        var now = $mdUtil.now();
 1474
 1475        if (!recent || (now - recent > delay)) {
 1476          func.apply(context, args);
 1477          recent = now;
 1478        }
 1479      };
 1480    },
 1481
 1482    /**
 1483     * Measures the number of milliseconds taken to run the provided callback
 1484     * function. Uses a high-precision timer if available.
 1485     */
 1486    time: function time(cb) {
 1487      var start = $mdUtil.now();
 1488      cb();
 1489      return $mdUtil.now() - start;
 1490    },
 1491
 1492    /**
 1493     * Create an implicit getter that caches its `getter()`
 1494     * lookup value
 1495     */
 1496    valueOnUse : function (scope, key, getter) {
 1497      var value = null, args = Array.prototype.slice.call(arguments);
 1498      var params = (args.length > 3) ? args.slice(3) : [ ];
 1499
 1500      Object.defineProperty(scope, key, {
 1501        get: function () {
 1502          if (value === null) value = getter.apply(scope, params);
 1503          return value;
 1504        }
 1505      });
 1506    },
 1507
 1508    /**
 1509     * Get a unique ID.
 1510     *
 1511     * @returns {string} an unique numeric string
 1512     */
 1513    nextUid: function() {
 1514      return '' + nextUniqueId++;
 1515    },
 1516
 1517    // Stop watchers and events from firing on a scope without destroying it,
 1518    // by disconnecting it from its parent and its siblings' linked lists.
 1519    disconnectScope: function disconnectScope(scope) {
 1520      if (!scope) return;
 1521
 1522      // we can't destroy the root scope or a scope that has been already destroyed
 1523      if (scope.$root === scope) return;
 1524      if (scope.$$destroyed) return;
 1525
 1526      var parent = scope.$parent;
 1527      scope.$$disconnected = true;
 1528
 1529      // See Scope.$destroy
 1530      if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling;
 1531      if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling;
 1532      if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
 1533      if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
 1534
 1535      scope.$$nextSibling = scope.$$prevSibling = null;
 1536
 1537    },
 1538
 1539    // Undo the effects of disconnectScope above.
 1540    reconnectScope: function reconnectScope(scope) {
 1541      if (!scope) return;
 1542
 1543      // we can't disconnect the root node or scope already disconnected
 1544      if (scope.$root === scope) return;
 1545      if (!scope.$$disconnected) return;
 1546
 1547      var child = scope;
 1548
 1549      var parent = child.$parent;
 1550      child.$$disconnected = false;
 1551      // See Scope.$new for this logic...
 1552      child.$$prevSibling = parent.$$childTail;
 1553      if (parent.$$childHead) {
 1554        parent.$$childTail.$$nextSibling = child;
 1555        parent.$$childTail = child;
 1556      } else {
 1557        parent.$$childHead = parent.$$childTail = child;
 1558      }
 1559    },
 1560
 1561    /*
 1562     * getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName
 1563     *
 1564     * @param el Element to start walking the DOM from
 1565     * @param check Either a string or a function. If a string is passed, it will be evaluated against
 1566     * each of the parent nodes' tag name. If a function is passed, the loop will call it with each of
 1567     * the parents and will use the return value to determine whether the node is a match.
 1568     * @param onlyParent Only start checking from the parent element, not `el`.
 1569     */
 1570    getClosest: function getClosest(el, validateWith, onlyParent) {
 1571      if ( angular.isString(validateWith) ) {
 1572        var tagName = validateWith.toUpperCase();
 1573        validateWith = function(el) {
 1574          return el.nodeName.toUpperCase() === tagName;
 1575        };
 1576      }
 1577
 1578      if (el instanceof angular.element) el = el[0];
 1579      if (onlyParent) el = el.parentNode;
 1580      if (!el) return null;
 1581
 1582      do {
 1583        if (validateWith(el)) {
 1584          return el;
 1585        }
 1586      } while (el = el.parentNode);
 1587
 1588      return null;
 1589    },
 1590
 1591    /**
 1592     * Build polyfill for the Node.contains feature (if needed)
 1593     */
 1594    elementContains: function(node, child) {
 1595      var hasContains = (window.Node && window.Node.prototype && Node.prototype.contains);
 1596      var findFn = hasContains ? angular.bind(node, node.contains) : angular.bind(node, function(arg) {
 1597        // compares the positions of two nodes and returns a bitmask
 1598        return (node === child) || !!(this.compareDocumentPosition(arg) & 16)
 1599      });
 1600
 1601      return findFn(child);
 1602    },
 1603
 1604    /**
 1605     * Functional equivalent for $element.filter(‘md-bottom-sheet’)
 1606     * useful with interimElements where the element and its container are important...
 1607     *
 1608     * @param {[]} elements to scan
 1609     * @param {string} name of node to find (e.g. 'md-dialog')
 1610     * @param {boolean=} optional flag to allow deep scans; defaults to 'false'.
 1611     * @param {boolean=} optional flag to enable log warnings; defaults to false
 1612     */
 1613    extractElementByName: function(element, nodeName, scanDeep, warnNotFound) {
 1614      var found = scanTree(element);
 1615      if (!found && !!warnNotFound) {
 1616        $log.warn( $mdUtil.supplant("Unable to find node '{0}' in element '{1}'.",[nodeName, element[0].outerHTML]) );
 1617      }
 1618
 1619      return angular.element(found || element);
 1620
 1621      /**
 1622       * Breadth-First tree scan for element with matching `nodeName`
 1623       */
 1624      function scanTree(element) {
 1625        return scanLevel(element) || (!!scanDeep ? scanChildren(element) : null);
 1626      }
 1627
 1628      /**
 1629       * Case-insensitive scan of current elements only (do not descend).
 1630       */
 1631      function scanLevel(element) {
 1632        if ( element ) {
 1633          for (var i = 0, len = element.length; i < len; i++) {
 1634            if (element[i].nodeName.toLowerCase() === nodeName) {
 1635              return element[i];
 1636            }
 1637          }
 1638        }
 1639        return null;
 1640      }
 1641
 1642      /**
 1643       * Scan children of specified node
 1644       */
 1645      function scanChildren(element) {
 1646        var found;
 1647        if ( element ) {
 1648          for (var i = 0, len = element.length; i < len; i++) {
 1649            var target = element[i];
 1650            if ( !found ) {
 1651              for (var j = 0, numChild = target.childNodes.length; j < numChild; j++) {
 1652                found = found || scanTree([target.childNodes[j]]);
 1653              }
 1654            }
 1655          }
 1656        }
 1657        return found;
 1658      }
 1659
 1660    },
 1661
 1662    /**
 1663     * Give optional properties with no value a boolean true if attr provided or false otherwise
 1664     */
 1665    initOptionalProperties: function(scope, attr, defaults) {
 1666      defaults = defaults || {};
 1667      angular.forEach(scope.$$isolateBindings, function(binding, key) {
 1668        if (binding.optional && angular.isUndefined(scope[key])) {
 1669          var attrIsDefined = angular.isDefined(attr[binding.attrName]);
 1670          scope[key] = angular.isDefined(defaults[key]) ? defaults[key] : attrIsDefined;
 1671        }
 1672      });
 1673    },
 1674
 1675    /**
 1676     * Alternative to $timeout calls with 0 delay.
 1677     * nextTick() coalesces all calls within a single frame
 1678     * to minimize $digest thrashing
 1679     *
 1680     * @param callback
 1681     * @param digest
 1682     * @returns {*}
 1683     */
 1684    nextTick: function(callback, digest, scope) {
 1685      //-- grab function reference for storing state details
 1686      var nextTick = $mdUtil.nextTick;
 1687      var timeout = nextTick.timeout;
 1688      var queue = nextTick.queue || [];
 1689
 1690      //-- add callback to the queue
 1691      queue.push({scope: scope, callback: callback});
 1692
 1693      //-- set default value for digest
 1694      if (digest == null) digest = true;
 1695
 1696      //-- store updated digest/queue values
 1697      nextTick.digest = nextTick.digest || digest;
 1698      nextTick.queue = queue;
 1699
 1700      //-- either return existing timeout or create a new one
 1701      return timeout || (nextTick.timeout = $timeout(processQueue, 0, false));
 1702
 1703      /**
 1704       * Grab a copy of the current queue
 1705       * Clear the queue for future use
 1706       * Process the existing queue
 1707       * Trigger digest if necessary
 1708       */
 1709      function processQueue() {
 1710        var queue = nextTick.queue;
 1711        var digest = nextTick.digest;
 1712
 1713        nextTick.queue = [];
 1714        nextTick.timeout = null;
 1715        nextTick.digest = false;
 1716
 1717        queue.forEach(function(queueItem) {
 1718          var skip = queueItem.scope && queueItem.scope.$$destroyed;
 1719          if (!skip) {
 1720            queueItem.callback();
 1721          }
 1722        });
 1723
 1724        if (digest) $rootScope.$digest();
 1725      }
 1726    },
 1727
 1728    /**
 1729     * Processes a template and replaces the start/end symbols if the application has
 1730     * overriden them.
 1731     *
 1732     * @param template The template to process whose start/end tags may be replaced.
 1733     * @returns {*}
 1734     */
 1735    processTemplate: function(template) {
 1736      if (usesStandardSymbols) {
 1737        return template;
 1738      } else {
 1739        if (!template || !angular.isString(template)) return template;
 1740        return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
 1741      }
 1742    },
 1743
 1744    /**
 1745     * Scan up dom hierarchy for enabled parent;
 1746     */
 1747    getParentWithPointerEvents: function (element) {
 1748      var parent = element.parent();
 1749
 1750      // jqLite might return a non-null, but still empty, parent; so check for parent and length
 1751      while (hasComputedStyle(parent, 'pointer-events', 'none')) {
 1752        parent = parent.parent();
 1753      }
 1754
 1755      return parent;
 1756    },
 1757
 1758    getNearestContentElement: function (element) {
 1759      var current = element.parent()[0];
 1760      // Look for the nearest parent md-content, stopping at the rootElement.
 1761      while (current && current !== $rootElement[0] && current !== document.body && current.nodeName.toUpperCase() !== 'MD-CONTENT') {
 1762        current = current.parentNode;
 1763      }
 1764      return current;
 1765    },
 1766
 1767    /**
 1768     * Checks if the current browser is natively supporting the `sticky` position.
 1769     * @returns {string} supported sticky property name
 1770     */
 1771    checkStickySupport: function() {
 1772      var stickyProp;
 1773      var testEl = angular.element('<div>');
 1774      $document[0].body.appendChild(testEl[0]);
 1775
 1776      var stickyProps = ['sticky', '-webkit-sticky'];
 1777      for (var i = 0; i < stickyProps.length; ++i) {
 1778        testEl.css({
 1779          position: stickyProps[i],
 1780          top: 0,
 1781          'z-index': 2
 1782        });
 1783
 1784        if (testEl.css('position') == stickyProps[i]) {
 1785          stickyProp = stickyProps[i];
 1786          break;
 1787        }
 1788      }
 1789
 1790      testEl.remove();
 1791
 1792      return stickyProp;
 1793    },
 1794
 1795    /**
 1796     * Parses an attribute value, mostly a string.
 1797     * By default checks for negated values and returns `false´ if present.
 1798     * Negated values are: (native falsy) and negative strings like:
 1799     * `false` or `0`.
 1800     * @param value Attribute value which should be parsed.
 1801     * @param negatedCheck When set to false, won't check for negated values.
 1802     * @returns {boolean}
 1803     */
 1804    parseAttributeBoolean: function(value, negatedCheck) {
 1805      return value === '' || !!value && (negatedCheck === false || value !== 'false' && value !== '0');
 1806    },
 1807
 1808    hasComputedStyle: hasComputedStyle,
 1809
 1810    /**
 1811     * Returns true if the parent form of the element has been submitted.
 1812     *
 1813     * @param element An Angular or HTML5 element.
 1814     *
 1815     * @returns {boolean}
 1816     */
 1817    isParentFormSubmitted: function(element) {
 1818      var parent = $mdUtil.getClosest(element, 'form');
 1819      var form = parent ? angular.element(parent).controller('form') : null;
 1820
 1821      return form ? form.$submitted : false;
 1822    },
 1823
 1824    /**
 1825     * Animate the requested element's scrollTop to the requested scrollPosition with basic easing.
 1826     *
 1827     * @param {!HTMLElement} element The element to scroll.
 1828     * @param {number} scrollEnd The new/final scroll position.
 1829     * @param {number=} duration Duration of the scroll. Default is 1000ms.
 1830     */
 1831    animateScrollTo: function(element, scrollEnd, duration) {
 1832      var scrollStart = element.scrollTop;
 1833      var scrollChange = scrollEnd - scrollStart;
 1834      var scrollingDown = scrollStart < scrollEnd;
 1835      var startTime = $mdUtil.now();
 1836
 1837      $$rAF(scrollChunk);
 1838
 1839      function scrollChunk() {
 1840        var newPosition = calculateNewPosition();
 1841
 1842        element.scrollTop = newPosition;
 1843
 1844        if (scrollingDown ? newPosition < scrollEnd : newPosition > scrollEnd) {
 1845          $$rAF(scrollChunk);
 1846        }
 1847      }
 1848
 1849      function calculateNewPosition() {
 1850        var easeDuration = duration || 1000;
 1851        var currentTime = $mdUtil.now() - startTime;
 1852
 1853        return ease(currentTime, scrollStart, scrollChange, easeDuration);
 1854      }
 1855
 1856      function ease(currentTime, start, change, duration) {
 1857        // If the duration has passed (which can occur if our app loses focus due to $$rAF), jump
 1858        // straight to the proper position
 1859        if (currentTime > duration) {
 1860          return start + change;
 1861        }
 1862
 1863        var ts = (currentTime /= duration) * currentTime;
 1864        var tc = ts * currentTime;
 1865
 1866        return start + change * (-2 * tc + 3 * ts);
 1867      }
 1868    },
 1869
 1870    /**
 1871     * Provides an easy mechanism for removing duplicates from an array.
 1872     *
 1873     *    var myArray = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];
 1874     *
 1875     *    $mdUtil.uniq(myArray) => [1, 2, 3, 4]
 1876     *
 1877     * @param {array} array The array whose unique values should be returned.
 1878     *
 1879     * @returns {array} A copy of the array containing only unique values.
 1880     */
 1881    uniq: function(array) {
 1882      if (!array) { return; }
 1883
 1884      return array.filter(function(value, index, self) {
 1885        return self.indexOf(value) === index;
 1886      });
 1887    }
 1888  };
 1889
 1890
 1891// Instantiate other namespace utility methods
 1892
 1893  $mdUtil.dom.animator = $$mdAnimate($mdUtil);
 1894
 1895  return $mdUtil;
 1896
 1897  function getNode(el) {
 1898    return el[0] || el;
 1899  }
 1900
 1901}
 1902
 1903/*
 1904 * Since removing jQuery from the demos, some code that uses `element.focus()` is broken.
 1905 * We need to add `element.focus()`, because it's testable unlike `element[0].focus`.
 1906 */
 1907
 1908angular.element.prototype.focus = angular.element.prototype.focus || function() {
 1909    if (this.length) {
 1910      this[0].focus();
 1911    }
 1912    return this;
 1913  };
 1914angular.element.prototype.blur = angular.element.prototype.blur || function() {
 1915    if (this.length) {
 1916      this[0].blur();
 1917    }
 1918    return this;
 1919  };
 1920
 1921})();
 1922(function(){
 1923"use strict";
 1924
 1925/**
 1926 * @ngdoc module
 1927 * @name material.core.compiler
 1928 * @description
 1929 * Angular Material template and element compiler.
 1930 */
 1931MdCompilerService.$inject = ["$q", "$templateRequest", "$injector", "$compile", "$controller"];
 1932angular
 1933  .module('material.core')
 1934  .service('$mdCompiler', MdCompilerService);
 1935
 1936/**
 1937 * @ngdoc service
 1938 * @name $mdCompiler
 1939 * @module material.core.compiler
 1940 * @description
 1941 * The $mdCompiler service is an abstraction of Angular's compiler, that allows developers
 1942 * to easily compile an element with options like in a Directive Definition Object.
 1943 *
 1944 * > The compiler powers a lot of components inside of Angular Material.
 1945 * > Like the `$mdPanel` or `$mdDialog`.
 1946 *
 1947 * @usage
 1948 *
 1949 * Basic Usage with a template
 1950 *
 1951 * <hljs lang="js">
 1952 *   $mdCompiler.compile({
 1953 *     templateUrl: 'modal.html',
 1954 *     controller: 'ModalCtrl',
 1955 *     locals: {
 1956 *       modal: myModalInstance;
 1957 *     }
 1958 *   }).then(function (compileData) {
 1959 *     compileData.element; // Compiled DOM element
 1960 *     compileData.link(myScope); // Instantiate controller and link element to scope.
 1961 *   });
 1962 * </hljs>
 1963 *
 1964 * Example with a content element
 1965 *
 1966 * <hljs lang="js">
 1967 *
 1968 *   // Create a virtual element and link it manually.
 1969 *   // The compiler doesn't need to recompile the element each time.
 1970 *   var myElement = $compile('<span>Test</span>')(myScope);
 1971 *
 1972 *   $mdCompiler.compile({
 1973 *     contentElement: myElement
 1974 *   }).then(function (compileData) {
 1975 *     compileData.element // Content Element (same as above)
 1976 *     compileData.link // This does nothing when using a contentElement.
 1977 *   });
 1978 * </hljs>
 1979 *
 1980 * > Content Element is a significant performance improvement when the developer already knows that the
 1981 * > compiled element will be always the same and the scope will not change either.
 1982 *
 1983 * The `contentElement` option also supports DOM elements which will be temporary removed and restored
 1984 * at its old position.
 1985 *
 1986 * <hljs lang="js">
 1987 *   var domElement = document.querySelector('#myElement');
 1988 *
 1989 *   $mdCompiler.compile({
 1990 *     contentElement: myElement
 1991 *   }).then(function (compileData) {
 1992 *     compileData.element // Content Element (same as above)
 1993 *     compileData.link // This does nothing when using a contentElement.
 1994 *   });
 1995 * </hljs>
 1996 *
 1997 * The `$mdCompiler` can also query for the element in the DOM itself.
 1998 *
 1999 * <hljs lang="js">
 2000 *   $mdCompiler.compile({
 2001 *     contentElement: '#myElement'
 2002 *   }).then(function (compileData) {
 2003 *     compileData.element // Content Element (same as above)
 2004 *     compileData.link // This does nothing when using a contentElement.
 2005 *   });
 2006 * </hljs>
 2007 *
 2008 */
 2009function MdCompilerService($q, $templateRequest, $injector, $compile, $controller) {
 2010  /** @private @const {!angular.$q} */
 2011  this.$q = $q;
 2012
 2013  /** @private @const {!angular.$templateRequest} */
 2014  this.$templateRequest = $templateRequest;
 2015
 2016  /** @private @const {!angular.$injector} */
 2017  this.$injector = $injector;
 2018
 2019  /** @private @const {!angular.$compile} */
 2020  this.$compile = $compile;
 2021
 2022  /** @private @const {!angular.$controller} */
 2023  this.$controller = $controller;
 2024}
 2025
 2026/**
 2027 * @ngdoc method
 2028 * @name $mdCompiler#compile
 2029 * @description
 2030 *
 2031 * A method to compile a HTML template with the Angular compiler.
 2032 * The `$mdCompiler` is wrapper around the Angular compiler and provides extra functionality
 2033 * like controller instantiation or async resolves.
 2034 *
 2035 * @param {!Object} options An options object, with the following properties:
 2036 *
 2037 *    - `controller` - `{string|Function}` Controller fn that should be associated with
 2038 *         newly created scope or the name of a registered controller if passed as a string.
 2039 *    - `controllerAs` - `{string=}` A controller alias name. If present the controller will be
 2040 *         published to scope under the `controllerAs` name.
 2041 *    - `contentElement` - `{string|Element}`: Instead of using a template, which will be
 2042 *         compiled each time, you can also use a DOM element.<br/>
 2043 *    - `template` - `{string=}` An html template as a string.
 2044 *    - `templateUrl` - `{string=}` A path to an html template.
 2045 *    - `transformTemplate` - `{function(template)=}` A function which transforms the template after
 2046 *        it is loaded. It will be given the template string as a parameter, and should
 2047 *        return a a new string representing the transformed template.
 2048 *    - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
 2049 *        be injected into the controller. If any of these dependencies are promises, the compiler
 2050 *        will wait for them all to be resolved, or if one is rejected before the controller is
 2051 *        instantiated `compile()` will fail..
 2052 *      * `key` - `{string}`: a name of a dependency to be injected into the controller.
 2053 *      * `factory` - `{string|function}`: If `string` then it is an alias for a service.
 2054 *        Otherwise if function, then it is injected and the return value is treated as the
 2055 *        dependency. If the result is a promise, it is resolved before its value is
 2056 *        injected into the controller.
 2057 *
 2058 * @returns {Object} promise A promise, which will be resolved with a `compileData` object.
 2059 * `compileData` has the following properties:
 2060 *
 2061 *   - `element` - `{element}`: an uncompiled element matching the provided template.
 2062 *   - `link` - `{function(scope)}`: A link function, which, when called, will compile
 2063 *     the element and instantiate the provided controller (if given).
 2064 *   - `locals` - `{object}`: The locals which will be passed into the controller once `link` is
 2065 *     called. If `bindToController` is true, they will be coppied to the ctrl instead
 2066 *
 2067 */
 2068MdCompilerService.prototype.compile = function(options) {
 2069
 2070  if (options.contentElement) {
 2071    return this._prepareContentElement(options);
 2072  } else {
 2073    return this._compileTemplate(options);
 2074  }
 2075
 2076};
 2077
 2078/**
 2079 * Instead of compiling any template, the compiler just fetches an existing HTML element from the DOM and
 2080 * provides a restore function to put the element back it old DOM position.
 2081 * @param {!Object} options Options to be used for the compiler.
 2082 * @private
 2083 */
 2084MdCompilerService.prototype._prepareContentElement = function(options) {
 2085
 2086  var contentElement = this._fetchContentElement(options);
 2087
 2088  return this.$q.resolve({
 2089    element: contentElement.element,
 2090    cleanup: contentElement.restore,
 2091    locals: {},
 2092    link: function() {
 2093      return contentElement.element;
 2094    }
 2095  });
 2096
 2097};
 2098
 2099/**
 2100 * Compiles a template by considering all options and waiting for all resolves to be ready.
 2101 * @param {!Object} options Compile options
 2102 * @returns {!Object} Compile data with link function.
 2103 * @private
 2104 */
 2105MdCompilerService.prototype._compileTemplate = function(options) {
 2106
 2107  var self = this;
 2108  var templateUrl = options.templateUrl;
 2109  var template = options.template || '';
 2110  var resolve = angular.extend({}, options.resolve);
 2111  var locals = angular.extend({}, options.locals);
 2112  var transformTemplate = options.transformTemplate || angular.identity;
 2113
 2114  // Take resolve values and invoke them.
 2115  // Resolves can either be a string (value: 'MyRegisteredAngularConst'),
 2116  // or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {})
 2117  angular.forEach(resolve, function(value, key) {
 2118    if (angular.isString(value)) {
 2119      resolve[key] = self.$injector.get(value);
 2120    } else {
 2121      resolve[key] = self.$injector.invoke(value);
 2122    }
 2123  });
 2124
 2125  // Add the locals, which are just straight values to inject
 2126  // eg locals: { three: 3 }, will inject three into the controller
 2127  angular.extend(resolve, locals);
 2128
 2129  if (templateUrl) {
 2130    resolve.$$ngTemplate = this.$templateRequest(templateUrl);
 2131  } else {
 2132    resolve.$$ngTemplate = this.$q.when(template);
 2133  }
 2134
 2135
 2136  // Wait for all the resolves to finish if they are promises
 2137  return this.$q.all(resolve).then(function(locals) {
 2138
 2139    var template = transformTemplate(locals.$$ngTemplate, options);
 2140    var element = options.element || angular.element('<div>').html(template.trim()).contents();
 2141
 2142    return self._compileElement(locals, element, options);
 2143  });
 2144
 2145};
 2146
 2147/**
 2148 * Method to compile an element with the given options.
 2149 * @param {!Object} locals Locals to be injected to the controller if present
 2150 * @param {!JQLite} element Element to be compiled and linked
 2151 * @param {!Object} options Options to be used for linking.
 2152 * @returns {!Object} Compile data with link function.
 2153 * @private
 2154 */
 2155MdCompilerService.prototype._compileElement = function(locals, element, options) {
 2156  var self = this;
 2157  var ngLinkFn = this.$compile(element);
 2158
 2159  var compileData = {
 2160    element: element,
 2161    cleanup: element.remove.bind(element),
 2162    locals: locals,
 2163    link: linkFn
 2164  };
 2165
 2166  function linkFn(scope) {
 2167    locals.$scope = scope;
 2168
 2169    // Instantiate controller if the developer provided one.
 2170    if (options.controller) {
 2171
 2172      var injectLocals = angular.extend(locals, {
 2173        $element: element
 2174      });
 2175
 2176      var invokeCtrl = self.$controller(options.controller, injectLocals, true, options.controllerAs);
 2177
 2178      if (options.bindToController) {
 2179        angular.extend(invokeCtrl.instance, locals);
 2180      }
 2181
 2182      var ctrl = invokeCtrl();
 2183
 2184      // Unique identifier for Angular Route ngView controllers.
 2185      element.data('$ngControllerController', ctrl);
 2186      element.children().data('$ngControllerController', ctrl);
 2187
 2188      // Expose the instantiated controller to the compile data
 2189      compileData.controller = ctrl;
 2190    }
 2191
 2192    // Invoke the Angular $compile link function.
 2193    return ngLinkFn(scope);
 2194  }
 2195
 2196  return compileData;
 2197
 2198};
 2199
 2200/**
 2201 * Fetches an element removing it from the DOM and using it temporary for the compiler.
 2202 * Elements which were fetched will be restored after use.
 2203 * @param {!Object} options Options to be used for the compilation.
 2204 * @returns {{element: !JQLite, restore: !Function}}
 2205 * @private
 2206 */
 2207MdCompilerService.prototype._fetchContentElement = function(options) {
 2208
 2209  var contentEl = options.contentElement;
 2210  var restoreFn = null;
 2211
 2212  if (angular.isString(contentEl)) {
 2213    contentEl = document.querySelector(contentEl);
 2214    restoreFn = createRestoreFn(contentEl);
 2215  } else {
 2216    contentEl = contentEl[0] || contentEl;
 2217
 2218    // When the element is visible in the DOM, then we restore it at close of the dialog.
 2219    // Otherwise it will be removed from the DOM after close.
 2220    if (document.contains(contentEl)) {
 2221      restoreFn = createRestoreFn(contentEl);
 2222    } else {
 2223      restoreFn = function() {
 2224        if (contentEl.parentNode) {
 2225          contentEl.parentNode.removeChild(contentEl);
 2226        }
 2227      }
 2228    }
 2229  }
 2230
 2231  return {
 2232    element: angular.element(contentEl),
 2233    restore: restoreFn
 2234  };
 2235
 2236  function createRestoreFn(element) {
 2237    var parent = element.parentNode;
 2238    var nextSibling = element.nextElementSibling;
 2239
 2240    return function() {
 2241      if (!nextSibling) {
 2242        // When the element didn't had any sibling, then it can be simply appended to the
 2243        // parent, because it plays no role, which index it had before.
 2244        parent.appendChild(element);
 2245      } else {
 2246        // When the element had a sibling, which marks the previous position of the element
 2247        // in the DOM, we insert it correctly before the sibling, to have the same index as
 2248        // before.
 2249        parent.insertBefore(element, nextSibling);
 2250      }
 2251    }
 2252  }
 2253};
 2254
 2255
 2256})();
 2257(function(){
 2258"use strict";
 2259
 2260/**
 2261 * @ngdoc module
 2262 * @name material.core.aria
 2263 * @description
 2264 * Aria Expectations for ngMaterial components.
 2265 */
 2266MdAriaService.$inject = ["$$rAF", "$log", "$window", "$interpolate"];
 2267angular
 2268  .module('material.core')
 2269  .provider('$mdAria', MdAriaProvider);
 2270
 2271/**
 2272 * @ngdoc service
 2273 * @name $mdAriaProvider
 2274 * @module material.core.aria
 2275 *
 2276 * @description
 2277 *
 2278 * Modify options of the `$mdAria` service, which will be used by most of the Angular Material
 2279 * components.
 2280 *
 2281 * You are able to disable `$mdAria` warnings, by using the following markup.
 2282 *
 2283 * <hljs lang="js">
 2284 *   app.config(function($mdAriaProvider) {
 2285 *     // Globally disables all ARIA warnings.
 2286 *     $mdAriaProvider.disableWarnings();
 2287 *   });
 2288 * </hljs>
 2289 *
 2290 */
 2291function MdAriaProvider() {
 2292
 2293  var config = {
 2294    /** Whether we should show ARIA warnings in the console if labels are missing on the element */
 2295    showWarnings: true
 2296  };
 2297
 2298  return {
 2299    disableWarnings: disableWarnings,
 2300    $get: ["$$rAF", "$log", "$window", "$interpolate", function($$rAF, $log, $window, $interpolate) {
 2301      return MdAriaService.apply(config, arguments);
 2302    }]
 2303  };
 2304
 2305  /**
 2306   * @ngdoc method
 2307   * @name $mdAriaProvider#disableWarnings
 2308   * @description Disables all ARIA warnings generated by Angular Material.
 2309   */
 2310  function disableWarnings() {
 2311    config.showWarnings = false;
 2312  }
 2313}
 2314
 2315/*
 2316 * @ngInject
 2317 */
 2318function MdAriaService($$rAF, $log, $window, $interpolate) {
 2319
 2320  // Load the showWarnings option from the current context and store it inside of a scope variable,
 2321  // because the context will be probably lost in some function calls.
 2322  var showWarnings = this.showWarnings;
 2323
 2324  return {
 2325    expect: expect,
 2326    expectAsync: expectAsync,
 2327    expectWithText: expectWithText,
 2328    expectWithoutText: expectWithoutText,
 2329    getText: getText
 2330  };
 2331
 2332  /**
 2333   * Check if expected attribute has been specified on the target element or child
 2334   * @param element
 2335   * @param attrName
 2336   * @param {optional} defaultValue What to set the attr to if no value is found
 2337   */
 2338  function expect(element, attrName, defaultValue) {
 2339
 2340    var node = angular.element(element)[0] || element;
 2341
 2342    // if node exists and neither it nor its children have the attribute
 2343    if (node &&
 2344       ((!node.hasAttribute(attrName) ||
 2345        node.getAttribute(attrName).length === 0) &&
 2346        !childHasAttribute(node, attrName))) {
 2347
 2348      defaultValue = angular.isString(defaultValue) ? defaultValue.trim() : '';
 2349      if (defaultValue.length) {
 2350        element.attr(attrName, defaultValue);
 2351      } else if (showWarnings) {
 2352        $log.warn('ARIA: Attribute "', attrName, '", required for accessibility, is missing on node:', node);
 2353      }
 2354
 2355    }
 2356  }
 2357
 2358  function expectAsync(element, attrName, defaultValueGetter) {
 2359    // Problem: when retrieving the element's contents synchronously to find the label,
 2360    // the text may not be defined yet in the case of a binding.
 2361    // There is a higher chance that a binding will be defined if we wait one frame.
 2362    $$rAF(function() {
 2363        expect(element, attrName, defaultValueGetter());
 2364    });
 2365  }
 2366
 2367  function expectWithText(element, attrName) {
 2368    var content = getText(element) || "";
 2369    var hasBinding = content.indexOf($interpolate.startSymbol()) > -1;
 2370
 2371    if (hasBinding) {
 2372      expectAsync(element, attrName, function() {
 2373        return getText(element);
 2374      });
 2375    } else {
 2376      expect(element, attrName, content);
 2377    }
 2378  }
 2379
 2380  function expectWithoutText(element, attrName) {
 2381    var content = getText(element);
 2382    var hasBinding = content.indexOf($interpolate.startSymbol()) > -1;
 2383
 2384    if ( !hasBinding && !content) {
 2385      expect(element, attrName, content);
 2386    }
 2387  }
 2388
 2389  function getText(element) {
 2390    element = element[0] || element;
 2391    var walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
 2392    var text = '';
 2393
 2394    var node;
 2395    while (node = walker.nextNode()) {
 2396      if (!isAriaHiddenNode(node)) {
 2397        text += node.textContent;
 2398      }
 2399    }
 2400
 2401    return text.trim() || '';
 2402
 2403    function isAriaHiddenNode(node) {
 2404      while (node.parentNode && (node = node.parentNode) !== element) {
 2405        if (node.getAttribute && node.getAttribute('aria-hidden') === 'true') {
 2406          return true;
 2407        }
 2408      }
 2409    }
 2410  }
 2411
 2412  function childHasAttribute(node, attrName) {
 2413    var hasChildren = node.hasChildNodes(),
 2414        hasAttr = false;
 2415
 2416    function isHidden(el) {
 2417      var style = el.currentStyle ? el.currentStyle : $window.getComputedStyle(el);
 2418      return (style.display === 'none');
 2419    }
 2420
 2421    if (hasChildren) {
 2422      var children = node.childNodes;
 2423      for (var i=0; i < children.length; i++) {
 2424        var child = children[i];
 2425        if (child.nodeType === 1 && child.hasAttribute(attrName)) {
 2426          if (!isHidden(child)) {
 2427            hasAttr = true;
 2428          }
 2429        }
 2430      }
 2431    }
 2432
 2433    return hasAttr;
 2434  }
 2435}
 2436
 2437})();
 2438(function(){
 2439"use strict";
 2440
 2441
 2442MdGesture.$inject = ["$$MdGestureHandler", "$$rAF", "$timeout"];
 2443attachToDocument.$inject = ["$mdGesture", "$$MdGestureHandler"];var HANDLERS = {};
 2444
 2445/* The state of the current 'pointer'
 2446 * The pointer represents the state of the current touch.
 2447 * It contains normalized x and y coordinates from DOM events,
 2448 * as well as other information abstracted from the DOM.
 2449 */
 2450
 2451var pointer, lastPointer, forceSkipClickHijack = false;
 2452
 2453/**
 2454 * The position of the most recent click if that click was on a label element.
 2455 * @type {{x: number, y: number}?}
 2456 */
 2457var lastLabelClickPos = null;
 2458
 2459// Used to attach event listeners once when multiple ng-apps are running.
 2460var isInitialized = false;
 2461
 2462angular
 2463  .module('material.core.gestures', [ ])
 2464  .provider('$mdGesture', MdGestureProvider)
 2465  .factory('$$MdGestureHandler', MdGestureHandler)
 2466  .run( attachToDocument );
 2467
 2468/**
 2469   * @ngdoc service
 2470   * @name $mdGestureProvider
 2471   * @module material.core.gestures
 2472   *
 2473   * @description
 2474   * In some scenarios on Mobile devices (without jQuery), the click events should NOT be hijacked.
 2475   * `$mdGestureProvider` is used to configure the Gesture module to ignore or skip click hijacking on mobile
 2476   * devices.
 2477   *
 2478   * <hljs lang="js">
 2479   *   app.config(function($mdGestureProvider) {
 2480   *
 2481   *     // For mobile devices without jQuery loaded, do not
 2482   *     // intercept click events during the capture phase.
 2483   *     $mdGestureProvider.skipClickHijack();
 2484   *
 2485   *   });
 2486   * </hljs>
 2487   *
 2488   */
 2489function MdGestureProvider() { }
 2490
 2491MdGestureProvider.prototype = {
 2492
 2493  // Publish access to setter to configure a variable  BEFORE the
 2494  // $mdGesture service is instantiated...
 2495  skipClickHijack: function() {
 2496    return forceSkipClickHijack = true;
 2497  },
 2498
 2499  /**
 2500   * $get is used to build an instance of $mdGesture
 2501   * @ngInject
 2502   */
 2503  $get : ["$$MdGestureHandler", "$$rAF", "$timeout", function($$MdGestureHandler, $$rAF, $timeout) {
 2504       return new MdGesture($$MdGestureHandler, $$rAF, $timeout);
 2505  }]
 2506};
 2507
 2508
 2509
 2510/**
 2511 * MdGesture factory construction function
 2512 * @ngInject
 2513 */
 2514function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
 2515  var userAgent = navigator.userAgent || navigator.vendor || window.opera;
 2516  var isIos = userAgent.match(/ipad|iphone|ipod/i);
 2517  var isAndroid = userAgent.match(/android/i);
 2518  var touchActionProperty = getTouchAction();
 2519  var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
 2520
 2521  var self = {
 2522    handler: addHandler,
 2523    register: register,
 2524    // On mobile w/out jQuery, we normally intercept clicks. Should we skip that?
 2525    isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack
 2526  };
 2527
 2528  if (self.isHijackingClicks) {
 2529    var maxClickDistance = 6;
 2530    self.handler('click', {
 2531      options: {
 2532        maxDistance: maxClickDistance
 2533      },
 2534      onEnd: checkDistanceAndEmit('click')
 2535    });
 2536
 2537    self.handler('focus', {
 2538      options: {
 2539        maxDistance: maxClickDistance
 2540      },
 2541      onEnd: function(ev, pointer) {
 2542        if (pointer.distance < this.state.options.maxDistance) {
 2543          if (canFocus(ev.target)) {
 2544            this.dispatchEvent(ev, 'focus', pointer);
 2545            ev.target.focus();
 2546          }
 2547        }
 2548
 2549        function canFocus(element) {
 2550          var focusableElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA', 'VIDEO', 'AUDIO'];
 2551
 2552          return (element.getAttribute('tabindex') != '-1') &&
 2553              !element.hasAttribute('DISABLED') &&
 2554              (element.hasAttribute('tabindex') || element.hasAttribute('href') || element.isContentEditable ||
 2555              (focusableElements.indexOf(element.nodeName) != -1));
 2556        }
 2557      }
 2558    });
 2559
 2560    self.handler('mouseup', {
 2561      options: {
 2562        maxDistance: maxClickDistance
 2563      },
 2564      onEnd: checkDistanceAndEmit('mouseup')
 2565    });
 2566
 2567    self.handler('mousedown', {
 2568      onStart: function(ev) {
 2569        this.dispatchEvent(ev, 'mousedown');
 2570      }
 2571    });
 2572  }
 2573
 2574  function checkDistanceAndEmit(eventName) {
 2575    return function(ev, pointer) {
 2576      if (pointer.distance < this.state.options.maxDistance) {
 2577        this.dispatchEvent(ev, eventName, pointer);
 2578      }
 2579    };
 2580  }
 2581
 2582  /*
 2583   * Register an element to listen for a handler.
 2584   * This allows an element to override the default options for a handler.
 2585   * Additionally, some handlers like drag and hold only dispatch events if
 2586   * the domEvent happens inside an element that's registered to listen for these events.
 2587   *
 2588   * @see GestureHandler for how overriding of default options works.
 2589   * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horziontal: false })
 2590   */
 2591  function register(element, handlerName, options) {
 2592    var handler = HANDLERS[handlerName.replace(/^\$md./, '')];
 2593    if (!handler) {
 2594      throw new Error('Failed to register element with handler ' + handlerName + '. ' +
 2595      'Available handlers: ' + Object.keys(HANDLERS).join(', '));
 2596    }
 2597    return handler.registerElement(element, options);
 2598  }
 2599
 2600  /*
 2601   * add a handler to $mdGesture. see below.
 2602   */
 2603  function addHandler(name, definition) {
 2604    var handler = new $$MdGestureHandler(name);
 2605    angular.extend(handler, definition);
 2606    HANDLERS[name] = handler;
 2607
 2608    return self;
 2609  }
 2610
 2611  /*
 2612   * Register handlers. These listen to touch/start/move events, interpret them,
 2613   * and dispatch gesture events depending on options & conditions. These are all
 2614   * instances of GestureHandler.
 2615   * @see GestureHandler
 2616   */
 2617  return self
 2618    /*
 2619     * The press handler dispatches an event on touchdown/touchend.
 2620     * It's a simple abstraction of touch/mouse/pointer start and end.
 2621     */
 2622    .handler('press', {
 2623      onStart: function (ev, pointer) {
 2624        this.dispatchEvent(ev, '$md.pressdown');
 2625      },
 2626      onEnd: function (ev, pointer) {
 2627        this.dispatchEvent(ev, '$md.pressup');
 2628      }
 2629    })
 2630
 2631    /*
 2632     * The hold handler dispatches an event if the user keeps their finger within
 2633     * the same <maxDistance> area for <delay> ms.
 2634     * The hold handler will only run if a parent of the touch target is registered
 2635     * to listen for hold events through $mdGesture.register()
 2636     */
 2637    .handler('hold', {
 2638      options: {
 2639        maxDistance: 6,
 2640        delay: 500
 2641      },
 2642      onCancel: function () {
 2643        $timeout.cancel(this.state.timeout);
 2644      },
 2645      onStart: function (ev, pointer) {
 2646        // For hold, require a parent to be registered with $mdGesture.register()
 2647        // Because we prevent scroll events, this is necessary.
 2648        if (!this.state.registeredParent) return this.cancel();
 2649
 2650        this.state.pos = {x: pointer.x, y: pointer.y};
 2651        this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() {
 2652          this.dispatchEvent(ev, '$md.hold');
 2653          this.cancel(); //we're done!
 2654        }), this.state.options.delay, false);
 2655      },
 2656      onMove: function (ev, pointer) {
 2657        // Don't scroll while waiting for hold.
 2658        // If we don't preventDefault touchmove events here, Android will assume we don't
 2659        // want to listen to anymore touch events. It will start scrolling and stop sending
 2660        // touchmove events.
 2661        if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
 2662
 2663        // If the user moves greater than <maxDistance> pixels, stop the hold timer
 2664        // set in onStart
 2665        var dx = this.state.pos.x - pointer.x;
 2666        var dy = this.state.pos.y - pointer.y;
 2667        if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) {
 2668          this.cancel();
 2669        }
 2670      },
 2671      onEnd: function () {
 2672        this.onCancel();
 2673      }
 2674    })
 2675
 2676    /*
 2677     * The drag handler dispatches a drag event if the user holds and moves his finger greater than
 2678     * <minDistance> px in the x or y direction, depending on options.horizontal.
 2679     * The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in
 2680     * the perpendicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>
 2681     * pixels vertically, this handler won't consider the move part of a drag.
 2682     */
 2683    .handler('drag', {
 2684      options: {
 2685        minDistance: 6,
 2686        horizontal: true,
 2687        cancelMultiplier: 1.5
 2688      },
 2689      onSetup: function(element, options) {
 2690        if (touchActionProperty) {
 2691          // We check for horizontal to be false, because otherwise we would overwrite the default opts.
 2692          this.oldTouchAction = element[0].style[touchActionProperty];
 2693          element[0].style[touchActionProperty] = options.horizontal ? 'pan-y' : 'pan-x';
 2694        }
 2695      },
 2696      onCleanup: function(element) {
 2697        if (this.oldTouchAction) {
 2698          element[0].style[touchActionProperty] = this.oldTouchAction;
 2699        }
 2700      },
 2701      onStart: function (ev) {
 2702        // For drag, require a parent to be registered with $mdGesture.register()
 2703        if (!this.state.registeredParent) this.cancel();
 2704      },
 2705      onMove: function (ev, pointer) {
 2706        var shouldStartDrag, shouldCancel;
 2707        // Don't scroll while deciding if this touchmove qualifies as a drag event.
 2708        // If we don't preventDefault touchmove events here, Android will assume we don't
 2709        // want to listen to anymore touch events. It will start scrolling and stop sending
 2710        // touchmove events.
 2711        if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
 2712
 2713        if (!this.state.dragPointer) {
 2714          if (this.state.options.horizontal) {
 2715            shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance;
 2716            shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier;
 2717          } else {
 2718            shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance;
 2719            shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier;
 2720          }
 2721
 2722          if (shouldStartDrag) {
 2723            // Create a new pointer representing this drag, starting at this point where the drag started.
 2724            this.state.dragPointer = makeStartPointer(ev);
 2725            updatePointerState(ev, this.state.dragPointer);
 2726            this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer);
 2727
 2728          } else if (shouldCancel) {
 2729            this.cancel();
 2730          }
 2731        } else {
 2732          this.dispatchDragMove(ev);
 2733        }
 2734      },
 2735      // Only dispatch dragmove events every frame; any more is unnecessary
 2736      dispatchDragMove: $$rAF.throttle(function (ev) {
 2737        // Make sure the drag didn't stop while waiting for the next frame
 2738        if (this.state.isRunning) {
 2739          updatePointerState(ev, this.state.dragPointer);
 2740          this.dispatchEvent(ev, '$md.drag', this.state.dragPointer);
 2741        }
 2742      }),
 2743      onEnd: function (ev, pointer) {
 2744        if (this.state.dragPointer) {
 2745          updatePointerState(ev, this.state.dragPointer);
 2746          this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer);
 2747        }
 2748      }
 2749    })
 2750
 2751    /*
 2752     * The swipe handler will dispatch a swipe event if, on the end of a touch,
 2753     * the velocity and distance were high enough.
 2754     */
 2755    .handler('swipe', {
 2756      options: {
 2757        minVelocity: 0.65,
 2758        minDistance: 10
 2759      },
 2760      onEnd: function (ev, pointer) {
 2761        var eventType;
 2762
 2763        if (Math.abs(pointer.velocityX) > this.state.options.minVelocity &&
 2764          Math.abs(pointer.distanceX) > this.state.options.minDistance) {
 2765          eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight';
 2766          this.dispatchEvent(ev, eventType);
 2767        }
 2768        else if (Math.abs(pointer.velocityY) > this.state.options.minVelocity &&
 2769          Math.abs(pointer.distanceY) > this.state.options.minDistance) {
 2770          eventType = pointer.directionY == 'up' ? '$md.swipeup' : '$md.swipedown';
 2771          this.dispatchEvent(ev, eventType);
 2772        }
 2773      }
 2774    });
 2775
 2776  function getTouchAction() {
 2777    var testEl = document.createElement('div');
 2778    var vendorPrefixes = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
 2779
 2780    for (var i = 0; i < vendorPrefixes.length; i++) {
 2781      var prefix = vendorPrefixes[i];
 2782      var property = prefix ? prefix + 'TouchAction' : 'touchAction';
 2783      if (angular.isDefined(testEl.style[property])) {
 2784        return property;
 2785      }
 2786    }
 2787  }
 2788
 2789}
 2790
 2791/**
 2792 * MdGestureHandler
 2793 * A GestureHandler is an object which is able to dispatch custom dom events
 2794 * based on native dom {touch,pointer,mouse}{start,move,end} events.
 2795 *
 2796 * A gesture will manage its lifecycle through the start,move,end, and cancel
 2797 * functions, which are called by native dom events.
 2798 *
 2799 * A gesture has the concept of 'options' (eg a swipe's required velocity), which can be
 2800 * overridden by elements registering through $mdGesture.register()
 2801 */
 2802function GestureHandler (name) {
 2803  this.name = name;
 2804  this.state = {};
 2805}
 2806
 2807function MdGestureHandler() {
 2808  var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
 2809
 2810  GestureHandler.prototype = {
 2811    options: {},
 2812    // jQuery listeners don't work with custom DOMEvents, so we have to dispatch events
 2813    // differently when jQuery is loaded
 2814    dispatchEvent: hasJQuery ?  jQueryDispatchEvent : nativeDispatchEvent,
 2815
 2816    // These are overridden by the registered handler
 2817    onSetup: angular.noop,
 2818    onCleanup: angular.noop,
 2819    onStart: angular.noop,
 2820    onMove: angular.noop,
 2821    onEnd: angular.noop,
 2822    onCancel: angular.noop,
 2823
 2824    // onStart sets up a new state for the handler, which includes options from the
 2825    // nearest registered parent element of ev.target.
 2826    start: function (ev, pointer) {
 2827      if (this.state.isRunning) return;
 2828      var parentTarget = this.getNearestParent(ev.target);
 2829      // Get the options from the nearest registered parent
 2830      var parentTargetOptions = parentTarget && parentTarget.$mdGesture[this.name] || {};
 2831
 2832      this.state = {
 2833        isRunning: true,
 2834        // Override the default options with the nearest registered parent's options
 2835        options: angular.extend({}, this.options, parentTargetOptions),
 2836        // Pass in the registered parent node to the state so the onStart listener can use
 2837        registeredParent: parentTarget
 2838      };
 2839      this.onStart(ev, pointer);
 2840    },
 2841    move: function (ev, pointer) {
 2842      if (!this.state.isRunning) return;
 2843      this.onMove(ev, pointer);
 2844    },
 2845    end: function (ev, pointer) {
 2846      if (!this.state.isRunning) return;
 2847      this.onEnd(ev, pointer);
 2848      this.state.isRunning = false;
 2849    },
 2850    cancel: function (ev, pointer) {
 2851      this.onCancel(ev, pointer);
 2852      this.state = {};
 2853    },
 2854
 2855    // Find and return the nearest parent element that has been registered to
 2856    // listen for this handler via $mdGesture.register(element, 'handlerName').
 2857    getNearestParent: function (node) {
 2858      var current = node;
 2859      while (current) {
 2860        if ((current.$mdGesture || {})[this.name]) {
 2861          return current;
 2862        }
 2863        current = current.parentNode;
 2864      }
 2865      return null;
 2866    },
 2867
 2868    // Called from $mdGesture.register when an element registers itself with a handler.
 2869    // Store the options the user gave on the DOMElement itself. These options will
 2870    // be retrieved with getNearestParent when the handler starts.
 2871    registerElement: function (element, options) {
 2872      var self = this;
 2873      element[0].$mdGesture = element[0].$mdGesture || {};
 2874      element[0].$mdGesture[this.name] = options || {};
 2875      element.on('$destroy', onDestroy);
 2876
 2877      self.onSetup(element, options || {});
 2878
 2879      return onDestroy;
 2880
 2881      function onDestroy() {
 2882        delete element[0].$mdGesture[self.name];
 2883        element.off('$destroy', onDestroy);
 2884
 2885        self.onCleanup(element, options || {});
 2886      }
 2887    }
 2888  };
 2889
 2890  return GestureHandler;
 2891
 2892  /*
 2893   * Dispatch an event with jQuery
 2894   * TODO: Make sure this sends bubbling events
 2895   *
 2896   * @param srcEvent the original DOM touch event that started this.
 2897   * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
 2898   * @param eventPointer the pointer object that matches this event.
 2899   */
 2900  function jQueryDispatchEvent(srcEvent, eventType, eventPointer) {
 2901    eventPointer = eventPointer || pointer;
 2902    var eventObj = new angular.element.Event(eventType);
 2903
 2904    eventObj.$material = true;
 2905    eventObj.pointer = eventPointer;
 2906    eventObj.srcEvent = srcEvent;
 2907
 2908    angular.extend(eventObj, {
 2909      clientX: eventPointer.x,
 2910      clientY: eventPointer.y,
 2911      screenX: eventPointer.x,
 2912      screenY: eventPointer.y,
 2913      pageX: eventPointer.x,
 2914      pageY: eventPointer.y,
 2915      ctrlKey: srcEvent.ctrlKey,
 2916      altKey: srcEvent.altKey,
 2917      shiftKey: srcEvent.shiftKey,
 2918      metaKey: srcEvent.metaKey
 2919    });
 2920    angular.element(eventPointer.target).trigger(eventObj);
 2921  }
 2922
 2923  /*
 2924   * NOTE: nativeDispatchEvent is very performance sensitive.
 2925   * @param srcEvent the original DOM touch event that started this.
 2926   * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
 2927   * @param eventPointer the pointer object that matches this event.
 2928   */
 2929  function nativeDispatchEvent(srcEvent, eventType, eventPointer) {
 2930    eventPointer = eventPointer || pointer;
 2931    var eventObj;
 2932
 2933    if (eventType === 'click' || eventType == 'mouseup' || eventType == 'mousedown' ) {
 2934      eventObj = document.createEvent('MouseEvents');
 2935      eventObj.initMouseEvent(
 2936        eventType, true, true, window, srcEvent.detail,
 2937        eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y,
 2938        srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey,
 2939        srcEvent.button, srcEvent.relatedTarget || null
 2940      );
 2941
 2942    } else {
 2943      eventObj = document.createEvent('CustomEvent');
 2944      eventObj.initCustomEvent(eventType, true, true, {});
 2945    }
 2946    eventObj.$material = true;
 2947    eventObj.pointer = eventPointer;
 2948    eventObj.srcEvent = srcEvent;
 2949    eventPointer.target.dispatchEvent(eventObj);
 2950  }
 2951
 2952}
 2953
 2954/**
 2955 * Attach Gestures: hook document and check shouldHijack clicks
 2956 * @ngInject
 2957 */
 2958function attachToDocument( $mdGesture, $$MdGestureHandler ) {
 2959
 2960  // Polyfill document.contains for IE11.
 2961  // TODO: move to util
 2962  document.contains || (document.contains = function (node) {
 2963    return document.body.contains(node);
 2964  });
 2965
 2966  if (!isInitialized && $mdGesture.isHijackingClicks ) {
 2967    /*
 2968     * If hijack clicks is true, we preventDefault any click that wasn't
 2969     * sent by ngMaterial. This is because on older Android & iOS, a false, or 'ghost',
 2970     * click event will be sent ~400ms after a touchend event happens.
 2971     * The only way to know if this click is real is to prevent any normal
 2972     * click events, and add a flag to events sent by material so we know not to prevent those.
 2973     *
 2974     * Two exceptions to click events that should be prevented are:
 2975     *  - click events sent by the keyboard (eg form submit)
 2976     *  - events that originate from an Ionic app
 2977     */
 2978    document.addEventListener('click'    , clickHijacker     , true);
 2979    document.addEventListener('mouseup'  , mouseInputHijacker, true);
 2980    document.addEventListener('mousedown', mouseInputHijacker, true);
 2981    document.addEventListener('focus'    , mouseInputHijacker, true);
 2982
 2983    isInitialized = true;
 2984  }
 2985
 2986  function mouseInputHijacker(ev) {
 2987    var isKeyClick = !ev.clientX && !ev.clientY;
 2988    if (!isKeyClick && !ev.$material && !ev.isIonicTap
 2989      && !isInputEventFromLabelClick(ev)) {
 2990      ev.preventDefault();
 2991      ev.stopPropagation();
 2992    }
 2993  }
 2994
 2995  function clickHijacker(ev) {
 2996    var isKeyClick = ev.clientX === 0 && ev.clientY === 0;
 2997    if (!isKeyClick && !ev.$material && !ev.isIonicTap
 2998      && !isInputEventFromLabelClick(ev)) {
 2999      ev.preventDefault();
 3000      ev.stopPropagation();
 3001      lastLabelClickPos = null;
 3002    } else {
 3003      lastLabelClickPos = null;
 3004      if (ev.target.tagName.toLowerCase() == 'label') {
 3005        lastLabelClickPos = {x: ev.x, y: ev.y};
 3006      }
 3007    }
 3008  }
 3009
 3010
 3011  // Listen to all events to cover all platforms.
 3012  var START_EVENTS = 'mousedown touchstart pointerdown';
 3013  var MOVE_EVENTS = 'mousemove touchmove pointermove';
 3014  var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel';
 3015
 3016  angular.element(document)
 3017    .on(START_EVENTS, gestureStart)
 3018    .on(MOVE_EVENTS, gestureMove)
 3019    .on(END_EVENTS, gestureEnd)
 3020    // For testing
 3021    .on('$$mdGestureReset', function gestureClearCache () {
 3022      lastPointer = pointer = null;
 3023    });
 3024
 3025  /*
 3026   * When a DOM event happens, run all registered gesture handlers' lifecycle
 3027   * methods which match the DOM event.
 3028   * Eg when a 'touchstart' event happens, runHandlers('start') will call and
 3029   * run `handler.cancel()` and `handler.start()` on all registered handlers.
 3030   */
 3031  function runHandlers(handlerEvent, event) {
 3032    var handler;
 3033    for (var name in HANDLERS) {
 3034      handler = HANDLERS[name];
 3035      if( handler instanceof $$MdGestureHandler ) {
 3036
 3037        if (handlerEvent === 'start') {
 3038          // Run cancel to reset any handlers' state
 3039          handler.cancel();
 3040        }
 3041        handler[handlerEvent](event, pointer);
 3042
 3043      }
 3044    }
 3045  }
 3046
 3047  /*
 3048   * gestureStart vets if a start event is legitimate (and not part of a 'ghost click' from iOS/Android)
 3049   * If it is legitimate, we initiate the pointer state and mark the current pointer's type
 3050   * For example, for a touchstart event, mark the current pointer as a 'touch' pointer, so mouse events
 3051   * won't effect it.
 3052   */
 3053  function gestureStart(ev) {
 3054    // If we're already touched down, abort
 3055    if (pointer) return;
 3056
 3057    var now = +Date.now();
 3058
 3059    // iOS & old android bug: after a touch event, a click event is sent 350 ms later.
 3060    // If <400ms have passed, don't allow an event of a different type than the previous event
 3061    if (lastPointer && !typesMatch(ev, lastPointer) && (now - lastPointer.endTime < 1500)) {
 3062      return;
 3063    }
 3064
 3065    pointer = makeStartPointer(ev);
 3066
 3067    runHandlers('start', ev);
 3068  }
 3069  /*
 3070   * If a move event happens of the right type, update the pointer and run all the move handlers.
 3071   * "of the right type": if a mousemove happens but our pointer started with a touch event, do nothing.
 3072   */
 3073  function gestureMove(ev) {
 3074    if (!pointer || !typesMatch(ev, pointer)) return;
 3075
 3076    updatePointerState(ev, pointer);
 3077    runHandlers('move', ev);
 3078  }
 3079  /*
 3080   * If an end event happens of the right type, update the pointer, run endHandlers, and save the pointer as 'lastPointer'
 3081   */
 3082  function gestureEnd(ev) {
 3083    if (!pointer || !typesMatch(ev, pointer)) return;
 3084
 3085    updatePointerState(ev, pointer);
 3086    pointer.endTime = +Date.now();
 3087
 3088    runHandlers('end', ev);
 3089
 3090    lastPointer = pointer;
 3091    pointer = null;
 3092  }
 3093
 3094}
 3095
 3096// ********************
 3097// Module Functions
 3098// ********************
 3099
 3100/*
 3101 * Initiate the pointer. x, y, and the pointer's type.
 3102 */
 3103function makeStartPointer(ev) {
 3104  var point = getEventPoint(ev);
 3105  var startPointer = {
 3106    startTime: +Date.now(),
 3107    target: ev.target,
 3108    // 'p' for pointer events, 'm' for mouse, 't' for touch
 3109    type: ev.type.charAt(0)
 3110  };
 3111  startPointer.startX = startPointer.x = point.pageX;
 3112  startPointer.startY = startPointer.y = point.pageY;
 3113  return startPointer;
 3114}
 3115
 3116/*
 3117 * return whether the pointer's type matches the event's type.
 3118 * Eg if a touch event happens but the pointer has a mouse type, return false.
 3119 */
 3120function typesMatch(ev, pointer) {
 3121  return ev && pointer && ev.type.charAt(0) === pointer.type;
 3122}
 3123
 3124/**
 3125 * Gets whether the given event is an input event that was caused by clicking on an
 3126 * associated label element.
 3127 *
 3128 * This is necessary because the browser will, upon clicking on a label element, fire an
 3129 * *extra* click event on its associated input (if any). mdGesture is able to flag the label
 3130 * click as with `$material` correctly, but not the second input click.
 3131 *
 3132 * In order to determine whether an input event is from a label click, we compare the (x, y) for
 3133 * the event to the (x, y) for the most recent label click (which is cleared whenever a non-label
 3134 * click occurs). Unfortunately, there are no event properties that tie the input and the label
 3135 * together (such as relatedTarget).
 3136 *
 3137 * @param {MouseEvent} event
 3138 * @returns {boolean}
 3139 */
 3140function isInputEventFromLabelClick(event) {
 3141  return lastLabelClickPos
 3142      && lastLabelClickPos.x == event.x
 3143      && lastLabelClickPos.y == event.y;
 3144}
 3145
 3146/*
 3147 * Update the given pointer based upon the given DOMEvent.
 3148 * Distance, velocity, direction, duration, etc
 3149 */
 3150function updatePointerState(ev, pointer) {
 3151  var point = getEventPoint(ev);
 3152  var x = pointer.x = point.pageX;
 3153  var y = pointer.y = point.pageY;
 3154
 3155  pointer.distanceX = x - pointer.startX;
 3156  pointer.distanceY = y - pointer.startY;
 3157  pointer.distance = Math.sqrt(
 3158    pointer.distanceX * pointer.distanceX + pointer.distanceY * pointer.distanceY
 3159  );
 3160
 3161  pointer.directionX = pointer.distanceX > 0 ? 'right' : pointer.distanceX < 0 ? 'left' : '';
 3162  pointer.directionY = pointer.distanceY > 0 ? 'down' : pointer.distanceY < 0 ? 'up' : '';
 3163
 3164  pointer.duration = +Date.now() - pointer.startTime;
 3165  pointer.velocityX = pointer.distanceX / pointer.duration;
 3166  pointer.velocityY = pointer.distanceY / pointer.duration;
 3167}
 3168
 3169/*
 3170 * Normalize the point where the DOM event happened whether it's touch or mouse.
 3171 * @returns point event obj with pageX and pageY on it.
 3172 */
 3173function getEventPoint(ev) {
 3174  ev = ev.originalEvent || ev; // support jQuery events
 3175  return (ev.touches && ev.touches[0]) ||
 3176    (ev.changedTouches && ev.changedTouches[0]) ||
 3177    ev;
 3178}
 3179
 3180})();
 3181(function(){
 3182"use strict";
 3183
 3184angular.module('material.core')
 3185  .provider('$$interimElement', InterimElementProvider);
 3186
 3187/*
 3188 * @ngdoc service
 3189 * @name $$interimElement
 3190 * @module material.core
 3191 *
 3192 * @description
 3193 *
 3194 * Factory that contructs `$$interimElement.$service` services.
 3195 * Used internally in material design for elements that appear on screen temporarily.
 3196 * The service provides a promise-like API for interacting with the temporary
 3197 * elements.
 3198 *
 3199 * ```js
 3200 * app.service('$mdToast', function($$interimElement) {
 3201 *   var $mdToast = $$interimElement(toastDefaultOptions);
 3202 *   return $mdToast;
 3203 * });
 3204 * ```
 3205 * @param {object=} defaultOptions Options used by default for the `show` method on the service.
 3206 *
 3207 * @returns {$$interimElement.$service}
 3208 *
 3209 */
 3210
 3211function InterimElementProvider() {
 3212  InterimElementFactory.$inject = ["$document", "$q", "$rootScope", "$timeout", "$rootElement", "$animate", "$mdUtil", "$mdCompiler", "$mdTheming", "$injector", "$exceptionHandler"];
 3213  createInterimElementProvider.$get = InterimElementFactory;
 3214  return createInterimElementProvider;
 3215
 3216  /**
 3217   * Returns a new provider which allows configuration of a new interimElement
 3218   * service. Allows configuration of default options & methods for options,
 3219   * as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method)
 3220   */
 3221  function createInterimElementProvider(interimFactoryName) {
 3222    factory.$inject = ["$$interimElement", "$injector"];
 3223    var EXPOSED_METHODS = ['onHide', 'onShow', 'onRemove'];
 3224
 3225    var customMethods = {};
 3226    var providerConfig = {
 3227      presets: {}
 3228    };
 3229
 3230    var provider = {
 3231      setDefaults: setDefaults,
 3232      addPreset: addPreset,
 3233      addMethod: addMethod,
 3234      $get: factory
 3235    };
 3236
 3237    /**
 3238     * all interim elements will come with the 'build' preset
 3239     */
 3240    provider.addPreset('build', {
 3241      methods: ['controller', 'controllerAs', 'resolve', 'multiple',
 3242        'template', 'templateUrl', 'themable', 'transformTemplate', 'parent', 'contentElement']
 3243    });
 3244
 3245    return provider;
 3246
 3247    /**
 3248     * Save the configured defaults to be used when the factory is instantiated
 3249     */
 3250    function setDefaults(definition) {
 3251      providerConfig.optionsFactory = definition.options;
 3252      providerConfig.methods = (definition.methods || []).concat(EXPOSED_METHODS);
 3253      return provider;
 3254    }
 3255
 3256    /**
 3257     * Add a method to the factory that isn't specific to any interim element operations
 3258     */
 3259
 3260    function addMethod(name, fn) {
 3261      customMethods[name] = fn;
 3262      return provider;
 3263    }
 3264
 3265    /**
 3266     * Save the configured preset to be used when the factory is instantiated
 3267     */
 3268    function addPreset(name, definition) {
 3269      definition = definition || {};
 3270      definition.methods = definition.methods || [];
 3271      definition.options = definition.options || function() { return {}; };
 3272
 3273      if (/^cancel|hide|show$/.test(name)) {
 3274        throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!");
 3275      }
 3276      if (definition.methods.indexOf('_options') > -1) {
 3277        throw new Error("Method '_options' in " + interimFactoryName + " is reserved!");
 3278      }
 3279      providerConfig.presets[name] = {
 3280        methods: definition.methods.concat(EXPOSED_METHODS),
 3281        optionsFactory: definition.options,
 3282        argOption: definition.argOption
 3283      };
 3284      return provider;
 3285    }
 3286
 3287    function addPresetMethod(presetName, methodName, method) {
 3288      providerConfig.presets[presetName][methodName] = method;
 3289    }
 3290
 3291    /**
 3292     * Create a factory that has the given methods & defaults implementing interimElement
 3293     */
 3294    /* @ngInject */
 3295    function factory($$interimElement, $injector) {
 3296      var defaultMethods;
 3297      var defaultOptions;
 3298      var interimElementService = $$interimElement();
 3299
 3300      /*
 3301       * publicService is what the developer will be using.
 3302       * It has methods hide(), cancel(), show(), build(), and any other
 3303       * presets which were set during the config phase.
 3304       */
 3305      var publicService = {
 3306        hide: interimElementService.hide,
 3307        cancel: interimElementService.cancel,
 3308        show: showInterimElement,
 3309
 3310        // Special internal method to destroy an interim element without animations
 3311        // used when navigation changes causes a $scope.$destroy() action
 3312        destroy : destroyInterimElement
 3313      };
 3314
 3315
 3316      defaultMethods = providerConfig.methods || [];
 3317      // This must be invoked after the publicService is initialized
 3318      defaultOptions = invokeFactory(providerConfig.optionsFactory, {});
 3319
 3320      // Copy over the simple custom methods
 3321      angular.forEach(customMethods, function(fn, name) {
 3322        publicService[name] = fn;
 3323      });
 3324
 3325      angular.forEach(providerConfig.presets, function(definition, name) {
 3326        var presetDefaults = invokeFactory(definition.optionsFactory, {});
 3327        var presetMethods = (definition.methods || []).concat(defaultMethods);
 3328
 3329        // Every interimElement built with a preset has a field called `$type`,
 3330        // which matches the name of the preset.
 3331        // Eg in preset 'confirm', options.$type === 'confirm'
 3332        angular.extend(presetDefaults, { $type: name });
 3333
 3334        // This creates a preset class which has setter methods for every
 3335        // method given in the `.addPreset()` function, as well as every
 3336        // method given in the `.setDefaults()` function.
 3337        //
 3338        // @example
 3339        // .setDefaults({
 3340        //   methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'],
 3341        //   options: dialogDefaultOptions
 3342        // })
 3343        // .addPreset('alert', {
 3344        //   methods: ['title', 'ok'],
 3345        //   options: alertDialogOptions
 3346        // })
 3347        //
 3348        // Set values will be passed to the options when interimElement.show() is called.
 3349        function Preset(opts) {
 3350          this._options = angular.extend({}, presetDefaults, opts);
 3351        }
 3352        angular.forEach(presetMethods, function(name) {
 3353          Preset.prototype[name] = function(value) {
 3354            this._options[name] = value;
 3355            return this;
 3356          };
 3357        });
 3358
 3359        // Create shortcut method for one-linear methods
 3360        if (definition.argOption) {
 3361          var methodName = 'show' + name.charAt(0).toUpperCase() + name.slice(1);
 3362          publicService[methodName] = function(arg) {
 3363            var config = publicService[name](arg);
 3364            return publicService.show(config);
 3365          };
 3366        }
 3367
 3368        // eg $mdDialog.alert() will return a new alert preset
 3369        publicService[name] = function(arg) {
 3370          // If argOption is supplied, eg `argOption: 'content'`, then we assume
 3371          // if the argument is not an options object then it is the `argOption` option.
 3372          //
 3373          // @example `$mdToast.simple('hello')` // sets options.content to hello
 3374          //                                     // because argOption === 'content'
 3375          if (arguments.length && definition.argOption &&
 3376              !angular.isObject(arg) && !angular.isArray(arg))  {
 3377
 3378            return (new Preset())[definition.argOption](arg);
 3379
 3380          } else {
 3381            return new Preset(arg);
 3382          }
 3383
 3384        };
 3385      });
 3386
 3387      return publicService;
 3388
 3389      /**
 3390       *
 3391       */
 3392      function showInterimElement(opts) {
 3393        // opts is either a preset which stores its options on an _options field,
 3394        // or just an object made up of options
 3395        opts = opts || { };
 3396        if (opts._options) opts = opts._options;
 3397
 3398        return interimElementService.show(
 3399          angular.extend({}, defaultOptions, opts)
 3400        );
 3401      }
 3402
 3403      /**
 3404       *  Special method to hide and destroy an interimElement WITHOUT
 3405       *  any 'leave` or hide animations ( an immediate force hide/remove )
 3406       *
 3407       *  NOTE: This calls the onRemove() subclass method for each component...
 3408       *  which must have code to respond to `options.$destroy == true`
 3409       */
 3410      function destroyInterimElement(opts) {
 3411          return interimElementService.destroy(opts);
 3412      }
 3413
 3414      /**
 3415       * Helper to call $injector.invoke with a local of the factory name for
 3416       * this provider.
 3417       * If an $mdDialog is providing options for a dialog and tries to inject
 3418       * $mdDialog, a circular dependency error will happen.
 3419       * We get around that by manually injecting $mdDialog as a local.
 3420       */
 3421      function invokeFactory(factory, defaultVal) {
 3422        var locals = {};
 3423        locals[interimFactoryName] = publicService;
 3424        return $injector.invoke(factory || function() { return defaultVal; }, {}, locals);
 3425      }
 3426
 3427    }
 3428
 3429  }
 3430
 3431  /* @ngInject */
 3432  function InterimElementFactory($document, $q, $rootScope, $timeout, $rootElement, $animate,
 3433                                 $mdUtil, $mdCompiler, $mdTheming, $injector, $exceptionHandler) {
 3434    return function createInterimElementService() {
 3435      var SHOW_CANCELLED = false;
 3436
 3437      /*
 3438       * @ngdoc service
 3439       * @name $$interimElement.$service
 3440       *
 3441       * @description
 3442       * A service used to control inserting and removing an element into the DOM.
 3443       *
 3444       */
 3445
 3446      var service;
 3447
 3448      var showPromises = []; // Promises for the interim's which are currently opening.
 3449      var hidePromises = []; // Promises for the interim's which are currently hiding.
 3450      var showingInterims = []; // Interim elements which are currently showing up.
 3451
 3452      // Publish instance $$interimElement service;
 3453      // ... used as $mdDialog, $mdToast, $mdMenu, and $mdSelect
 3454
 3455      return service = {
 3456        show: show,
 3457        hide: waitForInterim(hide),
 3458        cancel: waitForInterim(cancel),
 3459        destroy : destroy,
 3460        $injector_: $injector
 3461      };
 3462
 3463      /*
 3464       * @ngdoc method
 3465       * @name $$interimElement.$service#show
 3466       * @kind function
 3467       *
 3468       * @description
 3469       * Adds the `$interimElement` to the DOM and returns a special promise that will be resolved or rejected
 3470       * with hide or cancel, respectively. To external cancel/hide, developers should use the
 3471       *
 3472       * @param {*} options is hashMap of settings
 3473       * @returns a Promise
 3474       *
 3475       */
 3476      function show(options) {
 3477        options = options || {};
 3478        var interimElement = new InterimElement(options || {});
 3479
 3480        // When an interim element is currently showing, we have to cancel it.
 3481        // Just hiding it, will resolve the InterimElement's promise, the promise should be
 3482        // rejected instead.
 3483        var hideAction = options.multiple ? $q.resolve() : $q.all(showPromises);
 3484
 3485        if (!options.multiple) {
 3486          // Wait for all opening interim's to finish their transition.
 3487          hideAction = hideAction.then(function() {
 3488            // Wait for all closing and showing interim's to be completely closed.
 3489            var promiseArray = hidePromises.concat(showingInterims.map(service.cancel));
 3490            return $q.all(promiseArray);
 3491          });
 3492        }
 3493
 3494        var showAction = hideAction.then(function() {
 3495
 3496          return interimElement
 3497            .show()
 3498            .catch(function(reason) { return reason; })
 3499            .finally(function() {
 3500              showPromises.splice(showPromises.indexOf(showAction), 1);
 3501              showingInterims.push(interimElement);
 3502            });
 3503
 3504        });
 3505
 3506        showPromises.push(showAction);
 3507
 3508        // In Angular 1.6+, exceptions inside promises will cause a rejection. We need to handle
 3509        // the rejection and only log it if it's an error.
 3510        interimElement.deferred.promise.catch(function(fault) {
 3511          if (fault instanceof Error) {
 3512            $exceptionHandler(fault);
 3513          }
 3514
 3515          return fault;
 3516        });
 3517
 3518        // Return a promise that will be resolved when the interim
 3519        // element is hidden or cancelled...
 3520        return interimElement.deferred.promise;
 3521      }
 3522
 3523      /*
 3524       * @ngdoc method
 3525       * @name $$interimElement.$service#hide
 3526       * @kind function
 3527       *
 3528       * @description
 3529       * Removes the `$interimElement` from the DOM and resolves the promise returned from `show`
 3530       *
 3531       * @param {*} resolveParam Data to resolve the promise with
 3532       * @returns a Promise that will be resolved after the element has been removed.
 3533       *
 3534       */
 3535      function hide(reason, options) {
 3536        options = options || {};
 3537
 3538        if (options.closeAll) {
 3539          // We have to make a shallow copy of the array, because otherwise the map will break.
 3540          return $q.all(showingInterims.slice().reverse().map(closeElement));
 3541        } else if (options.closeTo !== undefined) {
 3542          return $q.all(showingInterims.slice(options.closeTo).map(closeElement));
 3543        }
 3544
 3545        // Hide the latest showing interim element.
 3546        return closeElement(showingInterims[showingInterims.length - 1]);
 3547
 3548        function closeElement(interim) {
 3549
 3550          var hideAction = interim
 3551            .remove(reason, false, options || { })
 3552            .catch(function(reason) { return reason; })
 3553            .finally(function() {
 3554              hidePromises.splice(hidePromises.indexOf(hideAction), 1);
 3555            });
 3556
 3557          showingInterims.splice(showingInterims.indexOf(interim), 1);
 3558          hidePromises.push(hideAction);
 3559
 3560          return interim.deferred.promise;
 3561        }
 3562      }
 3563
 3564      /*
 3565       * @ngdoc method
 3566       * @name $$interimElement.$service#cancel
 3567       * @kind function
 3568       *
 3569       * @description
 3570       * Removes the `$interimElement` from the DOM and rejects the promise returned from `show`
 3571       *
 3572       * @param {*} reason Data to reject the promise with
 3573       * @returns Promise that will be resolved after the element has been removed.
 3574       *
 3575       */
 3576      function cancel(reason, options) {
 3577        var interim = showingInterims.pop();
 3578        if (!interim) {
 3579          return $q.when(reason);
 3580        }
 3581
 3582        var cancelAction = interim
 3583          .remove(reason, true, options || {})
 3584          .catch(function(reason) { return reason; })
 3585          .finally(function() {
 3586            hidePromises.splice(hidePromises.indexOf(cancelAction), 1);
 3587          });
 3588
 3589        hidePromises.push(cancelAction);
 3590
 3591        // Since Angular 1.6.7, promises will be logged to $exceptionHandler when the promise
 3592        // is not handling the rejection. We create a pseudo catch handler, which will prevent the
 3593        // promise from being logged to the $exceptionHandler.
 3594        return interim.deferred.promise.catch(angular.noop);
 3595      }
 3596
 3597      /**
 3598       * Creates a function to wait for at least one interim element to be available.
 3599       * @param callbackFn Function to be used as callback
 3600       * @returns {Function}
 3601       */
 3602      function waitForInterim(callbackFn) {
 3603        return function() {
 3604          var fnArguments = arguments;
 3605
 3606          if (!showingInterims.length) {
 3607            // When there are still interim's opening, then wait for the first interim element to
 3608            // finish its open animation.
 3609            if (showPromises.length) {
 3610              return showPromises[0].finally(function () {
 3611                return callbackFn.apply(service, fnArguments);
 3612              });
 3613            }
 3614
 3615            return $q.when("No interim elements currently showing up.");
 3616          }
 3617
 3618          return callbackFn.apply(service, fnArguments);
 3619        };
 3620      }
 3621
 3622      /*
 3623       * Special method to quick-remove the interim element without animations
 3624       * Note: interim elements are in "interim containers"
 3625       */
 3626      function destroy(targetEl) {
 3627        var interim = !targetEl ? showingInterims.shift() : null;
 3628
 3629        var parentEl = angular.element(targetEl).length && angular.element(targetEl)[0].parentNode;
 3630
 3631        if (parentEl) {
 3632          // Try to find the interim in the stack which corresponds to the supplied DOM element.
 3633          var filtered = showingInterims.filter(function(entry) {
 3634            return entry.options.element[0] === parentEl;
 3635          });
 3636
 3637          // Note: This function might be called when the element already has been removed,
 3638          // in which case we won't find any matches.
 3639          if (filtered.length) {
 3640            interim = filtered[0];
 3641            showingInterims.splice(showingInterims.indexOf(interim), 1);
 3642          }
 3643        }
 3644
 3645        return interim ? interim.remove(SHOW_CANCELLED, false, { '$destroy': true }) :
 3646                         $q.when(SHOW_CANCELLED);
 3647      }
 3648
 3649      /*
 3650       * Internal Interim Element Object
 3651       * Used internally to manage the DOM element and related data
 3652       */
 3653      function InterimElement(options) {
 3654        var self, element, showAction = $q.when(true);
 3655
 3656        options = configureScopeAndTransitions(options);
 3657
 3658        return self = {
 3659          options : options,
 3660          deferred: $q.defer(),
 3661          show    : createAndTransitionIn,
 3662          remove  : transitionOutAndRemove
 3663        };
 3664
 3665        /**
 3666         * Compile, link, and show this interim element
 3667         * Use optional autoHided and transition-in effects
 3668         */
 3669        function createAndTransitionIn() {
 3670          return $q(function(resolve, reject) {
 3671
 3672            // Trigger onCompiling callback before the compilation starts.
 3673            // This is useful, when modifying options, which can be influenced by developers.
 3674            options.onCompiling && options.onCompiling(options);
 3675
 3676            compileElement(options)
 3677              .then(function( compiledData ) {
 3678                element = linkElement( compiledData, options );
 3679
 3680                // Expose the cleanup function from the compiler.
 3681                options.cleanupElement = compiledData.cleanup;
 3682
 3683                showAction = showElement(element, options, compiledData.controller)
 3684                  .then(resolve, rejectAll);
 3685              }).catch(rejectAll);
 3686
 3687            function rejectAll(fault) {
 3688              // Force the '$md<xxx>.show()' promise to reject
 3689              self.deferred.reject(fault);
 3690
 3691              // Continue rejection propagation
 3692              reject(fault);
 3693            }
 3694          });
 3695        }
 3696
 3697        /**
 3698         * After the show process has finished/rejected:
 3699         * - announce 'removing',
 3700         * - perform the transition-out, and
 3701         * - perform optional clean up scope.
 3702         */
 3703        function transitionOutAndRemove(response, isCancelled, opts) {
 3704
 3705          // abort if the show() and compile failed
 3706          if ( !element ) return $q.when(false);
 3707
 3708          options = angular.extend(options || {}, opts || {});
 3709          options.cancelAutoHide && options.cancelAutoHide();
 3710          options.element.triggerHandler('$mdInterimElementRemove');
 3711
 3712          if ( options.$destroy === true ) {
 3713
 3714            return hideElement(options.element, options).then(function(){
 3715              (isCancelled && rejectAll(response)) || resolveAll(response);
 3716            });
 3717
 3718          } else {
 3719            $q.when(showAction).finally(function() {
 3720              hideElement(options.element, options).then(function() {
 3721                isCancelled ? rejectAll(response) : resolveAll(response);
 3722              }, rejectAll);
 3723            });
 3724
 3725            return self.deferred.promise;
 3726          }
 3727
 3728
 3729          /**
 3730           * The `show()` returns a promise that will be resolved when the interim
 3731           * element is hidden or cancelled...
 3732           */
 3733          function resolveAll(response) {
 3734            self.deferred.resolve(response);
 3735          }
 3736
 3737          /**
 3738           * Force the '$md<xxx>.show()' promise to reject
 3739           */
 3740          function rejectAll(fault) {
 3741            self.deferred.reject(fault);
 3742          }
 3743        }
 3744
 3745        /**
 3746         * Prepare optional isolated scope and prepare $animate with default enter and leave
 3747         * transitions for the new element instance.
 3748         */
 3749        function configureScopeAndTransitions(options) {
 3750          options = options || { };
 3751          if ( options.template ) {
 3752            options.template = $mdUtil.processTemplate(options.template);
 3753          }
 3754
 3755          return angular.extend({
 3756            preserveScope: false,
 3757            cancelAutoHide : angular.noop,
 3758            scope: options.scope || $rootScope.$new(options.isolateScope),
 3759
 3760            /**
 3761             * Default usage to enable $animate to transition-in; can be easily overridden via 'options'
 3762             */
 3763            onShow: function transitionIn(scope, element, options) {
 3764              return $animate.enter(element, options.parent);
 3765            },
 3766
 3767            /**
 3768             * Default usage to enable $animate to transition-out; can be easily overridden via 'options'
 3769             */
 3770            onRemove: function transitionOut(scope, element) {
 3771              // Element could be undefined if a new element is shown before
 3772              // the old one finishes compiling.
 3773              return element && $animate.leave(element) || $q.when();
 3774            }
 3775          }, options );
 3776
 3777        }
 3778
 3779        /**
 3780         * Compile an element with a templateUrl, controller, and locals
 3781         */
 3782        function compileElement(options) {
 3783
 3784          var compiled = !options.skipCompile ? $mdCompiler.compile(options) : null;
 3785
 3786          return compiled || $q(function (resolve) {
 3787              resolve({
 3788                locals: {},
 3789                link: function () {
 3790                  return options.element;
 3791                }
 3792              });
 3793            });
 3794        }
 3795
 3796        /**
 3797         *  Link an element with compiled configuration
 3798         */
 3799        function linkElement(compileData, options){
 3800          angular.extend(compileData.locals, options);
 3801
 3802          var element = compileData.link(options.scope);
 3803
 3804          // Search for parent at insertion time, if not specified
 3805          options.element = element;
 3806          options.parent = findParent(element, options);
 3807          if (options.themable) $mdTheming(element);
 3808
 3809          return element;
 3810        }
 3811
 3812        /**
 3813         * Search for parent at insertion time, if not specified
 3814         */
 3815        function findParent(element, options) {
 3816          var parent = options.parent;
 3817
 3818          // Search for parent at insertion time, if not specified
 3819          if (angular.isFunction(parent)) {
 3820            parent = parent(options.scope, element, options);
 3821          } else if (angular.isString(parent)) {
 3822            parent = angular.element($document[0].querySelector(parent));
 3823          } else {
 3824            parent = angular.element(parent);
 3825          }
 3826
 3827          // If parent querySelector/getter function fails, or it's just null,
 3828          // find a default.
 3829          if (!(parent || {}).length) {
 3830            var el;
 3831            if ($rootElement[0] && $rootElement[0].querySelector) {
 3832              el = $rootElement[0].querySelector(':not(svg) > body');
 3833            }
 3834            if (!el) el = $rootElement[0];
 3835            if (el.nodeName == '#comment') {
 3836              el = $document[0].body;
 3837            }
 3838            return angular.element(el);
 3839          }
 3840
 3841          return parent;
 3842        }
 3843
 3844        /**
 3845         * If auto-hide is enabled, start timer and prepare cancel function
 3846         */
 3847        function startAutoHide() {
 3848          var autoHideTimer, cancelAutoHide = angular.noop;
 3849
 3850          if (options.hideDelay) {
 3851            autoHideTimer = $timeout(service.hide, options.hideDelay) ;
 3852            cancelAutoHide = function() {
 3853              $timeout.cancel(autoHideTimer);
 3854            };
 3855          }
 3856
 3857          // Cache for subsequent use
 3858          options.cancelAutoHide = function() {
 3859            cancelAutoHide();
 3860            options.cancelAutoHide = undefined;
 3861          };
 3862        }
 3863
 3864        /**
 3865         * Show the element ( with transitions), notify complete and start
 3866         * optional auto-Hide
 3867         */
 3868        function showElement(element, options, controller) {
 3869          // Trigger onShowing callback before the `show()` starts
 3870          var notifyShowing = options.onShowing || angular.noop;
 3871          // Trigger onComplete callback when the `show()` finishes
 3872          var notifyComplete = options.onComplete || angular.noop;
 3873
 3874          // Necessary for consistency between Angular 1.5 and 1.6.
 3875          try {
 3876            notifyShowing(options.scope, element, options, controller);
 3877          } catch (e) {
 3878            return $q.reject(e);
 3879          }
 3880
 3881          return $q(function (resolve, reject) {
 3882            try {
 3883              // Start transitionIn
 3884              $q.when(options.onShow(options.scope, element, options, controller))
 3885                .then(function () {
 3886                  notifyComplete(options.scope, element, options);
 3887                  startAutoHide();
 3888
 3889                  resolve(element);
 3890                }, reject);
 3891
 3892            } catch (e) {
 3893              reject(e.message);
 3894            }
 3895          });
 3896        }
 3897
 3898        function hideElement(element, options) {
 3899          var announceRemoving = options.onRemoving || angular.noop;
 3900
 3901          return $q(function (resolve, reject) {
 3902            try {
 3903              // Start transitionIn
 3904              var action = $q.when( options.onRemove(options.scope, element, options) || true );
 3905
 3906              // Trigger callback *before* the remove operation starts
 3907              announceRemoving(element, action);
 3908
 3909              if (options.$destroy) {
 3910                // For $destroy, onRemove should be synchronous
 3911                resolve(element);
 3912
 3913                if (!options.preserveScope && options.scope ) {
 3914                  // scope destroy should still be be done after the current digest is done
 3915                  action.then( function() { options.scope.$destroy(); });
 3916                }
 3917              } else {
 3918                // Wait until transition-out is done
 3919                action.then(function () {
 3920                  if (!options.preserveScope && options.scope ) {
 3921                    options.scope.$destroy();
 3922                  }
 3923
 3924                  resolve(element);
 3925                }, reject);
 3926              }
 3927            } catch (e) {
 3928              reject(e.message);
 3929            }
 3930          });
 3931        }
 3932
 3933      }
 3934    };
 3935
 3936  }
 3937
 3938}
 3939
 3940})();
 3941(function(){
 3942"use strict";
 3943
 3944/**
 3945 * @ngdoc module
 3946 * @name material.core.interaction
 3947 * @description
 3948 * User interaction detection to provide proper accessibility.
 3949 */
 3950MdInteractionService.$inject = ["$timeout", "$mdUtil"];
 3951angular
 3952  .module('material.core.interaction', [])
 3953  .service('$mdInteraction', MdInteractionService);
 3954
 3955
 3956/**
 3957 * @ngdoc service
 3958 * @name $mdInteraction
 3959 * @module material.core.interaction
 3960 *
 3961 * @description
 3962 *
 3963 * Service which keeps track of the last interaction type and validates them for several browsers.
 3964 * The service hooks into the document's body and listens for touch, mouse and keyboard events.
 3965 *
 3966 * The most recent interaction type can be retrieved by calling the `getLastInteractionType` method.
 3967 *
 3968 * Here is an example markup for using the interaction service.
 3969 *
 3970 * <hljs lang="js">
 3971 *   var lastType = $mdInteraction.getLastInteractionType();
 3972 *
 3973 *   if (lastType === 'keyboard') {
 3974 *     // We only restore the focus for keyboard users.
 3975 *     restoreFocus();
 3976 *   }
 3977 * </hljs>
 3978 *
 3979 */
 3980function MdInteractionService($timeout, $mdUtil) {
 3981  this.$timeout = $timeout;
 3982  this.$mdUtil = $mdUtil;
 3983
 3984  this.bodyElement = angular.element(document.body);
 3985  this.isBuffering = false;
 3986  this.bufferTimeout = null;
 3987  this.lastInteractionType = null;
 3988  this.lastInteractionTime = null;
 3989
 3990  // Type Mappings for the different events
 3991  // There will be three three interaction types
 3992  // `keyboard`, `mouse` and `touch`
 3993  // type `pointer` will be evaluated in `pointerMap` for IE Browser events
 3994  this.inputEventMap = {
 3995    'keydown': 'keyboard',
 3996    'mousedown': 'mouse',
 3997    'mouseenter': 'mouse',
 3998    'touchstart': 'touch',
 3999    'pointerdown': 'pointer',
 4000    'MSPointerDown': 'pointer'
 4001  };
 4002
 4003  // IE PointerDown events will be validated in `touch` or `mouse`
 4004  // Index numbers referenced here: https://msdn.microsoft.com/library/windows/apps/hh466130.aspx
 4005  this.iePointerMap = {
 4006    2: 'touch',
 4007    3: 'touch',
 4008    4: 'mouse'
 4009  };
 4010
 4011  this.initializeEvents();
 4012}
 4013
 4014/**
 4015 * Initializes the interaction service, by registering all interaction events to the
 4016 * body element.
 4017 */
 4018MdInteractionService.prototype.initializeEvents = function() {
 4019  // IE browsers can also trigger pointer events, which also leads to an interaction.
 4020  var pointerEvent = 'MSPointerEvent' in window ? 'MSPointerDown' : 'PointerEvent' in window ? 'pointerdown' : null;
 4021
 4022  this.bodyElement.on('keydown mousedown', this.onInputEvent.bind(this));
 4023
 4024  if ('ontouchstart' in document.documentElement) {
 4025    this.bodyElement.on('touchstart', this.onBufferInputEvent.bind(this));
 4026  }
 4027
 4028  if (pointerEvent) {
 4029    this.bodyElement.on(pointerEvent, this.onInputEvent.bind(this));
 4030  }
 4031
 4032};
 4033
 4034/**
 4035 * Event listener for normal interaction events, which should be tracked.
 4036 * @param event {MouseEvent|KeyboardEvent|PointerEvent|TouchEvent}
 4037 */
 4038MdInteractionService.prototype.onInputEvent = function(event) {
 4039  if (this.isBuffering) {
 4040    return;
 4041  }
 4042
 4043  var type = this.inputEventMap[event.type];
 4044
 4045  if (type === 'pointer') {
 4046    type = this.iePointerMap[event.pointerType] || event.pointerType;
 4047  }
 4048
 4049  this.lastInteractionType = type;
 4050  this.lastInteractionTime = this.$mdUtil.now();
 4051};
 4052
 4053/**
 4054 * Event listener for interaction events which should be buffered (touch events).
 4055 * @param event {TouchEvent}
 4056 */
 4057MdInteractionService.prototype.onBufferInputEvent = function(event) {
 4058  this.$timeout.cancel(this.bufferTimeout);
 4059
 4060  this.onInputEvent(event);
 4061  this.isBuffering = true;
 4062
 4063  // The timeout of 650ms is needed to delay the touchstart, because otherwise the touch will call
 4064  // the `onInput` function multiple times.
 4065  this.bufferTimeout = this.$timeout(function() {
 4066    this.isBuffering = false;
 4067  }.bind(this), 650, false);
 4068
 4069};
 4070
 4071/**
 4072 * @ngdoc method
 4073 * @name $mdInteraction#getLastInteractionType
 4074 * @description Retrieves the last interaction type triggered in body.
 4075 * @returns {string|null} Last interaction type.
 4076 */
 4077MdInteractionService.prototype.getLastInteractionType = function() {
 4078  return this.lastInteractionType;
 4079};
 4080
 4081/**
 4082 * @ngdoc method
 4083 * @name $mdInteraction#isUserInvoked
 4084 * @description Method to detect whether any interaction happened recently or not.
 4085 * @param {number=} checkDelay Time to check for any interaction to have been triggered.
 4086 * @returns {boolean} Whether there was any interaction or not.
 4087 */
 4088MdInteractionService.prototype.isUserInvoked = function(checkDelay) {
 4089  var delay = angular.isNumber(checkDelay) ? checkDelay : 15;
 4090
 4091  // Check for any interaction to be within the specified check time.
 4092  return this.lastInteractionTime >= this.$mdUtil.now() - delay;
 4093};
 4094
 4095})();
 4096(function(){
 4097"use strict";
 4098
 4099(function() {
 4100  'use strict';
 4101
 4102  var $mdUtil, $interpolate, $log;
 4103
 4104  var SUFFIXES = /(-gt)?-(sm|md|lg|print)/g;
 4105  var WHITESPACE = /\s+/g;
 4106
 4107  var FLEX_OPTIONS = ['grow', 'initial', 'auto', 'none', 'noshrink', 'nogrow' ];
 4108  var LAYOUT_OPTIONS = ['row', 'column'];
 4109  var ALIGNMENT_MAIN_AXIS= [ "", "start", "center", "end", "stretch", "space-around", "space-between" ];
 4110  var ALIGNMENT_CROSS_AXIS= [ "", "start", "center", "end", "stretch" ];
 4111
 4112  var config = {
 4113    /**
 4114     * Enable directive attribute-to-class conversions
 4115     * Developers can use `<body md-layout-css />` to quickly
 4116     * disable the Layout directives and prohibit the injection of Layout classNames
 4117     */
 4118    enabled: true,
 4119
 4120    /**
 4121     * List of mediaQuery breakpoints and associated suffixes
 4122     *
 4123     *   [
 4124     *    { suffix: "sm", mediaQuery: "screen and (max-width: 599px)" },
 4125     *    { suffix: "md", mediaQuery: "screen and (min-width: 600px) and (max-width: 959px)" }
 4126     *   ]
 4127     */
 4128    breakpoints: []
 4129  };
 4130
 4131  registerLayoutAPI( angular.module('material.core.layout', ['ng']) );
 4132
 4133  /**
 4134   *   registerLayoutAPI()
 4135   *
 4136   *   The original ngMaterial Layout solution used attribute selectors and CSS.
 4137   *
 4138   *  ```html
 4139   *  <div layout="column"> My Content </div>
 4140   *  ```
 4141   *
 4142   *  ```css
 4143   *  [layout] {
 4144   *    box-sizing: border-box;
 4145   *    display:flex;
 4146   *  }
 4147   *  [layout=column] {
 4148   *    flex-direction : column
 4149   *  }
 4150   *  ```
 4151   *
 4152   *  Use of attribute selectors creates significant performance impacts in some
 4153   *  browsers... mainly IE.
 4154   *
 4155   *  This module registers directives that allow the same layout attributes to be
 4156   *  interpreted and converted to class selectors. The directive will add equivalent classes to each element that
 4157   *  contains a Layout directive.
 4158   *
 4159   * ```html
 4160   *   <div layout="column" class="layout layout-column"> My Content </div>
 4161   *```
 4162   *
 4163   *  ```css
 4164   *  .layout {
 4165   *    box-sizing: border-box;
 4166   *    display:flex;
 4167   *  }
 4168   *  .layout-column {
 4169   *    flex-direction : column
 4170   *  }
 4171   *  ```
 4172   */
 4173  function registerLayoutAPI(module){
 4174    var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
 4175    var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
 4176
 4177    // NOTE: these are also defined in constants::MEDIA_PRIORITY and constants::MEDIA
 4178    var BREAKPOINTS     = [ "", "xs", "gt-xs", "sm", "gt-sm", "md", "gt-md", "lg", "gt-lg", "xl", "print" ];
 4179    var API_WITH_VALUES = [ "layout", "flex", "flex-order", "flex-offset", "layout-align" ];
 4180    var API_NO_VALUES   = [ "show", "hide", "layout-padding", "layout-margin" ];
 4181
 4182
 4183    // Build directive registration functions for the standard Layout API... for all breakpoints.
 4184    angular.forEach(BREAKPOINTS, function(mqb) {
 4185
 4186      // Attribute directives with expected, observable value(s)
 4187      angular.forEach( API_WITH_VALUES, function(name){
 4188        var fullName = mqb ? name + "-" + mqb : name;
 4189        module.directive( directiveNormalize(fullName), attributeWithObserve(fullName));
 4190      });
 4191
 4192      // Attribute directives with no expected value(s)
 4193      angular.forEach( API_NO_VALUES, function(name){
 4194        var fullName = mqb ? name + "-" + mqb : name;
 4195        module.directive( directiveNormalize(fullName), attributeWithoutValue(fullName));
 4196      });
 4197
 4198    });
 4199
 4200    // Register other, special directive functions for the Layout features:
 4201    module
 4202
 4203      .provider('$$mdLayout'     , function() {
 4204        // Publish internal service for Layouts
 4205        return {
 4206          $get : angular.noop,
 4207          validateAttributeValue : validateAttributeValue,
 4208          validateAttributeUsage : validateAttributeUsage,
 4209          /**
 4210           * Easy way to disable/enable the Layout API.
 4211           * When disabled, this stops all attribute-to-classname generations
 4212           */
 4213          disableLayouts  : function(isDisabled) {
 4214            config.enabled =  (isDisabled !== true);
 4215          }
 4216        };
 4217      })
 4218
 4219      .directive('mdLayoutCss'        , disableLayoutDirective )
 4220      .directive('ngCloak'            , buildCloakInterceptor('ng-cloak'))
 4221
 4222      .directive('layoutWrap'   , attributeWithoutValue('layout-wrap'))
 4223      .directive('layoutNowrap' , attributeWithoutValue('layout-nowrap'))
 4224      .directive('layoutNoWrap' , attributeWithoutValue('layout-no-wrap'))
 4225      .directive('layoutFill'   , attributeWithoutValue('layout-fill'))
 4226
 4227      // !! Deprecated attributes: use the `-lt` (aka less-than) notations
 4228
 4229      .directive('layoutLtMd'     , warnAttrNotSupported('layout-lt-md', true))
 4230      .directive('layoutLtLg'     , warnAttrNotSupported('layout-lt-lg', true))
 4231      .directive('flexLtMd'       , warnAttrNotSupported('flex-lt-md', true))
 4232      .directive('flexLtLg'       , warnAttrNotSupported('flex-lt-lg', true))
 4233
 4234      .directive('layoutAlignLtMd', warnAttrNotSupported('layout-align-lt-md'))
 4235      .directive('layoutAlignLtLg', warnAttrNotSupported('layout-align-lt-lg'))
 4236      .directive('flexOrderLtMd'  , warnAttrNotSupported('flex-order-lt-md'))
 4237      .directive('flexOrderLtLg'  , warnAttrNotSupported('flex-order-lt-lg'))
 4238      .directive('offsetLtMd'     , warnAttrNotSupported('flex-offset-lt-md'))
 4239      .directive('offsetLtLg'     , warnAttrNotSupported('flex-offset-lt-lg'))
 4240
 4241      .directive('hideLtMd'       , warnAttrNotSupported('hide-lt-md'))
 4242      .directive('hideLtLg'       , warnAttrNotSupported('hide-lt-lg'))
 4243      .directive('showLtMd'       , warnAttrNotSupported('show-lt-md'))
 4244      .directive('showLtLg'       , warnAttrNotSupported('show-lt-lg'))
 4245
 4246      // Determine if
 4247      .config( detectDisabledLayouts );
 4248
 4249    /**
 4250     * Converts snake_case to camelCase.
 4251     * Also there is special case for Moz prefix starting with upper case letter.
 4252     * @param name Name to normalize
 4253     */
 4254    function directiveNormalize(name) {
 4255      return name
 4256        .replace(PREFIX_REGEXP, '')
 4257        .replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
 4258          return offset ? letter.toUpperCase() : letter;
 4259        });
 4260    }
 4261
 4262  }
 4263
 4264
 4265  /**
 4266    * Detect if any of the HTML tags has a [md-layouts-disabled] attribute;
 4267    * If yes, then immediately disable all layout API features
 4268    *
 4269    * Note: this attribute should be specified on either the HTML or BODY tags
 4270    */
 4271   /**
 4272    * @ngInject
 4273    */
 4274   function detectDisabledLayouts() {
 4275     var isDisabled = !!document.querySelector('[md-layouts-disabled]');
 4276     config.enabled = !isDisabled;
 4277   }
 4278
 4279  /**
 4280   * Special directive that will disable ALL Layout conversions of layout
 4281   * attribute(s) to classname(s).
 4282   *
 4283   * <link rel="stylesheet" href="angular-material.min.css">
 4284   * <link rel="stylesheet" href="angular-material.layout.css">
 4285   *
 4286   * <body md-layout-css>
 4287   *  ...
 4288   * </body>
 4289   *
 4290   * Note: Using md-layout-css directive requires the developer to load the Material
 4291   * Layout Attribute stylesheet (which only uses attribute selectors):
 4292   *
 4293   *       `angular-material.layout.css`
 4294   *
 4295   * Another option is to use the LayoutProvider to configure and disable the attribute
 4296   * conversions; this would obviate the use of the `md-layout-css` directive
 4297   *
 4298   */
 4299  function disableLayoutDirective() {
 4300    // Return a 1x-only, first-match attribute directive
 4301    config.enabled = false;
 4302
 4303    return {
 4304      restrict : 'A',
 4305      priority : '900'
 4306    };
 4307  }
 4308
 4309  /**
 4310   * Tail-hook ngCloak to delay the uncloaking while Layout transformers
 4311   * finish processing. Eliminates flicker with Material.Layouts
 4312   */
 4313  function buildCloakInterceptor(className) {
 4314    return [ '$timeout', function($timeout){
 4315      return {
 4316        restrict : 'A',
 4317        priority : -10,   // run after normal ng-cloak
 4318        compile  : function( element ) {
 4319          if (!config.enabled) return angular.noop;
 4320
 4321          // Re-add the cloak
 4322          element.addClass(className);
 4323
 4324          return function( scope, element ) {
 4325            // Wait while layout injectors configure, then uncloak
 4326            // NOTE: $rAF does not delay enough... and this is a 1x-only event,
 4327            //       $timeout is acceptable.
 4328            $timeout( function(){
 4329              element.removeClass(className);
 4330            }, 10, false);
 4331          };
 4332        }
 4333      };
 4334    }];
 4335  }
 4336
 4337
 4338  // *********************************************************************************
 4339  //
 4340  // These functions create registration functions for ngMaterial Layout attribute directives
 4341  // This provides easy translation to switch ngMaterial attribute selectors to
 4342  // CLASS selectors and directives; which has huge performance implications
 4343  // for IE Browsers
 4344  //
 4345  // *********************************************************************************
 4346
 4347  /**
 4348   * Creates a directive registration function where a possible dynamic attribute
 4349   * value will be observed/watched.
 4350   * @param {string} className attribute name; eg `layout-gt-md` with value ="row"
 4351   */
 4352  function attributeWithObserve(className) {
 4353
 4354    return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) {
 4355      $mdUtil = _$mdUtil_;
 4356      $interpolate = _$interpolate_;
 4357      $log = _$log_;
 4358
 4359      return {
 4360        restrict: 'A',
 4361        compile: function(element, attr) {
 4362          var linkFn;
 4363          if (config.enabled) {
 4364            // immediately replace static (non-interpolated) invalid values...
 4365
 4366            validateAttributeUsage(className, attr, element, $log);
 4367
 4368            validateAttributeValue( className,
 4369              getNormalizedAttrValue(className, attr, ""),
 4370              buildUpdateFn(element, className, attr)
 4371            );
 4372
 4373            linkFn = translateWithValueToCssClass;
 4374          }
 4375
 4376          // Use for postLink to account for transforms after ng-transclude.
 4377          return linkFn || angular.noop;
 4378        }
 4379      };
 4380    }];
 4381
 4382    /**
 4383     * Add as transformed class selector(s), then
 4384     * remove the deprecated attribute selector
 4385     */
 4386    function translateWithValueToCssClass(scope, element, attrs) {
 4387      var updateFn = updateClassWithValue(element, className, attrs);
 4388      var unwatch = attrs.$observe(attrs.$normalize(className), updateFn);
 4389
 4390      updateFn(getNormalizedAttrValue(className, attrs, ""));
 4391      scope.$on("$destroy", function() { unwatch(); });
 4392    }
 4393  }
 4394
 4395  /**
 4396   * Creates a registration function for ngMaterial Layout attribute directive.
 4397   * This is a `simple` transpose of attribute usage to class usage; where we ignore
 4398   * any attribute value
 4399   */
 4400  function attributeWithoutValue(className) {
 4401    return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) {
 4402      $mdUtil = _$mdUtil_;
 4403      $interpolate = _$interpolate_;
 4404      $log = _$log_;
 4405
 4406      return {
 4407        restrict: 'A',
 4408        compile: function(element, attr) {
 4409          var linkFn;
 4410          if (config.enabled) {
 4411            // immediately replace static (non-interpolated) invalid values...
 4412
 4413            validateAttributeValue( className,
 4414              getNormalizedAttrValue(className, attr, ""),
 4415              buildUpdateFn(element, className, attr)
 4416            );
 4417
 4418            translateToCssClass(null, element);
 4419
 4420            // Use for postLink to account for transforms after ng-transclude.
 4421            linkFn = translateToCssClass;
 4422          }
 4423
 4424          return linkFn || angular.noop;
 4425        }
 4426      };
 4427    }];
 4428
 4429    /**
 4430     * Add as transformed class selector, then
 4431     * remove the deprecated attribute selector
 4432     */
 4433    function translateToCssClass(scope, element) {
 4434      element.addClass(className);
 4435    }
 4436  }
 4437
 4438
 4439
 4440  /**
 4441   * After link-phase, do NOT remove deprecated layout attribute selector.
 4442   * Instead watch the attribute so interpolated data-bindings to layout
 4443   * selectors will continue to be supported.
 4444   *
 4445   * $observe() the className and update with new class (after removing the last one)
 4446   *
 4447   * e.g. `layout="{{layoutDemo.direction}}"` will update...
 4448   *
 4449   * NOTE: The value must match one of the specified styles in the CSS.
 4450   * For example `flex-gt-md="{{size}}`  where `scope.size == 47` will NOT work since
 4451   * only breakpoints for 0, 5, 10, 15... 100, 33, 34, 66, 67 are defined.
 4452   *
 4453   */
 4454  function updateClassWithValue(element, className) {
 4455    var lastClass;
 4456
 4457    return function updateClassFn(newValue) {
 4458      var value = validateAttributeValue(className, newValue || "");
 4459      if ( angular.isDefined(value) ) {
 4460        if (lastClass) element.removeClass(lastClass);
 4461        lastClass = !value ? className : className + "-" + value.replace(WHITESPACE, "-");
 4462        element.addClass(lastClass);
 4463      }
 4464    };
 4465  }
 4466
 4467  /**
 4468   * Provide console warning that this layout attribute has been deprecated
 4469   *
 4470   */
 4471  function warnAttrNotSupported(className) {
 4472    var parts = className.split("-");
 4473    return ["$log", function($log) {
 4474      $log.warn(className + "has been deprecated. Please use a `" + parts[0] + "-gt-<xxx>` variant.");
 4475      return angular.noop;
 4476    }];
 4477  }
 4478
 4479  /**
 4480   * Centralize warnings for known flexbox issues (especially IE-related issues)
 4481   */
 4482  function validateAttributeUsage(className, attr, element, $log){
 4483    var message, usage, url;
 4484    var nodeName = element[0].nodeName.toLowerCase();
 4485
 4486    switch(className.replace(SUFFIXES,"")) {
 4487      case "flex":
 4488        if ((nodeName == "md-button") || (nodeName == "fieldset")){
 4489          // @see https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers
 4490          // Use <div flex> wrapper inside (preferred) or outside
 4491
 4492          usage = "<" + nodeName + " " + className + "></" + nodeName + ">";
 4493          url = "https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers";
 4494          message = "Markup '{0}' may not work as expected in IE Browsers. Consult '{1}' for details.";
 4495
 4496          $log.warn( $mdUtil.supplant(message, [usage, url]) );
 4497        }
 4498    }
 4499
 4500  }
 4501
 4502
 4503  /**
 4504   * For the Layout attribute value, validate or replace with default
 4505   * fallback value
 4506   */
 4507  function validateAttributeValue(className, value, updateFn) {
 4508    var origValue = value;
 4509
 4510    if (!needsInterpolation(value)) {
 4511      switch (className.replace(SUFFIXES,"")) {
 4512        case 'layout'        :
 4513          if ( !findIn(value, LAYOUT_OPTIONS) ) {
 4514            value = LAYOUT_OPTIONS[0];    // 'row';
 4515          }
 4516          break;
 4517
 4518        case 'flex'          :
 4519          if (!findIn(value, FLEX_OPTIONS)) {
 4520            if (isNaN(value)) {
 4521              value = '';
 4522            }
 4523          }
 4524          break;
 4525
 4526        case 'flex-offset' :
 4527        case 'flex-order'    :
 4528          if (!value || isNaN(+value)) {
 4529            value = '0';
 4530          }
 4531          break;
 4532
 4533        case 'layout-align'  :
 4534          var axis = extractAlignAxis(value);
 4535          value = $mdUtil.supplant("{main}-{cross}",axis);
 4536          break;
 4537
 4538        case 'layout-padding' :
 4539        case 'layout-margin'  :
 4540        case 'layout-fill'    :
 4541        case 'layout-wrap'    :
 4542        case 'layout-nowrap'  :
 4543        case 'layout-nowrap' :
 4544          value = '';
 4545          break;
 4546      }
 4547
 4548      if (value != origValue) {
 4549        (updateFn || angular.noop)(value);
 4550      }
 4551    }
 4552
 4553    return value;
 4554  }
 4555
 4556  /**
 4557   * Replace current attribute value with fallback value
 4558   */
 4559  function buildUpdateFn(element, className, attrs) {
 4560    return function updateAttrValue(fallback) {
 4561      if (!needsInterpolation(fallback)) {
 4562        // Do not modify the element's attribute value; so
 4563        // uses '<ui-layout layout="/api/sidebar.html" />' will not
 4564        // be affected. Just update the attrs value.
 4565        attrs[attrs.$normalize(className)] = fallback;
 4566      }
 4567    };
 4568  }
 4569
 4570  /**
 4571   * See if the original value has interpolation symbols:
 4572   * e.g.  flex-gt-md="{{triggerPoint}}"
 4573   */
 4574  function needsInterpolation(value) {
 4575    return (value || "").indexOf($interpolate.startSymbol()) > -1;
 4576  }
 4577
 4578  function getNormalizedAttrValue(className, attrs, defaultVal) {
 4579    var normalizedAttr = attrs.$normalize(className);
 4580    return attrs[normalizedAttr] ? attrs[normalizedAttr].replace(WHITESPACE, "-") : defaultVal || null;
 4581  }
 4582
 4583  function findIn(item, list, replaceWith) {
 4584    item = replaceWith && item ? item.replace(WHITESPACE, replaceWith) : item;
 4585
 4586    var found = false;
 4587    if (item) {
 4588      list.forEach(function(it) {
 4589        it = replaceWith ? it.replace(WHITESPACE, replaceWith) : it;
 4590        found = found || (it === item);
 4591      });
 4592    }
 4593    return found;
 4594  }
 4595
 4596  function extractAlignAxis(attrValue) {
 4597    var axis = {
 4598      main : "start",
 4599      cross: "stretch"
 4600    }, values;
 4601
 4602    attrValue = (attrValue || "");
 4603
 4604    if ( attrValue.indexOf("-") === 0 || attrValue.indexOf(" ") === 0) {
 4605      // For missing main-axis values
 4606      attrValue = "none" + attrValue;
 4607    }
 4608
 4609    values = attrValue.toLowerCase().trim().replace(WHITESPACE, "-").split("-");
 4610    if ( values.length && (values[0] === "space") ) {
 4611      // for main-axis values of "space-around" or "space-between"
 4612      values = [ values[0]+"-"+values[1],values[2] ];
 4613    }
 4614
 4615    if ( values.length > 0 ) axis.main  = values[0] || axis.main;
 4616    if ( values.length > 1 ) axis.cross = values[1] || axis.cross;
 4617
 4618    if ( ALIGNMENT_MAIN_AXIS.indexOf(axis.main) < 0 )   axis.main = "start";
 4619    if ( ALIGNMENT_CROSS_AXIS.indexOf(axis.cross) < 0 ) axis.cross = "stretch";
 4620
 4621    return axis;
 4622  }
 4623
 4624
 4625})();
 4626
 4627})();
 4628(function(){
 4629"use strict";
 4630
 4631/**
 4632 * @ngdoc module
 4633 * @name material.core.liveannouncer
 4634 * @description
 4635 * Angular Material Live Announcer to provide accessibility for Voice Readers.
 4636 */
 4637MdLiveAnnouncer.$inject = ["$timeout"];
 4638angular
 4639  .module('material.core')
 4640  .service('$mdLiveAnnouncer', MdLiveAnnouncer);
 4641
 4642/**
 4643 * @ngdoc service
 4644 * @name $mdLiveAnnouncer
 4645 * @module material.core.liveannouncer
 4646 *
 4647 * @description
 4648 *
 4649 * Service to announce messages to supported screenreaders.
 4650 *
 4651 * > The `$mdLiveAnnouncer` service is internally used for components to provide proper accessibility.
 4652 *
 4653 * <hljs lang="js">
 4654 *   module.controller('AppCtrl', function($mdLiveAnnouncer) {
 4655 *     // Basic announcement (Polite Mode)
 4656 *     $mdLiveAnnouncer.announce('Hey Google');
 4657 *
 4658 *     // Custom announcement (Assertive Mode)
 4659 *     $mdLiveAnnouncer.announce('Hey Google', 'assertive');
 4660 *   });
 4661 * </hljs>
 4662 *
 4663 */
 4664function MdLiveAnnouncer($timeout) {
 4665  /** @private @const @type {!angular.$timeout} */
 4666  this._$timeout = $timeout;
 4667
 4668  /** @private @const @type {!HTMLElement} */
 4669  this._liveElement = this._createLiveElement();
 4670
 4671  /** @private @const @type {!number} */
 4672  this._announceTimeout = 100;
 4673}
 4674
 4675/**
 4676 * @ngdoc method
 4677 * @name $mdLiveAnnouncer#announce
 4678 * @description Announces messages to supported screenreaders.
 4679 * @param {string} message Message to be announced to the screenreader
 4680 * @param {'off'|'polite'|'assertive'} politeness The politeness of the announcer element.
 4681 */
 4682MdLiveAnnouncer.prototype.announce = function(message, politeness) {
 4683  if (!politeness) {
 4684    politeness = 'polite';
 4685  }
 4686
 4687  var self = this;
 4688
 4689  self._liveElement.textContent = '';
 4690  self._liveElement.setAttribute('aria-live', politeness);
 4691
 4692  // This 100ms timeout is necessary for some browser + screen-reader combinations:
 4693  // - Both JAWS and NVDA over IE11 will not announce anything without a non-zero timeout.
 4694  // - With Chrome and IE11 with NVDA or JAWS, a repeated (identical) message won't be read a
 4695  //   second time without clearing and then using a non-zero delay.
 4696  // (using JAWS 17 at time of this writing).
 4697  self._$timeout(function() {
 4698    self._liveElement.textContent = message;
 4699  }, self._announceTimeout, false);
 4700};
 4701
 4702/**
 4703 * Creates a live announcer element, which listens for DOM changes and announces them
 4704 * to the screenreaders.
 4705 * @returns {!HTMLElement}
 4706 * @private
 4707 */
 4708MdLiveAnnouncer.prototype._createLiveElement = function() {
 4709  var liveEl = document.createElement('div');
 4710
 4711  liveEl.classList.add('md-visually-hidden');
 4712  liveEl.setAttribute('role', 'status');
 4713  liveEl.setAttribute('aria-atomic', 'true');
 4714  liveEl.setAttribute('aria-live', 'polite');
 4715
 4716  document.body.appendChild(liveEl);
 4717
 4718  return liveEl;
 4719};
 4720
 4721})();
 4722(function(){
 4723"use strict";
 4724
 4725/**
 4726 * @ngdoc service
 4727 * @name $$mdMeta
 4728 * @module material.core.meta
 4729 *
 4730 * @description
 4731 *
 4732 * A provider and a service that simplifies meta tags access
 4733 *
 4734 * Note: This is intended only for use with dynamic meta tags such as browser color and title.
 4735 * Tags that are only processed when the page is rendered (such as `charset`, and `http-equiv`)
 4736 * will not work since `$$mdMeta` adds the tags after the page has already been loaded.
 4737 *
 4738 * ```js
 4739 * app.config(function($$mdMetaProvider) {
 4740 *   var removeMeta = $$mdMetaProvider.setMeta('meta-name', 'content');
 4741 *   var metaValue  = $$mdMetaProvider.getMeta('meta-name'); // -> 'content'
 4742 *
 4743 *   removeMeta();
 4744 * });
 4745 *
 4746 * app.controller('myController', function($$mdMeta) {
 4747 *   var removeMeta = $$mdMeta.setMeta('meta-name', 'content');
 4748 *   var metaValue  = $$mdMeta.getMeta('meta-name'); // -> 'content'
 4749 *
 4750 *   removeMeta();
 4751 * });
 4752 * ```
 4753 *
 4754 * @returns {$$mdMeta.$service}
 4755 *
 4756 */
 4757angular.module('material.core.meta', [])
 4758  .provider('$$mdMeta', function () {
 4759    var head = angular.element(document.head);
 4760    var metaElements = {};
 4761
 4762    /**
 4763     * Checks if the requested element was written manually and maps it
 4764     *
 4765     * @param {string} name meta tag 'name' attribute value
 4766     * @returns {boolean} returns true if there is an element with the requested name
 4767     */
 4768    function mapExistingElement(name) {
 4769      if (metaElements[name]) {
 4770        return true;
 4771      }
 4772
 4773      var element = document.getElementsByName(name)[0];
 4774
 4775      if (!element) {
 4776        return false;
 4777      }
 4778
 4779      metaElements[name] = angular.element(element);
 4780
 4781      return true;
 4782    }
 4783
 4784    /**
 4785     * @ngdoc method
 4786     * @name $$mdMeta#setMeta
 4787     *
 4788     * @description
 4789     * Creates meta element with the 'name' and 'content' attributes,
 4790     * if the meta tag is already created than we replace the 'content' value
 4791     *
 4792     * @param {string} name meta tag 'name' attribute value
 4793     * @param {string} content meta tag 'content' attribute value
 4794     * @returns {function} remove function
 4795     *
 4796     */
 4797    function setMeta(name, content) {
 4798      mapExistingElement(name);
 4799
 4800      if (!metaElements[name]) {
 4801        var newMeta = angular.element('<meta name="' + name + '" content="' + content + '"/>');
 4802        head.append(newMeta);
 4803        metaElements[name] = newMeta;
 4804      }
 4805      else {
 4806        metaElements[name].attr('content', content);
 4807      }
 4808
 4809      return function () {
 4810        metaElements[name].attr('content', '');
 4811        metaElements[name].remove();
 4812        delete metaElements[name];
 4813      };
 4814    }
 4815
 4816    /**
 4817     * @ngdoc method
 4818     * @name $$mdMeta#getMeta
 4819     *
 4820     * @description
 4821     * Gets the 'content' attribute value of the wanted meta element
 4822     *
 4823     * @param {string} name meta tag 'name' attribute value
 4824     * @returns {string} content attribute value
 4825     */
 4826    function getMeta(name) {
 4827      if (!mapExistingElement(name)) {
 4828        throw Error('$$mdMeta: could not find a meta tag with the name \'' + name + '\'');
 4829      }
 4830
 4831      return metaElements[name].attr('content');
 4832    }
 4833
 4834    var module = {
 4835      setMeta: setMeta,
 4836      getMeta: getMeta
 4837    };
 4838
 4839    return angular.extend({}, module, {
 4840      $get: function () {
 4841        return module;
 4842      }
 4843    });
 4844  });
 4845})();
 4846(function(){
 4847"use strict";
 4848
 4849  /**
 4850   * @ngdoc module
 4851   * @name material.core.componentRegistry
 4852   *
 4853   * @description
 4854   * A component instance registration service.
 4855   * Note: currently this as a private service in the SideNav component.
 4856   */
 4857  ComponentRegistry.$inject = ["$log", "$q"];
 4858  angular.module('material.core')
 4859    .factory('$mdComponentRegistry', ComponentRegistry);
 4860
 4861  /*
 4862   * @private
 4863   * @ngdoc factory
 4864   * @name ComponentRegistry
 4865   * @module material.core.componentRegistry
 4866   *
 4867   */
 4868  function ComponentRegistry($log, $q) {
 4869
 4870    var self;
 4871    var instances = [ ];
 4872    var pendings = { };
 4873
 4874    return self = {
 4875      /**
 4876       * Used to print an error when an instance for a handle isn't found.
 4877       */
 4878      notFoundError: function(handle, msgContext) {
 4879        $log.error( (msgContext || "") + 'No instance found for handle', handle);
 4880      },
 4881      /**
 4882       * Return all registered instances as an array.
 4883       */
 4884      getInstances: function() {
 4885        return instances;
 4886      },
 4887
 4888      /**
 4889       * Get a registered instance.
 4890       * @param handle the String handle to look up for a registered instance.
 4891       */
 4892      get: function(handle) {
 4893        if ( !isValidID(handle) ) return null;
 4894
 4895        var i, j, instance;
 4896        for(i = 0, j = instances.length; i < j; i++) {
 4897          instance = instances[i];
 4898          if(instance.$$mdHandle === handle) {
 4899            return instance;
 4900          }
 4901        }
 4902        return null;
 4903      },
 4904
 4905      /**
 4906       * Register an instance.
 4907       * @param instance the instance to register
 4908       * @param handle the handle to identify the instance under.
 4909       */
 4910      register: function(instance, handle) {
 4911        if ( !handle ) return angular.noop;
 4912
 4913        instance.$$mdHandle = handle;
 4914        instances.push(instance);
 4915        resolveWhen();
 4916
 4917        return deregister;
 4918
 4919        /**
 4920         * Remove registration for an instance
 4921         */
 4922        function deregister() {
 4923          var index = instances.indexOf(instance);
 4924          if (index !== -1) {
 4925            instances.splice(index, 1);
 4926          }
 4927        }
 4928
 4929        /**
 4930         * Resolve any pending promises for this instance
 4931         */
 4932        function resolveWhen() {
 4933          var dfd = pendings[handle];
 4934          if ( dfd ) {
 4935            dfd.forEach(function (promise) {
 4936              promise.resolve(instance);
 4937            });
 4938            delete pendings[handle];
 4939          }
 4940        }
 4941      },
 4942
 4943      /**
 4944       * Async accessor to registered component instance
 4945       * If not available then a promise is created to notify
 4946       * all listeners when the instance is registered.
 4947       */
 4948      when : function(handle) {
 4949        if ( isValidID(handle) ) {
 4950          var deferred = $q.defer();
 4951          var instance = self.get(handle);
 4952
 4953          if ( instance )  {
 4954            deferred.resolve( instance );
 4955          } else {
 4956            if (pendings[handle] === undefined) {
 4957              pendings[handle] = [];
 4958            }
 4959            pendings[handle].push(deferred);
 4960          }
 4961
 4962          return deferred.promise;
 4963        }
 4964        return $q.reject("Invalid `md-component-id` value.");
 4965      }
 4966
 4967    };
 4968
 4969    function isValidID(handle){
 4970      return handle && (handle !== "");
 4971    }
 4972
 4973  }
 4974
 4975})();
 4976(function(){
 4977"use strict";
 4978
 4979(function() {
 4980  'use strict';
 4981
 4982  /**
 4983   * @ngdoc service
 4984   * @name $mdButtonInkRipple
 4985   * @module material.core
 4986   *
 4987   * @description
 4988   * Provides ripple effects for md-button.  See $mdInkRipple service for all possible configuration options.
 4989   *
 4990   * @param {object=} scope Scope within the current context
 4991   * @param {object=} element The element the ripple effect should be applied to
 4992   * @param {object=} options (Optional) Configuration options to override the default ripple configuration
 4993   */
 4994
 4995  MdButtonInkRipple.$inject = ["$mdInkRipple"];
 4996  angular.module('material.core')
 4997    .factory('$mdButtonInkRipple', MdButtonInkRipple);
 4998
 4999  function MdButtonInkRipple($mdInkRipple) {
 5000    return {
 5001      attach: function attachRipple(scope, element, options) {
 5002        options = angular.extend(optionsForElement(element), options);
 5003
 5004        return $mdInkRipple.attach(scope, element, options);
 5005      }
 5006    };
 5007
 5008    function optionsForElement(element) {
 5009      if (element.hasClass('md-icon-button')) {
 5010        return {
 5011          isMenuItem: element.hasClass('md-menu-item'),
 5012          fitRipple: true,
 5013          center: true
 5014        };
 5015      } else {
 5016        return {
 5017          isMenuItem: element.hasClass('md-menu-item'),
 5018          dimBackground: true
 5019        };
 5020      }
 5021    }
 5022  }
 5023})();
 5024
 5025})();
 5026(function(){
 5027"use strict";
 5028
 5029(function() {
 5030  'use strict';
 5031
 5032    /**
 5033   * @ngdoc service
 5034   * @name $mdCheckboxInkRipple
 5035   * @module material.core
 5036   *
 5037   * @description
 5038   * Provides ripple effects for md-checkbox.  See $mdInkRipple service for all possible configuration options.
 5039   *
 5040   * @param {object=} scope Scope within the current context
 5041   * @param {object=} element The element the ripple effect should be applied to
 5042   * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
 5043   */
 5044
 5045  MdCheckboxInkRipple.$inject = ["$mdInkRipple"];
 5046  angular.module('material.core')
 5047    .factory('$mdCheckboxInkRipple', MdCheckboxInkRipple);
 5048
 5049  function MdCheckboxInkRipple($mdInkRipple) {
 5050    return {
 5051      attach: attach
 5052    };
 5053
 5054    function attach(scope, element, options) {
 5055      return $mdInkRipple.attach(scope, element, angular.extend({
 5056        center: true,
 5057        dimBackground: false,
 5058        fitRipple: true
 5059      }, options));
 5060    }
 5061  }
 5062})();
 5063
 5064})();
 5065(function(){
 5066"use strict";
 5067
 5068(function() {
 5069  'use strict';
 5070
 5071  /**
 5072   * @ngdoc service
 5073   * @name $mdListInkRipple
 5074   * @module material.core
 5075   *
 5076   * @description
 5077   * Provides ripple effects for md-list.  See $mdInkRipple service for all possible configuration options.
 5078   *
 5079   * @param {object=} scope Scope within the current context
 5080   * @param {object=} element The element the ripple effect should be applied to
 5081   * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
 5082   */
 5083
 5084  MdListInkRipple.$inject = ["$mdInkRipple"];
 5085  angular.module('material.core')
 5086    .factory('$mdListInkRipple', MdListInkRipple);
 5087
 5088  function MdListInkRipple($mdInkRipple) {
 5089    return {
 5090      attach: attach
 5091    };
 5092
 5093    function attach(scope, element, options) {
 5094      return $mdInkRipple.attach(scope, element, angular.extend({
 5095        center: false,
 5096        dimBackground: true,
 5097        outline: false,
 5098        rippleSize: 'full'
 5099      }, options));
 5100    }
 5101  }
 5102})();
 5103
 5104})();
 5105(function(){
 5106"use strict";
 5107
 5108/**
 5109 * @ngdoc module
 5110 * @name material.core.ripple
 5111 * @description
 5112 * Ripple
 5113 */
 5114InkRippleCtrl.$inject = ["$scope", "$element", "rippleOptions", "$window", "$timeout", "$mdUtil", "$mdColorUtil"];
 5115InkRippleDirective.$inject = ["$mdButtonInkRipple", "$mdCheckboxInkRipple"];
 5116angular.module('material.core')
 5117    .provider('$mdInkRipple', InkRippleProvider)
 5118    .directive('mdInkRipple', InkRippleDirective)
 5119    .directive('mdNoInk', attrNoDirective)
 5120    .directive('mdNoBar', attrNoDirective)
 5121    .directive('mdNoStretch', attrNoDirective);
 5122
 5123var DURATION = 450;
 5124
 5125/**
 5126 * @ngdoc directive
 5127 * @name mdInkRipple
 5128 * @module material.core.ripple
 5129 *
 5130 * @description
 5131 * The `md-ink-ripple` directive allows you to specify the ripple color or id a ripple is allowed.
 5132 *
 5133 * @param {string|boolean} md-ink-ripple A color string `#FF0000` or boolean (`false` or `0`) for preventing ripple
 5134 *
 5135 * @usage
 5136 * ### String values
 5137 * <hljs lang="html">
 5138 *   <ANY md-ink-ripple="#FF0000">
 5139 *     Ripples in red
 5140 *   </ANY>
 5141 *
 5142 *   <ANY md-ink-ripple="false">
 5143 *     Not rippling
 5144 *   </ANY>
 5145 * </hljs>
 5146 *
 5147 * ### Interpolated values
 5148 * <hljs lang="html">
 5149 *   <ANY md-ink-ripple="{{ randomColor() }}">
 5150 *     Ripples with the return value of 'randomColor' function
 5151 *   </ANY>
 5152 *
 5153 *   <ANY md-ink-ripple="{{ canRipple() }}">
 5154 *     Ripples if 'canRipple' function return value is not 'false' or '0'
 5155 *   </ANY>
 5156 * </hljs>
 5157 */
 5158function InkRippleDirective ($mdButtonInkRipple, $mdCheckboxInkRipple) {
 5159  return {
 5160    controller: angular.noop,
 5161    link:       function (scope, element, attr) {
 5162      attr.hasOwnProperty('mdInkRippleCheckbox')
 5163          ? $mdCheckboxInkRipple.attach(scope, element)
 5164          : $mdButtonInkRipple.attach(scope, element);
 5165    }
 5166  };
 5167}
 5168
 5169/**
 5170 * @ngdoc service
 5171 * @name $mdInkRipple
 5172 * @module material.core.ripple
 5173 *
 5174 * @description
 5175 * `$mdInkRipple` is a service for adding ripples to any element
 5176 *
 5177 * @usage
 5178 * <hljs lang="js">
 5179 * app.factory('$myElementInkRipple', function($mdInkRipple) {
 5180 *   return {
 5181 *     attach: function (scope, element, options) {
 5182 *       return $mdInkRipple.attach(scope, element, angular.extend({
 5183 *         center: false,
 5184 *         dimBackground: true
 5185 *       }, options));
 5186 *     }
 5187 *   };
 5188 * });
 5189 *
 5190 * app.controller('myController', function ($scope, $element, $myElementInkRipple) {
 5191 *   $scope.onClick = function (ev) {
 5192 *     $myElementInkRipple.attach($scope, angular.element(ev.target), { center: true });
 5193 *   }
 5194 * });
 5195 * </hljs>
 5196 *
 5197 * ### Disabling ripples globally
 5198 * If you want to disable ink ripples globally, for all components, you can call the
 5199 * `disableInkRipple` method in your app's config.
 5200 *
 5201 * <hljs lang="js">
 5202 * app.config(function ($mdInkRippleProvider) {
 5203 *   $mdInkRippleProvider.disableInkRipple();
 5204 * });
 5205 */
 5206
 5207function InkRippleProvider () {
 5208  var isDisabledGlobally = false;
 5209
 5210  return {
 5211    disableInkRipple: disableInkRipple,
 5212    $get: ["$injector", function($injector) {
 5213      return { attach: attach };
 5214
 5215      /**
 5216       * @ngdoc method
 5217       * @name $mdInkRipple#attach
 5218       *
 5219       * @description
 5220       * Attaching given scope, element and options to inkRipple controller
 5221       *
 5222       * @param {object=} scope Scope within the current context
 5223       * @param {object=} element The element the ripple effect should be applied to
 5224       * @param {object=} options (Optional) Configuration options to override the defaultRipple configuration
 5225       * * `center` -  Whether the ripple should start from the center of the container element
 5226       * * `dimBackground` - Whether the background should be dimmed with the ripple color
 5227       * * `colorElement` - The element the ripple should take its color from, defined by css property `color`
 5228       * * `fitRipple` - Whether the ripple should fill the element
 5229       */
 5230      function attach (scope, element, options) {
 5231        if (isDisabledGlobally || element.controller('mdNoInk')) return angular.noop;
 5232        return $injector.instantiate(InkRippleCtrl, {
 5233          $scope:        scope,
 5234          $element:      element,
 5235          rippleOptions: options
 5236        });
 5237      }
 5238    }]
 5239  };
 5240
 5241  /**
 5242   * @ngdoc method
 5243   * @name $mdInkRipple#disableInkRipple
 5244   *
 5245   * @description
 5246   * A config-time method that, when called, disables ripples globally.
 5247   */
 5248  function disableInkRipple () {
 5249    isDisabledGlobally = true;
 5250  }
 5251}
 5252
 5253/**
 5254 * Controller used by the ripple service in order to apply ripples
 5255 * @ngInject
 5256 */
 5257function InkRippleCtrl ($scope, $element, rippleOptions, $window, $timeout, $mdUtil, $mdColorUtil) {
 5258  this.$window    = $window;
 5259  this.$timeout   = $timeout;
 5260  this.$mdUtil    = $mdUtil;
 5261  this.$mdColorUtil    = $mdColorUtil;
 5262  this.$scope     = $scope;
 5263  this.$element   = $element;
 5264  this.options    = rippleOptions;
 5265  this.mousedown  = false;
 5266  this.ripples    = [];
 5267  this.timeout    = null; // Stores a reference to the most-recent ripple timeout
 5268  this.lastRipple = null;
 5269
 5270  $mdUtil.valueOnUse(this, 'container', this.createContainer);
 5271
 5272  this.$element.addClass('md-ink-ripple');
 5273
 5274  // attach method for unit tests
 5275  ($element.controller('mdInkRipple') || {}).createRipple = angular.bind(this, this.createRipple);
 5276  ($element.controller('mdInkRipple') || {}).setColor = angular.bind(this, this.color);
 5277
 5278  this.bindEvents();
 5279}
 5280
 5281
 5282/**
 5283 * Either remove or unlock any remaining ripples when the user mouses off of the element (either by
 5284 * mouseup or mouseleave event)
 5285 */
 5286function autoCleanup (self, cleanupFn) {
 5287
 5288  if ( self.mousedown || self.lastRipple ) {
 5289    self.mousedown = false;
 5290    self.$mdUtil.nextTick( angular.bind(self, cleanupFn), false);
 5291  }
 5292
 5293}
 5294
 5295
 5296/**
 5297 * Returns the color that the ripple should be (either based on CSS or hard-coded)
 5298 * @returns {string}
 5299 */
 5300InkRippleCtrl.prototype.color = function (value) {
 5301  var self = this;
 5302
 5303  // If assigning a color value, apply it to background and the ripple color
 5304  if (angular.isDefined(value)) {
 5305    self._color = self._parseColor(value);
 5306  }
 5307
 5308  // If color lookup, use assigned, defined, or inherited
 5309  return self._color || self._parseColor( self.inkRipple() ) || self._parseColor( getElementColor() );
 5310
 5311  /**
 5312   * Finds the color element and returns its text color for use as default ripple color
 5313   * @returns {string}
 5314   */
 5315  function getElementColor () {
 5316    var items = self.options && self.options.colorElement ? self.options.colorElement : [];
 5317    var elem =  items.length ? items[ 0 ] : self.$element[ 0 ];
 5318
 5319    return elem ? self.$window.getComputedStyle(elem).color : 'rgb(0,0,0)';
 5320  }
 5321};
 5322
 5323/**
 5324 * Updating the ripple colors based on the current inkRipple value
 5325 * or the element's computed style color
 5326 */
 5327InkRippleCtrl.prototype.calculateColor = function () {
 5328  return this.color();
 5329};
 5330
 5331
 5332/**
 5333 * Takes a string color and converts it to RGBA format
 5334 * @param color {string}
 5335 * @param [multiplier] {int}
 5336 * @returns {string}
 5337 */
 5338
 5339InkRippleCtrl.prototype._parseColor = function parseColor (color, multiplier) {
 5340  multiplier = multiplier || 1;
 5341  var colorUtil = this.$mdColorUtil;
 5342
 5343  if (!color) return;
 5344  if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, (0.1 * multiplier).toString() + ')');
 5345  if (color.indexOf('rgb') === 0) return colorUtil.rgbToRgba(color);
 5346  if (color.indexOf('#') === 0) return colorUtil.hexToRgba(color);
 5347
 5348};
 5349
 5350/**
 5351 * Binds events to the root element for
 5352 */
 5353InkRippleCtrl.prototype.bindEvents = function () {
 5354  this.$element.on('mousedown', angular.bind(this, this.handleMousedown));
 5355  this.$element.on('mouseup touchend', angular.bind(this, this.handleMouseup));
 5356  this.$element.on('mouseleave', angular.bind(this, this.handleMouseup));
 5357  this.$element.on('touchmove', angular.bind(this, this.handleTouchmove));
 5358};
 5359
 5360/**
 5361 * Create a new ripple on every mousedown event from the root element
 5362 * @param event {MouseEvent}
 5363 */
 5364InkRippleCtrl.prototype.handleMousedown = function (event) {
 5365  if ( this.mousedown ) return;
 5366
 5367  // When jQuery is loaded, we have to get the original event
 5368  if (event.hasOwnProperty('originalEvent')) event = event.originalEvent;
 5369  this.mousedown = true;
 5370  if (this.options.center) {
 5371    this.createRipple(this.container.prop('clientWidth') / 2, this.container.prop('clientWidth') / 2);
 5372  } else {
 5373
 5374    // We need to calculate the relative coordinates if the target is a sublayer of the ripple element
 5375    if (event.srcElement !== this.$element[0]) {
 5376      var layerRect = this.$element[0].getBoundingClientRect();
 5377      var layerX = event.clientX - layerRect.left;
 5378      var layerY = event.clientY - layerRect.top;
 5379
 5380      this.createRipple(layerX, layerY);
 5381    } else {
 5382      this.createRipple(event.offsetX, event.offsetY);
 5383    }
 5384  }
 5385};
 5386
 5387/**
 5388 * Either remove or unlock any remaining ripples when the user mouses off of the element (either by
 5389 * mouseup, touchend or mouseleave event)
 5390 */
 5391InkRippleCtrl.prototype.handleMouseup = function () {
 5392  autoCleanup(this, this.clearRipples);
 5393};
 5394
 5395/**
 5396 * Either remove or unlock any remaining ripples when the user mouses off of the element (by
 5397 * touchmove)
 5398 */
 5399InkRippleCtrl.prototype.handleTouchmove = function () {
 5400  autoCleanup(this, this.deleteRipples);
 5401};
 5402
 5403/**
 5404 * Cycles through all ripples and attempts to remove them.
 5405 */
 5406InkRippleCtrl.prototype.deleteRipples = function () {
 5407  for (var i = 0; i < this.ripples.length; i++) {
 5408    this.ripples[ i ].remove();
 5409  }
 5410};
 5411
 5412/**
 5413 * Cycles through all ripples and attempts to remove them with fade.
 5414 * Depending on logic within `fadeInComplete`, some removals will be postponed.
 5415 */
 5416InkRippleCtrl.prototype.clearRipples = function () {
 5417  for (var i = 0; i < this.ripples.length; i++) {
 5418    this.fadeInComplete(this.ripples[ i ]);
 5419  }
 5420};
 5421
 5422/**
 5423 * Creates the ripple container element
 5424 * @returns {*}
 5425 */
 5426InkRippleCtrl.prototype.createContainer = function () {
 5427  var container = angular.element('<div class="md-ripple-container"></div>');
 5428  this.$element.append(container);
 5429  return container;
 5430};
 5431
 5432InkRippleCtrl.prototype.clearTimeout = function () {
 5433  if (this.timeout) {
 5434    this.$timeout.cancel(this.timeout);
 5435    this.timeout = null;
 5436  }
 5437};
 5438
 5439InkRippleCtrl.prototype.isRippleAllowed = function () {
 5440  var element = this.$element[0];
 5441  do {
 5442    if (!element.tagName || element.tagName === 'BODY') break;
 5443
 5444    if (element && angular.isFunction(element.hasAttribute)) {
 5445      if (element.hasAttribute('disabled')) return false;
 5446      if (this.inkRipple() === 'false' || this.inkRipple() === '0') return false;
 5447    }
 5448
 5449  } while (element = element.parentNode);
 5450  return true;
 5451};
 5452
 5453/**
 5454 * The attribute `md-ink-ripple` may be a static or interpolated
 5455 * color value OR a boolean indicator (used to disable ripples)
 5456 */
 5457InkRippleCtrl.prototype.inkRipple = function () {
 5458  return this.$element.attr('md-ink-ripple');
 5459};
 5460
 5461/**
 5462 * Creates a new ripple and adds it to the container.  Also tracks ripple in `this.ripples`.
 5463 * @param left
 5464 * @param top
 5465 */
 5466InkRippleCtrl.prototype.createRipple = function (left, top) {
 5467  if (!this.isRippleAllowed()) return;
 5468
 5469  var ctrl        = this;
 5470  var colorUtil   = ctrl.$mdColorUtil;
 5471  var ripple      = angular.element('<div class="md-ripple"></div>');
 5472  var width       = this.$element.prop('clientWidth');
 5473  var height      = this.$element.prop('clientHeight');
 5474  var x           = Math.max(Math.abs(width - left), left) * 2;
 5475  var y           = Math.max(Math.abs(height - top), top) * 2;
 5476  var size        = getSize(this.options.fitRipple, x, y);
 5477  var color       = this.calculateColor();
 5478
 5479  ripple.css({
 5480    left:            left + 'px',
 5481    top:             top + 'px',
 5482    background:      'black',
 5483    width:           size + 'px',
 5484    height:          size + 'px',
 5485    backgroundColor: colorUtil.rgbaToRgb(color),
 5486    borderColor:     colorUtil.rgbaToRgb(color)
 5487  });
 5488  this.lastRipple = ripple;
 5489
 5490  // we only want one timeout to be running at a time
 5491  this.clearTimeout();
 5492  this.timeout    = this.$timeout(function () {
 5493    ctrl.clearTimeout();
 5494    if (!ctrl.mousedown) ctrl.fadeInComplete(ripple);
 5495  }, DURATION * 0.35, false);
 5496
 5497  if (this.options.dimBackground) this.container.css({ backgroundColor: color });
 5498  this.container.append(ripple);
 5499  this.ripples.push(ripple);
 5500  ripple.addClass('md-ripple-placed');
 5501
 5502  this.$mdUtil.nextTick(function () {
 5503
 5504    ripple.addClass('md-ripple-scaled md-ripple-active');
 5505    ctrl.$timeout(function () {
 5506      ctrl.clearRipples();
 5507    }, DURATION, false);
 5508
 5509  }, false);
 5510
 5511  function getSize (fit, x, y) {
 5512    return fit
 5513        ? Math.max(x, y)
 5514        : Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
 5515  }
 5516};
 5517
 5518
 5519
 5520/**
 5521 * After fadeIn finishes, either kicks off the fade-out animation or queues the element for removal on mouseup
 5522 * @param ripple
 5523 */
 5524InkRippleCtrl.prototype.fadeInComplete = function (ripple) {
 5525  if (this.lastRipple === ripple) {
 5526    if (!this.timeout && !this.mousedown) {
 5527      this.removeRipple(ripple);
 5528    }
 5529  } else {
 5530    this.removeRipple(ripple);
 5531  }
 5532};
 5533
 5534/**
 5535 * Kicks off the animation for removing a ripple
 5536 * @param ripple {Element}
 5537 */
 5538InkRippleCtrl.prototype.removeRipple = function (ripple) {
 5539  var ctrl  = this;
 5540  var index = this.ripples.indexOf(ripple);
 5541  if (index < 0) return;
 5542  this.ripples.splice(this.ripples.indexOf(ripple), 1);
 5543  ripple.removeClass('md-ripple-active');
 5544  ripple.addClass('md-ripple-remove');
 5545  if (this.ripples.length === 0) this.container.css({ backgroundColor: '' });
 5546  // use a 2-second timeout in order to allow for the animation to finish
 5547  // we don't actually care how long the animation takes
 5548  this.$timeout(function () {
 5549    ctrl.fadeOutComplete(ripple);
 5550  }, DURATION, false);
 5551};
 5552
 5553/**
 5554 * Removes the provided ripple from the DOM
 5555 * @param ripple
 5556 */
 5557InkRippleCtrl.prototype.fadeOutComplete = function (ripple) {
 5558  ripple.remove();
 5559  this.lastRipple = null;
 5560};
 5561
 5562/**
 5563 * Used to create an empty directive.  This is used to track flag-directives whose children may have
 5564 * functionality based on them.
 5565 *
 5566 * Example: `md-no-ink` will potentially be used by all child directives.
 5567 */
 5568function attrNoDirective () {
 5569  return { controller: angular.noop };
 5570}
 5571
 5572})();
 5573(function(){
 5574"use strict";
 5575
 5576(function() {
 5577  'use strict';
 5578
 5579    /**
 5580   * @ngdoc service
 5581   * @name $mdTabInkRipple
 5582   * @module material.core
 5583   *
 5584   * @description
 5585   * Provides ripple effects for md-tabs.  See $mdInkRipple service for all possible configuration options.
 5586   *
 5587   * @param {object=} scope Scope within the current context
 5588   * @param {object=} element The element the ripple effect should be applied to
 5589   * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
 5590   */
 5591
 5592  MdTabInkRipple.$inject = ["$mdInkRipple"];
 5593  angular.module('material.core')
 5594    .factory('$mdTabInkRipple', MdTabInkRipple);
 5595
 5596  function MdTabInkRipple($mdInkRipple) {
 5597    return {
 5598      attach: attach
 5599    };
 5600
 5601    function attach(scope, element, options) {
 5602      return $mdInkRipple.attach(scope, element, angular.extend({
 5603        center: false,
 5604        dimBackground: true,
 5605        outline: false,
 5606        rippleSize: 'full'
 5607      }, options));
 5608    }
 5609  }
 5610})();
 5611
 5612})();
 5613(function(){
 5614"use strict";
 5615
 5616angular.module('material.core.theming.palette', [])
 5617.constant('$mdColorPalette', {
 5618  'red': {
 5619    '50': '#ffebee',
 5620    '100': '#ffcdd2',
 5621    '200': '#ef9a9a',
 5622    '300': '#e57373',
 5623    '400': '#ef5350',
 5624    '500': '#f44336',
 5625    '600': '#e53935',
 5626    '700': '#d32f2f',
 5627    '800': '#c62828',
 5628    '900': '#b71c1c',
 5629    'A100': '#ff8a80',
 5630    'A200': '#ff5252',
 5631    'A400': '#ff1744',
 5632    'A700': '#d50000',
 5633    'contrastDefaultColor': 'light',
 5634    'contrastDarkColors': '50 100 200 300 A100',
 5635    'contrastStrongLightColors': '400 500 600 700 A200 A400 A700'
 5636  },
 5637  'pink': {
 5638    '50': '#fce4ec',
 5639    '100': '#f8bbd0',
 5640    '200': '#f48fb1',
 5641    '300': '#f06292',
 5642    '400': '#ec407a',
 5643    '500': '#e91e63',
 5644    '600': '#d81b60',
 5645    '700': '#c2185b',
 5646    '800': '#ad1457',
 5647    '900': '#880e4f',
 5648    'A100': '#ff80ab',
 5649    'A200': '#ff4081',
 5650    'A400': '#f50057',
 5651    'A700': '#c51162',
 5652    'contrastDefaultColor': 'light',
 5653    'contrastDarkColors': '50 100 200 A100',
 5654    'contrastStrongLightColors': '500 600 A200 A400 A700'
 5655  },
 5656  'purple': {
 5657    '50': '#f3e5f5',
 5658    '100': '#e1bee7',
 5659    '200': '#ce93d8',
 5660    '300': '#ba68c8',
 5661    '400': '#ab47bc',
 5662    '500': '#9c27b0',
 5663    '600': '#8e24aa',
 5664    '700': '#7b1fa2',
 5665    '800': '#6a1b9a',
 5666    '900': '#4a148c',
 5667    'A100': '#ea80fc',
 5668    'A200': '#e040fb',
 5669    'A400': '#d500f9',
 5670    'A700': '#aa00ff',
 5671    'contrastDefaultColor': 'light',
 5672    'contrastDarkColors': '50 100 200 A100',
 5673    'contrastStrongLightColors': '300 400 A200 A400 A700'
 5674  },
 5675  'deep-purple': {
 5676    '50': '#ede7f6',
 5677    '100': '#d1c4e9',
 5678    '200': '#b39ddb',
 5679    '300': '#9575cd',
 5680    '400': '#7e57c2',
 5681    '500': '#673ab7',
 5682    '600': '#5e35b1',
 5683    '700': '#512da8',
 5684    '800': '#4527a0',
 5685    '900': '#311b92',
 5686    'A100': '#b388ff',
 5687    'A200': '#7c4dff',
 5688    'A400': '#651fff',
 5689    'A700': '#6200ea',
 5690    'contrastDefaultColor': 'light',
 5691    'contrastDarkColors': '50 100 200 A100',
 5692    'contrastStrongLightColors': '300 400 A200'
 5693  },
 5694  'indigo': {
 5695    '50': '#e8eaf6',
 5696    '100': '#c5cae9',
 5697    '200': '#9fa8da',
 5698    '300': '#7986cb',
 5699    '400': '#5c6bc0',
 5700    '500': '#3f51b5',
 5701    '600': '#3949ab',
 5702    '700': '#303f9f',
 5703    '800': '#283593',
 5704    '900': '#1a237e',
 5705    'A100': '#8c9eff',
 5706    'A200': '#536dfe',
 5707    'A400': '#3d5afe',
 5708    'A700': '#304ffe',
 5709    'contrastDefaultColor': 'light',
 5710    'contrastDarkColors': '50 100 200 A100',
 5711    'contrastStrongLightColors': '300 400 A200 A400'
 5712  },
 5713  'blue': {
 5714    '50': '#e3f2fd',
 5715    '100': '#bbdefb',
 5716    '200': '#90caf9',
 5717    '300': '#64b5f6',
 5718    '400': '#42a5f5',
 5719    '500': '#2196f3',
 5720    '600': '#1e88e5',
 5721    '700': '#1976d2',
 5722    '800': '#1565c0',
 5723    '900': '#0d47a1',
 5724    'A100': '#82b1ff',
 5725    'A200': '#448aff',
 5726    'A400': '#2979ff',
 5727    'A700': '#2962ff',
 5728    'contrastDefaultColor': 'light',
 5729    'contrastDarkColors': '50 100 200 300 400 A100',
 5730    'contrastStrongLightColors': '500 600 700 A200 A400 A700'
 5731  },
 5732  'light-blue': {
 5733    '50': '#e1f5fe',
 5734    '100': '#b3e5fc',
 5735    '200': '#81d4fa',
 5736    '300': '#4fc3f7',
 5737    '400': '#29b6f6',
 5738    '500': '#03a9f4',
 5739    '600': '#039be5',
 5740    '700': '#0288d1',
 5741    '800': '#0277bd',
 5742    '900': '#01579b',
 5743    'A100': '#80d8ff',
 5744    'A200': '#40c4ff',
 5745    'A400': '#00b0ff',
 5746    'A700': '#0091ea',
 5747    'contrastDefaultColor': 'dark',
 5748    'contrastLightColors': '600 700 800 900 A700',
 5749    'contrastStrongLightColors': '600 700 800 A700'
 5750  },
 5751  'cyan': {
 5752    '50': '#e0f7fa',
 5753    '100': '#b2ebf2',
 5754    '200': '#80deea',
 5755    '300': '#4dd0e1',
 5756    '400': '#26c6da',
 5757    '500': '#00bcd4',
 5758    '600': '#00acc1',
 5759    '700': '#0097a7',
 5760    '800': '#00838f',
 5761    '900': '#006064',
 5762    'A100': '#84ffff',
 5763    'A200': '#18ffff',
 5764    'A400': '#00e5ff',
 5765    'A700': '#00b8d4',
 5766    'contrastDefaultColor': 'dark',
 5767    'contrastLightColors': '700 800 900',
 5768    'contrastStrongLightColors': '700 800 900'
 5769  },
 5770  'teal': {
 5771    '50': '#e0f2f1',
 5772    '100': '#b2dfdb',
 5773    '200': '#80cbc4',
 5774    '300': '#4db6ac',
 5775    '400': '#26a69a',
 5776    '500': '#009688',
 5777    '600': '#00897b',
 5778    '700': '#00796b',
 5779    '800': '#00695c',
 5780    '900': '#004d40',
 5781    'A100': '#a7ffeb',
 5782    'A200': '#64ffda',
 5783    'A400': '#1de9b6',
 5784    'A700': '#00bfa5',
 5785    'contrastDefaultColor': 'dark',
 5786    'contrastLightColors': '500 600 700 800 900',
 5787    'contrastStrongLightColors': '500 600 700'
 5788  },
 5789  'green': {
 5790    '50': '#e8f5e9',
 5791    '100': '#c8e6c9',
 5792    '200': '#a5d6a7',
 5793    '300': '#81c784',
 5794    '400': '#66bb6a',
 5795    '500': '#4caf50',
 5796    '600': '#43a047',
 5797    '700': '#388e3c',
 5798    '800': '#2e7d32',
 5799    '900': '#1b5e20',
 5800    'A100': '#b9f6ca',
 5801    'A200': '#69f0ae',
 5802    'A400': '#00e676',
 5803    'A700': '#00c853',
 5804    'contrastDefaultColor': 'dark',
 5805    'contrastLightColors': '500 600 700 800 900',
 5806    'contrastStrongLightColors': '500 600 700'
 5807  },
 5808  'light-green': {
 5809    '50': '#f1f8e9',
 5810    '100': '#dcedc8',
 5811    '200': '#c5e1a5',
 5812    '300': '#aed581',
 5813    '400': '#9ccc65',
 5814    '500': '#8bc34a',
 5815    '600': '#7cb342',
 5816    '700': '#689f38',
 5817    '800': '#558b2f',
 5818    '900': '#33691e',
 5819    'A100': '#ccff90',
 5820    'A200': '#b2ff59',
 5821    'A400': '#76ff03',
 5822    'A700': '#64dd17',
 5823    'contrastDefaultColor': 'dark',
 5824    'contrastLightColors': '700 800 900',
 5825    'contrastStrongLightColors': '700 800 900'
 5826  },
 5827  'lime': {
 5828    '50': '#f9fbe7',
 5829    '100': '#f0f4c3',
 5830    '200': '#e6ee9c',
 5831    '300': '#dce775',
 5832    '400': '#d4e157',
 5833    '500': '#cddc39',
 5834    '600': '#c0ca33',
 5835    '700': '#afb42b',
 5836    '800': '#9e9d24',
 5837    '900': '#827717',
 5838    'A100': '#f4ff81',
 5839    'A200': '#eeff41',
 5840    'A400': '#c6ff00',
 5841    'A700': '#aeea00',
 5842    'contrastDefaultColor': 'dark',
 5843    'contrastLightColors': '900',
 5844    'contrastStrongLightColors': '900'
 5845  },
 5846  'yellow': {
 5847    '50': '#fffde7',
 5848    '100': '#fff9c4',
 5849    '200': '#fff59d',
 5850    '300': '#fff176',
 5851    '400': '#ffee58',
 5852    '500': '#ffeb3b',
 5853    '600': '#fdd835',
 5854    '700': '#fbc02d',
 5855    '800': '#f9a825',
 5856    '900': '#f57f17',
 5857    'A100': '#ffff8d',
 5858    'A200': '#ffff00',
 5859    'A400': '#ffea00',
 5860    'A700': '#ffd600',
 5861    'contrastDefaultColor': 'dark'
 5862  },
 5863  'amber': {
 5864    '50': '#fff8e1',
 5865    '100': '#ffecb3',
 5866    '200': '#ffe082',
 5867    '300': '#ffd54f',
 5868    '400': '#ffca28',
 5869    '500': '#ffc107',
 5870    '600': '#ffb300',
 5871    '700': '#ffa000',
 5872    '800': '#ff8f00',
 5873    '900': '#ff6f00',
 5874    'A100': '#ffe57f',
 5875    'A200': '#ffd740',
 5876    'A400': '#ffc400',
 5877    'A700': '#ffab00',
 5878    'contrastDefaultColor': 'dark'
 5879  },
 5880  'orange': {
 5881    '50': '#fff3e0',
 5882    '100': '#ffe0b2',
 5883    '200': '#ffcc80',
 5884    '300': '#ffb74d',
 5885    '400': '#ffa726',
 5886    '500': '#ff9800',
 5887    '600': '#fb8c00',
 5888    '700': '#f57c00',
 5889    '800': '#ef6c00',
 5890    '900': '#e65100',
 5891    'A100': '#ffd180',
 5892    'A200': '#ffab40',
 5893    'A400': '#ff9100',
 5894    'A700': '#ff6d00',
 5895    'contrastDefaultColor': 'dark',
 5896    'contrastLightColors': '800 900',
 5897    'contrastStrongLightColors': '800 900'
 5898  },
 5899  'deep-orange': {
 5900    '50': '#fbe9e7',
 5901    '100': '#ffccbc',
 5902    '200': '#ffab91',
 5903    '300': '#ff8a65',
 5904    '400': '#ff7043',
 5905    '500': '#ff5722',
 5906    '600': '#f4511e',
 5907    '700': '#e64a19',
 5908    '800': '#d84315',
 5909    '900': '#bf360c',
 5910    'A100': '#ff9e80',
 5911    'A200': '#ff6e40',
 5912    'A400': '#ff3d00',
 5913    'A700': '#dd2c00',
 5914    'contrastDefaultColor': 'light',
 5915    'contrastDarkColors': '50 100 200 300 400 A100 A200',
 5916    'contrastStrongLightColors': '500 600 700 800 900 A400 A700'
 5917  },
 5918  'brown': {
 5919    '50': '#efebe9',
 5920    '100': '#d7ccc8',
 5921    '200': '#bcaaa4',
 5922    '300': '#a1887f',
 5923    '400': '#8d6e63',
 5924    '500': '#795548',
 5925    '600': '#6d4c41',
 5926    '700': '#5d4037',
 5927    '800': '#4e342e',
 5928    '900': '#3e2723',
 5929    'A100': '#d7ccc8',
 5930    'A200': '#bcaaa4',
 5931    'A400': '#8d6e63',
 5932    'A700': '#5d4037',
 5933    'contrastDefaultColor': 'light',
 5934    'contrastDarkColors': '50 100 200 A100 A200',
 5935    'contrastStrongLightColors': '300 400'
 5936  },
 5937  'grey': {
 5938    '50': '#fafafa',
 5939    '100': '#f5f5f5',
 5940    '200': '#eeeeee',
 5941    '300': '#e0e0e0',
 5942    '400': '#bdbdbd',
 5943    '500': '#9e9e9e',
 5944    '600': '#757575',
 5945    '700': '#616161',
 5946    '800': '#424242',
 5947    '900': '#212121',
 5948    'A100': '#ffffff',
 5949    'A200': '#000000',
 5950    'A400': '#303030',
 5951    'A700': '#616161',
 5952    'contrastDefaultColor': 'dark',
 5953    'contrastLightColors': '600 700 800 900 A200 A400 A700'
 5954  },
 5955  'blue-grey': {
 5956    '50': '#eceff1',
 5957    '100': '#cfd8dc',
 5958    '200': '#b0bec5',
 5959    '300': '#90a4ae',
 5960    '400': '#78909c',
 5961    '500': '#607d8b',
 5962    '600': '#546e7a',
 5963    '700': '#455a64',
 5964    '800': '#37474f',
 5965    '900': '#263238',
 5966    'A100': '#cfd8dc',
 5967    'A200': '#b0bec5',
 5968    'A400': '#78909c',
 5969    'A700': '#455a64',
 5970    'contrastDefaultColor': 'light',
 5971    'contrastDarkColors': '50 100 200 300 A100 A200',
 5972    'contrastStrongLightColors': '400 500 700'
 5973  }
 5974});
 5975
 5976})();
 5977(function(){
 5978"use strict";
 5979
 5980(function(angular) {
 5981  'use strict';
 5982/**
 5983 * @ngdoc module
 5984 * @name material.core.theming
 5985 * @description
 5986 * Theming
 5987 */
 5988detectDisabledThemes.$inject = ["$mdThemingProvider"];
 5989ThemingDirective.$inject = ["$mdTheming", "$interpolate", "$parse", "$mdUtil", "$q", "$log"];
 5990ThemableDirective.$inject = ["$mdTheming"];
 5991ThemingProvider.$inject = ["$mdColorPalette", "$$mdMetaProvider"];
 5992generateAllThemes.$inject = ["$injector", "$mdTheming"];
 5993angular.module('material.core.theming', ['material.core.theming.palette', 'material.core.meta'])
 5994  .directive('mdTheme', ThemingDirective)
 5995  .directive('mdThemable', ThemableDirective)
 5996  .directive('mdThemesDisabled', disableThemesDirective )
 5997  .provider('$mdTheming', ThemingProvider)
 5998  .config( detectDisabledThemes )
 5999  .run(generateAllThemes);
 6000
 6001/**
 6002 * Detect if the HTML or the BODY tags has a [md-themes-disabled] attribute
 6003 * If yes, then immediately disable all theme stylesheet generation and DOM injection
 6004 */
 6005/**
 6006 * @ngInject
 6007 */
 6008function detectDisabledThemes($mdThemingProvider) {
 6009  var isDisabled = !!document.querySelector('[md-themes-disabled]');
 6010  $mdThemingProvider.disableTheming(isDisabled);
 6011}
 6012
 6013/**
 6014 * @ngdoc service
 6015 * @name $mdThemingProvider
 6016 * @module material.core.theming
 6017 *
 6018 * @description Provider to configure the `$mdTheming` service.
 6019 *
 6020 * ### Default Theme
 6021 * The `$mdThemingProvider` uses by default the following theme configuration:
 6022 *
 6023 * - Primary Palette: `Blue`
 6024 * - Accent Palette: `Pink`
 6025 * - Warn Palette: `Deep-Orange`
 6026 * - Background Palette: `Grey`
 6027 *
 6028 * If you don't want to use the `md-theme` directive on the elements itself, you may want to overwrite
 6029 * the default theme.<br/>
 6030 * This can be done by using the following markup.
 6031 *
 6032 * <hljs lang="js">
 6033 *   myAppModule.config(function($mdThemingProvider) {
 6034 *     $mdThemingProvider
 6035 *       .theme('default')
 6036 *       .primaryPalette('blue')
 6037 *       .accentPalette('teal')
 6038 *       .warnPalette('red')
 6039 *       .backgroundPalette('grey');
 6040 *   });
 6041 * </hljs>
 6042 *
 6043
 6044 * ### Dynamic Themes
 6045 *
 6046 * By default, if you change a theme at runtime, the `$mdTheming` service will not detect those changes.<br/>
 6047 * If you have an application, which changes its theme on runtime, you have to enable theme watching.
 6048 *
 6049 * <hljs lang="js">
 6050 *   myAppModule.config(function($mdThemingProvider) {
 6051 *     // Enable theme watching.
 6052 *     $mdThemingProvider.alwaysWatchTheme(true);
 6053 *   });
 6054 * </hljs>
 6055 *
 6056 * ### Custom Theme Styles
 6057 *
 6058 * Sometimes you may want to use your own theme styles for some custom components.<br/>
 6059 * You are able to register your own styles by using the following markup.
 6060 *
 6061 * <hljs lang="js">
 6062 *   myAppModule.config(function($mdThemingProvider) {
 6063 *     // Register our custom stylesheet into the theming provider.
 6064 *     $mdThemingProvider.registerStyles(STYLESHEET);
 6065 *   });
 6066 * </hljs>
 6067 *
 6068 * The `registerStyles` method only accepts strings as value, so you're actually not able to load an external
 6069 * stylesheet file into the `$mdThemingProvider`.
 6070 *
 6071 * If it's necessary to load an external stylesheet, we suggest using a bundler, which supports including raw content,
 6072 * like [raw-loader](https://github.com/webpack/raw-loader) for `webpack`.
 6073 *
 6074 * <hljs lang="js">
 6075 *   myAppModule.config(function($mdThemingProvider) {
 6076 *     // Register your custom stylesheet into the theming provider.
 6077 *     $mdThemingProvider.registerStyles(require('../styles/my-component.theme.css'));
 6078 *   });
 6079 * </hljs>
 6080 *
 6081 * ### Browser color
 6082 *
 6083 * Enables browser header coloring
 6084 * for more info please visit:
 6085 * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
 6086 *
 6087 * Options parameter: <br/>
 6088 * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
 6089 * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
 6090 *             'accent', 'background' and 'warn'. Default is `primary`. <br/>
 6091 * `hue`     - The hue from the selected palette. Default is `800`<br/>
 6092 *
 6093 * <hljs lang="js">
 6094 *   myAppModule.config(function($mdThemingProvider) {
 6095 *     // Enable browser color
 6096 *     $mdThemingProvider.enableBrowserColor({
 6097 *       theme: 'myTheme', // Default is 'default'
 6098 *       palette: 'accent', // Default is 'primary', any basic material palette and extended palettes are available
 6099 *       hue: '200' // Default is '800'
 6100 *     });
 6101 *   });
 6102 * </hljs>
 6103 */
 6104
 6105/**
 6106 * @ngdoc method
 6107 * @name $mdThemingProvider#registerStyles
 6108 * @param {string} styles The styles to be appended to Angular Material's built in theme css.
 6109 */
 6110/**
 6111 * @ngdoc method
 6112 * @name $mdThemingProvider#setNonce
 6113 * @param {string} nonceValue The nonce to be added as an attribute to the theme style tags.
 6114 * Setting a value allows the use of CSP policy without using the unsafe-inline directive.
 6115 */
 6116
 6117/**
 6118 * @ngdoc method
 6119 * @name $mdThemingProvider#setDefaultTheme
 6120 * @param {string} themeName Default theme name to be applied to elements. Default value is `default`.
 6121 */
 6122
 6123/**
 6124 * @ngdoc method
 6125 * @name $mdThemingProvider#alwaysWatchTheme
 6126 * @param {boolean} watch Whether or not to always watch themes for changes and re-apply
 6127 * classes when they change. Default is `false`. Enabling can reduce performance.
 6128 */
 6129
 6130/**
 6131 * @ngdoc method
 6132 * @name $mdThemingProvider#enableBrowserColor
 6133 * @param {Object=} options Options object for the browser color<br/>
 6134 * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
 6135 * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
 6136 *             'accent', 'background' and 'warn'. Default is `primary`. <br/>
 6137 * `hue`     - The hue from the selected palette. Default is `800`<br/>
 6138 * @returns {Function} remove function of the browser color
 6139 */
 6140
 6141/* Some Example Valid Theming Expressions
 6142 * =======================================
 6143 *
 6144 * Intention group expansion: (valid for primary, accent, warn, background)
 6145 *
 6146 * {{primary-100}} - grab shade 100 from the primary palette
 6147 * {{primary-100-0.7}} - grab shade 100, apply opacity of 0.7
 6148 * {{primary-100-contrast}} - grab shade 100's contrast color
 6149 * {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette
 6150 * {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-1
 6151 * {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue
 6152 * {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules
 6153 * {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue
 6154 * {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules
 6155 *
 6156 * Foreground expansion: Applies rgba to black/white foreground text
 6157 *
 6158 * {{foreground-1}} - used for primary text
 6159 * {{foreground-2}} - used for secondary text/divider
 6160 * {{foreground-3}} - used for disabled text
 6161 * {{foreground-4}} - used for dividers
 6162 *
 6163 */
 6164
 6165// In memory generated CSS rules; registered by theme.name
 6166var GENERATED = { };
 6167
 6168// In memory storage of defined themes and color palettes (both loaded by CSS, and user specified)
 6169var PALETTES;
 6170
 6171// Text Colors on light and dark backgrounds
 6172// @see https://www.google.com/design/spec/style/color.html#color-text-background-colors
 6173var DARK_FOREGROUND = {
 6174  name: 'dark',
 6175  '1': 'rgba(0,0,0,0.87)',
 6176  '2': 'rgba(0,0,0,0.54)',
 6177  '3': 'rgba(0,0,0,0.38)',
 6178  '4': 'rgba(0,0,0,0.12)'
 6179};
 6180var LIGHT_FOREGROUND = {
 6181  name: 'light',
 6182  '1': 'rgba(255,255,255,1.0)',
 6183  '2': 'rgba(255,255,255,0.7)',
 6184  '3': 'rgba(255,255,255,0.5)',
 6185  '4': 'rgba(255,255,255,0.12)'
 6186};
 6187
 6188var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)';
 6189var LIGHT_SHADOW = '';
 6190
 6191var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)');
 6192var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87)');
 6193var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)');
 6194
 6195var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background'];
 6196var DEFAULT_COLOR_TYPE = 'primary';
 6197
 6198// A color in a theme will use these hues by default, if not specified by user.
 6199var LIGHT_DEFAULT_HUES = {
 6200  'accent': {
 6201    'default': 'A200',
 6202    'hue-1': 'A100',
 6203    'hue-2': 'A400',
 6204    'hue-3': 'A700'
 6205  },
 6206  'background': {
 6207    'default': '50',
 6208    'hue-1': 'A100',
 6209    'hue-2': '100',
 6210    'hue-3': '300'
 6211  }
 6212};
 6213
 6214var DARK_DEFAULT_HUES = {
 6215  'background': {
 6216    'default': 'A400',
 6217    'hue-1': '800',
 6218    'hue-2': '900',
 6219    'hue-3': 'A200'
 6220  }
 6221};
 6222THEME_COLOR_TYPES.forEach(function(colorType) {
 6223  // Color types with unspecified default hues will use these default hue values
 6224  var defaultDefaultHues = {
 6225    'default': '500',
 6226    'hue-1': '300',
 6227    'hue-2': '800',
 6228    'hue-3': 'A100'
 6229  };
 6230  if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues;
 6231  if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues;
 6232});
 6233
 6234var VALID_HUE_VALUES = [
 6235  '50', '100', '200', '300', '400', '500', '600',
 6236  '700', '800', '900', 'A100', 'A200', 'A400', 'A700'
 6237];
 6238
 6239var themeConfig = {
 6240  disableTheming : false,   // Generate our themes at run time; also disable stylesheet DOM injection
 6241  generateOnDemand : false, // Whether or not themes are to be generated on-demand (vs. eagerly).
 6242  registeredStyles : [],    // Custom styles registered to be used in the theming of custom components.
 6243  nonce : null              // Nonce to be added as an attribute to the generated themes style tags.
 6244};
 6245
 6246/**
 6247 *
 6248 */
 6249function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
 6250  ThemingService.$inject = ["$rootScope", "$mdUtil", "$q", "$log"];
 6251  PALETTES = { };
 6252  var THEMES = { };
 6253
 6254  var themingProvider;
 6255
 6256  var alwaysWatchTheme = false;
 6257  var defaultTheme = 'default';
 6258
 6259  // Load JS Defined Palettes
 6260  angular.extend(PALETTES, $mdColorPalette);
 6261
 6262  // Default theme defined in core.js
 6263
 6264  /**
 6265   * Adds `theme-color` and `msapplication-navbutton-color` meta tags with the color parameter
 6266   * @param {string} color Hex value of the wanted browser color
 6267   * @returns {Function} Remove function of the meta tags
 6268   */
 6269  var setBrowserColor = function (color) {
 6270    // Chrome, Firefox OS and Opera
 6271    var removeChrome = $$mdMetaProvider.setMeta('theme-color', color);
 6272    // Windows Phone
 6273    var removeWindows = $$mdMetaProvider.setMeta('msapplication-navbutton-color', color);
 6274
 6275    return function () {
 6276      removeChrome();
 6277      removeWindows();
 6278    };
 6279  };
 6280
 6281  /**
 6282   * Enables browser header coloring
 6283   * for more info please visit:
 6284   * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
 6285   *
 6286   * The default color is `800` from `primary` palette of the `default` theme
 6287   *
 6288   * options are:
 6289   * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme
 6290   * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
 6291   *             'accent', 'background' and 'warn'. Default is `primary`
 6292   * `hue`     - The hue from the selected palette. Default is `800`
 6293   *
 6294   * @param {Object=} options Options object for the browser color
 6295   * @returns {Function} remove function of the browser color
 6296   */
 6297  var enableBrowserColor = function (options) {
 6298    options = angular.isObject(options) ? options : {};
 6299
 6300    var theme = options.theme || 'default';
 6301    var hue = options.hue || '800';
 6302
 6303    var palette = PALETTES[options.palette] ||
 6304      PALETTES[THEMES[theme].colors[options.palette || 'primary'].name];
 6305
 6306    var color = angular.isObject(palette[hue]) ? palette[hue].hex : palette[hue];
 6307
 6308    return setBrowserColor(color);
 6309  };
 6310
 6311  return themingProvider = {
 6312    definePalette: definePalette,
 6313    extendPalette: extendPalette,
 6314    theme: registerTheme,
 6315
 6316    /**
 6317     * return a read-only clone of the current theme configuration
 6318     */
 6319    configuration : function() {
 6320      return angular.extend( { }, themeConfig, {
 6321        defaultTheme : defaultTheme,
 6322        alwaysWatchTheme : alwaysWatchTheme,
 6323        registeredStyles : [].concat(themeConfig.registeredStyles)
 6324      });
 6325    },
 6326
 6327    /**
 6328     * Easy way to disable theming without having to use
 6329     * `.constant("$MD_THEME_CSS","");` This disables
 6330     * all dynamic theme style sheet generations and injections...
 6331     */
 6332    disableTheming: function(isDisabled) {
 6333      themeConfig.disableTheming = angular.isUndefined(isDisabled) || !!isDisabled;
 6334    },
 6335
 6336    registerStyles: function(styles) {
 6337      themeConfig.registeredStyles.push(styles);
 6338    },
 6339
 6340    setNonce: function(nonceValue) {
 6341      themeConfig.nonce = nonceValue;
 6342    },
 6343
 6344    generateThemesOnDemand: function(onDemand) {
 6345      themeConfig.generateOnDemand = onDemand;
 6346    },
 6347
 6348    setDefaultTheme: function(theme) {
 6349      defaultTheme = theme;
 6350    },
 6351
 6352    alwaysWatchTheme: function(alwaysWatch) {
 6353      alwaysWatchTheme = alwaysWatch;
 6354    },
 6355
 6356    enableBrowserColor: enableBrowserColor,
 6357
 6358    $get: ThemingService,
 6359    _LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,
 6360    _DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,
 6361    _PALETTES: PALETTES,
 6362    _THEMES: THEMES,
 6363    _parseRules: parseRules,
 6364    _rgba: rgba
 6365  };
 6366
 6367  // Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... });
 6368  function definePalette(name, map) {
 6369    map = map || {};
 6370    PALETTES[name] = checkPaletteValid(name, map);
 6371    return themingProvider;
 6372  }
 6373
 6374  // Returns an new object which is a copy of a given palette `name` with variables from
 6375  // `map` overwritten
 6376  // Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' });
 6377  function extendPalette(name, map) {
 6378    return checkPaletteValid(name,  angular.extend({}, PALETTES[name] || {}, map) );
 6379  }
 6380
 6381  // Make sure that palette has all required hues
 6382  function checkPaletteValid(name, map) {
 6383    var missingColors = VALID_HUE_VALUES.filter(function(field) {
 6384      return !map[field];
 6385    });
 6386    if (missingColors.length) {
 6387      throw new Error("Missing colors %1 in palette %2!"
 6388                      .replace('%1', missingColors.join(', '))
 6389                      .replace('%2', name));
 6390    }
 6391
 6392    return map;
 6393  }
 6394
 6395  // Register a theme (which is a collection of color palettes to use with various states
 6396  // ie. warn, accent, primary )
 6397  // Optionally inherit from an existing theme
 6398  // $mdThemingProvider.theme('custom-theme').primaryPalette('red');
 6399  function registerTheme(name, inheritFrom) {
 6400    if (THEMES[name]) return THEMES[name];
 6401
 6402    inheritFrom = inheritFrom || 'default';
 6403
 6404    var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom;
 6405    var theme = new Theme(name);
 6406
 6407    if (parentTheme) {
 6408      angular.forEach(parentTheme.colors, function(color, colorType) {
 6409        theme.colors[colorType] = {
 6410          name: color.name,
 6411          // Make sure a COPY of the hues is given to the child color,
 6412          // not the same reference.
 6413          hues: angular.extend({}, color.hues)
 6414        };
 6415      });
 6416    }
 6417    THEMES[name] = theme;
 6418
 6419    return theme;
 6420  }
 6421
 6422  function Theme(name) {
 6423    var self = this;
 6424    self.name = name;
 6425    self.colors = {};
 6426
 6427    self.dark = setDark;
 6428    setDark(false);
 6429
 6430    function setDark(isDark) {
 6431      isDark = arguments.length === 0 ? true : !!isDark;
 6432
 6433      // If no change, abort
 6434      if (isDark === self.isDark) return;
 6435
 6436      self.isDark = isDark;
 6437
 6438      self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND;
 6439      self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW;
 6440
 6441      // Light and dark themes have different default hues.
 6442      // Go through each existing color type for this theme, and for every
 6443      // hue value that is still the default hue value from the previous light/dark setting,
 6444      // set it to the default hue value from the new light/dark setting.
 6445      var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES;
 6446      var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES;
 6447      angular.forEach(newDefaultHues, function(newDefaults, colorType) {
 6448        var color = self.colors[colorType];
 6449        var oldDefaults = oldDefaultHues[colorType];
 6450        if (color) {
 6451          for (var hueName in color.hues) {
 6452            if (color.hues[hueName] === oldDefaults[hueName]) {
 6453              color.hues[hueName] = newDefaults[hueName];
 6454            }
 6455          }
 6456        }
 6457      });
 6458
 6459      return self;
 6460    }
 6461
 6462    THEME_COLOR_TYPES.forEach(function(colorType) {
 6463      var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType];
 6464      self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) {
 6465        var color = self.colors[colorType] = {
 6466          name: paletteName,
 6467          hues: angular.extend({}, defaultHues, hues)
 6468        };
 6469
 6470        Object.keys(color.hues).forEach(function(name) {
 6471          if (!defaultHues[name]) {
 6472            throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4"
 6473              .replace('%1', name)
 6474              .replace('%2', self.name)
 6475              .replace('%3', paletteName)
 6476              .replace('%4', Object.keys(defaultHues).join(', '))
 6477            );
 6478          }
 6479        });
 6480        Object.keys(color.hues).map(function(key) {
 6481          return color.hues[key];
 6482        }).forEach(function(hueValue) {
 6483          if (VALID_HUE_VALUES.indexOf(hueValue) == -1) {
 6484            throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5"
 6485              .replace('%1', hueValue)
 6486              .replace('%2', self.name)
 6487              .replace('%3', colorType)
 6488              .replace('%4', paletteName)
 6489              .replace('%5', VALID_HUE_VALUES.join(', '))
 6490            );
 6491          }
 6492        });
 6493        return self;
 6494      };
 6495
 6496      self[colorType + 'Color'] = function() {
 6497        var args = Array.prototype.slice.call(arguments);
 6498        console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' +
 6499                     'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.');
 6500        return self[colorType + 'Palette'].apply(self, args);
 6501      };
 6502    });
 6503  }
 6504
 6505  /**
 6506   * @ngdoc service
 6507   * @name $mdTheming
 6508   * @module material.core.theming
 6509   *
 6510   * @description
 6511   *
 6512   * Service that makes an element apply theming related <b>classes</b> to itself.
 6513   *
 6514   * <hljs lang="js">
 6515   * app.directive('myFancyDirective', function($mdTheming) {
 6516   *   return {
 6517   *     restrict: 'e',
 6518   *     link: function(scope, el, attrs) {
 6519   *       $mdTheming(el);
 6520   *     }
 6521   *   };
 6522   * });
 6523   * </hljs>
 6524   * @param {element=} element to apply theming to
 6525   */
 6526
 6527  /**
 6528   * @ngdoc property
 6529   * @name $mdTheming#THEMES
 6530   * @description
 6531   * Property to get all the themes defined
 6532   * @returns {Object} All the themes defined with their properties
 6533   */
 6534
 6535  /**
 6536   * @ngdoc property
 6537   * @name $mdTheming#PALETTES
 6538   * @description
 6539   * Property to get all the palettes defined
 6540   * @returns {Object} All the palettes defined with their colors
 6541   */
 6542
 6543  /**
 6544   * @ngdoc method
 6545   * @name $mdTheming#registered
 6546   * @description
 6547   * Determine is specified theme name is a valid, registered theme
 6548   * @param {string} themeName the theme to check if registered
 6549   * @returns {boolean} whether the theme is registered or not
 6550   */
 6551
 6552  /**
 6553   * @ngdoc method
 6554   * @name $mdTheming#defaultTheme
 6555   * @description
 6556   * Returns the default theme
 6557   * @returns {string} The default theme
 6558   */
 6559
 6560  /**
 6561   * @ngdoc method
 6562   * @name $mdTheming#generateTheme
 6563   * @description
 6564   * Lazy generate themes - by default, every theme is generated when defined.
 6565   * You can disable this in the configuration section using the
 6566   * `$mdThemingProvider.generateThemesOnDemand(true);`
 6567   *
 6568   * The theme name that is passed in must match the name of the theme that was defined as part of the configuration block.
 6569   *
 6570   * @param name {string} theme name to generate
 6571   */
 6572
 6573  /**
 6574   * @ngdoc method
 6575   * @name $mdTheming#setBrowserColor
 6576   * @description
 6577   * Sets browser header coloring
 6578   * for more info please visit:
 6579   * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
 6580   *
 6581   * The default color is `800` from `primary` palette of the `default` theme
 6582   *
 6583   * options are:<br/>
 6584   * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme.<br/>
 6585   * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
 6586   *             'accent', 'background' and 'warn'. Default is `primary`<br/>
 6587   * `hue`     - The hue from the selected palette. Default is `800`
 6588   *
 6589   * @param {Object} options Options object for the browser color
 6590   * @returns {Function} remove function of the browser color
 6591   */
 6592
 6593  /* @ngInject */
 6594  function ThemingService($rootScope, $mdUtil, $q, $log) {
 6595        // Allow us to be invoked via a linking function signature.
 6596    var applyTheme = function (scope, el) {
 6597          if (el === undefined) { el = scope; scope = undefined; }
 6598          if (scope === undefined) { scope = $rootScope; }
 6599          applyTheme.inherit(el, el);
 6600        };
 6601
 6602    Object.defineProperty(applyTheme, 'THEMES', {
 6603      get: function () {
 6604        return angular.extend({}, THEMES);
 6605      }
 6606    });
 6607    Object.defineProperty(applyTheme, 'PALETTES', {
 6608      get: function () {
 6609        return angular.extend({}, PALETTES);
 6610      }
 6611    });
 6612    Object.defineProperty(applyTheme, 'ALWAYS_WATCH', {
 6613      get: function () {
 6614        return alwaysWatchTheme;
 6615      }
 6616    });
 6617    applyTheme.inherit = inheritTheme;
 6618    applyTheme.registered = registered;
 6619    applyTheme.defaultTheme = function() { return defaultTheme; };
 6620    applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };
 6621    applyTheme.defineTheme = function(name, options) {
 6622      options = options || {};
 6623
 6624      var theme = registerTheme(name);
 6625
 6626      if (options.primary) {
 6627        theme.primaryPalette(options.primary);
 6628      }
 6629      if (options.accent) {
 6630        theme.accentPalette(options.accent);
 6631      }
 6632      if (options.warn) {
 6633        theme.warnPalette(options.warn);
 6634      }
 6635      if (options.background) {
 6636        theme.backgroundPalette(options.background);
 6637      }
 6638      if (options.dark){
 6639        theme.dark();
 6640      }
 6641
 6642      this.generateTheme(name);
 6643
 6644      return $q.resolve(name);
 6645    };
 6646    applyTheme.setBrowserColor = enableBrowserColor;
 6647
 6648    return applyTheme;
 6649
 6650    /**
 6651     * Determine is specified theme name is a valid, registered theme
 6652     */
 6653    function registered(themeName) {
 6654      if (themeName === undefined || themeName === '') return true;
 6655      return applyTheme.THEMES[themeName] !== undefined;
 6656    }
 6657
 6658    /**
 6659     * Get theme name for the element, then update with Theme CSS class
 6660     */
 6661    function inheritTheme (el, parent) {
 6662      var ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');
 6663
 6664      updateThemeClass(lookupThemeName());
 6665
 6666      if (ctrl) {
 6667        var watchTheme = alwaysWatchTheme ||
 6668                         ctrl.$shouldWatch ||
 6669                         $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'));
 6670
 6671        var unwatch = ctrl.registerChanges(function (name) {
 6672          updateThemeClass(name);
 6673
 6674          if (!watchTheme) {
 6675            unwatch();
 6676          }
 6677          else {
 6678            el.on('$destroy', unwatch);
 6679          }
 6680        });
 6681      }
 6682
 6683      /**
 6684       * Find the theme name from the parent controller or element data
 6685       */
 6686      function lookupThemeName() {
 6687        // As a few components (dialog) add their controllers later, we should also watch for a controller init.
 6688        return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme);
 6689      }
 6690
 6691      /**
 6692       * Remove old theme class and apply a new one
 6693       * NOTE: if not a valid theme name, then the current name is not changed
 6694       */
 6695      function updateThemeClass(theme) {
 6696        if (!theme) return;
 6697        if (!registered(theme)) {
 6698          $log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' +
 6699                    'Register it with $mdThemingProvider.theme().');
 6700        }
 6701
 6702        var oldTheme = el.data('$mdThemeName');
 6703        if (oldTheme) el.removeClass('md-' + oldTheme +'-theme');
 6704        el.addClass('md-' + theme + '-theme');
 6705        el.data('$mdThemeName', theme);
 6706        if (ctrl) {
 6707          el.data('$mdThemeController', ctrl);
 6708        }
 6709      }
 6710    }
 6711
 6712  }
 6713}
 6714
 6715function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
 6716  return {
 6717    priority: 101, // has to be more than 100 to be before interpolation (issue on IE)
 6718    link: {
 6719      pre: function(scope, el, attrs) {
 6720        var registeredCallbacks = [];
 6721
 6722        var startSymbol = $interpolate.startSymbol();
 6723        var endSymbol = $interpolate.endSymbol();
 6724
 6725        var theme = attrs.mdTheme.trim();
 6726
 6727        var hasInterpolation =
 6728          theme.substr(0, startSymbol.length) === startSymbol &&
 6729          theme.lastIndexOf(endSymbol) === theme.length - endSymbol.length;
 6730
 6731        var oneTimeOperator = '::';
 6732        var oneTimeBind = attrs.mdTheme
 6733            .split(startSymbol).join('')
 6734            .split(endSymbol).join('')
 6735            .trim()
 6736            .substr(0, oneTimeOperator.length) === oneTimeOperator;
 6737
 6738        var ctrl = {
 6739          registerChanges: function (cb, context) {
 6740            if (context) {
 6741              cb = angular.bind(context, cb);
 6742            }
 6743
 6744            registeredCallbacks.push(cb);
 6745
 6746            return function () {
 6747              var index = registeredCallbacks.indexOf(cb);
 6748
 6749              if (index > -1) {
 6750                registeredCallbacks.splice(index, 1);
 6751              }
 6752            };
 6753          },
 6754          $setTheme: function (theme) {
 6755            if (!$mdTheming.registered(theme)) {
 6756              $log.warn('attempted to use unregistered theme \'' + theme + '\'');
 6757            }
 6758
 6759            ctrl.$mdTheme = theme;
 6760
 6761            // Iterating backwards to support unregistering during iteration
 6762            // http://stackoverflow.com/a/9882349/890293
 6763            // we don't use `reverse()` of array because it mutates the array and we don't want it to get re-indexed
 6764            for (var i = registeredCallbacks.length; i--;) {
 6765              registeredCallbacks[i](theme);
 6766            }
 6767          },
 6768          $shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')) ||
 6769                        $mdTheming.ALWAYS_WATCH ||
 6770                        (hasInterpolation && !oneTimeBind)
 6771        };
 6772
 6773        el.data('$mdThemeController', ctrl);
 6774
 6775        var getTheme = function () {
 6776          var interpolation = $interpolate(attrs.mdTheme)(scope);
 6777          return $parse(interpolation)(scope) || interpolation;
 6778        };
 6779
 6780        var setParsedTheme = function (theme) {
 6781          if (typeof theme === 'string') {
 6782            return ctrl.$setTheme(theme);
 6783          }
 6784
 6785          $q.when( angular.isFunction(theme) ?  theme() : theme )
 6786            .then(function(name){
 6787              ctrl.$setTheme(name);
 6788            });
 6789        };
 6790
 6791        setParsedTheme(getTheme());
 6792
 6793        var unwatch = scope.$watch(getTheme, function(theme) {
 6794          if (theme) {
 6795            setParsedTheme(theme);
 6796
 6797            if (!ctrl.$shouldWatch) {
 6798              unwatch();
 6799            }
 6800          }
 6801        });
 6802      }
 6803    }
 6804  };
 6805}
 6806
 6807/**
 6808 * Special directive that will disable ALL runtime Theme style generation and DOM injection
 6809 *
 6810 * <link rel="stylesheet" href="angular-material.min.css">
 6811 * <link rel="stylesheet" href="angular-material.themes.css">
 6812 *
 6813 * <body md-themes-disabled>
 6814 *  ...
 6815 * </body>
 6816 *
 6817 * Note: Using md-themes-css directive requires the developer to load external
 6818 * theme stylesheets; e.g. custom themes from Material-Tools:
 6819 *
 6820 *       `angular-material.themes.css`
 6821 *
 6822 * Another option is to use the ThemingProvider to configure and disable the attribute
 6823 * conversions; this would obviate the use of the `md-themes-css` directive
 6824 *
 6825 */
 6826function disableThemesDirective() {
 6827  themeConfig.disableTheming = true;
 6828
 6829  // Return a 1x-only, first-match attribute directive
 6830  return {
 6831    restrict : 'A',
 6832    priority : '900'
 6833  };
 6834}
 6835
 6836function ThemableDirective($mdTheming) {
 6837  return $mdTheming;
 6838}
 6839
 6840function parseRules(theme, colorType, rules) {
 6841  checkValidPalette(theme, colorType);
 6842
 6843  rules = rules.replace(/THEME_NAME/g, theme.name);
 6844  var generatedRules = [];
 6845  var color = theme.colors[colorType];
 6846
 6847  var themeNameRegex = new RegExp('\\.md-' + theme.name + '-theme', 'g');
 6848  // Matches '{{ primary-color }}', etc
 6849  var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g');
 6850  var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow|default)-?(\d\.?\d*)?(contrast)?\s*\}\}'?"?/g;
 6851  var palette = PALETTES[color.name];
 6852
 6853  // find and replace simple variables where we use a specific hue, not an entire palette
 6854  // eg. "{{primary-100}}"
 6855  //\(' + THEME_COLOR_TYPES.join('\|') + '\)'
 6856  rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity, contrast) {
 6857    if (colorType === 'foreground') {
 6858      if (hue == 'shadow') {
 6859        return theme.foregroundShadow;
 6860      } else {
 6861        return theme.foregroundPalette[hue] || theme.foregroundPalette['1'];
 6862      }
 6863    }
 6864
 6865    // `default` is also accepted as a hue-value, because the background palettes are
 6866    // using it as a name for the default hue.
 6867    if (hue.indexOf('hue') === 0 || hue === 'default') {
 6868      hue = theme.colors[colorType].hues[hue];
 6869    }
 6870
 6871    return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '')[contrast ? 'contrast' : 'value'], opacity );
 6872  });
 6873
 6874  // For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3)
 6875  angular.forEach(color.hues, function(hueValue, hueName) {
 6876    var newRule = rules
 6877      .replace(hueRegex, function(match, _, colorType, hueType, opacity) {
 6878        return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity);
 6879      });
 6880    if (hueName !== 'default') {
 6881      newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName);
 6882    }
 6883
 6884    // Don't apply a selector rule to the default theme, making it easier to override
 6885    // styles of the base-component
 6886    if (theme.name == 'default') {
 6887      var themeRuleRegex = /((?:\s|>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)\.md-default-theme((?:\s|>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)/g;
 6888
 6889      newRule = newRule.replace(themeRuleRegex, function(match, start, end) {
 6890        return match + ', ' + start + end;
 6891      });
 6892    }
 6893    generatedRules.push(newRule);
 6894  });
 6895
 6896  return generatedRules;
 6897}
 6898
 6899var rulesByType = {};
 6900
 6901// Generate our themes at run time given the state of THEMES and PALETTES
 6902function generateAllThemes($injector, $mdTheming) {
 6903  var head = document.head;
 6904  var firstChild = head ? head.firstElementChild : null;
 6905  var themeCss = !themeConfig.disableTheming && $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : '';
 6906
 6907  // Append our custom registered styles to the theme stylesheet.
 6908  themeCss += themeConfig.registeredStyles.join('');
 6909
 6910  if ( !firstChild ) return;
 6911  if (themeCss.length === 0) return; // no rules, so no point in running this expensive task
 6912
 6913  // Expose contrast colors for palettes to ensure that text is always readable
 6914  angular.forEach(PALETTES, sanitizePalette);
 6915
 6916  // MD_THEME_CSS is a string generated by the build process that includes all the themable
 6917  // components as templates
 6918
 6919  // Break the CSS into individual rules
 6920  var rules = themeCss
 6921                  .split(/\}(?!(\}|'|"|;))/)
 6922                  .filter(function(rule) { return rule && rule.trim().length; })
 6923                  .map(function(rule) { return rule.trim() + '}'; });
 6924
 6925
 6926  var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g');
 6927
 6928  THEME_COLOR_TYPES.forEach(function(type) {
 6929    rulesByType[type] = '';
 6930  });
 6931
 6932
 6933  // Sort the rules based on type, allowing us to do color substitution on a per-type basis
 6934  rules.forEach(function(rule) {
 6935    var match = rule.match(ruleMatchRegex);
 6936    // First: test that if the rule has '.md-accent', it goes into the accent set of rules
 6937    for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) {
 6938      if (rule.indexOf('.md-' + type) > -1) {
 6939        return rulesByType[type] += rule;
 6940      }
 6941    }
 6942
 6943    // If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from
 6944    // there
 6945    for (i = 0; type = THEME_COLOR_TYPES[i]; i++) {
 6946      if (rule.indexOf(type) > -1) {
 6947        return rulesByType[type] += rule;
 6948      }
 6949    }
 6950
 6951    // Default to the primary array
 6952    return rulesByType[DEFAULT_COLOR_TYPE] += rule;
 6953  });
 6954
 6955  // If themes are being generated on-demand, quit here. The user will later manually
 6956  // call generateTheme to do this on a theme-by-theme basis.
 6957  if (themeConfig.generateOnDemand) return;
 6958
 6959  angular.forEach($mdTheming.THEMES, function(theme) {
 6960    if (!GENERATED[theme.name] && !($mdTheming.defaultTheme() !== 'default' && theme.name === 'default')) {
 6961      generateTheme(theme, theme.name, themeConfig.nonce);
 6962    }
 6963  });
 6964
 6965
 6966  // *************************
 6967  // Internal functions
 6968  // *************************
 6969
 6970  // The user specifies a 'default' contrast color as either light or dark,
 6971  // then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light)
 6972  function sanitizePalette(palette, name) {
 6973    var defaultContrast = palette.contrastDefaultColor;
 6974    var lightColors = palette.contrastLightColors || [];
 6975    var strongLightColors = palette.contrastStrongLightColors || [];
 6976    var darkColors = palette.contrastDarkColors || [];
 6977
 6978    // These colors are provided as space-separated lists
 6979    if (typeof lightColors === 'string') lightColors = lightColors.split(' ');
 6980    if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' ');
 6981    if (typeof darkColors === 'string') darkColors = darkColors.split(' ');
 6982
 6983    // Cleanup after ourselves
 6984    delete palette.contrastDefaultColor;
 6985    delete palette.contrastLightColors;
 6986    delete palette.contrastStrongLightColors;
 6987    delete palette.contrastDarkColors;
 6988
 6989    // Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR }
 6990    angular.forEach(palette, function(hueValue, hueName) {
 6991      if (angular.isObject(hueValue)) return; // Already converted
 6992      // Map everything to rgb colors
 6993      var rgbValue = colorToRgbaArray(hueValue);
 6994      if (!rgbValue) {
 6995        throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected."
 6996                        .replace('%1', hueValue)
 6997                        .replace('%2', palette.name)
 6998                        .replace('%3', hueName));
 6999      }
 7000
 7001      palette[hueName] = {
 7002        hex: palette[hueName],
 7003        value: rgbValue,
 7004        contrast: getContrastColor()
 7005      };
 7006      function getContrastColor() {
 7007        if (defaultContrast === 'light') {
 7008          if (darkColors.indexOf(hueName) > -1) {
 7009            return DARK_CONTRAST_COLOR;
 7010          } else {
 7011            return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
 7012              : LIGHT_CONTRAST_COLOR;
 7013          }
 7014        } else {
 7015          if (lightColors.indexOf(hueName) > -1) {
 7016            return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
 7017              : LIGHT_CONTRAST_COLOR;
 7018          } else {
 7019            return DARK_CONTRAST_COLOR;
 7020          }
 7021        }
 7022      }
 7023    });
 7024  }
 7025}
 7026
 7027function generateTheme(theme, name, nonce) {
 7028  var head = document.head;
 7029  var firstChild = head ? head.firstElementChild : null;
 7030
 7031  if (!GENERATED[name]) {
 7032    // For each theme, use the color palettes specified for
 7033    // `primary`, `warn` and `accent` to generate CSS rules.
 7034    THEME_COLOR_TYPES.forEach(function(colorType) {
 7035      var styleStrings = parseRules(theme, colorType, rulesByType[colorType]);
 7036      while (styleStrings.length) {
 7037        var styleContent = styleStrings.shift();
 7038        if (styleContent) {
 7039          var style = document.createElement('style');
 7040          style.setAttribute('md-theme-style', '');
 7041          if (nonce) {
 7042            style.setAttribute('nonce', nonce);
 7043          }
 7044          style.appendChild(document.createTextNode(styleContent));
 7045          head.insertBefore(style, firstChild);
 7046        }
 7047      }
 7048    });
 7049
 7050    GENERATED[theme.name] = true;
 7051  }
 7052
 7053}
 7054
 7055
 7056function checkValidPalette(theme, colorType) {
 7057  // If theme attempts to use a palette that doesnt exist, throw error
 7058  if (!PALETTES[ (theme.colors[colorType] || {}).name ]) {
 7059    throw new Error(
 7060      "You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3"
 7061                    .replace('%1', theme.name)
 7062                    .replace('%2', colorType)
 7063                    .replace('%3', Object.keys(PALETTES).join(', '))
 7064    );
 7065  }
 7066}
 7067
 7068function colorToRgbaArray(clr) {
 7069  if (angular.isArray(clr) && clr.length == 3) return clr;
 7070  if (/^rgb/.test(clr)) {
 7071    return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) {
 7072      return i == 3 ? parseFloat(value, 10) : parseInt(value, 10);
 7073    });
 7074  }
 7075  if (clr.charAt(0) == '#') clr = clr.substring(1);
 7076  if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return;
 7077
 7078  var dig = clr.length / 3;
 7079  var red = clr.substr(0, dig);
 7080  var grn = clr.substr(dig, dig);
 7081  var blu = clr.substr(dig * 2);
 7082  if (dig === 1) {
 7083    red += red;
 7084    grn += grn;
 7085    blu += blu;
 7086  }
 7087  return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)];
 7088}
 7089
 7090function rgba(rgbArray, opacity) {
 7091  if ( !rgbArray ) return "rgb('0,0,0')";
 7092
 7093  if (rgbArray.length == 4) {
 7094    rgbArray = angular.copy(rgbArray);
 7095    opacity ? rgbArray.pop() : opacity = rgbArray.pop();
 7096  }
 7097  return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ?
 7098    'rgba(' + rgbArray.join(',') + ',' + opacity + ')' :
 7099    'rgb(' + rgbArray.join(',') + ')';
 7100}
 7101
 7102
 7103})(window.angular);
 7104
 7105})();
 7106(function(){
 7107"use strict";
 7108
 7109// Polyfill angular < 1.4 (provide $animateCss)
 7110angular
 7111  .module('material.core')
 7112  .factory('$$mdAnimate', ["$q", "$timeout", "$mdConstant", "$animateCss", function($q, $timeout, $mdConstant, $animateCss){
 7113
 7114     // Since $$mdAnimate is injected into $mdUtil... use a wrapper function
 7115     // to subsequently inject $mdUtil as an argument to the AnimateDomUtils
 7116
 7117     return function($mdUtil) {
 7118       return AnimateDomUtils( $mdUtil, $q, $timeout, $mdConstant, $animateCss);
 7119     };
 7120   }]);
 7121
 7122/**
 7123 * Factory function that requires special injections
 7124 */
 7125function AnimateDomUtils($mdUtil, $q, $timeout, $mdConstant, $animateCss) {
 7126  var self;
 7127  return self = {
 7128    /**
 7129     *
 7130     */
 7131    translate3d : function( target, from, to, options ) {
 7132      return $animateCss(target, {
 7133        from: from,
 7134        to: to,
 7135        addClass: options.transitionInClass,
 7136        removeClass: options.transitionOutClass,
 7137        duration: options.duration
 7138      })
 7139      .start()
 7140      .then(function(){
 7141          // Resolve with reverser function...
 7142          return reverseTranslate;
 7143      });
 7144
 7145      /**
 7146       * Specific reversal of the request translate animation above...
 7147       */
 7148      function reverseTranslate (newFrom) {
 7149        return $animateCss(target, {
 7150           to: newFrom || from,
 7151           addClass: options.transitionOutClass,
 7152           removeClass: options.transitionInClass,
 7153           duration: options.duration
 7154        }).start();
 7155
 7156      }
 7157    },
 7158
 7159    /**
 7160     * Listen for transitionEnd event (with optional timeout)
 7161     * Announce completion or failure via promise handlers
 7162     */
 7163    waitTransitionEnd: function (element, opts) {
 7164      var TIMEOUT = 3000; // fallback is 3 secs
 7165
 7166      return $q(function(resolve, reject){
 7167        opts = opts || { };
 7168
 7169        // If there is no transition is found, resolve immediately
 7170        //
 7171        // NOTE: using $mdUtil.nextTick() causes delays/issues
 7172        if (noTransitionFound(opts.cachedTransitionStyles)) {
 7173          TIMEOUT = 0;
 7174        }
 7175
 7176        var timer = $timeout(finished, opts.timeout || TIMEOUT);
 7177        element.on($mdConstant.CSS.TRANSITIONEND, finished);
 7178
 7179        /**
 7180         * Upon timeout or transitionEnd, reject or resolve (respectively) this promise.
 7181         * NOTE: Make sure this transitionEnd didn't bubble up from a child
 7182         */
 7183        function finished(ev) {
 7184          if ( ev && ev.target !== element[0]) return;
 7185
 7186          if ( ev  ) $timeout.cancel(timer);
 7187          element.off($mdConstant.CSS.TRANSITIONEND, finished);
 7188
 7189          // Never reject since ngAnimate may cause timeouts due missed transitionEnd events
 7190          resolve();
 7191
 7192        }
 7193
 7194        /**
 7195         * Checks whether or not there is a transition.
 7196         *
 7197         * @param styles The cached styles to use for the calculation. If null, getComputedStyle()
 7198         * will be used.
 7199         *
 7200         * @returns {boolean} True if there is no transition/duration; false otherwise.
 7201         */
 7202        function noTransitionFound(styles) {
 7203          styles = styles || window.getComputedStyle(element[0]);
 7204
 7205          return styles.transitionDuration == '0s' || (!styles.transition && !styles.transitionProperty);
 7206        }
 7207
 7208      });
 7209    },
 7210
 7211    calculateTransformValues: function (element, originator) {
 7212      var origin = originator.element;
 7213      var bounds = originator.bounds;
 7214
 7215      if (origin || bounds) {
 7216        var originBnds = origin ? self.clientRect(origin) || currentBounds() : self.copyRect(bounds);
 7217        var dialogRect = self.copyRect(element[0].getBoundingClientRect());
 7218        var dialogCenterPt = self.centerPointFor(dialogRect);
 7219        var originCenterPt = self.centerPointFor(originBnds);
 7220
 7221        return {
 7222          centerX: originCenterPt.x - dialogCenterPt.x,
 7223          centerY: originCenterPt.y - dialogCenterPt.y,
 7224          scaleX: Math.round(100 * Math.min(0.5, originBnds.width / dialogRect.width)) / 100,
 7225          scaleY: Math.round(100 * Math.min(0.5, originBnds.height / dialogRect.height)) / 100
 7226        };
 7227      }
 7228      return {centerX: 0, centerY: 0, scaleX: 0.5, scaleY: 0.5};
 7229
 7230      /**
 7231       * This is a fallback if the origin information is no longer valid, then the
 7232       * origin bounds simply becomes the current bounds for the dialogContainer's parent
 7233       */
 7234      function currentBounds() {
 7235        var cntr = element ? element.parent() : null;
 7236        var parent = cntr ? cntr.parent() : null;
 7237
 7238        return parent ? self.clientRect(parent) : null;
 7239      }
 7240    },
 7241
 7242    /**
 7243     * Calculate the zoom transform from dialog to origin.
 7244     *
 7245     * We use this to set the dialog position immediately;
 7246     * then the md-transition-in actually translates back to
 7247     * `translate3d(0,0,0) scale(1.0)`...
 7248     *
 7249     * NOTE: all values are rounded to the nearest integer
 7250     */
 7251    calculateZoomToOrigin: function (element, originator) {
 7252      var zoomTemplate = "translate3d( {centerX}px, {centerY}px, 0 ) scale( {scaleX}, {scaleY} )";
 7253      var buildZoom = angular.bind(null, $mdUtil.supplant, zoomTemplate);
 7254
 7255      return buildZoom(self.calculateTransformValues(element, originator));
 7256    },
 7257
 7258    /**
 7259     * Calculate the slide transform from panel to origin.
 7260     * NOTE: all values are rounded to the nearest integer
 7261     */
 7262    calculateSlideToOrigin: function (element, originator) {
 7263      var slideTemplate = "translate3d( {centerX}px, {centerY}px, 0 )";
 7264      var buildSlide = angular.bind(null, $mdUtil.supplant, slideTemplate);
 7265
 7266      return buildSlide(self.calculateTransformValues(element, originator));
 7267    },
 7268
 7269    /**
 7270     * Enhance raw values to represent valid css stylings...
 7271     */
 7272    toCss : function( raw ) {
 7273      var css = { };
 7274      var lookups = 'left top right bottom width height x y min-width min-height max-width max-height';
 7275
 7276      angular.forEach(raw, function(value,key) {
 7277        if ( angular.isUndefined(value) ) return;
 7278
 7279        if ( lookups.indexOf(key) >= 0 ) {
 7280          css[key] = value + 'px';
 7281        } else {
 7282          switch (key) {
 7283            case 'transition':
 7284              convertToVendor(key, $mdConstant.CSS.TRANSITION, value);
 7285              break;
 7286            case 'transform':
 7287              convertToVendor(key, $mdConstant.CSS.TRANSFORM, value);
 7288              break;
 7289            case 'transformOrigin':
 7290              convertToVendor(key, $mdConstant.CSS.TRANSFORM_ORIGIN, value);
 7291              break;
 7292            case 'font-size':
 7293              css['font-size'] = value; // font sizes aren't always in px
 7294              break;
 7295          }
 7296        }
 7297      });
 7298
 7299      return css;
 7300
 7301      function convertToVendor(key, vendor, value) {
 7302        angular.forEach(vendor.split(' '), function (key) {
 7303          css[key] = value;
 7304        });
 7305      }
 7306    },
 7307
 7308    /**
 7309     * Convert the translate CSS value to key/value pair(s).
 7310     */
 7311    toTransformCss: function (transform, addTransition, transition) {
 7312      var css = {};
 7313      angular.forEach($mdConstant.CSS.TRANSFORM.split(' '), function (key) {
 7314        css[key] = transform;
 7315      });
 7316
 7317      if (addTransition) {
 7318        transition = transition || "all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) !important";
 7319        css.transition = transition;
 7320      }
 7321
 7322      return css;
 7323    },
 7324
 7325    /**
 7326     *  Clone the Rect and calculate the height/width if needed
 7327     */
 7328    copyRect: function (source, destination) {
 7329      if (!source) return null;
 7330
 7331      destination = destination || {};
 7332
 7333      angular.forEach('left top right bottom width height'.split(' '), function (key) {
 7334        destination[key] = Math.round(source[key]);
 7335      });
 7336
 7337      destination.width = destination.width || (destination.right - destination.left);
 7338      destination.height = destination.height || (destination.bottom - destination.top);
 7339
 7340      return destination;
 7341    },
 7342
 7343    /**
 7344     * Calculate ClientRect of element; return null if hidden or zero size
 7345     */
 7346    clientRect: function (element) {
 7347      var bounds = angular.element(element)[0].getBoundingClientRect();
 7348      var isPositiveSizeClientRect = function (rect) {
 7349        return rect && (rect.width > 0) && (rect.height > 0);
 7350      };
 7351
 7352      // If the event origin element has zero size, it has probably been hidden.
 7353      return isPositiveSizeClientRect(bounds) ? self.copyRect(bounds) : null;
 7354    },
 7355
 7356    /**
 7357     *  Calculate 'rounded' center point of Rect
 7358     */
 7359    centerPointFor: function (targetRect) {
 7360      return targetRect ? {
 7361        x: Math.round(targetRect.left + (targetRect.width / 2)),
 7362        y: Math.round(targetRect.top + (targetRect.height / 2))
 7363      } : { x : 0, y : 0 };
 7364    }
 7365
 7366  };
 7367}
 7368
 7369
 7370})();
 7371(function(){
 7372"use strict";
 7373
 7374if (angular.version.minor >= 4) {
 7375  angular.module('material.core.animate', []);
 7376} else {
 7377(function() {
 7378  "use strict";
 7379
 7380  var forEach = angular.forEach;
 7381
 7382  var WEBKIT = angular.isDefined(document.documentElement.style.WebkitAppearance);
 7383  var TRANSITION_PROP = WEBKIT ? 'WebkitTransition' : 'transition';
 7384  var ANIMATION_PROP = WEBKIT ? 'WebkitAnimation' : 'animation';
 7385  var PREFIX = WEBKIT ? '-webkit-' : '';
 7386
 7387  var TRANSITION_EVENTS = (WEBKIT ? 'webkitTransitionEnd ' : '') + 'transitionend';
 7388  var ANIMATION_EVENTS = (WEBKIT ? 'webkitAnimationEnd ' : '') + 'animationend';
 7389
 7390  var $$ForceReflowFactory = ['$document', function($document) {
 7391    return function() {
 7392      return $document[0].body.clientWidth + 1;
 7393    };
 7394  }];
 7395
 7396  var $$rAFMutexFactory = ['$$rAF', function($$rAF) {
 7397    return function() {
 7398      var passed = false;
 7399      $$rAF(function() {
 7400        passed = true;
 7401      });
 7402      return function(fn) {
 7403        passed ? fn() : $$rAF(fn);
 7404      };
 7405    };
 7406  }];
 7407
 7408  var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
 7409    var INITIAL_STATE = 0;
 7410    var DONE_PENDING_STATE = 1;
 7411    var DONE_COMPLETE_STATE = 2;
 7412
 7413    function AnimateRunner(host) {
 7414      this.setHost(host);
 7415
 7416      this._doneCallbacks = [];
 7417      this._runInAnimationFrame = $$rAFMutex();
 7418      this._state = 0;
 7419    }
 7420
 7421    AnimateRunner.prototype = {
 7422      setHost: function(host) {
 7423        this.host = host || {};
 7424      },
 7425
 7426      done: function(fn) {
 7427        if (this._state === DONE_COMPLETE_STATE) {
 7428          fn();
 7429        } else {
 7430          this._doneCallbacks.push(fn);
 7431        }
 7432      },
 7433
 7434      progress: angular.noop,
 7435
 7436      getPromise: function() {
 7437        if (!this.promise) {
 7438          var self = this;
 7439          this.promise = $q(function(resolve, reject) {
 7440            self.done(function(status) {
 7441              status === false ? reject() : resolve();
 7442            });
 7443          });
 7444        }
 7445        return this.promise;
 7446      },
 7447
 7448      then: function(resolveHandler, rejectHandler) {
 7449        return this.getPromise().then(resolveHandler, rejectHandler);
 7450      },
 7451
 7452      'catch': function(handler) {
 7453        return this.getPromise()['catch'](handler);
 7454      },
 7455
 7456      'finally': function(handler) {
 7457        return this.getPromise()['finally'](handler);
 7458      },
 7459
 7460      pause: function() {
 7461        if (this.host.pause) {
 7462          this.host.pause();
 7463        }
 7464      },
 7465
 7466      resume: function() {
 7467        if (this.host.resume) {
 7468          this.host.resume();
 7469        }
 7470      },
 7471
 7472      end: function() {
 7473        if (this.host.end) {
 7474          this.host.end();
 7475        }
 7476        this._resolve(true);
 7477      },
 7478
 7479      cancel: function() {
 7480        if (this.host.cancel) {
 7481          this.host.cancel();
 7482        }
 7483        this._resolve(false);
 7484      },
 7485
 7486      complete: function(response) {
 7487        var self = this;
 7488        if (self._state === INITIAL_STATE) {
 7489          self._state = DONE_PENDING_STATE;
 7490          self._runInAnimationFrame(function() {
 7491            self._resolve(response);
 7492          });
 7493        }
 7494      },
 7495
 7496      _resolve: function(response) {
 7497        if (this._state !== DONE_COMPLETE_STATE) {
 7498          forEach(this._doneCallbacks, function(fn) {
 7499            fn(response);
 7500          });
 7501          this._doneCallbacks.length = 0;
 7502          this._state = DONE_COMPLETE_STATE;
 7503        }
 7504      }
 7505    };
 7506
 7507    // Polyfill AnimateRunner.all which is used by input animations
 7508    AnimateRunner.all = function(runners, callback) {
 7509      var count = 0;
 7510      var status = true;
 7511      forEach(runners, function(runner) {
 7512        runner.done(onProgress);
 7513      });
 7514
 7515      function onProgress(response) {
 7516        status = status && response;
 7517        if (++count === runners.length) {
 7518          callback(status);
 7519        }
 7520      }
 7521    };
 7522
 7523    return AnimateRunner;
 7524  }];
 7525
 7526  angular
 7527    .module('material.core.animate', [])
 7528    .factory('$$forceReflow', $$ForceReflowFactory)
 7529    .factory('$$AnimateRunner', $$AnimateRunnerFactory)
 7530    .factory('$$rAFMutex', $$rAFMutexFactory)
 7531    .factory('$animateCss', ['$window', '$$rAF', '$$AnimateRunner', '$$forceReflow', '$$jqLite', '$timeout', '$animate',
 7532                     function($window,   $$rAF,   $$AnimateRunner,   $$forceReflow,   $$jqLite,   $timeout, $animate) {
 7533
 7534      function init(element, options) {
 7535
 7536        var temporaryStyles = [];
 7537        var node = getDomNode(element);
 7538        var areAnimationsAllowed = node && $animate.enabled();
 7539
 7540        var hasCompleteStyles = false;
 7541        var hasCompleteClasses = false;
 7542
 7543        if (areAnimationsAllowed) {
 7544          if (options.transitionStyle) {
 7545            temporaryStyles.push([PREFIX + 'transition', options.transitionStyle]);
 7546          }
 7547
 7548          if (options.keyframeStyle) {
 7549            temporaryStyles.push([PREFIX + 'animation', options.keyframeStyle]);
 7550          }
 7551
 7552          if (options.delay) {
 7553            temporaryStyles.push([PREFIX + 'transition-delay', options.delay + 's']);
 7554          }
 7555
 7556          if (options.duration) {
 7557            temporaryStyles.push([PREFIX + 'transition-duration', options.duration + 's']);
 7558          }
 7559
 7560          hasCompleteStyles = options.keyframeStyle ||
 7561              (options.to && (options.duration > 0 || options.transitionStyle));
 7562          hasCompleteClasses = !!options.addClass || !!options.removeClass;
 7563
 7564          blockTransition(element, true);
 7565        }
 7566
 7567        var hasCompleteAnimation = areAnimationsAllowed && (hasCompleteStyles || hasCompleteClasses);
 7568
 7569        applyAnimationFromStyles(element, options);
 7570
 7571        var animationClosed = false;
 7572        var events, eventFn;
 7573
 7574        return {
 7575          close: $window.close,
 7576          start: function() {
 7577            var runner = new $$AnimateRunner();
 7578            waitUntilQuiet(function() {
 7579              blockTransition(element, false);
 7580              if (!hasCompleteAnimation) {
 7581                return close();
 7582              }
 7583
 7584              forEach(temporaryStyles, function(entry) {
 7585                var key = entry[0];
 7586                var value = entry[1];
 7587                node.style[camelCase(key)] = value;
 7588              });
 7589
 7590              applyClasses(element, options);
 7591
 7592              var timings = computeTimings(element);
 7593              if (timings.duration === 0) {
 7594                return close();
 7595              }
 7596
 7597              var moreStyles = [];
 7598
 7599              if (options.easing) {
 7600                if (timings.transitionDuration) {
 7601                  moreStyles.push([PREFIX + 'transition-timing-function', options.easing]);
 7602                }
 7603                if (timings.animationDuration) {
 7604                  moreStyles.push([PREFIX + 'animation-timing-function', options.easing]);
 7605                }
 7606              }
 7607
 7608              if (options.delay && timings.animationDelay) {
 7609                moreStyles.push([PREFIX + 'animation-delay', options.delay + 's']);
 7610              }
 7611
 7612              if (options.duration && timings.animationDuration) {
 7613                moreStyles.push([PREFIX + 'animation-duration', options.duration + 's']);
 7614              }
 7615
 7616              forEach(moreStyles, function(entry) {
 7617                var key = entry[0];
 7618                var value = entry[1];
 7619                node.style[camelCase(key)] = value;
 7620                temporaryStyles.push(entry);
 7621              });
 7622
 7623              var maxDelay = timings.delay;
 7624              var maxDelayTime = maxDelay * 1000;
 7625              var maxDuration = timings.duration;
 7626              var maxDurationTime = maxDuration * 1000;
 7627              var startTime = Date.now();
 7628
 7629              events = [];
 7630              if (timings.transitionDuration) {
 7631                events.push(TRANSITION_EVENTS);
 7632              }
 7633              if (timings.animationDuration) {
 7634                events.push(ANIMATION_EVENTS);
 7635              }
 7636              events = events.join(' ');
 7637              eventFn = function(event) {
 7638                event.stopPropagation();
 7639                var ev = event.originalEvent || event;
 7640                var timeStamp = ev.timeStamp || Date.now();
 7641                var elapsedTime = parseFloat(ev.elapsedTime.toFixed(3));
 7642                if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
 7643                  close();
 7644                }
 7645              };
 7646              element.on(events, eventFn);
 7647
 7648              applyAnimationToStyles(element, options);
 7649
 7650              $timeout(close, maxDelayTime + maxDurationTime * 1.5, false);
 7651            });
 7652
 7653            return runner;
 7654
 7655            function close() {
 7656              if (animationClosed) return;
 7657              animationClosed = true;
 7658
 7659              if (events && eventFn) {
 7660                element.off(events, eventFn);
 7661              }
 7662              applyClasses(element, options);
 7663              applyAnimationStyles(element, options);
 7664              forEach(temporaryStyles, function(entry) {
 7665                node.style[camelCase(entry[0])] = '';
 7666              });
 7667              runner.complete(true);
 7668              return runner;
 7669            }
 7670          }
 7671        };
 7672      }
 7673
 7674      function applyClasses(element, options) {
 7675        if (options.addClass) {
 7676          $$jqLite.addClass(element, options.addClass);
 7677          options.addClass = null;
 7678        }
 7679        if (options.removeClass) {
 7680          $$jqLite.removeClass(element, options.removeClass);
 7681          options.removeClass = null;
 7682        }
 7683      }
 7684
 7685      function computeTimings(element) {
 7686        var node = getDomNode(element);
 7687        var cs = $window.getComputedStyle(node);
 7688        var tdr = parseMaxTime(cs[prop('transitionDuration')]);
 7689        var adr = parseMaxTime(cs[prop('animationDuration')]);
 7690        var tdy = parseMaxTime(cs[prop('transitionDelay')]);
 7691        var ady = parseMaxTime(cs[prop('animationDelay')]);
 7692
 7693        adr *= (parseInt(cs[prop('animationIterationCount')], 10) || 1);
 7694        var duration = Math.max(adr, tdr);
 7695        var delay = Math.max(ady, tdy);
 7696
 7697        return {
 7698          duration: duration,
 7699          delay: delay,
 7700          animationDuration: adr,
 7701          transitionDuration: tdr,
 7702          animationDelay: ady,
 7703          transitionDelay: tdy
 7704        };
 7705
 7706        function prop(key) {
 7707          return WEBKIT ? 'Webkit' + key.charAt(0).toUpperCase() + key.substr(1)
 7708                        : key;
 7709        }
 7710      }
 7711
 7712      function parseMaxTime(str) {
 7713        var maxValue = 0;
 7714        var values = (str || "").split(/\s*,\s*/);
 7715        forEach(values, function(value) {
 7716          // it's always safe to consider only second values and omit `ms` values since
 7717          // getComputedStyle will always handle the conversion for us
 7718          if (value.charAt(value.length - 1) == 's') {
 7719            value = value.substring(0, value.length - 1);
 7720          }
 7721          value = parseFloat(value) || 0;
 7722          maxValue = maxValue ? Math.max(value, maxValue) : value;
 7723        });
 7724        return maxValue;
 7725      }
 7726
 7727      var cancelLastRAFRequest;
 7728      var rafWaitQueue = [];
 7729      function waitUntilQuiet(callback) {
 7730        if (cancelLastRAFRequest) {
 7731          cancelLastRAFRequest(); //cancels the request
 7732        }
 7733        rafWaitQueue.push(callback);
 7734        cancelLastRAFRequest = $$rAF(function() {
 7735          cancelLastRAFRequest = null;
 7736
 7737          // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
 7738          // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
 7739          var pageWidth = $$forceReflow();
 7740
 7741          // we use a for loop to ensure that if the queue is changed
 7742          // during this looping then it will consider new requests
 7743          for (var i = 0; i < rafWaitQueue.length; i++) {
 7744            rafWaitQueue[i](pageWidth);
 7745          }
 7746          rafWaitQueue.length = 0;
 7747        });
 7748      }
 7749
 7750      function applyAnimationStyles(element, options) {
 7751        applyAnimationFromStyles(element, options);
 7752        applyAnimationToStyles(element, options);
 7753      }
 7754
 7755      function applyAnimationFromStyles(element, options) {
 7756        if (options.from) {
 7757          element.css(options.from);
 7758          options.from = null;
 7759        }
 7760      }
 7761
 7762      function applyAnimationToStyles(element, options) {
 7763        if (options.to) {
 7764          element.css(options.to);
 7765          options.to = null;
 7766        }
 7767      }
 7768
 7769      function getDomNode(element) {
 7770        for (var i = 0; i < element.length; i++) {
 7771          if (element[i].nodeType === 1) return element[i];
 7772        }
 7773      }
 7774
 7775      function blockTransition(element, bool) {
 7776        var node = getDomNode(element);
 7777        var key = camelCase(PREFIX + 'transition-delay');
 7778        node.style[key] = bool ? '-9999s' : '';
 7779      }
 7780
 7781      return init;
 7782    }]);
 7783
 7784  /**
 7785   * Older browsers [FF31] expect camelCase
 7786   * property keys.
 7787   * e.g.
 7788   *  animation-duration --> animationDuration
 7789   */
 7790  function camelCase(str) {
 7791    return str.replace(/-[a-z]/g, function(str) {
 7792      return str.charAt(1).toUpperCase();
 7793    });
 7794  }
 7795
 7796})();
 7797
 7798}
 7799
 7800})();
 7801(function(){
 7802"use strict";
 7803
 7804/**
 7805 * @ngdoc module
 7806 * @name material.components.autocomplete
 7807 */
 7808/*
 7809 * @see js folder for autocomplete implementation
 7810 */
 7811angular.module('material.components.autocomplete', [
 7812  'material.core',
 7813  'material.components.icon',
 7814  'material.components.virtualRepeat'
 7815]);
 7816
 7817})();
 7818(function(){
 7819"use strict";
 7820
 7821/*
 7822 * @ngdoc module
 7823 * @name material.components.backdrop
 7824 * @description Backdrop
 7825 */
 7826
 7827/**
 7828 * @ngdoc directive
 7829 * @name mdBackdrop
 7830 * @module material.components.backdrop
 7831 *
 7832 * @restrict E
 7833 *
 7834 * @description
 7835 * `<md-backdrop>` is a backdrop element used by other components, such as dialog and bottom sheet.
 7836 * Apply class `opaque` to make the backdrop use the theme backdrop color.
 7837 *
 7838 */
 7839
 7840angular
 7841  .module('material.components.backdrop', ['material.core'])
 7842  .directive('mdBackdrop', ["$mdTheming", "$mdUtil", "$animate", "$rootElement", "$window", "$log", "$$rAF", "$document", function BackdropDirective($mdTheming, $mdUtil, $animate, $rootElement, $window, $log, $$rAF, $document) {
 7843    var ERROR_CSS_POSITION = '<md-backdrop> may not work properly in a scrolled, static-positioned parent container.';
 7844
 7845    return {
 7846      restrict: 'E',
 7847      link: postLink
 7848    };
 7849
 7850    function postLink(scope, element, attrs) {
 7851      // backdrop may be outside the $rootElement, tell ngAnimate to animate regardless
 7852      if ($animate.pin) $animate.pin(element, $rootElement);
 7853
 7854      var bodyStyles;
 7855
 7856      $$rAF(function() {
 7857        // If body scrolling has been disabled using mdUtil.disableBodyScroll(),
 7858        // adjust the 'backdrop' height to account for the fixed 'body' top offset.
 7859        // Note that this can be pretty expensive and is better done inside the $$rAF.
 7860        bodyStyles = $window.getComputedStyle($document[0].body);
 7861
 7862        if (bodyStyles.position === 'fixed') {
 7863          var resizeHandler = $mdUtil.debounce(function(){
 7864            bodyStyles = $window.getComputedStyle($document[0].body);
 7865            resize();
 7866          }, 60, null, false);
 7867
 7868          resize();
 7869          angular.element($window).on('resize', resizeHandler);
 7870
 7871          scope.$on('$destroy', function() {
 7872            angular.element($window).off('resize', resizeHandler);
 7873          });
 7874        }
 7875
 7876        // Often $animate.enter() is used to append the backDrop element
 7877        // so let's wait until $animate is done...
 7878        var parent = element.parent();
 7879
 7880        if (parent.length) {
 7881          if (parent[0].nodeName === 'BODY') {
 7882            element.css('position', 'fixed');
 7883          }
 7884
 7885          var styles = $window.getComputedStyle(parent[0]);
 7886
 7887          if (styles.position === 'static') {
 7888            // backdrop uses position:absolute and will not work properly with parent position:static (default)
 7889            $log.warn(ERROR_CSS_POSITION);
 7890          }
 7891
 7892          // Only inherit the parent if the backdrop has a parent.
 7893          $mdTheming.inherit(element, parent);
 7894        }
 7895      });
 7896
 7897      function resize() {
 7898        var viewportHeight = parseInt(bodyStyles.height, 10) + Math.abs(parseInt(bodyStyles.top, 10));
 7899        element.css('height', viewportHeight + 'px');
 7900      }
 7901    }
 7902
 7903  }]);
 7904
 7905})();
 7906(function(){
 7907"use strict";
 7908
 7909/**
 7910 * @ngdoc module
 7911 * @name material.components.bottomSheet
 7912 * @description
 7913 * BottomSheet
 7914 */
 7915MdBottomSheetDirective.$inject = ["$mdBottomSheet"];
 7916MdBottomSheetProvider.$inject = ["$$interimElementProvider"];
 7917angular
 7918  .module('material.components.bottomSheet', [
 7919    'material.core',
 7920    'material.components.backdrop'
 7921  ])
 7922  .directive('mdBottomSheet', MdBottomSheetDirective)
 7923  .provider('$mdBottomSheet', MdBottomSheetProvider);
 7924
 7925/* @ngInject */
 7926function MdBottomSheetDirective($mdBottomSheet) {
 7927  return {
 7928    restrict: 'E',
 7929    link : function postLink(scope, element) {
 7930      element.addClass('_md');     // private md component indicator for styling
 7931
 7932      // When navigation force destroys an interimElement, then
 7933      // listen and $destroy() that interim instance...
 7934      scope.$on('$destroy', function() {
 7935        $mdBottomSheet.destroy();
 7936      });
 7937    }
 7938  };
 7939}
 7940
 7941
 7942/**
 7943 * @ngdoc service
 7944 * @name $mdBottomSheet
 7945 * @module material.components.bottomSheet
 7946 *
 7947 * @description
 7948 * `$mdBottomSheet` opens a bottom sheet over the app and provides a simple promise API.
 7949 *
 7950 * ## Restrictions
 7951 *
 7952 * - The bottom sheet's template must have an outer `<md-bottom-sheet>` element.
 7953 * - Add the `md-grid` class to the bottom sheet for a grid layout.
 7954 * - Add the `md-list` class to the bottom sheet for a list layout.
 7955 *
 7956 * @usage
 7957 * <hljs lang="html">
 7958 * <div ng-controller="MyController">
 7959 *   <md-button ng-click="openBottomSheet()">
 7960 *     Open a Bottom Sheet!
 7961 *   </md-button>
 7962 * </div>
 7963 * </hljs>
 7964 * <hljs lang="js">
 7965 * var app = angular.module('app', ['ngMaterial']);
 7966 * app.controller('MyController', function($scope, $mdBottomSheet) {
 7967 *   $scope.openBottomSheet = function() {
 7968 *     $mdBottomSheet.show({
 7969 *       template: '<md-bottom-sheet>Hello!</md-bottom-sheet>'
 7970 *     });
 7971 *   };
 7972 * });
 7973 * </hljs>
 7974 */
 7975
 7976 /**
 7977 * @ngdoc method
 7978 * @name $mdBottomSheet#show
 7979 *
 7980 * @description
 7981 * Show a bottom sheet with the specified options.
 7982 *
 7983 * @param {object} options An options object, with the following properties:
 7984 *
 7985 *   - `templateUrl` - `{string=}`: The url of an html template file that will
 7986 *   be used as the content of the bottom sheet. Restrictions: the template must
 7987 *   have an outer `md-bottom-sheet` element.
 7988 *   - `template` - `{string=}`: Same as templateUrl, except this is an actual
 7989 *   template string.
 7990 *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
 7991 *     This scope will be destroyed when the bottom sheet is removed unless `preserveScope` is set to true.
 7992 *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
 7993 *   - `controller` - `{string=}`: The controller to associate with this bottom sheet.
 7994 *   - `locals` - `{string=}`: An object containing key/value pairs. The keys will
 7995 *   be used as names of values to inject into the controller. For example,
 7996 *   `locals: {three: 3}` would inject `three` into the controller with the value
 7997 *   of 3.
 7998 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the bottom sheet to
 7999 *     close it. Default true.
 8000 *   - `bindToController` - `{boolean=}`: When set to true, the locals will be bound to the controller instance.
 8001 *   - `disableBackdrop` - `{boolean=}`: When set to true, the bottomsheet will not show a backdrop.
 8002 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the bottom sheet.
 8003 *     Default true.
 8004 *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
 8005 *   and the bottom sheet will not open until the promises resolve.
 8006 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
 8007 *   - `parent` - `{element=}`: The element to append the bottom sheet to. The `parent` may be a `function`, `string`,
 8008 *   `object`, or null. Defaults to appending to the body of the root element (or the root element) of the application.
 8009 *   e.g. angular.element(document.getElementById('content')) or "#content"
 8010 *   - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the bottom sheet is open.
 8011 *     Default true.
 8012 *
 8013 * @returns {promise} A promise that can be resolved with `$mdBottomSheet.hide()` or
 8014 * rejected with `$mdBottomSheet.cancel()`.
 8015 */
 8016
 8017/**
 8018 * @ngdoc method
 8019 * @name $mdBottomSheet#hide
 8020 *
 8021 * @description
 8022 * Hide the existing bottom sheet and resolve the promise returned from
 8023 * `$mdBottomSheet.show()`. This call will close the most recently opened/current bottomsheet (if any).
 8024 *
 8025 * @param {*=} response An argument for the resolved promise.
 8026 *
 8027 */
 8028
 8029/**
 8030 * @ngdoc method
 8031 * @name $mdBottomSheet#cancel
 8032 *
 8033 * @description
 8034 * Hide the existing bottom sheet and reject the promise returned from
 8035 * `$mdBottomSheet.show()`.
 8036 *
 8037 * @param {*=} response An argument for the rejected promise.
 8038 *
 8039 */
 8040
 8041function MdBottomSheetProvider($$interimElementProvider) {
 8042  // how fast we need to flick down to close the sheet, pixels/ms
 8043  bottomSheetDefaults.$inject = ["$animate", "$mdConstant", "$mdUtil", "$mdTheming", "$mdBottomSheet", "$rootElement", "$mdGesture", "$log"];
 8044  var CLOSING_VELOCITY = 0.5;
 8045  var PADDING = 80; // same as css
 8046
 8047  return $$interimElementProvider('$mdBottomSheet')
 8048    .setDefaults({
 8049      methods: ['disableParentScroll', 'escapeToClose', 'clickOutsideToClose'],
 8050      options: bottomSheetDefaults
 8051    });
 8052
 8053  /* @ngInject */
 8054  function bottomSheetDefaults($animate, $mdConstant, $mdUtil, $mdTheming, $mdBottomSheet, $rootElement,
 8055                               $mdGesture, $log) {
 8056    var backdrop;
 8057
 8058    return {
 8059      themable: true,
 8060      onShow: onShow,
 8061      onRemove: onRemove,
 8062      disableBackdrop: false,
 8063      escapeToClose: true,
 8064      clickOutsideToClose: true,
 8065      disableParentScroll: true
 8066    };
 8067
 8068
 8069    function onShow(scope, element, options, controller) {
 8070
 8071      element = $mdUtil.extractElementByName(element, 'md-bottom-sheet');
 8072
 8073      // prevent tab focus or click focus on the bottom-sheet container
 8074      element.attr('tabindex',"-1");
 8075
 8076      // Once the md-bottom-sheet has `ng-cloak` applied on his template the opening animation will not work properly.
 8077      // This is a very common problem, so we have to notify the developer about this.
 8078      if (element.hasClass('ng-cloak')) {
 8079        var message = '$mdBottomSheet: using `<md-bottom-sheet ng-cloak >` will affect the bottom-sheet opening animations.';
 8080        $log.warn( message, element[0] );
 8081      }
 8082
 8083      if (!options.disableBackdrop) {
 8084        // Add a backdrop that will close on click
 8085        backdrop = $mdUtil.createBackdrop(scope, "md-bottom-sheet-backdrop md-opaque");
 8086
 8087        // Prevent mouse focus on backdrop; ONLY programatic focus allowed.
 8088        // This allows clicks on backdrop to propogate to the $rootElement and
 8089        // ESC key events to be detected properly.
 8090        
 8091        backdrop[0].tabIndex = -1;
 8092
 8093        if (options.clickOutsideToClose) {
 8094          backdrop.on('click', function() {
 8095            $mdUtil.nextTick($mdBottomSheet.cancel,true);
 8096          });
 8097        }
 8098
 8099        $mdTheming.inherit(backdrop, options.parent);
 8100
 8101        $animate.enter(backdrop, options.parent, null);
 8102      }
 8103
 8104      var bottomSheet = new BottomSheet(element, options.parent);
 8105      options.bottomSheet = bottomSheet;
 8106
 8107      $mdTheming.inherit(bottomSheet.element, options.parent);
 8108
 8109      if (options.disableParentScroll) {
 8110        options.restoreScroll = $mdUtil.disableScrollAround(bottomSheet.element, options.parent);
 8111      }
 8112
 8113      return $animate.enter(bottomSheet.element, options.parent, backdrop)
 8114        .then(function() {
 8115          var focusable = $mdUtil.findFocusTarget(element) || angular.element(
 8116            element[0].querySelector('button') ||
 8117            element[0].querySelector('a') ||
 8118            element[0].querySelector($mdUtil.prefixer('ng-click', true))
 8119          ) || backdrop;
 8120
 8121          if (options.escapeToClose) {
 8122            options.rootElementKeyupCallback = function(e) {
 8123              if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
 8124                $mdUtil.nextTick($mdBottomSheet.cancel,true);
 8125              }
 8126            };
 8127
 8128            $rootElement.on('keyup', options.rootElementKeyupCallback);
 8129            focusable && focusable.focus();
 8130          }
 8131        });
 8132
 8133    }
 8134
 8135    function onRemove(scope, element, options) {
 8136
 8137      var bottomSheet = options.bottomSheet;
 8138
 8139      if (!options.disableBackdrop) $animate.leave(backdrop);
 8140      return $animate.leave(bottomSheet.element).then(function() {
 8141        if (options.disableParentScroll) {
 8142          options.restoreScroll();
 8143          delete options.restoreScroll;
 8144        }
 8145
 8146        bottomSheet.cleanup();
 8147      });
 8148    }
 8149
 8150    /**
 8151     * BottomSheet class to apply bottom-sheet behavior to an element
 8152     */
 8153    function BottomSheet(element, parent) {
 8154      var deregister = $mdGesture.register(parent, 'drag', { horizontal: false });
 8155      parent.on('$md.dragstart', onDragStart)
 8156        .on('$md.drag', onDrag)
 8157        .on('$md.dragend', onDragEnd);
 8158
 8159      return {
 8160        element: element,
 8161        cleanup: function cleanup() {
 8162          deregister();
 8163          parent.off('$md.dragstart', onDragStart);
 8164          parent.off('$md.drag', onDrag);
 8165          parent.off('$md.dragend', onDragEnd);
 8166        }
 8167      };
 8168
 8169      function onDragStart(ev) {
 8170        // Disable transitions on transform so that it feels fast
 8171        element.css($mdConstant.CSS.TRANSITION_DURATION, '0ms');
 8172      }
 8173
 8174      function onDrag(ev) {
 8175        var transform = ev.pointer.distanceY;
 8176        if (transform < 5) {
 8177          // Slow down drag when trying to drag up, and stop after PADDING
 8178          transform = Math.max(-PADDING, transform / 2);
 8179        }
 8180        element.css($mdConstant.CSS.TRANSFORM, 'translate3d(0,' + (PADDING + transform) + 'px,0)');
 8181      }
 8182
 8183      function onDragEnd(ev) {
 8184        if (ev.pointer.distanceY > 0 &&
 8185            (ev.pointer.distanceY > 20 || Math.abs(ev.pointer.velocityY) > CLOSING_VELOCITY)) {
 8186          var distanceRemaining = element.prop('offsetHeight') - ev.pointer.distanceY;
 8187          var transitionDuration = Math.min(distanceRemaining / ev.pointer.velocityY * 0.75, 500);
 8188          element.css($mdConstant.CSS.TRANSITION_DURATION, transitionDuration + 'ms');
 8189          $mdUtil.nextTick($mdBottomSheet.cancel,true);
 8190        } else {
 8191          element.css($mdConstant.CSS.TRANSITION_DURATION, '');
 8192          element.css($mdConstant.CSS.TRANSFORM, '');
 8193        }
 8194      }
 8195    }
 8196
 8197  }
 8198
 8199}
 8200
 8201})();
 8202(function(){
 8203"use strict";
 8204
 8205/**
 8206 * @ngdoc module
 8207 * @name material.components.button
 8208 * @description
 8209 *
 8210 * Button
 8211 */
 8212MdButtonDirective.$inject = ["$mdButtonInkRipple", "$mdTheming", "$mdAria", "$mdInteraction"];
 8213MdAnchorDirective.$inject = ["$mdTheming"];
 8214angular
 8215    .module('material.components.button', [ 'material.core' ])
 8216    .directive('mdButton', MdButtonDirective)
 8217    .directive('a', MdAnchorDirective);
 8218
 8219
 8220/**
 8221 * @private
 8222 * @restrict E
 8223 *
 8224 * @description
 8225 * `a` is an anchor directive used to inherit theme colors for md-primary, md-accent, etc.
 8226 *
 8227 * @usage
 8228 *
 8229 * <hljs lang="html">
 8230 *  <md-content md-theme="myTheme">
 8231 *    <a href="#chapter1" class="md-accent"></a>
 8232 *  </md-content>
 8233 * </hljs>
 8234 */
 8235function MdAnchorDirective($mdTheming) {
 8236  return {
 8237    restrict : 'E',
 8238    link : function postLink(scope, element) {
 8239      // Make sure to inherit theme so stand-alone anchors
 8240      // support theme colors for md-primary, md-accent, etc.
 8241      $mdTheming(element);
 8242    }
 8243  };
 8244}
 8245
 8246
 8247/**
 8248 * @ngdoc directive
 8249 * @name mdButton
 8250 * @module material.components.button
 8251 *
 8252 * @restrict E
 8253 *
 8254 * @description
 8255 * `<md-button>` is a button directive with optional ink ripples (default enabled).
 8256 *
 8257 * If you supply a `href` or `ng-href` attribute, it will become an `<a>` element. Otherwise, it
 8258 * will become a `<button>` element. As per the
 8259 * [Material Design specifications](https://material.google.com/style/color.html#color-color-palette)
 8260 * the FAB button background is filled with the accent color [by default]. The primary color palette
 8261 * may be used with the `md-primary` class.
 8262 *
 8263 * Developers can also change the color palette of the button, by using the following classes
 8264 * - `md-primary`
 8265 * - `md-accent`
 8266 * - `md-warn`
 8267 *
 8268 * See for example
 8269 *
 8270 * <hljs lang="html">
 8271 *   <md-button class="md-primary">Primary Button</md-button>
 8272 * </hljs>
 8273 *
 8274 * Button can be also raised, which means that they will use the current color palette to fill the button.
 8275 *
 8276 * <hljs lang="html">
 8277 *   <md-button class="md-accent md-raised">Raised and Accent Button</md-button>
 8278 * </hljs>
 8279 *
 8280 * It is also possible to disable the focus effect on the button, by using the following markup.
 8281 *
 8282 * <hljs lang="html">
 8283 *   <md-button class="md-no-focus">No Focus Style</md-button>
 8284 * </hljs>
 8285 *
 8286 * @param {boolean=} md-no-ink If present, disable ripple ink effects.
 8287 * @param {expression=} ng-disabled En/Disable based on the expression
 8288 * @param {string=} md-ripple-size Overrides the default ripple size logic. Options: `full`, `partial`, `auto`
 8289 * @param {string=} aria-label Adds alternative text to button for accessibility, useful for icon buttons.
 8290 * If no default text is found, a warning will be logged.
 8291 *
 8292 * @usage
 8293 *
 8294 * Regular buttons:
 8295 *
 8296 * <hljs lang="html">
 8297 *  <md-button> Flat Button </md-button>
 8298 *  <md-button href="http://google.com"> Flat link </md-button>
 8299 *  <md-button class="md-raised"> Raised Button </md-button>
 8300 *  <md-button ng-disabled="true"> Disabled Button </md-button>
 8301 *  <md-button>
 8302 *    <md-icon md-svg-src="your/icon.svg"></md-icon>
 8303 *    Register Now
 8304 *  </md-button>
 8305 * </hljs>
 8306 *
 8307 * FAB buttons:
 8308 *
 8309 * <hljs lang="html">
 8310 *  <md-button class="md-fab" aria-label="FAB">
 8311 *    <md-icon md-svg-src="your/icon.svg"></md-icon>
 8312 *  </md-button>
 8313 *  <!-- mini-FAB -->
 8314 *  <md-button class="md-fab md-mini" aria-label="Mini FAB">
 8315 *    <md-icon md-svg-src="your/icon.svg"></md-icon>
 8316 *  </md-button>
 8317 *  <!-- Button with SVG Icon -->
 8318 *  <md-button class="md-icon-button" aria-label="Custom Icon Button">
 8319 *    <md-icon md-svg-icon="path/to/your.svg"></md-icon>
 8320 *  </md-button>
 8321 * </hljs>
 8322 */
 8323function MdButtonDirective($mdButtonInkRipple, $mdTheming, $mdAria, $mdInteraction) {
 8324
 8325  return {
 8326    restrict: 'EA',
 8327    replace: true,
 8328    transclude: true,
 8329    template: getTemplate,
 8330    link: postLink
 8331  };
 8332
 8333  function isAnchor(attr) {
 8334    return angular.isDefined(attr.href) || angular.isDefined(attr.ngHref) || angular.isDefined(attr.ngLink) || angular.isDefined(attr.uiSref);
 8335  }
 8336
 8337  function getTemplate(element, attr) {
 8338    if (isAnchor(attr)) {
 8339      return '<a class="md-button" ng-transclude></a>';
 8340    } else {
 8341      //If buttons don't have type="button", they will submit forms automatically.
 8342      var btnType = (typeof attr.type === 'undefined') ? 'button' : attr.type;
 8343      return '<button class="md-button" type="' + btnType + '" ng-transclude></button>';
 8344    }
 8345  }
 8346
 8347  function postLink(scope, element, attr) {
 8348    $mdTheming(element);
 8349    $mdButtonInkRipple.attach(scope, element);
 8350
 8351    // Use async expect to support possible bindings in the button label
 8352    $mdAria.expectWithoutText(element, 'aria-label');
 8353
 8354    // For anchor elements, we have to set tabindex manually when the
 8355    // element is disabled
 8356    if (isAnchor(attr) && angular.isDefined(attr.ngDisabled) ) {
 8357      scope.$watch(attr.ngDisabled, function(isDisabled) {
 8358        element.attr('tabindex', isDisabled ? -1 : 0);
 8359      });
 8360    }
 8361
 8362    // disabling click event when disabled is true
 8363    element.on('click', function(e){
 8364      if (attr.disabled === true) {
 8365        e.preventDefault();
 8366        e.stopImmediatePropagation();
 8367      }
 8368    });
 8369
 8370    if (!element.hasClass('md-no-focus')) {
 8371
 8372      element.on('focus', function() {
 8373
 8374        // Only show the focus effect when being focused through keyboard interaction or programmatically
 8375        if (!$mdInteraction.isUserInvoked() || $mdInteraction.getLastInteractionType() === 'keyboard') {
 8376          element.addClass('md-focused');
 8377        }
 8378
 8379      });
 8380
 8381      element.on('blur', function() {
 8382        element.removeClass('md-focused');
 8383      });
 8384    }
 8385
 8386  }
 8387
 8388}
 8389
 8390})();
 8391(function(){
 8392"use strict";
 8393
 8394/**
 8395 * @ngdoc module
 8396 * @name material.components.card
 8397 *
 8398 * @description
 8399 * Card components.
 8400 */
 8401mdCardDirective.$inject = ["$mdTheming"];
 8402angular.module('material.components.card', [
 8403    'material.core'
 8404  ])
 8405  .directive('mdCard', mdCardDirective);
 8406
 8407
 8408/**
 8409 * @ngdoc directive
 8410 * @name mdCard
 8411 * @module material.components.card
 8412 *
 8413 * @restrict E
 8414 *
 8415 * @description
 8416 * The `<md-card>` directive is a container element used within `<md-content>` containers.
 8417 *
 8418 * An image included as a direct descendant will fill the card's width. If you want to avoid this,
 8419 * you can add the `md-image-no-fill` class to the parent element. The `<md-card-content>`
 8420 * container will wrap text content and provide padding. An `<md-card-footer>` element can be
 8421 * optionally included to put content flush against the bottom edge of the card.
 8422 *
 8423 * Action buttons can be included in an `<md-card-actions>` element, similar to `<md-dialog-actions>`.
 8424 * You can then position buttons using layout attributes.
 8425 *
 8426 * Card is built with:
 8427 * * `<md-card-header>` - Header for the card, holds avatar, text and squared image
 8428 *  - `<md-card-avatar>` - Card avatar
 8429 *    - `md-user-avatar` - Class for user image
 8430 *    - `<md-icon>`
 8431 *  - `<md-card-header-text>` - Contains elements for the card description
 8432 *    - `md-title` - Class for the card title
 8433 *    - `md-subhead` - Class for the card sub header
 8434 * * `<img>` - Image for the card
 8435 * * `<md-card-title>` - Card content title
 8436 *  - `<md-card-title-text>`
 8437 *    - `md-headline` - Class for the card content title
 8438 *    - `md-subhead` - Class for the card content sub header
 8439 *  - `<md-card-title-media>` - Squared image within the title
 8440 *    - `md-media-sm` - Class for small image
 8441 *    - `md-media-md` - Class for medium image
 8442 *    - `md-media-lg` - Class for large image
 8443 *    - `md-media-xl` - Class for extra large image
 8444 * * `<md-card-content>` - Card content
 8445 * * `<md-card-actions>` - Card actions
 8446 *  - `<md-card-icon-actions>` - Icon actions
 8447 *
 8448 * Cards have constant width and variable heights; where the maximum height is limited to what can
 8449 * fit within a single view on a platform, but it can temporarily expand as needed.
 8450 *
 8451 * @usage
 8452 * ### Card with optional footer
 8453 * <hljs lang="html">
 8454 * <md-card>
 8455 *  <img src="card-image.png" class="md-card-image" alt="image caption">
 8456 *  <md-card-content>
 8457 *    <h2>Card headline</h2>
 8458 *    <p>Card content</p>
 8459 *  </md-card-content>
 8460 *  <md-card-footer>
 8461 *    Card footer
 8462 *  </md-card-footer>
 8463 * </md-card>
 8464 * </hljs>
 8465 *
 8466 * ### Card with actions
 8467 * <hljs lang="html">
 8468 * <md-card>
 8469 *  <img src="card-image.png" class="md-card-image" alt="image caption">
 8470 *  <md-card-content>
 8471 *    <h2>Card headline</h2>
 8472 *    <p>Card content</p>
 8473 *  </md-card-content>
 8474 *  <md-card-actions layout="row" layout-align="end center">
 8475 *    <md-button>Action 1</md-button>
 8476 *    <md-button>Action 2</md-button>
 8477 *  </md-card-actions>
 8478 * </md-card>
 8479 * </hljs>
 8480 *
 8481 * ### Card with header, image, title actions and content
 8482 * <hljs lang="html">
 8483 * <md-card>
 8484 *   <md-card-header>
 8485 *     <md-card-avatar>
 8486 *       <img class="md-user-avatar" src="avatar.png"/>
 8487 *     </md-card-avatar>
 8488 *     <md-card-header-text>
 8489 *       <span class="md-title">Title</span>
 8490 *       <span class="md-subhead">Sub header</span>
 8491 *     </md-card-header-text>
 8492 *   </md-card-header>
 8493 *   <img ng-src="card-image.png" class="md-card-image" alt="image caption">
 8494 *   <md-card-title>
 8495 *     <md-card-title-text>
 8496 *       <span class="md-headline">Card headline</span>
 8497 *       <span class="md-subhead">Card subheader</span>
 8498 *     </md-card-title-text>
 8499 *   </md-card-title>
 8500 *   <md-card-actions layout="row" layout-align="start center">
 8501 *     <md-button>Action 1</md-button>
 8502 *     <md-button>Action 2</md-button>
 8503 *     <md-card-icon-actions>
 8504 *       <md-button class="md-icon-button" aria-label="icon">
 8505 *         <md-icon md-svg-icon="icon"></md-icon>
 8506 *       </md-button>
 8507 *     </md-card-icon-actions>
 8508 *   </md-card-actions>
 8509 *   <md-card-content>
 8510 *     <p>
 8511 *      Card content
 8512 *     </p>
 8513 *   </md-card-content>
 8514 * </md-card>
 8515 * </hljs>
 8516 */
 8517function mdCardDirective($mdTheming) {
 8518  return {
 8519    restrict: 'E',
 8520    link: function ($scope, $element, attr) {
 8521      $element.addClass('_md');     // private md component indicator for styling
 8522      $mdTheming($element);
 8523    }
 8524  };
 8525}
 8526
 8527})();
 8528(function(){
 8529"use strict";
 8530
 8531/**
 8532 * @ngdoc module
 8533 * @name material.components.checkbox
 8534 * @description Checkbox module!
 8535 */
 8536MdCheckboxDirective.$inject = ["inputDirective", "$mdAria", "$mdConstant", "$mdTheming", "$mdUtil", "$mdInteraction"];
 8537angular
 8538  .module('material.components.checkbox', ['material.core'])
 8539  .directive('mdCheckbox', MdCheckboxDirective);
 8540
 8541/**
 8542 * @ngdoc directive
 8543 * @name mdCheckbox
 8544 * @module material.components.checkbox
 8545 * @restrict E
 8546 *
 8547 * @description
 8548 * The checkbox directive is used like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
 8549 *
 8550 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-color-schemes)
 8551 * the checkbox is in the accent color by default. The primary color palette may be used with
 8552 * the `md-primary` class.
 8553 *
 8554 * @param {string} ng-model Assignable angular expression to data-bind to.
 8555 * @param {string=} name Property name of the form under which the control is published.
 8556 * @param {expression=} ng-true-value The value to which the expression should be set when selected.
 8557 * @param {expression=} ng-false-value The value to which the expression should be set when not selected.
 8558 * @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element.
 8559 * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects
 8560 * @param {string=} aria-label Adds label to checkbox for accessibility.
 8561 *     Defaults to checkbox's text. If no default text is found, a warning will be logged.
 8562 * @param {expression=} md-indeterminate This determines when the checkbox should be rendered as 'indeterminate'.
 8563 *     If a truthy expression or no value is passed in the checkbox renders in the md-indeterminate state.
 8564 *     If falsy expression is passed in it just looks like a normal unchecked checkbox.
 8565 *     The indeterminate, checked, and unchecked states are mutually exclusive. A box cannot be in any two states at the same time.
 8566 *     Adding the 'md-indeterminate' attribute overrides any checked/unchecked rendering logic.
 8567 *     When using the 'md-indeterminate' attribute use 'ng-checked' to define rendering logic instead of using 'ng-model'.
 8568 * @param {expression=} ng-checked If this expression evaluates as truthy, the 'md-checked' css class is added to the checkbox and it
 8569 *     will appear checked.
 8570 *
 8571 * @usage
 8572 * <hljs lang="html">
 8573 * <md-checkbox ng-model="isChecked" aria-label="Finished?">
 8574 *   Finished ?
 8575 * </md-checkbox>
 8576 *
 8577 * <md-checkbox md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
 8578 *   No Ink Effects
 8579 * </md-checkbox>
 8580 *
 8581 * <md-checkbox ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
 8582 *   Disabled
 8583 * </md-checkbox>
 8584 *
 8585 * </hljs>
 8586 *
 8587 */
 8588function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $mdUtil, $mdInteraction) {
 8589  inputDirective = inputDirective[0];
 8590
 8591  return {
 8592    restrict: 'E',
 8593    transclude: true,
 8594    require: ['^?mdInputContainer', '?ngModel', '?^form'],
 8595    priority: $mdConstant.BEFORE_NG_ARIA,
 8596    template:
 8597      '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
 8598        '<div class="md-icon"></div>' +
 8599      '</div>' +
 8600      '<div ng-transclude class="md-label"></div>',
 8601    compile: compile
 8602  };
 8603
 8604  // **********************************************************
 8605  // Private Methods
 8606  // **********************************************************
 8607
 8608  function compile (tElement, tAttrs) {
 8609    tAttrs.$set('tabindex', tAttrs.tabindex || '0');
 8610    tAttrs.$set('type', 'checkbox');
 8611    tAttrs.$set('role', tAttrs.type);
 8612
 8613    return  {
 8614      pre: function(scope, element) {
 8615        // Attach a click handler during preLink, in order to immediately stop propagation
 8616        // (especially for ng-click) when the checkbox is disabled.
 8617        element.on('click', function(e) {
 8618          if (this.hasAttribute('disabled')) {
 8619            e.stopImmediatePropagation();
 8620          }
 8621        });
 8622      },
 8623      post: postLink
 8624    };
 8625
 8626    function postLink(scope, element, attr, ctrls) {
 8627      var isIndeterminate;
 8628      var containerCtrl = ctrls[0];
 8629      var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
 8630      var formCtrl = ctrls[2];
 8631
 8632      if (containerCtrl) {
 8633        var isErrorGetter = containerCtrl.isErrorGetter || function() {
 8634          return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (formCtrl && formCtrl.$submitted));
 8635        };
 8636
 8637        containerCtrl.input = element;
 8638
 8639        scope.$watch(isErrorGetter, containerCtrl.setInvalid);
 8640      }
 8641
 8642      $mdTheming(element);
 8643
 8644      // Redirect focus events to the root element, because IE11 is always focusing the container element instead
 8645      // of the md-checkbox element. This causes issues when using ngModelOptions: `updateOnBlur`
 8646      element.children().on('focus', function() {
 8647        element.focus();
 8648      });
 8649
 8650      if ($mdUtil.parseAttributeBoolean(attr.mdIndeterminate)) {
 8651        setIndeterminateState();
 8652        scope.$watch(attr.mdIndeterminate, setIndeterminateState);
 8653      }
 8654
 8655      if (attr.ngChecked) {
 8656        scope.$watch(scope.$eval.bind(scope, attr.ngChecked), function(value) {
 8657          ngModelCtrl.$setViewValue(value);
 8658          ngModelCtrl.$render();
 8659        });
 8660      }
 8661
 8662      $$watchExpr('ngDisabled', 'tabindex', {
 8663        true: '-1',
 8664        false: attr.tabindex
 8665      });
 8666
 8667      $mdAria.expectWithText(element, 'aria-label');
 8668
 8669      // Reuse the original input[type=checkbox] directive from Angular core.
 8670      // This is a bit hacky as we need our own event listener and own render
 8671      // function.
 8672      inputDirective.link.pre(scope, {
 8673        on: angular.noop,
 8674        0: {}
 8675      }, attr, [ngModelCtrl]);
 8676
 8677      element.on('click', listener)
 8678        .on('keypress', keypressHandler)
 8679        .on('focus', function() {
 8680          if ($mdInteraction.getLastInteractionType() === 'keyboard') {
 8681            element.addClass('md-focused');
 8682          }
 8683        })
 8684        .on('blur', function() {
 8685          element.removeClass('md-focused');
 8686        });
 8687
 8688      ngModelCtrl.$render = render;
 8689
 8690      function $$watchExpr(expr, htmlAttr, valueOpts) {
 8691        if (attr[expr]) {
 8692          scope.$watch(attr[expr], function(val) {
 8693            if (valueOpts[val]) {
 8694              element.attr(htmlAttr, valueOpts[val]);
 8695            }
 8696          });
 8697        }
 8698      }
 8699
 8700      function keypressHandler(ev) {
 8701        var keyCode = ev.which || ev.keyCode;
 8702        if (keyCode === $mdConstant.KEY_CODE.SPACE || keyCode === $mdConstant.KEY_CODE.ENTER) {
 8703          ev.preventDefault();
 8704          element.addClass('md-focused');
 8705          listener(ev);
 8706        }
 8707      }
 8708
 8709      function listener(ev) {
 8710        // skipToggle boolean is used by the switch directive to prevent the click event
 8711        // when releasing the drag. There will be always a click if releasing the drag over the checkbox
 8712        if (element[0].hasAttribute('disabled') || scope.skipToggle) {
 8713          return;
 8714        }
 8715
 8716        scope.$apply(function() {
 8717          // Toggle the checkbox value...
 8718          var viewValue = attr.ngChecked ? attr.checked : !ngModelCtrl.$viewValue;
 8719
 8720          ngModelCtrl.$setViewValue(viewValue, ev && ev.type);
 8721          ngModelCtrl.$render();
 8722        });
 8723      }
 8724
 8725      function render() {
 8726        // Cast the $viewValue to a boolean since it could be undefined
 8727        element.toggleClass('md-checked', !!ngModelCtrl.$viewValue && !isIndeterminate);
 8728      }
 8729
 8730      function setIndeterminateState(newValue) {
 8731        isIndeterminate = newValue !== false;
 8732        if (isIndeterminate) {
 8733          element.attr('aria-checked', 'mixed');
 8734        }
 8735        element.toggleClass('md-indeterminate', isIndeterminate);
 8736      }
 8737    }
 8738  }
 8739}
 8740
 8741})();
 8742(function(){
 8743"use strict";
 8744
 8745/**
 8746 * @ngdoc module
 8747 * @name material.components.chips
 8748 */
 8749/*
 8750 * @see js folder for chips implementation
 8751 */
 8752angular.module('material.components.chips', [
 8753  'material.core',
 8754  'material.components.autocomplete'
 8755]);
 8756
 8757})();
 8758(function(){
 8759"use strict";
 8760
 8761(function () {
 8762  "use strict";
 8763
 8764  /**
 8765   *  Use a RegExp to check if the `md-colors="<expression>"` is static string
 8766   *  or one that should be observed and dynamically interpolated.
 8767   */
 8768  MdColorsDirective.$inject = ["$mdColors", "$mdUtil", "$log", "$parse"];
 8769  MdColorsService.$inject = ["$mdTheming", "$mdUtil", "$log"];
 8770  var STATIC_COLOR_EXPRESSION = /^{((\s|,)*?["'a-zA-Z-]+?\s*?:\s*?('|")[a-zA-Z0-9-.]*('|"))+\s*}$/;
 8771  var colorPalettes = null;
 8772
 8773  /**
 8774   * @ngdoc module
 8775   * @name material.components.colors
 8776   *
 8777   * @description
 8778   * Define $mdColors service and a `md-colors=""` attribute directive
 8779   */
 8780  angular
 8781    .module('material.components.colors', ['material.core'])
 8782    .directive('mdColors', MdColorsDirective)
 8783    .service('$mdColors', MdColorsService);
 8784
 8785  /**
 8786   * @ngdoc service
 8787   * @name $mdColors
 8788   * @module material.components.colors
 8789   *
 8790   * @description
 8791   * With only defining themes, one couldn't get non ngMaterial elements colored with Material colors,
 8792   * `$mdColors` service is used by the md-color directive to convert the 1..n color expressions to RGBA values and will apply
 8793   * those values to element as CSS property values.
 8794   *
 8795   *  @usage
 8796   *  <hljs lang="js">
 8797   *    angular.controller('myCtrl', function ($mdColors) {
 8798   *      var color = $mdColors.getThemeColor('myTheme-red-200-0.5');
 8799   *      ...
 8800   *    });
 8801   *  </hljs>
 8802   *
 8803   */
 8804  function MdColorsService($mdTheming, $mdUtil, $log) {
 8805    colorPalettes = colorPalettes || Object.keys($mdTheming.PALETTES);
 8806
 8807    // Publish service instance
 8808    return {
 8809      applyThemeColors: applyThemeColors,
 8810      getThemeColor: getThemeColor,
 8811      hasTheme: hasTheme
 8812    };
 8813
 8814    // ********************************************
 8815    // Internal Methods
 8816    // ********************************************
 8817
 8818    /**
 8819     * @ngdoc method
 8820     * @name $mdColors#applyThemeColors
 8821     *
 8822     * @description
 8823     * Gets a color json object, keys are css properties and values are string of the wanted color
 8824     * Then calculate the rgba() values based on the theme color parts
 8825     *
 8826     * @param {DOMElement} element the element to apply the styles on.
 8827     * @param {object} colorExpression json object, keys are css properties and values are string of the wanted color,
 8828     * for example: `{color: 'red-A200-0.3'}`.
 8829     *
 8830     * @usage
 8831     * <hljs lang="js">
 8832     *   app.directive('myDirective', function($mdColors) {
 8833     *     return {
 8834     *       ...
 8835     *       link: function (scope, elem) {
 8836     *         $mdColors.applyThemeColors(elem, {color: 'red'});
 8837     *       }
 8838     *    }
 8839     *   });
 8840     * </hljs>
 8841     */
 8842    function applyThemeColors(element, colorExpression) {
 8843      try {
 8844        if (colorExpression) {
 8845          // Assign the calculate RGBA color values directly as inline CSS
 8846          element.css(interpolateColors(colorExpression));
 8847        }
 8848      } catch (e) {
 8849        $log.error(e.message);
 8850      }
 8851
 8852    }
 8853
 8854    /**
 8855     * @ngdoc method
 8856     * @name $mdColors#getThemeColor
 8857     *
 8858     * @description
 8859     * Get parsed color from expression
 8860     *
 8861     * @param {string} expression string of a color expression (for instance `'red-700-0.8'`)
 8862     *
 8863     * @returns {string} a css color expression (for instance `rgba(211, 47, 47, 0.8)`)
 8864     *
 8865     * @usage
 8866     *  <hljs lang="js">
 8867     *    angular.controller('myCtrl', function ($mdColors) {
 8868     *      var color = $mdColors.getThemeColor('myTheme-red-200-0.5');
 8869     *      ...
 8870     *    });
 8871     *  </hljs>
 8872     */
 8873    function getThemeColor(expression) {
 8874      var color = extractColorOptions(expression);
 8875
 8876      return parseColor(color);
 8877    }
 8878
 8879    /**
 8880     * Return the parsed color
 8881     * @param color hashmap of color definitions
 8882     * @param contrast whether use contrast color for foreground
 8883     * @returns rgba color string
 8884     */
 8885    function parseColor(color, contrast) {
 8886      contrast = contrast || false;
 8887      var rgbValues = $mdTheming.PALETTES[color.palette][color.hue];
 8888
 8889      rgbValues = contrast ? rgbValues.contrast : rgbValues.value;
 8890
 8891      return $mdUtil.supplant('rgba({0}, {1}, {2}, {3})',
 8892        [rgbValues[0], rgbValues[1], rgbValues[2], rgbValues[3] || color.opacity]
 8893      );
 8894    }
 8895
 8896    /**
 8897     * Convert the color expression into an object with scope-interpolated values
 8898     * Then calculate the rgba() values based on the theme color parts
 8899     *
 8900     * @results Hashmap of CSS properties with associated `rgba( )` string vales
 8901     *
 8902     *
 8903     */
 8904    function interpolateColors(themeColors) {
 8905      var rgbColors = {};
 8906
 8907      var hasColorProperty = themeColors.hasOwnProperty('color');
 8908
 8909      angular.forEach(themeColors, function (value, key) {
 8910        var color = extractColorOptions(value);
 8911        var hasBackground = key.indexOf('background') > -1;
 8912
 8913        rgbColors[key] = parseColor(color);
 8914        if (hasBackground && !hasColorProperty) {
 8915          rgbColors.color = parseColor(color, true);
 8916        }
 8917      });
 8918
 8919      return rgbColors;
 8920    }
 8921
 8922    /**
 8923     * Check if expression has defined theme
 8924     * e.g.
 8925     * 'myTheme-primary' => true
 8926     * 'red-800' => false
 8927     */
 8928    function hasTheme(expression) {
 8929      return angular.isDefined($mdTheming.THEMES[expression.split('-')[0]]);
 8930    }
 8931
 8932    /**
 8933     * For the evaluated expression, extract the color parts into a hash map
 8934     */
 8935    function extractColorOptions(expression) {
 8936      var parts = expression.split('-');
 8937      var hasTheme = angular.isDefined($mdTheming.THEMES[parts[0]]);
 8938      var theme = hasTheme ? parts.splice(0, 1)[0] : $mdTheming.defaultTheme();
 8939
 8940      return {
 8941        theme: theme,
 8942        palette: extractPalette(parts, theme),
 8943        hue: extractHue(parts, theme),
 8944        opacity: parts[2] || 1
 8945      };
 8946    }
 8947
 8948    /**
 8949     * Calculate the theme palette name
 8950     */
 8951    function extractPalette(parts, theme) {
 8952      // If the next section is one of the palettes we assume it's a two word palette
 8953      // Two word palette can be also written in camelCase, forming camelCase to dash-case
 8954
 8955      var isTwoWord = parts.length > 1 && colorPalettes.indexOf(parts[1]) !== -1;
 8956      var palette = parts[0].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
 8957
 8958      if (isTwoWord)  palette = parts[0] + '-' + parts.splice(1, 1);
 8959
 8960      if (colorPalettes.indexOf(palette) === -1) {
 8961        // If the palette is not in the palette list it's one of primary/accent/warn/background
 8962        var scheme = $mdTheming.THEMES[theme].colors[palette];
 8963        if (!scheme) {
 8964          throw new Error($mdUtil.supplant('mdColors: couldn\'t find \'{palette}\' in the palettes.', {palette: palette}));
 8965        }
 8966        palette = scheme.name;
 8967      }
 8968
 8969      return palette;
 8970    }
 8971
 8972    function extractHue(parts, theme) {
 8973      var themeColors = $mdTheming.THEMES[theme].colors;
 8974
 8975      if (parts[1] === 'hue') {
 8976        var hueNumber = parseInt(parts.splice(2, 1)[0], 10);
 8977
 8978        if (hueNumber < 1 || hueNumber > 3) {
 8979          throw new Error($mdUtil.supplant('mdColors: \'hue-{hueNumber}\' is not a valid hue, can be only \'hue-1\', \'hue-2\' and \'hue-3\'', {hueNumber: hueNumber}));
 8980        }
 8981        parts[1] = 'hue-' + hueNumber;
 8982
 8983        if (!(parts[0] in themeColors)) {
 8984          throw new Error($mdUtil.supplant('mdColors: \'hue-x\' can only be used with [{availableThemes}], but was used with \'{usedTheme}\'', {
 8985            availableThemes: Object.keys(themeColors).join(', '),
 8986            usedTheme: parts[0]
 8987          }));
 8988        }
 8989
 8990        return themeColors[parts[0]].hues[parts[1]];
 8991      }
 8992
 8993      return parts[1] || themeColors[parts[0] in themeColors ? parts[0] : 'primary'].hues['default'];
 8994    }
 8995  }
 8996
 8997  /**
 8998   * @ngdoc directive
 8999   * @name mdColors
 9000   * @module material.components.colors
 9001   *
 9002   * @restrict A
 9003   *
 9004   * @description
 9005   * `mdColors` directive will apply the theme-based color expression as RGBA CSS style values.
 9006   *
 9007   *   The format will be similar to our color defining in the scss files:
 9008   *
 9009   *   ## `[?theme]-[palette]-[?hue]-[?opacity]`
 9010   *   - [theme]    - default value is the default theme
 9011   *   - [palette]  - can be either palette name or primary/accent/warn/background
 9012   *   - [hue]      - default is 500 (hue-x can be used with primary/accent/warn/background)
 9013   *   - [opacity]  - default is 1
 9014   *
 9015   *   > `?` indicates optional parameter
 9016   *
 9017   * @usage
 9018   * <hljs lang="html">
 9019   *   <div md-colors="{background: 'myTheme-accent-900-0.43'}">
 9020   *     <div md-colors="{color: 'red-A100', 'border-color': 'primary-600'}">
 9021   *       <span>Color demo</span>
 9022   *     </div>
 9023   *   </div>
 9024   * </hljs>
 9025   *
 9026   * `mdColors` directive will automatically watch for changes in the expression if it recognizes an interpolation
 9027   * expression or a function. For performance options, you can use `::` prefix to the `md-colors` expression
 9028   * to indicate a one-time data binding.
 9029   * <hljs lang="html">
 9030   *   <md-card md-colors="::{background: '{{theme}}-primary-700'}">
 9031   *   </md-card>
 9032   * </hljs>
 9033   *
 9034   */
 9035  function MdColorsDirective($mdColors, $mdUtil, $log, $parse) {
 9036    return {
 9037      restrict: 'A',
 9038      require: ['^?mdTheme'],
 9039      compile: function (tElem, tAttrs) {
 9040        var shouldWatch = shouldColorsWatch();
 9041
 9042        return function (scope, element, attrs, ctrl) {
 9043          var mdThemeController = ctrl[0];
 9044
 9045          var lastColors = {};
 9046
 9047          var parseColors = function (theme) {
 9048            if (typeof theme !== 'string') {
 9049              theme = '';
 9050            }
 9051
 9052            if (!attrs.mdColors) {
 9053              attrs.mdColors = '{}';
 9054            }
 9055
 9056            /**
 9057             * Json.parse() does not work because the keys are not quoted;
 9058             * use $parse to convert to a hash map
 9059             */
 9060            var colors = $parse(attrs.mdColors)(scope);
 9061
 9062            /**
 9063             * If mdTheme is defined up the DOM tree
 9064             * we add mdTheme theme to colors who doesn't specified a theme
 9065             *
 9066             * # example
 9067             * <hljs lang="html">
 9068             *   <div md-theme="myTheme">
 9069             *     <div md-colors="{background: 'primary-600'}">
 9070             *       <span md-colors="{background: 'mySecondTheme-accent-200'}">Color demo</span>
 9071             *     </div>
 9072             *   </div>
 9073             * </hljs>
 9074             *
 9075             * 'primary-600' will be 'myTheme-primary-600',
 9076             * but 'mySecondTheme-accent-200' will stay the same cause it has a theme prefix
 9077             */
 9078            if (mdThemeController) {
 9079              Object.keys(colors).forEach(function (prop) {
 9080                var color = colors[prop];
 9081                if (!$mdColors.hasTheme(color)) {
 9082                  colors[prop] = (theme || mdThemeController.$mdTheme) + '-' + color;
 9083                }
 9084              });
 9085            }
 9086
 9087            cleanElement(colors);
 9088
 9089            return colors;
 9090          };
 9091
 9092          var cleanElement = function (colors) {
 9093            if (!angular.equals(colors, lastColors)) {
 9094              var keys = Object.keys(lastColors);
 9095
 9096              if (lastColors.background && !keys.color) {
 9097                keys.push('color');
 9098              }
 9099
 9100              keys.forEach(function (key) {
 9101                element.css(key, '');
 9102              });
 9103            }
 9104
 9105            lastColors = colors;
 9106          };
 9107
 9108          /**
 9109           * Registering for mgTheme changes and asking mdTheme controller run our callback whenever a theme changes
 9110           */
 9111          var unregisterChanges = angular.noop;
 9112
 9113          if (mdThemeController) {
 9114            unregisterChanges = mdThemeController.registerChanges(function (theme) {
 9115              $mdColors.applyThemeColors(element, parseColors(theme));
 9116            });
 9117          }
 9118
 9119          scope.$on('$destroy', function () {
 9120            unregisterChanges();
 9121          });
 9122
 9123          try {
 9124            if (shouldWatch) {
 9125              scope.$watch(parseColors, angular.bind(this,
 9126                $mdColors.applyThemeColors, element
 9127              ), true);
 9128            }
 9129            else {
 9130              $mdColors.applyThemeColors(element, parseColors());
 9131            }
 9132
 9133          }
 9134          catch (e) {
 9135            $log.error(e.message);
 9136          }
 9137
 9138        };
 9139
 9140        function shouldColorsWatch() {
 9141          // Simulate 1x binding and mark mdColorsWatch == false
 9142          var rawColorExpression = tAttrs.mdColors;
 9143          var bindOnce = rawColorExpression.indexOf('::') > -1;
 9144          var isStatic = bindOnce ? true : STATIC_COLOR_EXPRESSION.test(tAttrs.mdColors);
 9145
 9146          // Remove it for the postLink...
 9147          tAttrs.mdColors = rawColorExpression.replace('::', '');
 9148
 9149          var hasWatchAttr = angular.isDefined(tAttrs.mdColorsWatch);
 9150
 9151          return (bindOnce || isStatic) ? false :
 9152            hasWatchAttr ? $mdUtil.parseAttributeBoolean(tAttrs.mdColorsWatch) : true;
 9153        }
 9154      }
 9155    };
 9156
 9157  }
 9158
 9159
 9160})();
 9161
 9162})();
 9163(function(){
 9164"use strict";
 9165
 9166/**
 9167 * @ngdoc module
 9168 * @name material.components.content
 9169 *
 9170 * @description
 9171 * Scrollable content
 9172 */
 9173mdContentDirective.$inject = ["$mdTheming"];
 9174angular.module('material.components.content', [
 9175  'material.core'
 9176])
 9177  .directive('mdContent', mdContentDirective);
 9178
 9179/**
 9180 * @ngdoc directive
 9181 * @name mdContent
 9182 * @module material.components.content
 9183 *
 9184 * @restrict E
 9185 *
 9186 * @description
 9187 *
 9188 * The `<md-content>` directive is a container element useful for scrollable content. It achieves
 9189 * this by setting the CSS `overflow` property to `auto` so that content can properly scroll.
 9190 *
 9191 * In general, `<md-content>` components are not designed to be nested inside one another. If
 9192 * possible, it is better to make them siblings. This often results in a better user experience as
 9193 * having nested scrollbars may confuse the user.
 9194 *
 9195 * ## Troubleshooting
 9196 *
 9197 * In some cases, you may wish to apply the `md-no-momentum` class to ensure that Safari's
 9198 * momentum scrolling is disabled. Momentum scrolling can cause flickering issues while scrolling
 9199 * SVG icons and some other components.
 9200 *
 9201 * Additionally, we now also offer the `md-no-flicker` class which can be applied to any element
 9202 * and uses a Webkit-specific filter of `blur(0px)` that forces GPU rendering of all elements
 9203 * inside (which eliminates the flicker on iOS devices).
 9204 *
 9205 * _<b>Note:</b> Forcing an element to render on the GPU can have unintended side-effects, especially
 9206 * related to the z-index of elements. Please use with caution and only on the elements needed._
 9207 *
 9208 * @usage
 9209 *
 9210 * Add the `[layout-padding]` attribute to make the content padded.
 9211 *
 9212 * <hljs lang="html">
 9213 *  <md-content layout-padding>
 9214 *      Lorem ipsum dolor sit amet, ne quod novum mei.
 9215 *  </md-content>
 9216 * </hljs>
 9217 */
 9218
 9219function mdContentDirective($mdTheming) {
 9220  return {
 9221    restrict: 'E',
 9222    controller: ['$scope', '$element', ContentController],
 9223    link: function(scope, element) {
 9224      element.addClass('_md');     // private md component indicator for styling
 9225
 9226      $mdTheming(element);
 9227      scope.$broadcast('$mdContentLoaded', element);
 9228
 9229      iosScrollFix(element[0]);
 9230    }
 9231  };
 9232
 9233  function ContentController($scope, $element) {
 9234    this.$scope = $scope;
 9235    this.$element = $element;
 9236  }
 9237}
 9238
 9239function iosScrollFix(node) {
 9240  // IOS FIX:
 9241  // If we scroll where there is no more room for the webview to scroll,
 9242  // by default the webview itself will scroll up and down, this looks really
 9243  // bad.  So if we are scrolling to the very top or bottom, add/subtract one
 9244  angular.element(node).on('$md.pressdown', function(ev) {
 9245    // Only touch events
 9246    if (ev.pointer.type !== 't') return;
 9247    // Don't let a child content's touchstart ruin it for us.
 9248    if (ev.$materialScrollFixed) return;
 9249    ev.$materialScrollFixed = true;
 9250
 9251    if (node.scrollTop === 0) {
 9252      node.scrollTop = 1;
 9253    } else if (node.scrollHeight === node.scrollTop + node.offsetHeight) {
 9254      node.scrollTop -= 1;
 9255    }
 9256  });
 9257}
 9258
 9259})();
 9260(function(){
 9261"use strict";
 9262
 9263/**
 9264 * @ngdoc module
 9265 * @name material.components.datepicker
 9266 * @description Module for the datepicker component.
 9267 */
 9268
 9269angular.module('material.components.datepicker', [
 9270  'material.core',
 9271  'material.components.icon',
 9272  'material.components.virtualRepeat'
 9273]);
 9274
 9275})();
 9276(function(){
 9277"use strict";
 9278
 9279/**
 9280 * @ngdoc module
 9281 * @name material.components.dialog
 9282 */
 9283MdDialogDirective.$inject = ["$$rAF", "$mdTheming", "$mdDialog"];
 9284MdDialogProvider.$inject = ["$$interimElementProvider"];
 9285angular
 9286  .module('material.components.dialog', [
 9287    'material.core',
 9288    'material.components.backdrop'
 9289  ])
 9290  .directive('mdDialog', MdDialogDirective)
 9291  .provider('$mdDialog', MdDialogProvider);
 9292
 9293/**
 9294 * @ngdoc directive
 9295 * @name mdDialog
 9296 * @module material.components.dialog
 9297 *
 9298 * @restrict E
 9299 *
 9300 * @description
 9301 * `<md-dialog>` - The dialog's template must be inside this element.
 9302 *
 9303 * Inside, use an `<md-dialog-content>` element for the dialog's content, and use
 9304 * an `<md-dialog-actions>` element for the dialog's actions.
 9305 *
 9306 * ## CSS
 9307 * - `.md-dialog-content` - class that sets the padding on the content as the spec file
 9308 *
 9309 * ## Notes
 9310 * - If you specify an `id` for the `<md-dialog>`, the `<md-dialog-content>` will have the same `id`
 9311 * prefixed with `dialogContent_`.
 9312 *
 9313 * @usage
 9314 * ### Dialog template
 9315 * <hljs lang="html">
 9316 * <md-dialog aria-label="List dialog">
 9317 *   <md-dialog-content>
 9318 *     <md-list>
 9319 *       <md-list-item ng-repeat="item in items">
 9320 *         <p>Number {{item}}</p>
 9321 *       </md-list-item>
 9322 *     </md-list>
 9323 *   </md-dialog-content>
 9324 *   <md-dialog-actions>
 9325 *     <md-button ng-click="closeDialog()" class="md-primary">Close Dialog</md-button>
 9326 *   </md-dialog-actions>
 9327 * </md-dialog>
 9328 * </hljs>
 9329 */
 9330function MdDialogDirective($$rAF, $mdTheming, $mdDialog) {
 9331  return {
 9332    restrict: 'E',
 9333    link: function(scope, element) {
 9334      element.addClass('_md');     // private md component indicator for styling
 9335
 9336      $mdTheming(element);
 9337      $$rAF(function() {
 9338        var images;
 9339        var content = element[0].querySelector('md-dialog-content');
 9340
 9341        if (content) {
 9342          images = content.getElementsByTagName('img');
 9343          addOverflowClass();
 9344          //-- delayed image loading may impact scroll height, check after images are loaded
 9345          angular.element(images).on('load', addOverflowClass);
 9346        }
 9347
 9348        scope.$on('$destroy', function() {
 9349          $mdDialog.destroy(element);
 9350        });
 9351
 9352        /**
 9353         *
 9354         */
 9355        function addOverflowClass() {
 9356          element.toggleClass('md-content-overflow', content.scrollHeight > content.clientHeight);
 9357        }
 9358
 9359
 9360      });
 9361    }
 9362  };
 9363}
 9364
 9365/**
 9366 * @ngdoc service
 9367 * @name $mdDialog
 9368 * @module material.components.dialog
 9369 *
 9370 * @description
 9371 * `$mdDialog` opens a dialog over the app to inform users about critical information or require
 9372 *  them to make decisions. There are two approaches for setup: a simple promise API
 9373 *  and regular object syntax.
 9374 *
 9375 * ## Restrictions
 9376 *
 9377 * - The dialog is always given an isolate scope.
 9378 * - The dialog's template must have an outer `<md-dialog>` element.
 9379 *   Inside, use an `<md-dialog-content>` element for the dialog's content, and use
 9380 *   an `<md-dialog-actions>` element for the dialog's actions.
 9381 * - Dialogs must cover the entire application to keep interactions inside of them.
 9382 * Use the `parent` option to change where dialogs are appended.
 9383 *
 9384 * ## Sizing
 9385 * - Complex dialogs can be sized with `flex="percentage"`, i.e. `flex="66"`.
 9386 * - Default max-width is 80% of the `rootElement` or `parent`.
 9387 *
 9388 * ## CSS
 9389 * - `.md-dialog-content` - class that sets the padding on the content as the spec file
 9390 *
 9391 * @usage
 9392 * <hljs lang="html">
 9393 * <div  ng-app="demoApp" ng-controller="EmployeeController">
 9394 *   <div>
 9395 *     <md-button ng-click="showAlert()" class="md-raised md-warn">
 9396 *       Employee Alert!
 9397 *       </md-button>
 9398 *   </div>
 9399 *   <div>
 9400 *     <md-button ng-click="showDialog($event)" class="md-raised">
 9401 *       Custom Dialog
 9402 *       </md-button>
 9403 *   </div>
 9404 *   <div>
 9405 *     <md-button ng-click="closeAlert()" ng-disabled="!hasAlert()" class="md-raised">
 9406 *       Close Alert
 9407 *     </md-button>
 9408 *   </div>
 9409 *   <div>
 9410 *     <md-button ng-click="showGreeting($event)" class="md-raised md-primary" >
 9411 *       Greet Employee
 9412 *       </md-button>
 9413 *   </div>
 9414 * </div>
 9415 * </hljs>
 9416 *
 9417 * ### JavaScript: object syntax
 9418 * <hljs lang="js">
 9419 * (function(angular, undefined){
 9420 *   "use strict";
 9421 *
 9422 *   angular
 9423 *    .module('demoApp', ['ngMaterial'])
 9424 *    .controller('AppCtrl', AppController);
 9425 *
 9426 *   function AppController($scope, $mdDialog) {
 9427 *     var alert;
 9428 *     $scope.showAlert = showAlert;
 9429 *     $scope.showDialog = showDialog;
 9430 *     $scope.items = [1, 2, 3];
 9431 *
 9432 *     // Internal method
 9433 *     function showAlert() {
 9434 *       alert = $mdDialog.alert({
 9435 *         title: 'Attention',
 9436 *         textContent: 'This is an example of how easy dialogs can be!',
 9437 *         ok: 'Close'
 9438 *       });
 9439 *
 9440 *       $mdDialog
 9441 *         .show( alert )
 9442 *         .finally(function() {
 9443 *           alert = undefined;
 9444 *         });
 9445 *     }
 9446 *
 9447 *     function showDialog($event) {
 9448 *        var parentEl = angular.element(document.body);
 9449 *        $mdDialog.show({
 9450 *          parent: parentEl,
 9451 *          targetEvent: $event,
 9452 *          template:
 9453 *            '<md-dialog aria-label="List dialog">' +
 9454 *            '  <md-dialog-content>'+
 9455 *            '    <md-list>'+
 9456 *            '      <md-list-item ng-repeat="item in items">'+
 9457 *            '       <p>Number {{item}}</p>' +
 9458 *            '      </md-item>'+
 9459 *            '    </md-list>'+
 9460 *            '  </md-dialog-content>' +
 9461 *            '  <md-dialog-actions>' +
 9462 *            '    <md-button ng-click="closeDialog()" class="md-primary">' +
 9463 *            '      Close Dialog' +
 9464 *            '    </md-button>' +
 9465 *            '  </md-dialog-actions>' +
 9466 *            '</md-dialog>',
 9467 *          locals: {
 9468 *            items: $scope.items
 9469 *          },
 9470 *          controller: DialogController
 9471 *       });
 9472 *       function DialogController($scope, $mdDialog, items) {
 9473 *         $scope.items = items;
 9474 *         $scope.closeDialog = function() {
 9475 *           $mdDialog.hide();
 9476 *         }
 9477 *       }
 9478 *     }
 9479 *   }
 9480 * })(angular);
 9481 * </hljs>
 9482 *
 9483 * ### Multiple Dialogs
 9484 * Using the `multiple` option for the `$mdDialog` service allows developers to show multiple dialogs
 9485 * at the same time.
 9486 *
 9487 * <hljs lang="js">
 9488 *   // From plain options
 9489 *   $mdDialog.show({
 9490 *     multiple: true
 9491 *   });
 9492 *
 9493 *   // From a dialog preset
 9494 *   $mdDialog.show(
 9495 *     $mdDialog
 9496 *       .alert()
 9497 *       .multiple(true)
 9498 *   );
 9499 *
 9500 * </hljs>
 9501 *
 9502 * ### Pre-Rendered Dialogs
 9503 * By using the `contentElement` option, it is possible to use an already existing element in the DOM.
 9504 *
 9505 * > Pre-rendered dialogs will be not linked to any scope and will not instantiate any new controller.<br/>
 9506 * > You can manually link the elements to a scope or instantiate a controller from the template (`ng-controller`)
 9507 *
 9508 * <hljs lang="js">
 9509 *   $scope.showPrerenderedDialog = function() {
 9510 *     $mdDialog.show({
 9511 *       contentElement: '#myStaticDialog',
 9512 *       parent: angular.element(document.body)
 9513 *     });
 9514 *   };
 9515 * </hljs>
 9516 *
 9517 * When using a string as value, `$mdDialog` will automatically query the DOM for the specified CSS selector.
 9518 *
 9519 * <hljs lang="html">
 9520 *   <div style="visibility: hidden">
 9521 *     <div class="md-dialog-container" id="myStaticDialog">
 9522 *       <md-dialog>
 9523 *         This is a pre-rendered dialog.
 9524 *       </md-dialog>
 9525 *     </div>
 9526 *   </div>
 9527 * </hljs>
 9528 *
 9529 * **Notice**: It is important, to use the `.md-dialog-container` as the content element, otherwise the dialog
 9530 * will not show up.
 9531 *
 9532 * It also possible to use a DOM element for the `contentElement` option.
 9533 * - `contentElement: document.querySelector('#myStaticDialog')`
 9534 * - `contentElement: angular.element(TEMPLATE)`
 9535 *
 9536 * When using a `template` as content element, it will be not compiled upon open.
 9537 * This allows you to compile the element yourself and use it each time the dialog opens.
 9538 *
 9539 * ### Custom Presets
 9540 * Developers are also able to create their own preset, which can be easily used without repeating
 9541 * their options each time.
 9542 *
 9543 * <hljs lang="js">
 9544 *   $mdDialogProvider.addPreset('testPreset', {
 9545 *     options: function() {
 9546 *       return {
 9547 *         template:
 9548 *           '<md-dialog>' +
 9549 *             'This is a custom preset' +
 9550 *           '</md-dialog>',
 9551 *         controllerAs: 'dialog',
 9552 *         bindToController: true,
 9553 *         clickOutsideToClose: true,
 9554 *         escapeToClose: true
 9555 *       };
 9556 *     }
 9557 *   });
 9558 * </hljs>
 9559 *
 9560 * After you created your preset at config phase, you can easily access it.
 9561 *
 9562 * <hljs lang="js">
 9563 *   $mdDialog.show(
 9564 *     $mdDialog.testPreset()
 9565 *   );
 9566 * </hljs>
 9567 *
 9568 * ### JavaScript: promise API syntax, custom dialog template
 9569 * <hljs lang="js">
 9570 * (function(angular, undefined){
 9571 *   "use strict";
 9572 *
 9573 *   angular
 9574 *     .module('demoApp', ['ngMaterial'])
 9575 *     .controller('EmployeeController', EmployeeEditor)
 9576 *     .controller('GreetingController', GreetingController);
 9577 *
 9578 *   // Fictitious Employee Editor to show how to use simple and complex dialogs.
 9579 *
 9580 *   function EmployeeEditor($scope, $mdDialog) {
 9581 *     var alert;
 9582 *
 9583 *     $scope.showAlert = showAlert;
 9584 *     $scope.closeAlert = closeAlert;
 9585 *     $scope.showGreeting = showCustomGreeting;
 9586 *
 9587 *     $scope.hasAlert = function() { return !!alert };
 9588 *     $scope.userName = $scope.userName || 'Bobby';
 9589 *
 9590 *     // Dialog #1 - Show simple alert dialog and cache
 9591 *     // reference to dialog instance
 9592 *
 9593 *     function showAlert() {
 9594 *       alert = $mdDialog.alert()
 9595 *         .title('Attention, ' + $scope.userName)
 9596 *         .textContent('This is an example of how easy dialogs can be!')
 9597 *         .ok('Close');
 9598 *
 9599 *       $mdDialog
 9600 *           .show( alert )
 9601 *           .finally(function() {
 9602 *             alert = undefined;
 9603 *           });
 9604 *     }
 9605 *
 9606 *     // Close the specified dialog instance and resolve with 'finished' flag
 9607 *     // Normally this is not needed, just use '$mdDialog.hide()' to close
 9608 *     // the most recent dialog popup.
 9609 *
 9610 *     function closeAlert() {
 9611 *       $mdDialog.hide( alert, "finished" );
 9612 *       alert = undefined;
 9613 *     }
 9614 *
 9615 *     // Dialog #2 - Demonstrate more complex dialogs construction and popup.
 9616 *
 9617 *     function showCustomGreeting($event) {
 9618 *         $mdDialog.show({
 9619 *           targetEvent: $event,
 9620 *           template:
 9621 *             '<md-dialog>' +
 9622 *
 9623 *             '  <md-dialog-content>Hello {{ employee }}!</md-dialog-content>' +
 9624 *
 9625 *             '  <md-dialog-actions>' +
 9626 *             '    <md-button ng-click="closeDialog()" class="md-primary">' +
 9627 *             '      Close Greeting' +
 9628 *             '    </md-button>' +
 9629 *             '  </md-dialog-actions>' +
 9630 *             '</md-dialog>',
 9631 *           controller: 'GreetingController',
 9632 *           onComplete: afterShowAnimation,
 9633 *           locals: { employee: $scope.userName }
 9634 *         });
 9635 *
 9636 *         // When the 'enter' animation finishes...
 9637 *
 9638 *         function afterShowAnimation(scope, element, options) {
 9639 *            // post-show code here: DOM element focus, etc.
 9640 *         }
 9641 *     }
 9642 *
 9643 *     // Dialog #3 - Demonstrate use of ControllerAs and passing $scope to dialog
 9644 *     //             Here we used ng-controller="GreetingController as vm" and
 9645 *     //             $scope.vm === <controller instance>
 9646 *
 9647 *     function showCustomGreeting() {
 9648 *
 9649 *        $mdDialog.show({
 9650 *           clickOutsideToClose: true,
 9651 *
 9652 *           scope: $scope,        // use parent scope in template
 9653 *           preserveScope: true,  // do not forget this if use parent scope
 9654
 9655 *           // Since GreetingController is instantiated with ControllerAs syntax
 9656 *           // AND we are passing the parent '$scope' to the dialog, we MUST
 9657 *           // use 'vm.<xxx>' in the template markup
 9658 *
 9659 *           template: '<md-dialog>' +
 9660 *                     '  <md-dialog-content>' +
 9661 *                     '     Hi There {{vm.employee}}' +
 9662 *                     '  </md-dialog-content>' +
 9663 *                     '</md-dialog>',
 9664 *
 9665 *           controller: function DialogController($scope, $mdDialog) {
 9666 *             $scope.closeDialog = function() {
 9667 *               $mdDialog.hide();
 9668 *             }
 9669 *           }
 9670 *        });
 9671 *     }
 9672 *
 9673 *   }
 9674 *
 9675 *   // Greeting controller used with the more complex 'showCustomGreeting()' custom dialog
 9676 *
 9677 *   function GreetingController($scope, $mdDialog, employee) {
 9678 *     // Assigned from construction <code>locals</code> options...
 9679 *     $scope.employee = employee;
 9680 *
 9681 *     $scope.closeDialog = function() {
 9682 *       // Easily hides most recent dialog shown...
 9683 *       // no specific instance reference is needed.
 9684 *       $mdDialog.hide();
 9685 *     };
 9686 *   }
 9687 *
 9688 * })(angular);
 9689 * </hljs>
 9690 */
 9691
 9692/**
 9693 * @ngdoc method
 9694 * @name $mdDialog#alert
 9695 *
 9696 * @description
 9697 * Builds a preconfigured dialog with the specified message.
 9698 *
 9699 * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
 9700 *
 9701 * - $mdDialogPreset#title(string) - Sets the alert title.
 9702 * - $mdDialogPreset#textContent(string) - Sets the alert message.
 9703 * - $mdDialogPreset#htmlContent(string) - Sets the alert message as HTML. Requires ngSanitize
 9704 *     module to be loaded. HTML is not run through Angular's compiler.
 9705 * - $mdDialogPreset#ok(string) - Sets the alert "Okay" button text.
 9706 * - $mdDialogPreset#theme(string) - Sets the theme of the alert dialog.
 9707 * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option,
 9708 *     the location of the click will be used as the starting point for the opening animation
 9709 *     of the the dialog.
 9710 *
 9711 */
 9712
 9713/**
 9714 * @ngdoc method
 9715 * @name $mdDialog#confirm
 9716 *
 9717 * @description
 9718 * Builds a preconfigured dialog with the specified message. You can call show and the promise returned
 9719 * will be resolved only if the user clicks the confirm action on the dialog.
 9720 *
 9721 * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
 9722 *
 9723 * Additionally, it supports the following methods:
 9724 *
 9725 * - $mdDialogPreset#title(string) - Sets the confirm title.
 9726 * - $mdDialogPreset#textContent(string) - Sets the confirm message.
 9727 * - $mdDialogPreset#htmlContent(string) - Sets the confirm message as HTML. Requires ngSanitize
 9728 *     module to be loaded. HTML is not run through Angular's compiler.
 9729 * - $mdDialogPreset#ok(string) - Sets the confirm "Okay" button text.
 9730 * - $mdDialogPreset#cancel(string) - Sets the confirm "Cancel" button text.
 9731 * - $mdDialogPreset#theme(string) - Sets the theme of the confirm dialog.
 9732 * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option,
 9733 *     the location of the click will be used as the starting point for the opening animation
 9734 *     of the the dialog.
 9735 *
 9736 */
 9737
 9738/**
 9739 * @ngdoc method
 9740 * @name $mdDialog#prompt
 9741 *
 9742 * @description
 9743 * Builds a preconfigured dialog with the specified message and input box. You can call show and the promise returned
 9744 * will be resolved only if the user clicks the prompt action on the dialog, passing the input value as the first argument.
 9745 *
 9746 * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
 9747 *
 9748 * Additionally, it supports the following methods:
 9749 *
 9750 * - $mdDialogPreset#title(string) - Sets the prompt title.
 9751 * - $mdDialogPreset#textContent(string) - Sets the prompt message.
 9752 * - $mdDialogPreset#htmlContent(string) - Sets the prompt message as HTML. Requires ngSanitize
 9753 *     module to be loaded. HTML is not run through Angular's compiler.
 9754 * - $mdDialogPreset#placeholder(string) - Sets the placeholder text for the input.
 9755 * - $mdDialogPreset#initialValue(string) - Sets the initial value for the prompt input.
 9756 * - $mdDialogPreset#ok(string) - Sets the prompt "Okay" button text.
 9757 * - $mdDialogPreset#cancel(string) - Sets the prompt "Cancel" button text.
 9758 * - $mdDialogPreset#theme(string) - Sets the theme of the prompt dialog.
 9759 * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option,
 9760 *     the location of the click will be used as the starting point for the opening animation
 9761 *     of the the dialog.
 9762 *
 9763 */
 9764
 9765/**
 9766 * @ngdoc method
 9767 * @name $mdDialog#show
 9768 *
 9769 * @description
 9770 * Show a dialog with the specified options.
 9771 *
 9772 * @param {object} optionsOrPreset Either provide an `$mdDialogPreset` returned from `alert()`, and
 9773 * `confirm()`, or an options object with the following properties:
 9774 *   - `templateUrl` - `{string=}`: The url of a template that will be used as the content
 9775 *   of the dialog.
 9776 *   - `template` - `{string=}`: HTML template to show in the dialog. This **must** be trusted HTML
 9777 *      with respect to Angular's [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
 9778 *      This template should **never** be constructed with any kind of user input or user data.
 9779 *   - `contentElement` - `{string|Element}`: Instead of using a template, which will be compiled each time a
 9780 *     dialog opens, you can also use a DOM element.<br/>
 9781 *     * When specifying an element, which is present on the DOM, `$mdDialog` will temporary fetch the element into
 9782 *       the dialog and restores it at the old DOM position upon close.
 9783 *     * When specifying a string, the string be used as a CSS selector, to lookup for the element in the DOM.
 9784 *   - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template with a
 9785 *     `<md-dialog>` tag if one is not provided. Defaults to true. Can be disabled if you provide a
 9786 *     custom dialog directive.
 9787 *   - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option,
 9788 *     the location of the click will be used as the starting point for the opening animation
 9789 *     of the the dialog.
 9790 *   - `openFrom` - `{string|Element|object}`: The query selector, DOM element or the Rect object
 9791 *     that is used to determine the bounds (top, left, height, width) from which the Dialog will
 9792 *     originate.
 9793 *   - `closeTo` - `{string|Element|object}`: The query selector, DOM element or the Rect object
 9794 *     that is used to determine the bounds (top, left, height, width) to which the Dialog will
 9795 *     target.
 9796 *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified,
 9797 *     it will create a new isolate scope.
 9798 *     This scope will be destroyed when the dialog is removed unless `preserveScope` is set to true.
 9799 *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
 9800 *   - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the dialog is open.
 9801 *     Default true.
 9802 *   - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog.
 9803 *     Default true.
 9804 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the dialog to
 9805 *     close it. Default false.
 9806 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the dialog.
 9807 *     Default true.
 9808 *   - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on open. Only disable if
 9809 *     focusing some other way, as focus management is required for dialogs to be accessible.
 9810 *     Defaults to true.
 9811 *   - `controller` - `{function|string=}`: The controller to associate with the dialog. The controller
 9812 *     will be injected with the local `$mdDialog`, which passes along a scope for the dialog.
 9813 *   - `locals` - `{object=}`: An object containing key/value pairs. The keys will be used as names
 9814 *     of values to inject into the controller. For example, `locals: {three: 3}` would inject
 9815 *     `three` into the controller, with the value 3. If `bindToController` is true, they will be
 9816 *     copied to the controller instead.
 9817 *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
 9818 *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values, and the
 9819 *     dialog will not open until all of the promises resolve.
 9820 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
 9821 *   - `parent` - `{element=}`: The element to append the dialog to. Defaults to appending
 9822 *     to the root element of the application.
 9823 *   - `onShowing` - `function(scope, element)`: Callback function used to announce the show() action is
 9824 *     starting.
 9825 *   - `onComplete` - `function(scope, element)`: Callback function used to announce when the show() action is
 9826 *     finished.
 9827 *   - `onRemoving` - `function(element, removePromise)`: Callback function used to announce the
 9828 *      close/hide() action is starting. This allows developers to run custom animations
 9829 *      in parallel the close animations.
 9830 *   - `fullscreen` `{boolean=}`: An option to toggle whether the dialog should show in fullscreen
 9831 *      or not. Defaults to `false`.
 9832 * @returns {promise} A promise that can be resolved with `$mdDialog.hide()` or
 9833 * rejected with `$mdDialog.cancel()`.
 9834 */
 9835
 9836/**
 9837 * @ngdoc method
 9838 * @name $mdDialog#hide
 9839 *
 9840 * @description
 9841 * Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`.
 9842 *
 9843 * @param {*=} response An argument for the resolved promise.
 9844 *
 9845 * @returns {promise} A promise that is resolved when the dialog has been closed.
 9846 */
 9847
 9848/**
 9849 * @ngdoc method
 9850 * @name $mdDialog#cancel
 9851 *
 9852 * @description
 9853 * Hide an existing dialog and reject the promise returned from `$mdDialog.show()`.
 9854 *
 9855 * @param {*=} response An argument for the rejected promise.
 9856 *
 9857 * @returns {promise} A promise that is resolved when the dialog has been closed.
 9858 */
 9859
 9860function MdDialogProvider($$interimElementProvider) {
 9861  // Elements to capture and redirect focus when the user presses tab at the dialog boundary.
 9862  advancedDialogOptions.$inject = ["$mdDialog", "$mdConstant"];
 9863  dialogDefaultOptions.$inject = ["$mdDialog", "$mdAria", "$mdUtil", "$mdConstant", "$animate", "$document", "$window", "$rootElement", "$log", "$injector", "$mdTheming", "$interpolate", "$mdInteraction"];
 9864  var topFocusTrap, bottomFocusTrap;
 9865
 9866  return $$interimElementProvider('$mdDialog')
 9867    .setDefaults({
 9868      methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose',
 9869          'targetEvent', 'closeTo', 'openFrom', 'parent', 'fullscreen', 'multiple'],
 9870      options: dialogDefaultOptions
 9871    })
 9872    .addPreset('alert', {
 9873      methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'theme',
 9874          'css'],
 9875      options: advancedDialogOptions
 9876    })
 9877    .addPreset('confirm', {
 9878      methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'cancel',
 9879          'theme', 'css'],
 9880      options: advancedDialogOptions
 9881    })
 9882    .addPreset('prompt', {
 9883      methods: ['title', 'htmlContent', 'textContent', 'initialValue', 'content', 'placeholder', 'ariaLabel',
 9884          'ok', 'cancel', 'theme', 'css'],
 9885      options: advancedDialogOptions
 9886    });
 9887
 9888  /* @ngInject */
 9889  function advancedDialogOptions($mdDialog, $mdConstant) {
 9890    return {
 9891      template: [
 9892        '<md-dialog md-theme="{{ dialog.theme || dialog.defaultTheme }}" aria-label="{{ dialog.ariaLabel }}" ng-class="dialog.css">',
 9893        '  <md-dialog-content class="md-dialog-content" role="document" tabIndex="-1">',
 9894        '    <h2 class="md-title">{{ dialog.title }}</h2>',
 9895        '    <div ng-if="::dialog.mdHtmlContent" class="md-dialog-content-body" ',
 9896        '        ng-bind-html="::dialog.mdHtmlContent"></div>',
 9897        '    <div ng-if="::!dialog.mdHtmlContent" class="md-dialog-content-body">',
 9898        '      <p>{{::dialog.mdTextContent}}</p>',
 9899        '    </div>',
 9900        '    <md-input-container md-no-float ng-if="::dialog.$type == \'prompt\'" class="md-prompt-input-container">',
 9901        '      <input ng-keypress="dialog.keypress($event)" md-autofocus ng-model="dialog.result" ' +
 9902        '             placeholder="{{::dialog.placeholder}}">',
 9903        '    </md-input-container>',
 9904        '  </md-dialog-content>',
 9905        '  <md-dialog-actions>',
 9906        '    <md-button ng-if="dialog.$type === \'confirm\' || dialog.$type === \'prompt\'"' +
 9907        '               ng-click="dialog.abort()" class="md-primary md-cancel-button">',
 9908        '      {{ dialog.cancel }}',
 9909        '    </md-button>',
 9910        '    <md-button ng-click="dialog.hide()" class="md-primary md-confirm-button" md-autofocus="dialog.$type===\'alert\'">',
 9911        '      {{ dialog.ok }}',
 9912        '    </md-button>',
 9913        '  </md-dialog-actions>',
 9914        '</md-dialog>'
 9915      ].join('').replace(/\s\s+/g, ''),
 9916      controller: function mdDialogCtrl() {
 9917        var isPrompt = this.$type == 'prompt';
 9918
 9919        if (isPrompt && this.initialValue) {
 9920          this.result = this.initialValue;
 9921        }
 9922
 9923        this.hide = function() {
 9924          $mdDialog.hide(isPrompt ? this.result : true);
 9925        };
 9926        this.abort = function() {
 9927          $mdDialog.cancel();
 9928        };
 9929        this.keypress = function($event) {
 9930          if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) {
 9931            $mdDialog.hide(this.result);
 9932          }
 9933        };
 9934      },
 9935      controllerAs: 'dialog',
 9936      bindToController: true,
 9937    };
 9938  }
 9939
 9940  /* @ngInject */
 9941  function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document, $window, $rootElement,
 9942                                $log, $injector, $mdTheming, $interpolate, $mdInteraction) {
 9943
 9944    return {
 9945      hasBackdrop: true,
 9946      isolateScope: true,
 9947      onCompiling: beforeCompile,
 9948      onShow: onShow,
 9949      onShowing: beforeShow,
 9950      onRemove: onRemove,
 9951      clickOutsideToClose: false,
 9952      escapeToClose: true,
 9953      targetEvent: null,
 9954      closeTo: null,
 9955      openFrom: null,
 9956      focusOnOpen: true,
 9957      disableParentScroll: true,
 9958      autoWrap: true,
 9959      fullscreen: false,
 9960      transformTemplate: function(template, options) {
 9961        // Make the dialog container focusable, because otherwise the focus will be always redirected to
 9962        // an element outside of the container, and the focus trap won't work probably..
 9963        // Also the tabindex is needed for the `escapeToClose` functionality, because
 9964        // the keyDown event can't be triggered when the focus is outside of the container.
 9965        var startSymbol = $interpolate.startSymbol();
 9966        var endSymbol = $interpolate.endSymbol();
 9967        var theme = startSymbol + (options.themeWatch ? '' : '::') + 'theme' + endSymbol;
 9968        return '<div class="md-dialog-container" tabindex="-1" md-theme="' + theme + '">' + validatedTemplate(template) + '</div>';
 9969
 9970        /**
 9971         * The specified template should contain a <md-dialog> wrapper element....
 9972         */
 9973        function validatedTemplate(template) {
 9974          if (options.autoWrap && !/<\/md-dialog>/g.test(template)) {
 9975            return '<md-dialog>' + (template || '') + '</md-dialog>';
 9976          } else {
 9977            return template || '';
 9978          }
 9979        }
 9980      }
 9981    };
 9982
 9983    function beforeCompile(options) {
 9984      // Automatically apply the theme, if the user didn't specify a theme explicitly.
 9985      // Those option changes need to be done, before the compilation has started, because otherwise
 9986      // the option changes will be not available in the $mdCompilers locales.
 9987      options.defaultTheme = $mdTheming.defaultTheme();
 9988
 9989      detectTheming(options);
 9990    }
 9991
 9992    function beforeShow(scope, element, options, controller) {
 9993
 9994      if (controller) {
 9995        var mdHtmlContent = controller.htmlContent || options.htmlContent || '';
 9996        var mdTextContent = controller.textContent || options.textContent ||
 9997            controller.content || options.content || '';
 9998
 9999        if (mdHtmlContent && !$injector.has('$sanitize')) {
10000          throw Error('The ngSanitize module must be loaded in order to use htmlContent.');
10001        }
10002
10003        if (mdHtmlContent && mdTextContent) {
10004          throw Error('md-dialog cannot have both `htmlContent` and `textContent`');
10005        }
10006
10007        // Only assign the content if nothing throws, otherwise it'll still be compiled.
10008        controller.mdHtmlContent = mdHtmlContent;
10009        controller.mdTextContent = mdTextContent;
10010      }
10011    }
10012
10013    /** Show method for dialogs */
10014    function onShow(scope, element, options, controller) {
10015      angular.element($document[0].body).addClass('md-dialog-is-showing');
10016
10017      var dialogElement = element.find('md-dialog');
10018
10019      // Once a dialog has `ng-cloak` applied on his template the dialog animation will not work properly.
10020      // This is a very common problem, so we have to notify the developer about this.
10021      if (dialogElement.hasClass('ng-cloak')) {
10022        var message = '$mdDialog: using `<md-dialog ng-cloak>` will affect the dialog opening animations.';
10023        $log.warn( message, element[0] );
10024      }
10025
10026      captureParentAndFromToElements(options);
10027      configureAria(dialogElement, options);
10028      showBackdrop(scope, element, options);
10029      activateListeners(element, options);
10030
10031      return dialogPopIn(element, options)
10032        .then(function() {
10033          lockScreenReader(element, options);
10034          warnDeprecatedActions();
10035          focusOnOpen();
10036        });
10037
10038      /**
10039       * Check to see if they used the deprecated .md-actions class and log a warning
10040       */
10041      function warnDeprecatedActions() {
10042        if (element[0].querySelector('.md-actions')) {
10043          $log.warn('Using a class of md-actions is deprecated, please use <md-dialog-actions>.');
10044        }
10045      }
10046
10047      /**
10048       * For alerts, focus on content... otherwise focus on
10049       * the close button (or equivalent)
10050       */
10051      function focusOnOpen() {
10052        if (options.focusOnOpen) {
10053          var target = $mdUtil.findFocusTarget(element) || findCloseButton() || dialogElement;
10054          target.focus();
10055        }
10056
10057        /**
10058         * If no element with class dialog-close, try to find the last
10059         * button child in md-actions and assume it is a close button.
10060         *
10061         * If we find no actions at all, log a warning to the console.
10062         */
10063        function findCloseButton() {
10064          return element[0].querySelector('.dialog-close, md-dialog-actions button:last-child');
10065        }
10066      }
10067    }
10068
10069    /**
10070     * Remove function for all dialogs
10071     */
10072    function onRemove(scope, element, options) {
10073      options.deactivateListeners();
10074      options.unlockScreenReader();
10075      options.hideBackdrop(options.$destroy);
10076
10077      // Remove the focus traps that we added earlier for keeping focus within the dialog.
10078      if (topFocusTrap && topFocusTrap.parentNode) {
10079        topFocusTrap.parentNode.removeChild(topFocusTrap);
10080      }
10081
10082      if (bottomFocusTrap && bottomFocusTrap.parentNode) {
10083        bottomFocusTrap.parentNode.removeChild(bottomFocusTrap);
10084      }
10085
10086      // For navigation $destroy events, do a quick, non-animated removal,
10087      // but for normal closes (from clicks, etc) animate the removal
10088      return !!options.$destroy ? detachAndClean() : animateRemoval().then( detachAndClean );
10089
10090      /**
10091       * For normal closes, animate the removal.
10092       * For forced closes (like $destroy events), skip the animations
10093       */
10094      function animateRemoval() {
10095        return dialogPopOut(element, options);
10096      }
10097
10098      /**
10099       * Detach the element
10100       */
10101      function detachAndClean() {
10102        angular.element($document[0].body).removeClass('md-dialog-is-showing');
10103
10104        // Reverse the container stretch if using a content element.
10105        if (options.contentElement) {
10106          options.reverseContainerStretch();
10107        }
10108
10109        // Exposed cleanup function from the $mdCompiler.
10110        options.cleanupElement();
10111
10112        // Restores the focus to the origin element if the last interaction upon opening was a keyboard.
10113        if (!options.$destroy && options.originInteraction === 'keyboard') {
10114          options.origin.focus();
10115        }
10116      }
10117    }
10118
10119    function detectTheming(options) {
10120      // Once the user specifies a targetEvent, we will automatically try to find the correct
10121      // nested theme.
10122      var targetEl;
10123      if (options.targetEvent && options.targetEvent.target) {
10124        targetEl = angular.element(options.targetEvent.target);
10125      }
10126
10127      var themeCtrl = targetEl && targetEl.controller('mdTheme');
10128
10129      if (!themeCtrl) {
10130        return;
10131      }
10132
10133      options.themeWatch = themeCtrl.$shouldWatch;
10134
10135      var theme = options.theme || themeCtrl.$mdTheme;
10136
10137      if (theme) {
10138        options.scope.theme = theme;
10139      }
10140
10141      var unwatch = themeCtrl.registerChanges(function (newTheme) {
10142        options.scope.theme = newTheme;
10143
10144        if (!options.themeWatch) {
10145          unwatch();
10146        }
10147      });
10148    }
10149
10150    /**
10151     * Capture originator/trigger/from/to element information (if available)
10152     * and the parent container for the dialog; defaults to the $rootElement
10153     * unless overridden in the options.parent
10154     */
10155    function captureParentAndFromToElements(options) {
10156          options.origin = angular.extend({
10157            element: null,
10158            bounds: null,
10159            focus: angular.noop
10160          }, options.origin || {});
10161
10162          options.parent   = getDomElement(options.parent, $rootElement);
10163          options.closeTo  = getBoundingClientRect(getDomElement(options.closeTo));
10164          options.openFrom = getBoundingClientRect(getDomElement(options.openFrom));
10165
10166          if ( options.targetEvent ) {
10167            options.origin = getBoundingClientRect(options.targetEvent.target, options.origin);
10168            options.originInteraction = $mdInteraction.getLastInteractionType();
10169          }
10170
10171
10172          /**
10173           * Identify the bounding RECT for the target element
10174           *
10175           */
10176          function getBoundingClientRect (element, orig) {
10177            var source = angular.element((element || {}));
10178            if (source && source.length) {
10179              // Compute and save the target element's bounding rect, so that if the
10180              // element is hidden when the dialog closes, we can shrink the dialog
10181              // back to the same position it expanded from.
10182              //
10183              // Checking if the source is a rect object or a DOM element
10184              var bounds = {top:0,left:0,height:0,width:0};
10185              var hasFn = angular.isFunction(source[0].getBoundingClientRect);
10186
10187              return angular.extend(orig || {}, {
10188                  element : hasFn ? source : undefined,
10189                  bounds  : hasFn ? source[0].getBoundingClientRect() : angular.extend({}, bounds, source[0]),
10190                  focus   : angular.bind(source, source.focus),
10191              });
10192            }
10193          }
10194
10195          /**
10196           * If the specifier is a simple string selector, then query for
10197           * the DOM element.
10198           */
10199          function getDomElement(element, defaultElement) {
10200            if (angular.isString(element)) {
10201              element = $document[0].querySelector(element);
10202            }
10203
10204            // If we have a reference to a raw dom element, always wrap it in jqLite
10205            return angular.element(element || defaultElement);
10206          }
10207
10208        }
10209
10210    /**
10211     * Listen for escape keys and outside clicks to auto close
10212     */
10213    function activateListeners(element, options) {
10214      var window = angular.element($window);
10215      var onWindowResize = $mdUtil.debounce(function() {
10216        stretchDialogContainerToViewport(element, options);
10217      }, 60);
10218
10219      var removeListeners = [];
10220      var smartClose = function() {
10221        // Only 'confirm' dialogs have a cancel button... escape/clickOutside will
10222        // cancel or fallback to hide.
10223        var closeFn = ( options.$type == 'alert' ) ? $mdDialog.hide : $mdDialog.cancel;
10224        $mdUtil.nextTick(closeFn, true);
10225      };
10226
10227      if (options.escapeToClose) {
10228        var parentTarget = options.parent;
10229        var keyHandlerFn = function(ev) {
10230          if (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
10231            ev.stopPropagation();
10232            ev.preventDefault();
10233
10234            smartClose();
10235          }
10236        };
10237
10238        // Add keydown listeners
10239        element.on('keydown', keyHandlerFn);
10240        parentTarget.on('keydown', keyHandlerFn);
10241
10242        // Queue remove listeners function
10243        removeListeners.push(function() {
10244
10245          element.off('keydown', keyHandlerFn);
10246          parentTarget.off('keydown', keyHandlerFn);
10247
10248        });
10249      }
10250
10251      // Register listener to update dialog on window resize
10252      window.on('resize', onWindowResize);
10253
10254      removeListeners.push(function() {
10255        window.off('resize', onWindowResize);
10256      });
10257
10258      if (options.clickOutsideToClose) {
10259        var target = element;
10260        var sourceElem;
10261
10262        // Keep track of the element on which the mouse originally went down
10263        // so that we can only close the backdrop when the 'click' started on it.
10264        // A simple 'click' handler does not work,
10265        // it sets the target object as the element the mouse went down on.
10266        var mousedownHandler = function(ev) {
10267          sourceElem = ev.target;
10268        };
10269
10270        // We check if our original element and the target is the backdrop
10271        // because if the original was the backdrop and the target was inside the dialog
10272        // we don't want to dialog to close.
10273        var mouseupHandler = function(ev) {
10274          if (sourceElem === target[0] && ev.target === target[0]) {
10275            ev.stopPropagation();
10276            ev.preventDefault();
10277
10278            smartClose();
10279          }
10280        };
10281
10282        // Add listeners
10283        target.on('mousedown', mousedownHandler);
10284        target.on('mouseup', mouseupHandler);
10285
10286        // Queue remove listeners function
10287        removeListeners.push(function() {
10288          target.off('mousedown', mousedownHandler);
10289          target.off('mouseup', mouseupHandler);
10290        });
10291      }
10292
10293      // Attach specific `remove` listener handler
10294      options.deactivateListeners = function() {
10295        removeListeners.forEach(function(removeFn) {
10296          removeFn();
10297        });
10298        options.deactivateListeners = null;
10299      };
10300    }
10301
10302    /**
10303     * Show modal backdrop element...
10304     */
10305    function showBackdrop(scope, element, options) {
10306
10307      if (options.disableParentScroll) {
10308        // !! DO this before creating the backdrop; since disableScrollAround()
10309        //    configures the scroll offset; which is used by mdBackDrop postLink()
10310        options.restoreScroll = $mdUtil.disableScrollAround(element, options.parent);
10311      }
10312
10313      if (options.hasBackdrop) {
10314        options.backdrop = $mdUtil.createBackdrop(scope, "md-dialog-backdrop md-opaque");
10315        $animate.enter(options.backdrop, options.parent);
10316      }
10317
10318      /**
10319       * Hide modal backdrop element...
10320       */
10321      options.hideBackdrop = function hideBackdrop($destroy) {
10322        if (options.backdrop) {
10323          if ( !!$destroy ) options.backdrop.remove();
10324          else              $animate.leave(options.backdrop);
10325        }
10326
10327
10328        if (options.disableParentScroll) {
10329          options.restoreScroll && options.restoreScroll();
10330          delete options.restoreScroll;
10331        }
10332
10333        options.hideBackdrop = null;
10334      };
10335    }
10336
10337    /**
10338     * Inject ARIA-specific attributes appropriate for Dialogs
10339     */
10340    function configureAria(element, options) {
10341
10342      var role = (options.$type === 'alert') ? 'alertdialog' : 'dialog';
10343      var dialogContent = element.find('md-dialog-content');
10344      var existingDialogId = element.attr('id');
10345      var dialogContentId = 'dialogContent_' + (existingDialogId || $mdUtil.nextUid());
10346
10347      element.attr({
10348        'role': role,
10349        'tabIndex': '-1'
10350      });
10351
10352      if (dialogContent.length === 0) {
10353        dialogContent = element;
10354        // If the dialog element already had an ID, don't clobber it.
10355        if (existingDialogId) {
10356          dialogContentId = existingDialogId;
10357        }
10358      }
10359
10360      dialogContent.attr('id', dialogContentId);
10361      element.attr('aria-describedby', dialogContentId);
10362
10363      if (options.ariaLabel) {
10364        $mdAria.expect(element, 'aria-label', options.ariaLabel);
10365      }
10366      else {
10367        $mdAria.expectAsync(element, 'aria-label', function() {
10368          var words = dialogContent.text().split(/\s+/);
10369          if (words.length > 3) words = words.slice(0, 3).concat('...');
10370          return words.join(' ');
10371        });
10372      }
10373
10374      // Set up elements before and after the dialog content to capture focus and
10375      // redirect back into the dialog.
10376      topFocusTrap = document.createElement('div');
10377      topFocusTrap.classList.add('md-dialog-focus-trap');
10378      topFocusTrap.tabIndex = 0;
10379
10380      bottomFocusTrap = topFocusTrap.cloneNode(false);
10381
10382      // When focus is about to move out of the dialog, we want to intercept it and redirect it
10383      // back to the dialog element.
10384      var focusHandler = function() {
10385        element.focus();
10386      };
10387      topFocusTrap.addEventListener('focus', focusHandler);
10388      bottomFocusTrap.addEventListener('focus', focusHandler);
10389
10390      // The top focus trap inserted immeidately before the md-dialog element (as a sibling).
10391      // The bottom focus trap is inserted at the very end of the md-dialog element (as a child).
10392      element[0].parentNode.insertBefore(topFocusTrap, element[0]);
10393      element.after(bottomFocusTrap);
10394    }
10395
10396    /**
10397     * Prevents screen reader interaction behind modal window
10398     * on swipe interfaces
10399     */
10400    function lockScreenReader(element, options) {
10401      var isHidden = true;
10402
10403      // get raw DOM node
10404      walkDOM(element[0]);
10405
10406      options.unlockScreenReader = function() {
10407        isHidden = false;
10408        walkDOM(element[0]);
10409
10410        options.unlockScreenReader = null;
10411      };
10412
10413      /**
10414       * Walk DOM to apply or remove aria-hidden on sibling nodes
10415       * and parent sibling nodes
10416       *
10417       */
10418      function walkDOM(element) {
10419        while (element.parentNode) {
10420          if (element === document.body) {
10421            return;
10422          }
10423          var children = element.parentNode.children;
10424          for (var i = 0; i < children.length; i++) {
10425            // skip over child if it is an ascendant of the dialog
10426            // or a script or style tag
10427            if (element !== children[i] && !isNodeOneOf(children[i], ['SCRIPT', 'STYLE'])) {
10428              children[i].setAttribute('aria-hidden', isHidden);
10429            }
10430          }
10431
10432          walkDOM(element = element.parentNode);
10433        }
10434      }
10435    }
10436
10437    /**
10438     * Ensure the dialog container fill-stretches to the viewport
10439     */
10440    function stretchDialogContainerToViewport(container, options) {
10441      var isFixed = $window.getComputedStyle($document[0].body).position == 'fixed';
10442      var backdrop = options.backdrop ? $window.getComputedStyle(options.backdrop[0]) : null;
10443      var height = backdrop ? Math.min($document[0].body.clientHeight, Math.ceil(Math.abs(parseInt(backdrop.height, 10)))) : 0;
10444
10445      var previousStyles = {
10446        top: container.css('top'),
10447        height: container.css('height')
10448      };
10449
10450      // If the body is fixed, determine the distance to the viewport in relative from the parent.
10451      var parentTop = Math.abs(options.parent[0].getBoundingClientRect().top);
10452
10453      container.css({
10454        top: (isFixed ? parentTop : 0) + 'px',
10455        height: height ? height + 'px' : '100%'
10456      });
10457
10458      return function() {
10459        // Reverts the modified styles back to the previous values.
10460        // This is needed for contentElements, which should have the same styles after close
10461        // as before.
10462        container.css(previousStyles);
10463      };
10464    }
10465
10466    /**
10467     *  Dialog open and pop-in animation
10468     */
10469    function dialogPopIn(container, options) {
10470      // Add the `md-dialog-container` to the DOM
10471      options.parent.append(container);
10472      options.reverseContainerStretch = stretchDialogContainerToViewport(container, options);
10473
10474      var dialogEl = container.find('md-dialog');
10475      var animator = $mdUtil.dom.animator;
10476      var buildTranslateToOrigin = animator.calculateZoomToOrigin;
10477      var translateOptions = {transitionInClass: 'md-transition-in', transitionOutClass: 'md-transition-out'};
10478      var from = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.openFrom || options.origin));
10479      var to = animator.toTransformCss("");  // defaults to center display (or parent or $rootElement)
10480
10481      dialogEl.toggleClass('md-dialog-fullscreen', !!options.fullscreen);
10482
10483      return animator
10484        .translate3d(dialogEl, from, to, translateOptions)
10485        .then(function(animateReversal) {
10486
10487          // Build a reversal translate function synced to this translation...
10488          options.reverseAnimate = function() {
10489            delete options.reverseAnimate;
10490
10491            if (options.closeTo) {
10492              // Using the opposite classes to create a close animation to the closeTo element
10493              translateOptions = {transitionInClass: 'md-transition-out', transitionOutClass: 'md-transition-in'};
10494              from = to;
10495              to = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.closeTo));
10496
10497              return animator
10498                .translate3d(dialogEl, from, to,translateOptions);
10499            }
10500
10501            return animateReversal(
10502              to = animator.toTransformCss(
10503                // in case the origin element has moved or is hidden,
10504                // let's recalculate the translateCSS
10505                buildTranslateToOrigin(dialogEl, options.origin)
10506              )
10507            );
10508
10509          };
10510
10511          // Function to revert the generated animation styles on the dialog element.
10512          // Useful when using a contentElement instead of a template.
10513          options.clearAnimate = function() {
10514            delete options.clearAnimate;
10515
10516            // Remove the transition classes, added from $animateCSS, since those can't be removed
10517            // by reversely running the animator.
10518            dialogEl.removeClass([
10519              translateOptions.transitionOutClass,
10520              translateOptions.transitionInClass
10521            ].join(' '));
10522
10523            // Run the animation reversely to remove the previous added animation styles.
10524            return animator.translate3d(dialogEl, to, animator.toTransformCss(''), {});
10525          };
10526
10527          return true;
10528        });
10529    }
10530
10531    /**
10532     * Dialog close and pop-out animation
10533     */
10534    function dialogPopOut(container, options) {
10535      return options.reverseAnimate().then(function() {
10536        if (options.contentElement) {
10537          // When we use a contentElement, we want the element to be the same as before.
10538          // That means, that we have to clear all the animation properties, like transform.
10539          options.clearAnimate();
10540        }
10541      });
10542    }
10543
10544    /**
10545     * Utility function to filter out raw DOM nodes
10546     */
10547    function isNodeOneOf(elem, nodeTypeArray) {
10548      if (nodeTypeArray.indexOf(elem.nodeName) !== -1) {
10549        return true;
10550      }
10551    }
10552
10553  }
10554}
10555
10556})();
10557(function(){
10558"use strict";
10559
10560/**
10561 * @ngdoc module
10562 * @name material.components.divider
10563 * @description Divider module!
10564 */
10565MdDividerDirective.$inject = ["$mdTheming"];
10566angular.module('material.components.divider', [
10567  'material.core'
10568])
10569  .directive('mdDivider', MdDividerDirective);
10570
10571/**
10572 * @ngdoc directive
10573 * @name mdDivider
10574 * @module material.components.divider
10575 * @restrict E
10576 *
10577 * @description
10578 * Dividers group and separate content within lists and page layouts using strong visual and spatial distinctions. This divider is a thin rule, lightweight enough to not distract the user from content.
10579 *
10580 * @param {boolean=} md-inset Add this attribute to activate the inset divider style.
10581 * @usage
10582 * <hljs lang="html">
10583 * <md-divider></md-divider>
10584 *
10585 * <md-divider md-inset></md-divider>
10586 * </hljs>
10587 *
10588 */
10589function MdDividerDirective($mdTheming) {
10590  return {
10591    restrict: 'E',
10592    link: $mdTheming
10593  };
10594}
10595
10596})();
10597(function(){
10598"use strict";
10599
10600(function() {
10601  'use strict';
10602
10603  /**
10604   * @ngdoc module
10605   * @name material.components.fabActions
10606   */
10607  MdFabActionsDirective.$inject = ["$mdUtil"];
10608  angular
10609    .module('material.components.fabActions', ['material.core'])
10610    .directive('mdFabActions', MdFabActionsDirective);
10611
10612  /**
10613   * @ngdoc directive
10614   * @name mdFabActions
10615   * @module material.components.fabActions
10616   *
10617   * @restrict E
10618   *
10619   * @description
10620   * The `<md-fab-actions>` directive is used inside of a `<md-fab-speed-dial>` or
10621   * `<md-fab-toolbar>` directive to mark an element (or elements) as the actions and setup the
10622   * proper event listeners.
10623   *
10624   * @usage
10625   * See the `<md-fab-speed-dial>` or `<md-fab-toolbar>` directives for example usage.
10626   */
10627  function MdFabActionsDirective($mdUtil) {
10628    return {
10629      restrict: 'E',
10630
10631      require: ['^?mdFabSpeedDial', '^?mdFabToolbar'],
10632
10633      compile: function(element, attributes) {
10634        var children = element.children();
10635
10636        var hasNgRepeat = $mdUtil.prefixer().hasAttribute(children, 'ng-repeat');
10637
10638        // Support both ng-repeat and static content
10639        if (hasNgRepeat) {
10640          children.addClass('md-fab-action-item');
10641        } else {
10642          // Wrap every child in a new div and add a class that we can scale/fling independently
10643          children.wrap('<div class="md-fab-action-item">');
10644        }
10645      }
10646    };
10647  }
10648
10649})();
10650
10651})();
10652(function(){
10653"use strict";
10654
10655(function() {
10656  'use strict';
10657
10658  MdFabController.$inject = ["$scope", "$element", "$animate", "$mdUtil", "$mdConstant", "$timeout"];
10659  angular.module('material.components.fabShared', ['material.core'])
10660    .controller('MdFabController', MdFabController);
10661
10662  function MdFabController($scope, $element, $animate, $mdUtil, $mdConstant, $timeout) {
10663    var vm = this;
10664    var initialAnimationAttempts = 0;
10665
10666    // NOTE: We use async eval(s) below to avoid conflicts with any existing digest loops
10667
10668    vm.open = function() {
10669      $scope.$evalAsync("vm.isOpen = true");
10670    };
10671
10672    vm.close = function() {
10673      // Async eval to avoid conflicts with existing digest loops
10674      $scope.$evalAsync("vm.isOpen = false");
10675
10676      // Focus the trigger when the element closes so users can still tab to the next item
10677      $element.find('md-fab-trigger')[0].focus();
10678    };
10679
10680    // Toggle the open/close state when the trigger is clicked
10681    vm.toggle = function() {
10682      $scope.$evalAsync("vm.isOpen = !vm.isOpen");
10683    };
10684
10685    /*
10686     * Angular Lifecycle hook for newer Angular versions.
10687     * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
10688     */
10689    vm.$onInit = function() {
10690      setupDefaults();
10691      setupListeners();
10692      setupWatchers();
10693
10694      fireInitialAnimations();
10695    };
10696
10697    // For Angular 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
10698    // manually call the $onInit hook.
10699    if (angular.version.major === 1 && angular.version.minor <= 4) {
10700      this.$onInit();
10701    }
10702
10703    function setupDefaults() {
10704      // Set the default direction to 'down' if none is specified
10705      vm.direction = vm.direction || 'down';
10706
10707      // Set the default to be closed
10708      vm.isOpen = vm.isOpen || false;
10709
10710      // Start the keyboard interaction at the first action
10711      resetActionIndex();
10712
10713      // Add an animations waiting class so we know not to run
10714      $element.addClass('md-animations-waiting');
10715    }
10716
10717    function setupListeners() {
10718      var eventTypes = [
10719        'click', 'focusin', 'focusout'
10720      ];
10721
10722      // Add our listeners
10723      angular.forEach(eventTypes, function(eventType) {
10724        $element.on(eventType, parseEvents);
10725      });
10726
10727      // Remove our listeners when destroyed
10728      $scope.$on('$destroy', function() {
10729        angular.forEach(eventTypes, function(eventType) {
10730          $element.off(eventType, parseEvents);
10731        });
10732
10733        // remove any attached keyboard handlers in case element is removed while
10734        // speed dial is open
10735        disableKeyboard();
10736      });
10737    }
10738
10739    var closeTimeout;
10740    function parseEvents(event) {
10741      // If the event is a click, just handle it
10742      if (event.type == 'click') {
10743        handleItemClick(event);
10744      }
10745
10746      // If we focusout, set a timeout to close the element
10747      if (event.type == 'focusout' && !closeTimeout) {
10748        closeTimeout = $timeout(function() {
10749          vm.close();
10750        }, 100, false);
10751      }
10752
10753      // If we see a focusin and there is a timeout about to run, cancel it so we stay open
10754      if (event.type == 'focusin' && closeTimeout) {
10755        $timeout.cancel(closeTimeout);
10756        closeTimeout = null;
10757      }
10758    }
10759
10760    function resetActionIndex() {
10761      vm.currentActionIndex = -1;
10762    }
10763
10764    function setupWatchers() {
10765      // Watch for changes to the direction and update classes/attributes
10766      $scope.$watch('vm.direction', function(newDir, oldDir) {
10767        // Add the appropriate classes so we can target the direction in the CSS
10768        $animate.removeClass($element, 'md-' + oldDir);
10769        $animate.addClass($element, 'md-' + newDir);
10770
10771        // Reset the action index since it may have changed
10772        resetActionIndex();
10773      });
10774
10775      var trigger, actions;
10776
10777      // Watch for changes to md-open
10778      $scope.$watch('vm.isOpen', function(isOpen) {
10779        // Reset the action index since it may have changed
10780        resetActionIndex();
10781
10782        // We can't get the trigger/actions outside of the watch because the component hasn't been
10783        // linked yet, so we wait until the first watch fires to cache them.
10784        if (!trigger || !actions) {
10785          trigger = getTriggerElement();
10786          actions = getActionsElement();
10787        }
10788
10789        if (isOpen) {
10790          enableKeyboard();
10791        } else {
10792          disableKeyboard();
10793        }
10794
10795        var toAdd = isOpen ? 'md-is-open' : '';
10796        var toRemove = isOpen ? '' : 'md-is-open';
10797
10798        // Set the proper ARIA attributes
10799        trigger.attr('aria-haspopup', true);
10800        trigger.attr('aria-expanded', isOpen);
10801        actions.attr('aria-hidden', !isOpen);
10802
10803        // Animate the CSS classes
10804        $animate.setClass($element, toAdd, toRemove);
10805      });
10806    }
10807
10808    function fireInitialAnimations() {
10809      // If the element is actually visible on the screen
10810      if ($element[0].scrollHeight > 0) {
10811        // Fire our animation
10812        $animate.addClass($element, '_md-animations-ready').then(function() {
10813          // Remove the waiting class
10814          $element.removeClass('md-animations-waiting');
10815        });
10816      }
10817
10818      // Otherwise, try for up to 1 second before giving up
10819      else if (initialAnimationAttempts < 10) {
10820        $timeout(fireInitialAnimations, 100);
10821
10822        // Increment our counter
10823        initialAnimationAttempts = initialAnimationAttempts + 1;
10824      }
10825    }
10826
10827    function enableKeyboard() {
10828      $element.on('keydown', keyPressed);
10829
10830      // On the next tick, setup a check for outside clicks; we do this on the next tick to avoid
10831      // clicks/touches that result in the isOpen attribute changing (e.g. a bound radio button)
10832      $mdUtil.nextTick(function() {
10833        angular.element(document).on('click touchend', checkForOutsideClick);
10834      });
10835
10836      // TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but
10837      // this breaks accessibility, especially on mobile, since you have no arrow keys to press
10838      //resetActionTabIndexes();
10839    }
10840
10841    function disableKeyboard() {
10842      $element.off('keydown', keyPressed);
10843      angular.element(document).off('click touchend', checkForOutsideClick);
10844    }
10845
10846    function checkForOutsideClick(event) {
10847      if (event.target) {
10848        var closestTrigger = $mdUtil.getClosest(event.target, 'md-fab-trigger');
10849        var closestActions = $mdUtil.getClosest(event.target, 'md-fab-actions');
10850
10851        if (!closestTrigger && !closestActions) {
10852          vm.close();
10853        }
10854      }
10855    }
10856
10857    function keyPressed(event) {
10858      switch (event.which) {
10859        case $mdConstant.KEY_CODE.ESCAPE: vm.close(); event.preventDefault(); return false;
10860        case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false;
10861        case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false;
10862        case $mdConstant.KEY_CODE.RIGHT_ARROW: doKeyRight(event); return false;
10863        case $mdConstant.KEY_CODE.DOWN_ARROW: doKeyDown(event); return false;
10864      }
10865    }
10866
10867    function doActionPrev(event) {
10868      focusAction(event, -1);
10869    }
10870
10871    function doActionNext(event) {
10872      focusAction(event, 1);
10873    }
10874
10875    function focusAction(event, direction) {
10876      var actions = resetActionTabIndexes();
10877
10878      // Increment/decrement the counter with restrictions
10879      vm.currentActionIndex = vm.currentActionIndex + direction;
10880      vm.currentActionIndex = Math.min(actions.length - 1, vm.currentActionIndex);
10881      vm.currentActionIndex = Math.max(0, vm.currentActionIndex);
10882
10883      // Focus the element
10884      var focusElement =  angular.element(actions[vm.currentActionIndex]).children()[0];
10885      angular.element(focusElement).attr('tabindex', 0);
10886      focusElement.focus();
10887
10888      // Make sure the event doesn't bubble and cause something else
10889      event.preventDefault();
10890      event.stopImmediatePropagation();
10891    }
10892
10893    function resetActionTabIndexes() {
10894      // Grab all of the actions
10895      var actions = getActionsElement()[0].querySelectorAll('.md-fab-action-item');
10896
10897      // Disable all other actions for tabbing
10898      angular.forEach(actions, function(action) {
10899        angular.element(angular.element(action).children()[0]).attr('tabindex', -1);
10900      });
10901
10902      return actions;
10903    }
10904
10905    function doKeyLeft(event) {
10906      if (vm.direction === 'left') {
10907        doActionNext(event);
10908      } else {
10909        doActionPrev(event);
10910      }
10911    }
10912
10913    function doKeyUp(event) {
10914      if (vm.direction === 'down') {
10915        doActionPrev(event);
10916      } else {
10917        doActionNext(event);
10918      }
10919    }
10920
10921    function doKeyRight(event) {
10922      if (vm.direction === 'left') {
10923        doActionPrev(event);
10924      } else {
10925        doActionNext(event);
10926      }
10927    }
10928
10929    function doKeyDown(event) {
10930      if (vm.direction === 'up') {
10931        doActionPrev(event);
10932      } else {
10933        doActionNext(event);
10934      }
10935    }
10936
10937    function isTrigger(element) {
10938      return $mdUtil.getClosest(element, 'md-fab-trigger');
10939    }
10940
10941    function isAction(element) {
10942      return $mdUtil.getClosest(element, 'md-fab-actions');
10943    }
10944
10945    function handleItemClick(event) {
10946      if (isTrigger(event.target)) {
10947        vm.toggle();
10948      }
10949
10950      if (isAction(event.target)) {
10951        vm.close();
10952      }
10953    }
10954
10955    function getTriggerElement() {
10956      return $element.find('md-fab-trigger');
10957    }
10958
10959    function getActionsElement() {
10960      return $element.find('md-fab-actions');
10961    }
10962  }
10963})();
10964
10965})();
10966(function(){
10967"use strict";
10968
10969(function() {
10970  'use strict';
10971
10972  /**
10973   * The duration of the CSS animation in milliseconds.
10974   *
10975   * @type {number}
10976   */
10977  MdFabSpeedDialFlingAnimation.$inject = ["$timeout"];
10978  MdFabSpeedDialScaleAnimation.$inject = ["$timeout"];
10979  var cssAnimationDuration = 300;
10980
10981  /**
10982   * @ngdoc module
10983   * @name material.components.fabSpeedDial
10984   */
10985  angular
10986    // Declare our module
10987    .module('material.components.fabSpeedDial', [
10988      'material.core',
10989      'material.components.fabShared',
10990      'material.components.fabActions'
10991    ])
10992
10993    // Register our directive
10994    .directive('mdFabSpeedDial', MdFabSpeedDialDirective)
10995
10996    // Register our custom animations
10997    .animation('.md-fling', MdFabSpeedDialFlingAnimation)
10998    .animation('.md-scale', MdFabSpeedDialScaleAnimation)
10999
11000    // Register a service for each animation so that we can easily inject them into unit tests
11001    .service('mdFabSpeedDialFlingAnimation', MdFabSpeedDialFlingAnimation)
11002    .service('mdFabSpeedDialScaleAnimation', MdFabSpeedDialScaleAnimation);
11003
11004  /**
11005   * @ngdoc directive
11006   * @name mdFabSpeedDial
11007   * @module material.components.fabSpeedDial
11008   *
11009   * @restrict E
11010   *
11011   * @description
11012   * The `<md-fab-speed-dial>` directive is used to present a series of popup elements (usually
11013   * `<md-button>`s) for quick access to common actions.
11014   *
11015   * There are currently two animations available by applying one of the following classes to
11016   * the component:
11017   *
11018   *  - `md-fling` - The speed dial items appear from underneath the trigger and move into their
11019   *    appropriate positions.
11020   *  - `md-scale` - The speed dial items appear in their proper places by scaling from 0% to 100%.
11021   *
11022   * You may also easily position the trigger by applying one one of the following classes to the
11023   * `<md-fab-speed-dial>` element:
11024   *  - `md-fab-top-left`
11025   *  - `md-fab-top-right`
11026   *  - `md-fab-bottom-left`
11027   *  - `md-fab-bottom-right`
11028   *
11029   * These CSS classes use `position: absolute`, so you need to ensure that the container element
11030   * also uses `position: absolute` or `position: relative` in order for them to work.
11031   *
11032   * Additionally, you may use the standard `ng-mouseenter` and `ng-mouseleave` directives to
11033   * open or close the speed dial. However, if you wish to allow users to hover over the empty
11034   * space where the actions will appear, you must also add the `md-hover-full` class to the speed
11035   * dial element. Without this, the hover effect will only occur on top of the trigger.
11036   *
11037   * See the demos for more information.
11038   *
11039   * ## Troubleshooting
11040   *
11041   * If your speed dial shows the closing animation upon launch, you may need to use `ng-cloak` on
11042   * the parent container to ensure that it is only visible once ready. We have plans to remove this
11043   * necessity in the future.
11044   *
11045   * @usage
11046   * <hljs lang="html">
11047   * <md-fab-speed-dial md-direction="up" class="md-fling">
11048   *   <md-fab-trigger>
11049   *     <md-button aria-label="Add..."><md-icon md-svg-src="/img/icons/plus.svg"></md-icon></md-button>
11050   *   </md-fab-trigger>
11051   *
11052   *   <md-fab-actions>
11053   *     <md-button aria-label="Add User">
11054   *       <md-icon md-svg-src="/img/icons/user.svg"></md-icon>
11055   *     </md-button>
11056   *
11057   *     <md-button aria-label="Add Group">
11058   *       <md-icon md-svg-src="/img/icons/group.svg"></md-icon>
11059   *     </md-button>
11060   *   </md-fab-actions>
11061   * </md-fab-speed-dial>
11062   * </hljs>
11063   *
11064   * @param {string} md-direction From which direction you would like the speed dial to appear
11065   * relative to the trigger element.
11066   * @param {expression=} md-open Programmatically control whether or not the speed-dial is visible.
11067   */
11068  function MdFabSpeedDialDirective() {
11069    return {
11070      restrict: 'E',
11071
11072      scope: {
11073        direction: '@?mdDirection',
11074        isOpen: '=?mdOpen'
11075      },
11076
11077      bindToController: true,
11078      controller: 'MdFabController',
11079      controllerAs: 'vm',
11080
11081      link: FabSpeedDialLink
11082    };
11083
11084    function FabSpeedDialLink(scope, element) {
11085      // Prepend an element to hold our CSS variables so we can use them in the animations below
11086      element.prepend('<div class="_md-css-variables"></div>');
11087    }
11088  }
11089
11090  function MdFabSpeedDialFlingAnimation($timeout) {
11091    function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
11092
11093    function runAnimation(element) {
11094      // Don't run if we are still waiting and we are not ready
11095      if (element.hasClass('md-animations-waiting') && !element.hasClass('_md-animations-ready')) {
11096        return;
11097      }
11098
11099      var el = element[0];
11100      var ctrl = element.controller('mdFabSpeedDial');
11101      var items = el.querySelectorAll('.md-fab-action-item');
11102
11103      // Grab our trigger element
11104      var triggerElement = el.querySelector('md-fab-trigger');
11105
11106      // Grab our element which stores CSS variables
11107      var variablesElement = el.querySelector('._md-css-variables');
11108
11109      // Setup JS variables based on our CSS variables
11110      var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
11111
11112      // Always reset the items to their natural position/state
11113      angular.forEach(items, function(item, index) {
11114        var styles = item.style;
11115
11116        styles.transform = styles.webkitTransform = '';
11117        styles.transitionDelay = '';
11118        styles.opacity = 1;
11119
11120        // Make the items closest to the trigger have the highest z-index
11121        styles.zIndex = (items.length - index) + startZIndex;
11122      });
11123
11124      // Set the trigger to be above all of the actions so they disappear behind it.
11125      triggerElement.style.zIndex = startZIndex + items.length + 1;
11126
11127      // If the control is closed, hide the items behind the trigger
11128      if (!ctrl.isOpen) {
11129        angular.forEach(items, function(item, index) {
11130          var newPosition, axis;
11131          var styles = item.style;
11132
11133          // Make sure to account for differences in the dimensions of the trigger verses the items
11134          // so that we can properly center everything; this helps hide the item's shadows behind
11135          // the trigger.
11136          var triggerItemHeightOffset = (triggerElement.clientHeight - item.clientHeight) / 2;
11137          var triggerItemWidthOffset = (triggerElement.clientWidth - item.clientWidth) / 2;
11138
11139          switch (ctrl.direction) {
11140            case 'up':
11141              newPosition = (item.scrollHeight * (index + 1) + triggerItemHeightOffset);
11142              axis = 'Y';
11143              break;
11144            case 'down':
11145              newPosition = -(item.scrollHeight * (index + 1) + triggerItemHeightOffset);
11146              axis = 'Y';
11147              break;
11148            case 'left':
11149              newPosition = (item.scrollWidth * (index + 1) + triggerItemWidthOffset);
11150              axis = 'X';
11151              break;
11152            case 'right':
11153              newPosition = -(item.scrollWidth * (index + 1) + triggerItemWidthOffset);
11154              axis = 'X';
11155              break;
11156          }
11157
11158          var newTranslate = 'translate' + axis + '(' + newPosition + 'px)';
11159
11160          styles.transform = styles.webkitTransform = newTranslate;
11161        });
11162      }
11163    }
11164
11165    return {
11166      addClass: function(element, className, done) {
11167        if (element.hasClass('md-fling')) {
11168          runAnimation(element);
11169          delayDone(done);
11170        } else {
11171          done();
11172        }
11173      },
11174      removeClass: function(element, className, done) {
11175        runAnimation(element);
11176        delayDone(done);
11177      }
11178    };
11179  }
11180
11181  function MdFabSpeedDialScaleAnimation($timeout) {
11182    function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
11183
11184    var delay = 65;
11185
11186    function runAnimation(element) {
11187      var el = element[0];
11188      var ctrl = element.controller('mdFabSpeedDial');
11189      var items = el.querySelectorAll('.md-fab-action-item');
11190
11191      // Grab our element which stores CSS variables
11192      var variablesElement = el.querySelector('._md-css-variables');
11193
11194      // Setup JS variables based on our CSS variables
11195      var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
11196
11197      // Always reset the items to their natural position/state
11198      angular.forEach(items, function(item, index) {
11199        var styles = item.style,
11200          offsetDelay = index * delay;
11201
11202        styles.opacity = ctrl.isOpen ? 1 : 0;
11203        styles.transform = styles.webkitTransform = ctrl.isOpen ? 'scale(1)' : 'scale(0)';
11204        styles.transitionDelay = (ctrl.isOpen ? offsetDelay : (items.length - offsetDelay)) + 'ms';
11205
11206        // Make the items closest to the trigger have the highest z-index
11207        styles.zIndex = (items.length - index) + startZIndex;
11208      });
11209    }
11210
11211    return {
11212      addClass: function(element, className, done) {
11213        runAnimation(element);
11214        delayDone(done);
11215      },
11216
11217      removeClass: function(element, className, done) {
11218        runAnimation(element);
11219        delayDone(done);
11220      }
11221    };
11222  }
11223})();
11224
11225})();
11226(function(){
11227"use strict";
11228
11229(function() {
11230  'use strict';
11231
11232  /**
11233   * @ngdoc module
11234   * @name material.components.fabToolbar
11235   */
11236  angular
11237    // Declare our module
11238    .module('material.components.fabToolbar', [
11239      'material.core',
11240      'material.components.fabShared',
11241      'material.components.fabActions'
11242    ])
11243
11244    // Register our directive
11245    .directive('mdFabToolbar', MdFabToolbarDirective)
11246
11247    // Register our custom animations
11248    .animation('.md-fab-toolbar', MdFabToolbarAnimation)
11249
11250    // Register a service for the animation so that we can easily inject it into unit tests
11251    .service('mdFabToolbarAnimation', MdFabToolbarAnimation);
11252
11253  /**
11254   * @ngdoc directive
11255   * @name mdFabToolbar
11256   * @module material.components.fabToolbar
11257   *
11258   * @restrict E
11259   *
11260   * @description
11261   *
11262   * The `<md-fab-toolbar>` directive is used to present a toolbar of elements (usually `<md-button>`s)
11263   * for quick access to common actions when a floating action button is activated (via click or
11264   * keyboard navigation).
11265   *
11266   * You may also easily position the trigger by applying one one of the following classes to the
11267   * `<md-fab-toolbar>` element:
11268   *  - `md-fab-top-left`
11269   *  - `md-fab-top-right`
11270   *  - `md-fab-bottom-left`
11271   *  - `md-fab-bottom-right`
11272   *
11273   * These CSS classes use `position: absolute`, so you need to ensure that the container element
11274   * also uses `position: absolute` or `position: relative` in order for them to work.
11275   *
11276   * @usage
11277   *
11278   * <hljs lang="html">
11279   * <md-fab-toolbar md-direction='left'>
11280   *   <md-fab-trigger>
11281   *     <md-button aria-label="Add..."><md-icon md-svg-src="/img/icons/plus.svg"></md-icon></md-button>
11282   *   </md-fab-trigger>
11283   *
11284   *   <md-toolbar>
11285   *    <md-fab-actions>
11286   *      <md-button aria-label="Add User">
11287   *        <md-icon md-svg-src="/img/icons/user.svg"></md-icon>
11288   *      </md-button>
11289   *
11290   *      <md-button aria-label="Add Group">
11291   *        <md-icon md-svg-src="/img/icons/group.svg"></md-icon>
11292   *      </md-button>
11293   *    </md-fab-actions>
11294   *   </md-toolbar>
11295   * </md-fab-toolbar>
11296   * </hljs>
11297   *
11298   * @param {string} md-direction From which direction you would like the toolbar items to appear
11299   * relative to the trigger element. Supports `left` and `right` directions.
11300   * @param {expression=} md-open Programmatically control whether or not the toolbar is visible.
11301   */
11302  function MdFabToolbarDirective() {
11303    return {
11304      restrict: 'E',
11305      transclude: true,
11306      template: '<div class="md-fab-toolbar-wrapper">' +
11307      '  <div class="md-fab-toolbar-content" ng-transclude></div>' +
11308      '</div>',
11309
11310      scope: {
11311        direction: '@?mdDirection',
11312        isOpen: '=?mdOpen'
11313      },
11314
11315      bindToController: true,
11316      controller: 'MdFabController',
11317      controllerAs: 'vm',
11318
11319      link: link
11320    };
11321
11322    function link(scope, element, attributes) {
11323      // Add the base class for animations
11324      element.addClass('md-fab-toolbar');
11325
11326      // Prepend the background element to the trigger's button
11327      element.find('md-fab-trigger').find('button')
11328        .prepend('<div class="md-fab-toolbar-background"></div>');
11329    }
11330  }
11331
11332  function MdFabToolbarAnimation() {
11333
11334    function runAnimation(element, className, done) {
11335      // If no className was specified, don't do anything
11336      if (!className) {
11337        return;
11338      }
11339
11340      var el = element[0];
11341      var ctrl = element.controller('mdFabToolbar');
11342
11343      // Grab the relevant child elements
11344      var backgroundElement = el.querySelector('.md-fab-toolbar-background');
11345      var triggerElement = el.querySelector('md-fab-trigger button');
11346      var toolbarElement = el.querySelector('md-toolbar');
11347      var iconElement = el.querySelector('md-fab-trigger button md-icon');
11348      var actions = element.find('md-fab-actions').children();
11349
11350      // If we have both elements, use them to position the new background
11351      if (triggerElement && backgroundElement) {
11352        // Get our variables
11353        var color = window.getComputedStyle(triggerElement).getPropertyValue('background-color');
11354        var width = el.offsetWidth;
11355        var height = el.offsetHeight;
11356
11357        // Make it twice as big as it should be since we scale from the center
11358        var scale = 2 * (width / triggerElement.offsetWidth);
11359
11360        // Set some basic styles no matter what animation we're doing
11361        backgroundElement.style.backgroundColor = color;
11362        backgroundElement.style.borderRadius = width + 'px';
11363
11364        // If we're open
11365        if (ctrl.isOpen) {
11366          // Turn on toolbar pointer events when closed
11367          toolbarElement.style.pointerEvents = 'inherit';
11368
11369          backgroundElement.style.width = triggerElement.offsetWidth + 'px';
11370          backgroundElement.style.height = triggerElement.offsetHeight + 'px';
11371          backgroundElement.style.transform = 'scale(' + scale + ')';
11372
11373          // Set the next close animation to have the proper delays
11374          backgroundElement.style.transitionDelay = '0ms';
11375          iconElement && (iconElement.style.transitionDelay = '.3s');
11376
11377          // Apply a transition delay to actions
11378          angular.forEach(actions, function(action, index) {
11379            action.style.transitionDelay = (actions.length - index) * 25 + 'ms';
11380          });
11381        } else {
11382          // Turn off toolbar pointer events when closed
11383          toolbarElement.style.pointerEvents = 'none';
11384
11385          // Scale it back down to the trigger's size
11386          backgroundElement.style.transform = 'scale(1)';
11387
11388          // Reset the position
11389          backgroundElement.style.top = '0';
11390
11391          if (element.hasClass('md-right')) {
11392            backgroundElement.style.left = '0';
11393            backgroundElement.style.right = null;
11394          }
11395
11396          if (element.hasClass('md-left')) {
11397            backgroundElement.style.right = '0';
11398            backgroundElement.style.left = null;
11399          }
11400
11401          // Set the next open animation to have the proper delays
11402          backgroundElement.style.transitionDelay = '200ms';
11403          iconElement && (iconElement.style.transitionDelay = '0ms');
11404
11405          // Apply a transition delay to actions
11406          angular.forEach(actions, function(action, index) {
11407            action.style.transitionDelay = 200 + (index * 25) + 'ms';
11408          });
11409        }
11410      }
11411    }
11412
11413    return {
11414      addClass: function(element, className, done) {
11415        runAnimation(element, className, done);
11416        done();
11417      },
11418
11419      removeClass: function(element, className, done) {
11420        runAnimation(element, className, done);
11421        done();
11422      }
11423    };
11424  }
11425})();
11426
11427})();
11428(function(){
11429"use strict";
11430
11431/**
11432 * @ngdoc module
11433 * @name material.components.gridList
11434 */
11435GridListController.$inject = ["$mdUtil"];
11436GridLayoutFactory.$inject = ["$mdUtil"];
11437GridListDirective.$inject = ["$interpolate", "$mdConstant", "$mdGridLayout", "$mdMedia"];
11438GridTileDirective.$inject = ["$mdMedia"];
11439angular.module('material.components.gridList', ['material.core'])
11440       .directive('mdGridList', GridListDirective)
11441       .directive('mdGridTile', GridTileDirective)
11442       .directive('mdGridTileFooter', GridTileCaptionDirective)
11443       .directive('mdGridTileHeader', GridTileCaptionDirective)
11444       .factory('$mdGridLayout', GridLayoutFactory);
11445
11446/**
11447 * @ngdoc directive
11448 * @name mdGridList
11449 * @module material.components.gridList
11450 * @restrict E
11451 * @description
11452 * Grid lists are an alternative to standard list views. Grid lists are distinct
11453 * from grids used for layouts and other visual presentations.
11454 *
11455 * A grid list is best suited to presenting a homogenous data type, typically
11456 * images, and is optimized for visual comprehension and differentiating between
11457 * like data types.
11458 *
11459 * A grid list is a continuous element consisting of tessellated, regular
11460 * subdivisions called cells that contain tiles (`md-grid-tile`).
11461 *
11462 * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7OVlEaXZ5YmU1Xzg/components_grids_usage2.png"
11463 *    style="width: 300px; height: auto; margin-right: 16px;" alt="Concept of grid explained visually">
11464 * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7VGhsOE5idWlJWXM/components_grids_usage3.png"
11465 *    style="width: 300px; height: auto;" alt="Grid concepts legend">
11466 *
11467 * Cells are arrayed vertically and horizontally within the grid.
11468 *
11469 * Tiles hold content and can span one or more cells vertically or horizontally.
11470 *
11471 * ### Responsive Attributes
11472 *
11473 * The `md-grid-list` directive supports "responsive" attributes, which allow
11474 * different `md-cols`, `md-gutter` and `md-row-height` values depending on the
11475 * currently matching media query.
11476 *
11477 * In order to set a responsive attribute, first define the fallback value with
11478 * the standard attribute name, then add additional attributes with the
11479 * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
11480 * (ie. `md-cols-lg="8"`)
11481 *
11482 * @param {number} md-cols Number of columns in the grid.
11483 * @param {string} md-row-height One of
11484 * <ul>
11485 *   <li>CSS length - Fixed height rows (eg. `8px` or `1rem`)</li>
11486 *   <li>`{width}:{height}` - Ratio of width to height (eg.
11487 *   `md-row-height="16:9"`)</li>
11488 *   <li>`"fit"` - Height will be determined by subdividing the available
11489 *   height by the number of rows</li>
11490 * </ul>
11491 * @param {string=} md-gutter The amount of space between tiles in CSS units
11492 *     (default 1px)
11493 * @param {expression=} md-on-layout Expression to evaluate after layout. Event
11494 *     object is available as `$event`, and contains performance information.
11495 *
11496 * @usage
11497 * Basic:
11498 * <hljs lang="html">
11499 * <md-grid-list md-cols="5" md-gutter="1em" md-row-height="4:3">
11500 *   <md-grid-tile></md-grid-tile>
11501 * </md-grid-list>
11502 * </hljs>
11503 *
11504 * Fixed-height rows:
11505 * <hljs lang="html">
11506 * <md-grid-list md-cols="4" md-row-height="200px" ...>
11507 *   <md-grid-tile></md-grid-tile>
11508 * </md-grid-list>
11509 * </hljs>
11510 *
11511 * Fit rows:
11512 * <hljs lang="html">
11513 * <md-grid-list md-cols="4" md-row-height="fit" style="height: 400px;" ...>
11514 *   <md-grid-tile></md-grid-tile>
11515 * </md-grid-list>
11516 * </hljs>
11517 *
11518 * Using responsive attributes:
11519 * <hljs lang="html">
11520 * <md-grid-list
11521 *     md-cols-sm="2"
11522 *     md-cols-md="4"
11523 *     md-cols-lg="8"
11524 *     md-cols-gt-lg="12"
11525 *     ...>
11526 *   <md-grid-tile></md-grid-tile>
11527 * </md-grid-list>
11528 * </hljs>
11529 */
11530function GridListDirective($interpolate, $mdConstant, $mdGridLayout, $mdMedia) {
11531  return {
11532    restrict: 'E',
11533    controller: GridListController,
11534    scope: {
11535      mdOnLayout: '&'
11536    },
11537    link: postLink
11538  };
11539
11540  function postLink(scope, element, attrs, ctrl) {
11541    element.addClass('_md');     // private md component indicator for styling
11542    
11543    // Apply semantics
11544    element.attr('role', 'list');
11545
11546    // Provide the controller with a way to trigger layouts.
11547    ctrl.layoutDelegate = layoutDelegate;
11548
11549    var invalidateLayout = angular.bind(ctrl, ctrl.invalidateLayout),
11550        unwatchAttrs = watchMedia();
11551      scope.$on('$destroy', unwatchMedia);
11552
11553    /**
11554     * Watches for changes in media, invalidating layout as necessary.
11555     */
11556    function watchMedia() {
11557      for (var mediaName in $mdConstant.MEDIA) {
11558        $mdMedia(mediaName); // initialize
11559        $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
11560            .addListener(invalidateLayout);
11561      }
11562      return $mdMedia.watchResponsiveAttributes(
11563          ['md-cols', 'md-row-height', 'md-gutter'], attrs, layoutIfMediaMatch);
11564    }
11565
11566    function unwatchMedia() {
11567      ctrl.layoutDelegate = angular.noop;
11568
11569      unwatchAttrs();
11570      for (var mediaName in $mdConstant.MEDIA) {
11571        $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
11572            .removeListener(invalidateLayout);
11573      }
11574    }
11575
11576    /**
11577     * Performs grid layout if the provided mediaName matches the currently
11578     * active media type.
11579     */
11580    function layoutIfMediaMatch(mediaName) {
11581      if (mediaName == null) {
11582        // TODO(shyndman): It would be nice to only layout if we have
11583        // instances of attributes using this media type
11584        ctrl.invalidateLayout();
11585      } else if ($mdMedia(mediaName)) {
11586        ctrl.invalidateLayout();
11587      }
11588    }
11589
11590    var lastLayoutProps;
11591
11592    /**
11593     * Invokes the layout engine, and uses its results to lay out our
11594     * tile elements.
11595     *
11596     * @param {boolean} tilesInvalidated Whether tiles have been
11597     *    added/removed/moved since the last layout. This is to avoid situations
11598     *    where tiles are replaced with properties identical to their removed
11599     *    counterparts.
11600     */
11601    function layoutDelegate(tilesInvalidated) {
11602      var tiles = getTileElements();
11603      var props = {
11604        tileSpans: getTileSpans(tiles),
11605        colCount: getColumnCount(),
11606        rowMode: getRowMode(),
11607        rowHeight: getRowHeight(),
11608        gutter: getGutter()
11609      };
11610
11611      if (!tilesInvalidated && angular.equals(props, lastLayoutProps)) {
11612        return;
11613      }
11614
11615      var performance =
11616        $mdGridLayout(props.colCount, props.tileSpans, tiles)
11617          .map(function(tilePositions, rowCount) {
11618            return {
11619              grid: {
11620                element: element,
11621                style: getGridStyle(props.colCount, rowCount,
11622                    props.gutter, props.rowMode, props.rowHeight)
11623              },
11624              tiles: tilePositions.map(function(ps, i) {
11625                return {
11626                  element: angular.element(tiles[i]),
11627                  style: getTileStyle(ps.position, ps.spans,
11628                      props.colCount, rowCount,
11629                      props.gutter, props.rowMode, props.rowHeight)
11630                }
11631              })
11632            }
11633          })
11634          .reflow()
11635          .performance();
11636
11637      // Report layout
11638      scope.mdOnLayout({
11639        $event: {
11640          performance: performance
11641        }
11642      });
11643
11644      lastLayoutProps = props;
11645    }
11646
11647    // Use $interpolate to do some simple string interpolation as a convenience.
11648
11649    var startSymbol = $interpolate.startSymbol();
11650    var endSymbol = $interpolate.endSymbol();
11651
11652    // Returns an expression wrapped in the interpolator's start and end symbols.
11653    function expr(exprStr) {
11654      return startSymbol + exprStr + endSymbol;
11655    }
11656
11657    // The amount of space a single 1x1 tile would take up (either width or height), used as
11658    // a basis for other calculations. This consists of taking the base size percent (as would be
11659    // if evenly dividing the size between cells), and then subtracting the size of one gutter.
11660    // However, since there are no gutters on the edges, each tile only uses a fration
11661    // (gutterShare = numGutters / numCells) of the gutter size. (Imagine having one gutter per
11662    // tile, and then breaking up the extra gutter on the edge evenly among the cells).
11663    var UNIT = $interpolate(expr('share') + '% - (' + expr('gutter') + ' * ' + expr('gutterShare') + ')');
11664
11665    // The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value.
11666    // The position comes the size of a 1x1 tile plus gutter for each previous tile in the
11667    // row/column (offset).
11668    var POSITION  = $interpolate('calc((' + expr('unit') + ' + ' + expr('gutter') + ') * ' + expr('offset') + ')');
11669
11670    // The actual size of a tile, e.g., width or height, taking rowSpan or colSpan into account.
11671    // This is computed by multiplying the base unit by the rowSpan/colSpan, and then adding back
11672    // in the space that the gutter would normally have used (which was already accounted for in
11673    // the base unit calculation).
11674    var DIMENSION = $interpolate('calc((' + expr('unit') + ') * ' + expr('span') + ' + (' + expr('span') + ' - 1) * ' + expr('gutter') + ')');
11675
11676    /**
11677     * Gets the styles applied to a tile element described by the given parameters.
11678     * @param {{row: number, col: number}} position The row and column indices of the tile.
11679     * @param {{row: number, col: number}} spans The rowSpan and colSpan of the tile.
11680     * @param {number} colCount The number of columns.
11681     * @param {number} rowCount The number of rows.
11682     * @param {string} gutter The amount of space between tiles. This will be something like
11683     *     '5px' or '2em'.
11684     * @param {string} rowMode The row height mode. Can be one of:
11685     *     'fixed': all rows have a fixed size, given by rowHeight,
11686     *     'ratio': row height defined as a ratio to width, or
11687     *     'fit': fit to the grid-list element height, divinding evenly among rows.
11688     * @param {string|number} rowHeight The height of a row. This is only used for 'fixed' mode and
11689     *     for 'ratio' mode. For 'ratio' mode, this is the *ratio* of width-to-height (e.g., 0.75).
11690     * @returns {Object} Map of CSS properties to be applied to the style element. Will define
11691     *     values for top, left, width, height, marginTop, and paddingTop.
11692     */
11693    function getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) {
11694      // TODO(shyndman): There are style caching opportunities here.
11695
11696      // Percent of the available horizontal space that one column takes up.
11697      var hShare = (1 / colCount) * 100;
11698
11699      // Fraction of the gutter size that each column takes up.
11700      var hGutterShare = (colCount - 1) / colCount;
11701
11702      // Base horizontal size of a column.
11703      var hUnit = UNIT({share: hShare, gutterShare: hGutterShare, gutter: gutter});
11704
11705      // The width and horizontal position of each tile is always calculated the same way, but the
11706      // height and vertical position depends on the rowMode.
11707      var style = {
11708        left: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }),
11709        width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }),
11710        // resets
11711        paddingTop: '',
11712        marginTop: '',
11713        top: '',
11714        height: ''
11715      };
11716
11717      switch (rowMode) {
11718        case 'fixed':
11719          // In fixed mode, simply use the given rowHeight.
11720          style.top = POSITION({ unit: rowHeight, offset: position.row, gutter: gutter });
11721          style.height = DIMENSION({ unit: rowHeight, span: spans.row, gutter: gutter });
11722          break;
11723
11724        case 'ratio':
11725          // Percent of the available vertical space that one row takes up. Here, rowHeight holds
11726          // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333.
11727          var vShare = hShare / rowHeight;
11728
11729          // Base veritcal size of a row.
11730          var vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
11731
11732          // padidngTop and marginTop are used to maintain the given aspect ratio, as
11733          // a percentage-based value for these properties is applied to the *width* of the
11734          // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
11735          style.paddingTop = DIMENSION({ unit: vUnit, span: spans.row, gutter: gutter});
11736          style.marginTop = POSITION({ unit: vUnit, offset: position.row, gutter: gutter });
11737          break;
11738
11739        case 'fit':
11740          // Fraction of the gutter size that each column takes up.
11741          var vGutterShare = (rowCount - 1) / rowCount;
11742
11743          // Percent of the available vertical space that one row takes up.
11744          var vShare = (1 / rowCount) * 100;
11745
11746          // Base vertical size of a row.
11747          var vUnit = UNIT({share: vShare, gutterShare: vGutterShare, gutter: gutter});
11748
11749          style.top = POSITION({unit: vUnit, offset: position.row, gutter: gutter});
11750          style.height = DIMENSION({unit: vUnit, span: spans.row, gutter: gutter});
11751          break;
11752      }
11753
11754      return style;
11755    }
11756
11757    function getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) {
11758      var style = {};
11759
11760      switch(rowMode) {
11761        case 'fixed':
11762          style.height = DIMENSION({ unit: rowHeight, span: rowCount, gutter: gutter });
11763          style.paddingBottom = '';
11764          break;
11765
11766        case 'ratio':
11767          // rowHeight is width / height
11768          var hGutterShare = colCount === 1 ? 0 : (colCount - 1) / colCount,
11769              hShare = (1 / colCount) * 100,
11770              vShare = hShare * (1 / rowHeight),
11771              vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
11772
11773          style.height = '';
11774          style.paddingBottom = DIMENSION({ unit: vUnit, span: rowCount, gutter: gutter});
11775          break;
11776
11777        case 'fit':
11778          // noop, as the height is user set
11779          break;
11780      }
11781
11782      return style;
11783    }
11784
11785    function getTileElements() {
11786      return [].filter.call(element.children(), function(ele) {
11787        return ele.tagName == 'MD-GRID-TILE' && !ele.$$mdDestroyed;
11788      });
11789    }
11790
11791    /**
11792     * Gets an array of objects containing the rowspan and colspan for each tile.
11793     * @returns {Array<{row: number, col: number}>}
11794     */
11795    function getTileSpans(tileElements) {
11796      return [].map.call(tileElements, function(ele) {
11797        var ctrl = angular.element(ele).controller('mdGridTile');
11798        return {
11799          row: parseInt(
11800              $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-rowspan'), 10) || 1,
11801          col: parseInt(
11802              $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-colspan'), 10) || 1
11803        };
11804      });
11805    }
11806
11807    function getColumnCount() {
11808      var colCount = parseInt($mdMedia.getResponsiveAttribute(attrs, 'md-cols'), 10);
11809      if (isNaN(colCount)) {
11810        throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value';
11811      }
11812      return colCount;
11813    }
11814
11815    function getGutter() {
11816      return applyDefaultUnit($mdMedia.getResponsiveAttribute(attrs, 'md-gutter') || 1);
11817    }
11818
11819    function getRowHeight() {
11820      var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
11821      if (!rowHeight) {
11822        throw 'md-grid-list: md-row-height attribute was not found';
11823      }
11824
11825      switch (getRowMode()) {
11826        case 'fixed':
11827          return applyDefaultUnit(rowHeight);
11828        case 'ratio':
11829          var whRatio = rowHeight.split(':');
11830          return parseFloat(whRatio[0]) / parseFloat(whRatio[1]);
11831        case 'fit':
11832          return 0; // N/A
11833      }
11834    }
11835
11836    function getRowMode() {
11837      var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
11838      if (!rowHeight) {
11839        throw 'md-grid-list: md-row-height attribute was not found';
11840      }
11841
11842      if (rowHeight == 'fit') {
11843        return 'fit';
11844      } else if (rowHeight.indexOf(':') !== -1) {
11845        return 'ratio';
11846      } else {
11847        return 'fixed';
11848      }
11849    }
11850
11851    function applyDefaultUnit(val) {
11852      return /\D$/.test(val) ? val : val + 'px';
11853    }
11854  }
11855}
11856
11857/* @ngInject */
11858function GridListController($mdUtil) {
11859  this.layoutInvalidated = false;
11860  this.tilesInvalidated = false;
11861  this.$timeout_ = $mdUtil.nextTick;
11862  this.layoutDelegate = angular.noop;
11863}
11864
11865GridListController.prototype = {
11866  invalidateTiles: function() {
11867    this.tilesInvalidated = true;
11868    this.invalidateLayout();
11869  },
11870
11871  invalidateLayout: function() {
11872    if (this.layoutInvalidated) {
11873      return;
11874    }
11875    this.layoutInvalidated = true;
11876    this.$timeout_(angular.bind(this, this.layout));
11877  },
11878
11879  layout: function() {
11880    try {
11881      this.layoutDelegate(this.tilesInvalidated);
11882    } finally {
11883      this.layoutInvalidated = false;
11884      this.tilesInvalidated = false;
11885    }
11886  }
11887};
11888
11889
11890/* @ngInject */
11891function GridLayoutFactory($mdUtil) {
11892  var defaultAnimator = GridTileAnimator;
11893
11894  /**
11895   * Set the reflow animator callback
11896   */
11897  GridLayout.animateWith = function(customAnimator) {
11898    defaultAnimator = !angular.isFunction(customAnimator) ? GridTileAnimator : customAnimator;
11899  };
11900
11901  return GridLayout;
11902
11903  /**
11904   * Publish layout function
11905   */
11906  function GridLayout(colCount, tileSpans) {
11907      var self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime;
11908
11909      layoutTime = $mdUtil.time(function() {
11910        layoutInfo = calculateGridFor(colCount, tileSpans);
11911      });
11912
11913      return self = {
11914
11915        /**
11916         * An array of objects describing each tile's position in the grid.
11917         */
11918        layoutInfo: function() {
11919          return layoutInfo;
11920        },
11921
11922        /**
11923         * Maps grid positioning to an element and a set of styles using the
11924         * provided updateFn.
11925         */
11926        map: function(updateFn) {
11927          mapTime = $mdUtil.time(function() {
11928            var info = self.layoutInfo();
11929            gridStyles = updateFn(info.positioning, info.rowCount);
11930          });
11931          return self;
11932        },
11933
11934        /**
11935         * Default animator simply sets the element.css( <styles> ). An alternate
11936         * animator can be provided as an argument. The function has the following
11937         * signature:
11938         *
11939         *    function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>)
11940         */
11941        reflow: function(animatorFn) {
11942          reflowTime = $mdUtil.time(function() {
11943            var animator = animatorFn || defaultAnimator;
11944            animator(gridStyles.grid, gridStyles.tiles);
11945          });
11946          return self;
11947        },
11948
11949        /**
11950         * Timing for the most recent layout run.
11951         */
11952        performance: function() {
11953          return {
11954            tileCount: tileSpans.length,
11955            layoutTime: layoutTime,
11956            mapTime: mapTime,
11957            reflowTime: reflowTime,
11958            totalTime: layoutTime + mapTime + reflowTime
11959          };
11960        }
11961      };
11962    }
11963
11964  /**
11965   * Default Gridlist animator simple sets the css for each element;
11966   * NOTE: any transitions effects must be manually set in the CSS.
11967   * e.g.
11968   *
11969   *  md-grid-tile {
11970   *    transition: all 700ms ease-out 50ms;
11971   *  }
11972   *
11973   */
11974  function GridTileAnimator(grid, tiles) {
11975    grid.element.css(grid.style);
11976    tiles.forEach(function(t) {
11977      t.element.css(t.style);
11978    })
11979  }
11980
11981  /**
11982   * Calculates the positions of tiles.
11983   *
11984   * The algorithm works as follows:
11985   *    An Array<Number> with length colCount (spaceTracker) keeps track of
11986   *    available tiling positions, where elements of value 0 represents an
11987   *    empty position. Space for a tile is reserved by finding a sequence of
11988   *    0s with length <= than the tile's colspan. When such a space has been
11989   *    found, the occupied tile positions are incremented by the tile's
11990   *    rowspan value, as these positions have become unavailable for that
11991   *    many rows.
11992   *
11993   *    If the end of a row has been reached without finding space for the
11994   *    tile, spaceTracker's elements are each decremented by 1 to a minimum
11995   *    of 0. Rows are searched in this fashion until space is found.
11996   */
11997  function calculateGridFor(colCount, tileSpans) {
11998    var curCol = 0,
11999        curRow = 0,
12000        spaceTracker = newSpaceTracker();
12001
12002    return {
12003      positioning: tileSpans.map(function(spans, i) {
12004        return {
12005          spans: spans,
12006          position: reserveSpace(spans, i)
12007        };
12008      }),
12009      rowCount: curRow + Math.max.apply(Math, spaceTracker)
12010    };
12011
12012    function reserveSpace(spans, i) {
12013      if (spans.col > colCount) {
12014        throw 'md-grid-list: Tile at position ' + i + ' has a colspan ' +
12015            '(' + spans.col + ') that exceeds the column count ' +
12016            '(' + colCount + ')';
12017      }
12018
12019      var start = 0,
12020          end = 0;
12021
12022      // TODO(shyndman): This loop isn't strictly necessary if you can
12023      // determine the minimum number of rows before a space opens up. To do
12024      // this, recognize that you've iterated across an entire row looking for
12025      // space, and if so fast-forward by the minimum rowSpan count. Repeat
12026      // until the required space opens up.
12027      while (end - start < spans.col) {
12028        if (curCol >= colCount) {
12029          nextRow();
12030          continue;
12031        }
12032
12033        start = spaceTracker.indexOf(0, curCol);
12034        if (start === -1 || (end = findEnd(start + 1)) === -1) {
12035          start = end = 0;
12036          nextRow();
12037          continue;
12038        }
12039
12040        curCol = end + 1;
12041      }
12042
12043      adjustRow(start, spans.col, spans.row);
12044      curCol = start + spans.col;
12045
12046      return {
12047        col: start,
12048        row: curRow
12049      };
12050    }
12051
12052    function nextRow() {
12053      curCol = 0;
12054      curRow++;
12055      adjustRow(0, colCount, -1); // Decrement row spans by one
12056    }
12057
12058    function adjustRow(from, cols, by) {
12059      for (var i = from; i < from + cols; i++) {
12060        spaceTracker[i] = Math.max(spaceTracker[i] + by, 0);
12061      }
12062    }
12063
12064    function findEnd(start) {
12065      var i;
12066      for (i = start; i < spaceTracker.length; i++) {
12067        if (spaceTracker[i] !== 0) {
12068          return i;
12069        }
12070      }
12071
12072      if (i === spaceTracker.length) {
12073        return i;
12074      }
12075    }
12076
12077    function newSpaceTracker() {
12078      var tracker = [];
12079      for (var i = 0; i < colCount; i++) {
12080        tracker.push(0);
12081      }
12082      return tracker;
12083    }
12084  }
12085}
12086
12087/**
12088 * @ngdoc directive
12089 * @name mdGridTile
12090 * @module material.components.gridList
12091 * @restrict E
12092 * @description
12093 * Tiles contain the content of an `md-grid-list`. They span one or more grid
12094 * cells vertically or horizontally, and use `md-grid-tile-{footer,header}` to
12095 * display secondary content.
12096 *
12097 * ### Responsive Attributes
12098 *
12099 * The `md-grid-tile` directive supports "responsive" attributes, which allow
12100 * different `md-rowspan` and `md-colspan` values depending on the currently
12101 * matching media query.
12102 *
12103 * In order to set a responsive attribute, first define the fallback value with
12104 * the standard attribute name, then add additional attributes with the
12105 * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
12106 * (ie. `md-colspan-sm="4"`)
12107 *
12108 * @param {number=} md-colspan The number of columns to span (default 1). Cannot
12109 *    exceed the number of columns in the grid. Supports interpolation.
12110 * @param {number=} md-rowspan The number of rows to span (default 1). Supports
12111 *     interpolation.
12112 *
12113 * @usage
12114 * With header:
12115 * <hljs lang="html">
12116 * <md-grid-tile>
12117 *   <md-grid-tile-header>
12118 *     <h3>This is a header</h3>
12119 *   </md-grid-tile-header>
12120 * </md-grid-tile>
12121 * </hljs>
12122 *
12123 * With footer:
12124 * <hljs lang="html">
12125 * <md-grid-tile>
12126 *   <md-grid-tile-footer>
12127 *     <h3>This is a footer</h3>
12128 *   </md-grid-tile-footer>
12129 * </md-grid-tile>
12130 * </hljs>
12131 *
12132 * Spanning multiple rows/columns:
12133 * <hljs lang="html">
12134 * <md-grid-tile md-colspan="2" md-rowspan="3">
12135 * </md-grid-tile>
12136 * </hljs>
12137 *
12138 * Responsive attributes:
12139 * <hljs lang="html">
12140 * <md-grid-tile md-colspan="1" md-colspan-sm="3" md-colspan-md="5">
12141 * </md-grid-tile>
12142 * </hljs>
12143 */
12144function GridTileDirective($mdMedia) {
12145  return {
12146    restrict: 'E',
12147    require: '^mdGridList',
12148    template: '<figure ng-transclude></figure>',
12149    transclude: true,
12150    scope: {},
12151    // Simple controller that exposes attributes to the grid directive
12152    controller: ["$attrs", function($attrs) {
12153      this.$attrs = $attrs;
12154    }],
12155    link: postLink
12156  };
12157
12158  function postLink(scope, element, attrs, gridCtrl) {
12159    // Apply semantics
12160    element.attr('role', 'listitem');
12161
12162    // If our colspan or rowspan changes, trigger a layout
12163    var unwatchAttrs = $mdMedia.watchResponsiveAttributes(['md-colspan', 'md-rowspan'],
12164        attrs, angular.bind(gridCtrl, gridCtrl.invalidateLayout));
12165
12166    // Tile registration/deregistration
12167    gridCtrl.invalidateTiles();
12168    scope.$on('$destroy', function() {
12169      // Mark the tile as destroyed so it is no longer considered in layout,
12170      // even if the DOM element sticks around (like during a leave animation)
12171      element[0].$$mdDestroyed = true;
12172      unwatchAttrs();
12173      gridCtrl.invalidateLayout();
12174    });
12175
12176    if (angular.isDefined(scope.$parent.$index)) {
12177      scope.$watch(function() { return scope.$parent.$index; },
12178        function indexChanged(newIdx, oldIdx) {
12179          if (newIdx === oldIdx) {
12180            return;
12181          }
12182          gridCtrl.invalidateTiles();
12183        });
12184    }
12185  }
12186}
12187
12188
12189function GridTileCaptionDirective() {
12190  return {
12191    template: '<figcaption ng-transclude></figcaption>',
12192    transclude: true
12193  };
12194}
12195
12196})();
12197(function(){
12198"use strict";
12199
12200/**
12201 * @ngdoc module
12202 * @name material.components.icon
12203 * @description
12204 * Icon
12205 */
12206angular.module('material.components.icon', ['material.core']);
12207
12208})();
12209(function(){
12210"use strict";
12211
12212/**
12213 * @ngdoc module
12214 * @name material.components.input
12215 */
12216mdInputContainerDirective.$inject = ["$mdTheming", "$parse"];
12217inputTextareaDirective.$inject = ["$mdUtil", "$window", "$mdAria", "$timeout", "$mdGesture"];
12218mdMaxlengthDirective.$inject = ["$animate", "$mdUtil"];
12219placeholderDirective.$inject = ["$compile"];
12220ngMessageDirective.$inject = ["$mdUtil"];
12221mdSelectOnFocusDirective.$inject = ["$timeout"];
12222mdInputInvalidMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
12223ngMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
12224ngMessageAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
12225var inputModule = angular.module('material.components.input', [
12226    'material.core'
12227  ])
12228  .directive('mdInputContainer', mdInputContainerDirective)
12229  .directive('label', labelDirective)
12230  .directive('input', inputTextareaDirective)
12231  .directive('textarea', inputTextareaDirective)
12232  .directive('mdMaxlength', mdMaxlengthDirective)
12233  .directive('placeholder', placeholderDirective)
12234  .directive('ngMessages', ngMessagesDirective)
12235  .directive('ngMessage', ngMessageDirective)
12236  .directive('ngMessageExp', ngMessageDirective)
12237  .directive('mdSelectOnFocus', mdSelectOnFocusDirective)
12238
12239  .animation('.md-input-invalid', mdInputInvalidMessagesAnimation)
12240  .animation('.md-input-messages-animation', ngMessagesAnimation)
12241  .animation('.md-input-message-animation', ngMessageAnimation);
12242
12243// If we are running inside of tests; expose some extra services so that we can test them
12244if (window._mdMocksIncluded) {
12245  inputModule.service('$$mdInput', function() {
12246    return {
12247      // special accessor to internals... useful for testing
12248      messages: {
12249        show        : showInputMessages,
12250        hide        : hideInputMessages,
12251        getElement  : getMessagesElement
12252      }
12253    }
12254  })
12255
12256  // Register a service for each animation so that we can easily inject them into unit tests
12257  .service('mdInputInvalidAnimation', mdInputInvalidMessagesAnimation)
12258  .service('mdInputMessagesAnimation', ngMessagesAnimation)
12259  .service('mdInputMessageAnimation', ngMessageAnimation);
12260}
12261
12262/**
12263 * @ngdoc directive
12264 * @name mdInputContainer
12265 * @module material.components.input
12266 *
12267 * @restrict E
12268 *
12269 * @description
12270 * `<md-input-container>` is the parent of any input or textarea element.
12271 *
12272 * Input and textarea elements will not behave properly unless the md-input-container
12273 * parent is provided.
12274 *
12275 * A single `<md-input-container>` should contain only one `<input>` element, otherwise it will throw an error.
12276 *
12277 * <b>Exception:</b> Hidden inputs (`<input type="hidden" />`) are ignored and will not throw an error, so
12278 * you may combine these with other inputs.
12279 *
12280 * <b>Note:</b> When using `ngMessages` with your input element, make sure the message and container elements
12281 * are *block* elements, otherwise animations applied to the messages will not look as intended. Either use a `div` and
12282 * apply the `ng-message` and `ng-messages` classes respectively, or use the `md-block` class on your element.
12283 *
12284 * @param md-is-error {expression=} When the given expression evaluates to true, the input container
12285 *   will go into error state. Defaults to erroring if the input has been touched and is invalid.
12286 * @param md-no-float {boolean=} When present, `placeholder` attributes on the input will not be converted to floating
12287 *   labels.
12288 *
12289 * @usage
12290 * <hljs lang="html">
12291 *
12292 * <md-input-container>
12293 *   <label>Username</label>
12294 *   <input type="text" ng-model="user.name">
12295 * </md-input-container>
12296 *
12297 * <md-input-container>
12298 *   <label>Description</label>
12299 *   <textarea ng-model="user.description"></textarea>
12300 * </md-input-container>
12301 *
12302 * </hljs>
12303 *
12304 * <h3>When disabling floating labels</h3>
12305 * <hljs lang="html">
12306 *
12307 * <md-input-container md-no-float>
12308 *   <input type="text" placeholder="Non-Floating Label">
12309 * </md-input-container>
12310 *
12311 * </hljs>
12312 */
12313function mdInputContainerDirective($mdTheming, $parse) {
12314
12315  ContainerCtrl.$inject = ["$scope", "$element", "$attrs", "$animate"];
12316  var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT', 'MD-SELECT'];
12317
12318  var LEFT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
12319    return selectors.concat(['md-icon ~ ' + isel, '.md-icon ~ ' + isel]);
12320  }, []).join(",");
12321
12322  var RIGHT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
12323    return selectors.concat([isel + ' ~ md-icon', isel + ' ~ .md-icon']);
12324  }, []).join(",");
12325
12326  return {
12327    restrict: 'E',
12328    compile: compile,
12329    controller: ContainerCtrl
12330  };
12331
12332  function compile(tElement) {
12333    // Check for both a left & right icon
12334    var leftIcon = tElement[0].querySelector(LEFT_SELECTORS);
12335    var rightIcon = tElement[0].querySelector(RIGHT_SELECTORS);
12336
12337    if (leftIcon) { tElement.addClass('md-icon-left'); }
12338    if (rightIcon) { tElement.addClass('md-icon-right'); }
12339
12340    return function postLink(scope, element) {
12341      $mdTheming(element);
12342    };
12343  }
12344
12345  function ContainerCtrl($scope, $element, $attrs, $animate) {
12346    var self = this;
12347
12348    self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);
12349
12350    self.delegateClick = function() {
12351      self.input.focus();
12352    };
12353    self.element = $element;
12354    self.setFocused = function(isFocused) {
12355      $element.toggleClass('md-input-focused', !!isFocused);
12356    };
12357    self.setHasValue = function(hasValue) {
12358      $element.toggleClass('md-input-has-value', !!hasValue);
12359    };
12360    self.setHasPlaceholder = function(hasPlaceholder) {
12361      $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder);
12362    };
12363    self.setInvalid = function(isInvalid) {
12364      if (isInvalid) {
12365        $animate.addClass($element, 'md-input-invalid');
12366      } else {
12367        $animate.removeClass($element, 'md-input-invalid');
12368      }
12369    };
12370    $scope.$watch(function() {
12371      return self.label && self.input;
12372    }, function(hasLabelAndInput) {
12373      if (hasLabelAndInput && !self.label.attr('for')) {
12374        self.label.attr('for', self.input.attr('id'));
12375      }
12376    });
12377  }
12378}
12379
12380function labelDirective() {
12381  return {
12382    restrict: 'E',
12383    require: '^?mdInputContainer',
12384    link: function(scope, element, attr, containerCtrl) {
12385      if (!containerCtrl || attr.mdNoFloat || element.hasClass('md-container-ignore')) return;
12386
12387      containerCtrl.label = element;
12388      scope.$on('$destroy', function() {
12389        containerCtrl.label = null;
12390      });
12391    }
12392  };
12393}
12394
12395/**
12396 * @ngdoc directive
12397 * @name mdInput
12398 * @restrict E
12399 * @module material.components.input
12400 *
12401 * @description
12402 * You can use any `<input>` or `<textarea>` element as a child of an `<md-input-container>`. This
12403 * allows you to build complex forms for data entry.
12404 *
12405 * When the input is required and uses a floating label, then the label will automatically contain
12406 * an asterisk (`*`).<br/>
12407 * This behavior can be disabled by using the `md-no-asterisk` attribute.
12408 *
12409 * @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is
12410 *   specified, a character counter will be shown underneath the input.<br/><br/>
12411 *   The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
12412 *   want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
12413 *   or maxlength attributes.<br/><br/>
12414 *   **Note:** Only valid for text/string inputs (not numeric).
12415 *
12416 * @param {string=} aria-label Aria-label is required when no label is present.  A warning message
12417 *   will be logged in the console if not present.
12418 * @param {string=} placeholder An alternative approach to using aria-label when the label is not
12419 *   PRESENT. The placeholder text is copied to the aria-label attribute.
12420 * @param md-no-autogrow {boolean=} When present, textareas will not grow automatically.
12421 * @param md-no-asterisk {boolean=} When present, an asterisk will not be appended to the inputs floating label
12422 * @param md-no-resize {boolean=} Disables the textarea resize handle.
12423 * @param {number=} max-rows The maximum amount of rows for a textarea.
12424 * @param md-detect-hidden {boolean=} When present, textareas will be sized properly when they are
12425 *   revealed after being hidden. This is off by default for performance reasons because it
12426 *   guarantees a reflow every digest cycle.
12427 *
12428 * @usage
12429 * <hljs lang="html">
12430 * <md-input-container>
12431 *   <label>Color</label>
12432 *   <input type="text" ng-model="color" required md-maxlength="10">
12433 * </md-input-container>
12434 * </hljs>
12435 *
12436 * <h3>With Errors</h3>
12437 *
12438 * `md-input-container` also supports errors using the standard `ng-messages` directives and
12439 * animates the messages when they become visible using from the `ngEnter`/`ngLeave` events or
12440 * the `ngShow`/`ngHide` events.
12441 *
12442 * By default, the messages will be hidden until the input is in an error state. This is based off
12443 * of the `md-is-error` expression of the `md-input-container`. This gives the user a chance to
12444 * fill out the form before the errors become visible.
12445 *
12446 * <hljs lang="html">
12447 * <form name="colorForm">
12448 *   <md-input-container>
12449 *     <label>Favorite Color</label>
12450 *     <input name="favoriteColor" ng-model="favoriteColor" required>
12451 *     <div ng-messages="colorForm.favoriteColor.$error">
12452 *       <div ng-message="required">This is required!</div>
12453 *     </div>
12454 *   </md-input-container>
12455 * </form>
12456 * </hljs>
12457 *
12458 * We automatically disable this auto-hiding functionality if you provide any of the following
12459 * visibility directives on the `ng-messages` container:
12460 *
12461 *  - `ng-if`
12462 *  - `ng-show`/`ng-hide`
12463 *  - `ng-switch-when`/`ng-switch-default`
12464 *
12465 * You can also disable this functionality manually by adding the `md-auto-hide="false"` expression
12466 * to the `ng-messages` container. This may be helpful if you always want to see the error messages
12467 * or if you are building your own visibilty directive.
12468 *
12469 * _<b>Note:</b> The `md-auto-hide` attribute is a static string that is  only checked upon
12470 * initialization of the `ng-messages` directive to see if it equals the string `false`._
12471 *
12472 * <hljs lang="html">
12473 * <form name="userForm">
12474 *   <md-input-container>
12475 *     <label>Last Name</label>
12476 *     <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
12477 *     <div ng-messages="userForm.lastName.$error" ng-show="userForm.lastName.$dirty">
12478 *       <div ng-message="required">This is required!</div>
12479 *       <div ng-message="md-maxlength">That's too long!</div>
12480 *       <div ng-message="minlength">That's too short!</div>
12481 *     </div>
12482 *   </md-input-container>
12483 *   <md-input-container>
12484 *     <label>Biography</label>
12485 *     <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
12486 *     <div ng-messages="userForm.bio.$error" ng-show="userForm.bio.$dirty">
12487 *       <div ng-message="required">This is required!</div>
12488 *       <div ng-message="md-maxlength">That's too long!</div>
12489 *     </div>
12490 *   </md-input-container>
12491 *   <md-input-container>
12492 *     <input aria-label='title' ng-model='title'>
12493 *   </md-input-container>
12494 *   <md-input-container>
12495 *     <input placeholder='title' ng-model='title'>
12496 *   </md-input-container>
12497 * </form>
12498 * </hljs>
12499 *
12500 * <h3>Notes</h3>
12501 *
12502 * - Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
12503 * - Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
12504 *
12505 * The `md-input` and `md-input-container` directives use very specific positioning to achieve the
12506 * error animation effects. Therefore, it is *not* advised to use the Layout system inside of the
12507 * `<md-input-container>` tags. Instead, use relative or absolute positioning.
12508 *
12509 *
12510 * <h3>Textarea directive</h3>
12511 * The `textarea` element within a `md-input-container` has the following specific behavior:
12512 * - By default the `textarea` grows as the user types. This can be disabled via the `md-no-autogrow`
12513 * attribute.
12514 * - If a `textarea` has the `rows` attribute, it will treat the `rows` as the minimum height and will
12515 * continue growing as the user types. For example a textarea with `rows="3"` will be 3 lines of text
12516 * high initially. If no rows are specified, the directive defaults to 1.
12517 * - The textarea's height gets set on initialization, as well as while the user is typing. In certain situations
12518 * (e.g. while animating) the directive might have been initialized, before the element got it's final height. In
12519 * those cases, you can trigger a resize manually by broadcasting a `md-resize-textarea` event on the scope.
12520 * - If you wan't a `textarea` to stop growing at a certain point, you can specify the `max-rows` attribute.
12521 * - The textarea's bottom border acts as a handle which users can drag, in order to resize the element vertically.
12522 * Once the user has resized a `textarea`, the autogrowing functionality becomes disabled. If you don't want a
12523 * `textarea` to be resizeable by the user, you can add the `md-no-resize` attribute.
12524 */
12525
12526function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) {
12527  return {
12528    restrict: 'E',
12529    require: ['^?mdInputContainer', '?ngModel', '?^form'],
12530    link: postLink
12531  };
12532
12533  function postLink(scope, element, attr, ctrls) {
12534
12535    var containerCtrl = ctrls[0];
12536    var hasNgModel = !!ctrls[1];
12537    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
12538    var parentForm = ctrls[2];
12539    var isReadonly = angular.isDefined(attr.readonly);
12540    var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
12541    var tagName = element[0].tagName.toLowerCase();
12542
12543
12544    if (!containerCtrl) return;
12545    if (attr.type === 'hidden') {
12546      element.attr('aria-hidden', 'true');
12547      return;
12548    } else if (containerCtrl.input) {
12549      if (containerCtrl.input[0].contains(element[0])) {
12550        return;
12551      } else {
12552        throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
12553      }
12554    }
12555    containerCtrl.input = element;
12556
12557    setupAttributeWatchers();
12558
12559    // Add an error spacer div after our input to provide space for the char counter and any ng-messages
12560    var errorsSpacer = angular.element('<div class="md-errors-spacer">');
12561    element.after(errorsSpacer);
12562
12563    if (!containerCtrl.label) {
12564      $mdAria.expect(element, 'aria-label', attr.placeholder);
12565    }
12566
12567    element.addClass('md-input');
12568    if (!element.attr('id')) {
12569      element.attr('id', 'input_' + $mdUtil.nextUid());
12570    }
12571
12572    // This works around a Webkit issue where number inputs, placed in a flexbox, that have
12573    // a `min` and `max` will collapse to about 1/3 of their proper width. Please check #7349
12574    // for more info. Also note that we don't override the `step` if the user has specified it,
12575    // in order to prevent some unexpected behaviour.
12576    if (tagName === 'input' && attr.type === 'number' && attr.min && attr.max && !attr.step) {
12577      element.attr('step', 'any');
12578    } else if (tagName === 'textarea') {
12579      setupTextarea();
12580    }
12581
12582    // If the input doesn't have an ngModel, it may have a static value. For that case,
12583    // we have to do one initial check to determine if the container should be in the
12584    // "has a value" state.
12585    if (!hasNgModel) {
12586      inputCheckValue();
12587    }
12588
12589    var isErrorGetter = containerCtrl.isErrorGetter || function() {
12590      return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
12591    };
12592
12593    scope.$watch(isErrorGetter, containerCtrl.setInvalid);
12594
12595    // When the developer uses the ngValue directive for the input, we have to observe the attribute, because
12596    // Angular's ngValue directive is just setting the `value` attribute.
12597    if (attr.ngValue) {
12598      attr.$observe('value', inputCheckValue);
12599    }
12600
12601    ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
12602    ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);
12603
12604    element.on('input', inputCheckValue);
12605
12606    if (!isReadonly) {
12607      element
12608        .on('focus', function(ev) {
12609          $mdUtil.nextTick(function() {
12610            containerCtrl.setFocused(true);
12611          });
12612        })
12613        .on('blur', function(ev) {
12614          $mdUtil.nextTick(function() {
12615            containerCtrl.setFocused(false);
12616            inputCheckValue();
12617          });
12618        });
12619    }
12620
12621    scope.$on('$destroy', function() {
12622      containerCtrl.setFocused(false);
12623      containerCtrl.setHasValue(false);
12624      containerCtrl.input = null;
12625    });
12626
12627    /** Gets run through ngModel's pipeline and set the `has-value` class on the container. */
12628    function ngModelPipelineCheckValue(arg) {
12629      containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
12630      return arg;
12631    }
12632
12633    function setupAttributeWatchers() {
12634      if (containerCtrl.label) {
12635        attr.$observe('required', function (value) {
12636          // We don't need to parse the required value, it's always a boolean because of angular's
12637          // required directive.
12638          containerCtrl.label.toggleClass('md-required', value && !mdNoAsterisk);
12639        });
12640      }
12641    }
12642
12643    function inputCheckValue() {
12644      // An input's value counts if its length > 0,
12645      // or if the input's validity state says it has bad input (eg string in a number input)
12646      containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity || {}).badInput);
12647    }
12648
12649    function setupTextarea() {
12650      var isAutogrowing = !attr.hasOwnProperty('mdNoAutogrow');
12651
12652      attachResizeHandle();
12653
12654      if (!isAutogrowing) return;
12655
12656      // Can't check if height was or not explicity set,
12657      // so rows attribute will take precedence if present
12658      var minRows = attr.hasOwnProperty('rows') ? parseInt(attr.rows) : NaN;
12659      var maxRows = attr.hasOwnProperty('maxRows') ? parseInt(attr.maxRows) : NaN;
12660      var scopeResizeListener = scope.$on('md-resize-textarea', growTextarea);
12661      var lineHeight = null;
12662      var node = element[0];
12663
12664      // This timeout is necessary, because the browser needs a little bit
12665      // of time to calculate the `clientHeight` and `scrollHeight`.
12666      $timeout(function() {
12667        $mdUtil.nextTick(growTextarea);
12668      }, 10, false);
12669
12670      // We could leverage ngModel's $parsers here, however it
12671      // isn't reliable, because Angular trims the input by default,
12672      // which means that growTextarea won't fire when newlines and
12673      // spaces are added.
12674      element.on('input', growTextarea);
12675
12676      // We should still use the $formatters, because they fire when
12677      // the value was changed from outside the textarea.
12678      if (hasNgModel) {
12679        ngModelCtrl.$formatters.push(formattersListener);
12680      }
12681
12682      if (!minRows) {
12683        element.attr('rows', 1);
12684      }
12685
12686      angular.element($window).on('resize', growTextarea);
12687      scope.$on('$destroy', disableAutogrow);
12688
12689      function growTextarea() {
12690        // temporarily disables element's flex so its height 'runs free'
12691        element
12692          .attr('rows', 1)
12693          .css('height', 'auto')
12694          .addClass('md-no-flex');
12695
12696        var height = getHeight();
12697
12698        if (!lineHeight) {
12699          // offsetHeight includes padding which can throw off our value
12700          var originalPadding = element[0].style.padding || '';
12701          lineHeight = element.css('padding', 0).prop('offsetHeight');
12702          element[0].style.padding = originalPadding;
12703        }
12704
12705        if (minRows && lineHeight) {
12706          height = Math.max(height, lineHeight * minRows);
12707        }
12708
12709        if (maxRows && lineHeight) {
12710          var maxHeight = lineHeight * maxRows;
12711
12712          if (maxHeight < height) {
12713            element.attr('md-no-autogrow', '');
12714            height = maxHeight;
12715          } else {
12716            element.removeAttr('md-no-autogrow');
12717          }
12718        }
12719
12720        if (lineHeight) {
12721          element.attr('rows', Math.round(height / lineHeight));
12722        }
12723
12724        element
12725          .css('height', height + 'px')
12726          .removeClass('md-no-flex');
12727      }
12728
12729      function getHeight() {
12730        var offsetHeight = node.offsetHeight;
12731        var line = node.scrollHeight - offsetHeight;
12732        return offsetHeight + Math.max(line, 0);
12733      }
12734
12735      function formattersListener(value) {
12736        $mdUtil.nextTick(growTextarea);
12737        return value;
12738      }
12739
12740      function disableAutogrow() {
12741        if (!isAutogrowing) return;
12742
12743        isAutogrowing = false;
12744        angular.element($window).off('resize', growTextarea);
12745        scopeResizeListener && scopeResizeListener();
12746        element
12747          .attr('md-no-autogrow', '')
12748          .off('input', growTextarea);
12749
12750        if (hasNgModel) {
12751          var listenerIndex = ngModelCtrl.$formatters.indexOf(formattersListener);
12752
12753          if (listenerIndex > -1) {
12754            ngModelCtrl.$formatters.splice(listenerIndex, 1);
12755          }
12756        }
12757      }
12758
12759      function attachResizeHandle() {
12760        if (attr.hasOwnProperty('mdNoResize')) return;
12761
12762        var handle = angular.element('<div class="md-resize-handle"></div>');
12763        var isDragging = false;
12764        var dragStart = null;
12765        var startHeight = 0;
12766        var container = containerCtrl.element;
12767        var dragGestureHandler = $mdGesture.register(handle, 'drag', { horizontal: false });
12768
12769
12770        element.wrap('<div class="md-resize-wrapper">').after(handle);
12771        handle.on('mousedown', onMouseDown);
12772
12773        container
12774          .on('$md.dragstart', onDragStart)
12775          .on('$md.drag', onDrag)
12776          .on('$md.dragend', onDragEnd);
12777
12778        scope.$on('$destroy', function() {
12779          handle
12780            .off('mousedown', onMouseDown)
12781            .remove();
12782
12783          container
12784            .off('$md.dragstart', onDragStart)
12785            .off('$md.drag', onDrag)
12786            .off('$md.dragend', onDragEnd);
12787
12788          dragGestureHandler();
12789          handle = null;
12790          container = null;
12791          dragGestureHandler = null;
12792        });
12793
12794        function onMouseDown(ev) {
12795          ev.preventDefault();
12796          isDragging = true;
12797          dragStart = ev.clientY;
12798          startHeight = parseFloat(element.css('height')) || element.prop('offsetHeight');
12799        }
12800
12801        function onDragStart(ev) {
12802          if (!isDragging) return;
12803          ev.preventDefault();
12804          disableAutogrow();
12805          container.addClass('md-input-resized');
12806        }
12807
12808        function onDrag(ev) {
12809          if (!isDragging) return;
12810
12811          element.css('height', (startHeight + ev.pointer.distanceY) + 'px');
12812        }
12813
12814        function onDragEnd(ev) {
12815          if (!isDragging) return;
12816          isDragging = false;
12817          container.removeClass('md-input-resized');
12818        }
12819      }
12820
12821      // Attach a watcher to detect when the textarea gets shown.
12822      if (attr.hasOwnProperty('mdDetectHidden')) {
12823
12824        var handleHiddenChange = function() {
12825          var wasHidden = false;
12826
12827          return function() {
12828            var isHidden = node.offsetHeight === 0;
12829
12830            if (isHidden === false && wasHidden === true) {
12831              growTextarea();
12832            }
12833
12834            wasHidden = isHidden;
12835          };
12836        }();
12837
12838        // Check every digest cycle whether the visibility of the textarea has changed.
12839        // Queue up to run after the digest cycle is complete.
12840        scope.$watch(function() {
12841          $mdUtil.nextTick(handleHiddenChange, false);
12842          return true;
12843        });
12844      }
12845    }
12846  }
12847}
12848
12849function mdMaxlengthDirective($animate, $mdUtil) {
12850  return {
12851    restrict: 'A',
12852    require: ['ngModel', '^mdInputContainer'],
12853    link: postLink
12854  };
12855
12856  function postLink(scope, element, attr, ctrls) {
12857    var maxlength;
12858    var ngModelCtrl = ctrls[0];
12859    var containerCtrl = ctrls[1];
12860    var charCountEl, errorsSpacer;
12861
12862    // Wait until the next tick to ensure that the input has setup the errors spacer where we will
12863    // append our counter
12864    $mdUtil.nextTick(function() {
12865      errorsSpacer = angular.element(containerCtrl.element[0].querySelector('.md-errors-spacer'));
12866      charCountEl = angular.element('<div class="md-char-counter">');
12867
12868      // Append our character counter inside the errors spacer
12869      errorsSpacer.append(charCountEl);
12870
12871      // Stop model from trimming. This makes it so whitespace
12872      // over the maxlength still counts as invalid.
12873      attr.$set('ngTrim', 'false');
12874
12875      scope.$watch(attr.mdMaxlength, function(value) {
12876        maxlength = value;
12877        if (angular.isNumber(value) && value > 0) {
12878          if (!charCountEl.parent().length) {
12879            $animate.enter(charCountEl, errorsSpacer);
12880          }
12881          renderCharCount();
12882        } else {
12883          $animate.leave(charCountEl);
12884        }
12885      });
12886
12887      ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
12888        if (!angular.isNumber(maxlength) || maxlength < 0) {
12889          return true;
12890        }
12891
12892        // We always update the char count, when the modelValue has changed.
12893        // Using the $validators for triggering the update works very well.
12894        renderCharCount();
12895
12896        return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
12897      };
12898    });
12899
12900    function renderCharCount(value) {
12901      // If we have not been appended to the body yet; do not render
12902      if (!charCountEl.parent) {
12903        return value;
12904      }
12905
12906      // Force the value into a string since it may be a number,
12907      // which does not have a length property.
12908      charCountEl.text(String(element.val() || value || '').length + ' / ' + maxlength);
12909      return value;
12910    }
12911  }
12912}
12913
12914function placeholderDirective($compile) {
12915  return {
12916    restrict: 'A',
12917    require: '^^?mdInputContainer',
12918    priority: 200,
12919    link: {
12920      // Note that we need to do this in the pre-link, as opposed to the post link, if we want to
12921      // support data bindings in the placeholder. This is necessary, because we have a case where
12922      // we transfer the placeholder value to the `<label>` and we remove it from the original `<input>`.
12923      // If we did this in the post-link, Angular would have set up the observers already and would be
12924      // re-adding the attribute, even though we removed it from the element.
12925      pre: preLink
12926    }
12927  };
12928
12929  function preLink(scope, element, attr, inputContainer) {
12930    // If there is no input container, just return
12931    if (!inputContainer) return;
12932
12933    var label = inputContainer.element.find('label');
12934    var noFloat = inputContainer.element.attr('md-no-float');
12935
12936    // If we have a label, or they specify the md-no-float attribute, just return
12937    if ((label && label.length) || noFloat === '' || scope.$eval(noFloat)) {
12938      // Add a placeholder class so we can target it in the CSS
12939      inputContainer.setHasPlaceholder(true);
12940      return;
12941    }
12942
12943    // md-select handles placeholders on it's own
12944    if (element[0].nodeName != 'MD-SELECT') {
12945      // Move the placeholder expression to the label
12946      var newLabel = angular.element('<label ng-click="delegateClick()" tabindex="-1">' + attr.placeholder + '</label>');
12947
12948      // Note that we unset it via `attr`, in order to get Angular
12949      // to remove any observers that it might have set up. Otherwise
12950      // the attribute will be added on the next digest.
12951      attr.$set('placeholder', null);
12952
12953      // We need to compile the label manually in case it has any bindings.
12954      // A gotcha here is that we first add the element to the DOM and we compile
12955      // it later. This is necessary, because if we compile the element beforehand,
12956      // it won't be able to find the `mdInputContainer` controller.
12957      inputContainer.element
12958        .addClass('md-icon-float')
12959        .prepend(newLabel);
12960
12961      $compile(newLabel)(scope);
12962    }
12963  }
12964}
12965
12966/**
12967 * @ngdoc directive
12968 * @name mdSelectOnFocus
12969 * @module material.components.input
12970 *
12971 * @restrict A
12972 *
12973 * @description
12974 * The `md-select-on-focus` directive allows you to automatically select the element's input text on focus.
12975 *
12976 * <h3>Notes</h3>
12977 * - The use of `md-select-on-focus` is restricted to `<input>` and `<textarea>` elements.
12978 *
12979 * @usage
12980 * <h3>Using with an Input</h3>
12981 * <hljs lang="html">
12982 *
12983 * <md-input-container>
12984 *   <label>Auto Select</label>
12985 *   <input type="text" md-select-on-focus>
12986 * </md-input-container>
12987 * </hljs>
12988 *
12989 * <h3>Using with a Textarea</h3>
12990 * <hljs lang="html">
12991 *
12992 * <md-input-container>
12993 *   <label>Auto Select</label>
12994 *   <textarea md-select-on-focus>This text will be selected on focus.</textarea>
12995 * </md-input-container>
12996 *
12997 * </hljs>
12998 */
12999function mdSelectOnFocusDirective($timeout) {
13000
13001  return {
13002    restrict: 'A',
13003    link: postLink
13004  };
13005
13006  function postLink(scope, element, attr) {
13007    if (element[0].nodeName !== 'INPUT' && element[0].nodeName !== "TEXTAREA") return;
13008
13009    var preventMouseUp = false;
13010
13011    element
13012      .on('focus', onFocus)
13013      .on('mouseup', onMouseUp);
13014
13015    scope.$on('$destroy', function() {
13016      element
13017        .off('focus', onFocus)
13018        .off('mouseup', onMouseUp);
13019    });
13020
13021    function onFocus() {
13022      preventMouseUp = true;
13023
13024      $timeout(function() {
13025        // Use HTMLInputElement#select to fix firefox select issues.
13026        // The debounce is here for Edge's sake, otherwise the selection doesn't work.
13027        element[0].select();
13028
13029        // This should be reset from inside the `focus`, because the event might
13030        // have originated from something different than a click, e.g. a keyboard event.
13031        preventMouseUp = false;
13032      }, 1, false);
13033    }
13034
13035    // Prevents the default action of the first `mouseup` after a focus.
13036    // This is necessary, because browsers fire a `mouseup` right after the element
13037    // has been focused. In some browsers (Firefox in particular) this can clear the
13038    // selection. There are examples of the problem in issue #7487.
13039    function onMouseUp(event) {
13040      if (preventMouseUp) {
13041        event.preventDefault();
13042      }
13043    }
13044  }
13045}
13046
13047var visibilityDirectives = ['ngIf', 'ngShow', 'ngHide', 'ngSwitchWhen', 'ngSwitchDefault'];
13048function ngMessagesDirective() {
13049  return {
13050    restrict: 'EA',
13051    link: postLink,
13052
13053    // This is optional because we don't want target *all* ngMessage instances, just those inside of
13054    // mdInputContainer.
13055    require: '^^?mdInputContainer'
13056  };
13057
13058  function postLink(scope, element, attrs, inputContainer) {
13059    // If we are not a child of an input container, don't do anything
13060    if (!inputContainer) return;
13061
13062    // Add our animation class
13063    element.toggleClass('md-input-messages-animation', true);
13064
13065    // Add our md-auto-hide class to automatically hide/show messages when container is invalid
13066    element.toggleClass('md-auto-hide', true);
13067
13068    // If we see some known visibility directives, remove the md-auto-hide class
13069    if (attrs.mdAutoHide == 'false' || hasVisibiltyDirective(attrs)) {
13070      element.toggleClass('md-auto-hide', false);
13071    }
13072  }
13073
13074  function hasVisibiltyDirective(attrs) {
13075    return visibilityDirectives.some(function(attr) {
13076      return attrs[attr];
13077    });
13078  }
13079}
13080
13081function ngMessageDirective($mdUtil) {
13082  return {
13083    restrict: 'EA',
13084    compile: compile,
13085    priority: 100
13086  };
13087
13088  function compile(tElement) {
13089    if (!isInsideInputContainer(tElement)) {
13090
13091      // When the current element is inside of a document fragment, then we need to check for an input-container
13092      // in the postLink, because the element will be later added to the DOM and is currently just in a temporary
13093      // fragment, which causes the input-container check to fail.
13094      if (isInsideFragment()) {
13095        return function (scope, element) {
13096          if (isInsideInputContainer(element)) {
13097            // Inside of the postLink function, a ngMessage directive will be a comment element, because it's
13098            // currently hidden. To access the shown element, we need to use the element from the compile function.
13099            initMessageElement(tElement);
13100          }
13101        };
13102      }
13103    } else {
13104      initMessageElement(tElement);
13105    }
13106
13107    function isInsideFragment() {
13108      var nextNode = tElement[0];
13109      while (nextNode = nextNode.parentNode) {
13110        if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
13111          return true;
13112        }
13113      }
13114      return false;
13115    }
13116
13117    function isInsideInputContainer(element) {
13118      return !!$mdUtil.getClosest(element, "md-input-container");
13119    }
13120
13121    function initMessageElement(element) {
13122      // Add our animation class
13123      element.toggleClass('md-input-message-animation', true);
13124    }
13125  }
13126}
13127
13128var $$AnimateRunner, $animateCss, $mdUtil, $log;
13129
13130function mdInputInvalidMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
13131  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
13132
13133  return {
13134    addClass: function(element, className, done) {
13135      showInputMessages(element, done);
13136    }
13137
13138    // NOTE: We do not need the removeClass method, because the message ng-leave animation will fire
13139  };
13140}
13141
13142function ngMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
13143  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
13144
13145  return {
13146    enter: function(element, done) {
13147      showInputMessages(element, done);
13148    },
13149
13150    leave: function(element, done) {
13151      hideInputMessages(element, done);
13152    },
13153
13154    addClass: function(element, className, done) {
13155      if (className == "ng-hide") {
13156        hideInputMessages(element, done);
13157      } else {
13158        done();
13159      }
13160    },
13161
13162    removeClass: function(element, className, done) {
13163      if (className == "ng-hide") {
13164        showInputMessages(element, done);
13165      } else {
13166        done();
13167      }
13168    }
13169  };
13170}
13171
13172function ngMessageAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
13173  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
13174
13175  return {
13176    enter: function(element, done) {
13177      var animator = showMessage(element);
13178
13179      animator.start().done(done);
13180    },
13181
13182    leave: function(element, done) {
13183      var animator = hideMessage(element);
13184
13185      animator.start().done(done);
13186    }
13187  };
13188}
13189
13190function showInputMessages(element, done) {
13191  var animators = [], animator;
13192  var messages = getMessagesElement(element);
13193  var children = messages.children();
13194
13195  if (messages.length == 0 || children.length == 0) {
13196    $log.warn('mdInput messages show animation called on invalid messages element: ', element);
13197    done();
13198    return;
13199  }
13200
13201  angular.forEach(children, function(child) {
13202    animator = showMessage(angular.element(child));
13203
13204    animators.push(animator.start());
13205  });
13206
13207  $$AnimateRunner.all(animators, done);
13208}
13209
13210function hideInputMessages(element, done) {
13211  var animators = [], animator;
13212  var messages = getMessagesElement(element);
13213  var children = messages.children();
13214
13215  if (messages.length == 0 || children.length == 0) {
13216    $log.warn('mdInput messages hide animation called on invalid messages element: ', element);
13217    done();
13218    return;
13219  }
13220
13221  angular.forEach(children, function(child) {
13222    animator = hideMessage(angular.element(child));
13223
13224    animators.push(animator.start());
13225  });
13226
13227  $$AnimateRunner.all(animators, done);
13228}
13229
13230function showMessage(element) {
13231  var height = parseInt(window.getComputedStyle(element[0]).height);
13232  var topMargin = parseInt(window.getComputedStyle(element[0]).marginTop);
13233
13234  var messages = getMessagesElement(element);
13235  var container = getInputElement(element);
13236
13237  // Check to see if the message is already visible so we can skip
13238  var alreadyVisible = (topMargin > -height);
13239
13240  // If we have the md-auto-hide class, the md-input-invalid animation will fire, so we can skip
13241  if (alreadyVisible || (messages.hasClass('md-auto-hide') && !container.hasClass('md-input-invalid'))) {
13242    return $animateCss(element, {});
13243  }
13244
13245  return $animateCss(element, {
13246    event: 'enter',
13247    structural: true,
13248    from: {"opacity": 0, "margin-top": -height + "px"},
13249    to: {"opacity": 1, "margin-top": "0"},
13250    duration: 0.3
13251  });
13252}
13253
13254function hideMessage(element) {
13255  var height = element[0].offsetHeight;
13256  var styles = window.getComputedStyle(element[0]);
13257
13258  // If we are already hidden, just return an empty animation
13259  if (parseInt(styles.opacity) === 0) {
13260    return $animateCss(element, {});
13261  }
13262
13263  // Otherwise, animate
13264  return $animateCss(element, {
13265    event: 'leave',
13266    structural: true,
13267    from: {"opacity": 1, "margin-top": 0},
13268    to: {"opacity": 0, "margin-top": -height + "px"},
13269    duration: 0.3
13270  });
13271}
13272
13273function getInputElement(element) {
13274  var inputContainer = element.controller('mdInputContainer');
13275
13276  return inputContainer.element;
13277}
13278
13279function getMessagesElement(element) {
13280  // If we ARE the messages element, just return ourself
13281  if (element.hasClass('md-input-messages-animation')) {
13282    return element;
13283  }
13284
13285  // If we are a ng-message element, we need to traverse up the DOM tree
13286  if (element.hasClass('md-input-message-animation')) {
13287    return angular.element($mdUtil.getClosest(element, function(node) {
13288      return node.classList.contains('md-input-messages-animation');
13289    }));
13290  }
13291
13292  // Otherwise, we can traverse down
13293  return angular.element(element[0].querySelector('.md-input-messages-animation'));
13294}
13295
13296function saveSharedServices(_$$AnimateRunner_, _$animateCss_, _$mdUtil_, _$log_) {
13297  $$AnimateRunner = _$$AnimateRunner_;
13298  $animateCss = _$animateCss_;
13299  $mdUtil = _$mdUtil_;
13300  $log = _$log_;
13301}
13302
13303})();
13304(function(){
13305"use strict";
13306
13307/**
13308 * @ngdoc module
13309 * @name material.components.list
13310 * @description
13311 * List module
13312 */
13313MdListController.$inject = ["$scope", "$element", "$mdListInkRipple"];
13314mdListDirective.$inject = ["$mdTheming"];
13315mdListItemDirective.$inject = ["$mdAria", "$mdConstant", "$mdUtil", "$timeout"];
13316angular.module('material.components.list', [
13317  'material.core'
13318])
13319  .controller('MdListController', MdListController)
13320  .directive('mdList', mdListDirective)
13321  .directive('mdListItem', mdListItemDirective);
13322
13323/**
13324 * @ngdoc directive
13325 * @name mdList
13326 * @module material.components.list
13327 *
13328 * @restrict E
13329 *
13330 * @description
13331 * The `<md-list>` directive is a list container for 1..n `<md-list-item>` tags.
13332 *
13333 * @usage
13334 * <hljs lang="html">
13335 * <md-list>
13336 *   <md-list-item class="md-2-line" ng-repeat="item in todos">
13337 *     <md-checkbox ng-model="item.done"></md-checkbox>
13338 *     <div class="md-list-item-text">
13339 *       <h3>{{item.title}}</h3>
13340 *       <p>{{item.description}}</p>
13341 *     </div>
13342 *   </md-list-item>
13343 * </md-list>
13344 * </hljs>
13345 */
13346
13347function mdListDirective($mdTheming) {
13348  return {
13349    restrict: 'E',
13350    compile: function(tEl) {
13351      tEl[0].setAttribute('role', 'list');
13352      return $mdTheming;
13353    }
13354  };
13355}
13356/**
13357 * @ngdoc directive
13358 * @name mdListItem
13359 * @module material.components.list
13360 *
13361 * @restrict E
13362 *
13363 * @description
13364 * A `md-list-item` element can be used to represent some information in a row.<br/>
13365 *
13366 * @usage
13367 * ### Single Row Item
13368 * <hljs lang="html">
13369 *   <md-list-item>
13370 *     <span>Single Row Item</span>
13371 *   </md-list-item>
13372 * </hljs>
13373 *
13374 * ### Multiple Lines
13375 * By using the following markup, you will be able to have two lines inside of one `md-list-item`.
13376 *
13377 * <hljs lang="html">
13378 *   <md-list-item class="md-2-line">
13379 *     <div class="md-list-item-text" layout="column">
13380 *       <p>First Line</p>
13381 *       <p>Second Line</p>
13382 *     </div>
13383 *   </md-list-item>
13384 * </hljs>
13385 *
13386 * It is also possible to have three lines inside of one list item.
13387 *
13388 * <hljs lang="html">
13389 *   <md-list-item class="md-3-line">
13390 *     <div class="md-list-item-text" layout="column">
13391 *       <p>First Line</p>
13392 *       <p>Second Line</p>
13393 *       <p>Third Line</p>
13394 *     </div>
13395 *   </md-list-item>
13396 * </hljs>
13397 *
13398 * ### Secondary Items
13399 * Secondary items are elements which will be aligned at the end of the `md-list-item`.
13400 *
13401 * <hljs lang="html">
13402 *   <md-list-item>
13403 *     <span>Single Row Item</span>
13404 *     <md-button class="md-secondary">
13405 *       Secondary Button
13406 *     </md-button>
13407 *   </md-list-item>
13408 * </hljs>
13409 *
13410 * It also possible to have multiple secondary items inside of one `md-list-item`.
13411 *
13412 * <hljs lang="html">
13413 *   <md-list-item>
13414 *     <span>Single Row Item</span>
13415 *     <md-button class="md-secondary">First Button</md-button>
13416 *     <md-button class="md-secondary">Second Button</md-button>
13417 *   </md-list-item>
13418 * </hljs>
13419 *
13420 * ### Proxy Item
13421 * Proxies are elements, which will execute their specific action on click<br/>
13422 * Currently supported proxy items are
13423 * - `md-checkbox` (Toggle)
13424 * - `md-switch` (Toggle)
13425 * - `md-menu` (Open)
13426 *
13427 * This means, when using a supported proxy item inside of `md-list-item`, the list item will
13428 * automatically become clickable and executes the associated action of the proxy element on click.
13429 *
13430 * It is possible to disable this behavior by applying the `md-no-proxy` class to the list item.
13431 *
13432 * <hljs lang="html">
13433 *   <md-list-item class="md-no-proxy">
13434 *     <span>No Proxy List</span>
13435 *     <md-checkbox class="md-secondary"></md-checkbox>
13436 *   </md-list-item>
13437 * </hljs>
13438 *
13439 * Here are a few examples of proxy elements inside of a list item.
13440 *
13441 * <hljs lang="html">
13442 *   <md-list-item>
13443 *     <span>First Line</span>
13444 *     <md-checkbox class="md-secondary"></md-checkbox>
13445 *   </md-list-item>
13446 * </hljs>
13447 *
13448 * The `md-checkbox` element will be automatically detected as a proxy element and will toggle on click.
13449 *
13450 * <hljs lang="html">
13451 *   <md-list-item>
13452 *     <span>First Line</span>
13453 *     <md-switch class="md-secondary"></md-switch>
13454 *   </md-list-item>
13455 * </hljs>
13456 *
13457 * The recognized `md-switch` will toggle its state, when the user clicks on the `md-list-item`.
13458 *
13459 * It is also possible to have a `md-menu` inside of a `md-list-item`.
13460 * <hljs lang="html">
13461 *   <md-list-item>
13462 *     <p>Click anywhere to fire the secondary action</p>
13463 *     <md-menu class="md-secondary">
13464 *       <md-button class="md-icon-button">
13465 *         <md-icon md-svg-icon="communication:message"></md-icon>
13466 *       </md-button>
13467 *       <md-menu-content width="4">
13468 *         <md-menu-item>
13469 *           <md-button>
13470 *             Redial
13471 *           </md-button>
13472 *         </md-menu-item>
13473 *         <md-menu-item>
13474 *           <md-button>
13475 *             Check voicemail
13476 *           </md-button>
13477 *         </md-menu-item>
13478 *         <md-menu-divider></md-menu-divider>
13479 *         <md-menu-item>
13480 *           <md-button>
13481 *             Notifications
13482 *           </md-button>
13483 *         </md-menu-item>
13484 *       </md-menu-content>
13485 *     </md-menu>
13486 *   </md-list-item>
13487 * </hljs>
13488 *
13489 * The menu will automatically open, when the users clicks on the `md-list-item`.<br/>
13490 *
13491 * If the developer didn't specify any position mode on the menu, the `md-list-item` will automatically detect the
13492 * position mode and applies it to the `md-menu`.
13493 *
13494 * ### Avatars
13495 * Sometimes you may want to have some avatars inside of the `md-list-item `.<br/>
13496 * You are able to create a optimized icon for the list item, by applying the `.md-avatar` class on the `<img>` element.
13497 *
13498 * <hljs lang="html">
13499 *   <md-list-item>
13500 *     <img src="my-avatar.png" class="md-avatar">
13501 *     <span>Alan Turing</span>
13502 * </hljs>
13503 *
13504 * When using `<md-icon>` for an avatar, you have to use the `.md-avatar-icon` class.
13505 * <hljs lang="html">
13506 *   <md-list-item>
13507 *     <md-icon class="md-avatar-icon" md-svg-icon="avatars:timothy"></md-icon>
13508 *     <span>Timothy Kopra</span>
13509 *   </md-list-item>
13510 * </hljs>
13511 *
13512 * In cases, you have a `md-list-item`, which doesn't have any avatar,
13513 * but you want to align it with the other avatar items, you have to use the `.md-offset` class.
13514 *
13515 * <hljs lang="html">
13516 *   <md-list-item class="md-offset">
13517 *     <span>Jon Doe</span>
13518 *   </md-list-item>
13519 * </hljs>
13520 *
13521 * ### DOM modification
13522 * The `md-list-item` component automatically detects if the list item should be clickable.
13523 *
13524 * ---
13525 * If the `md-list-item` is clickable, we wrap all content inside of a `<div>` and create
13526 * an overlaying button, which will will execute the given actions (like `ng-href`, `ng-click`)
13527 *
13528 * We create an overlaying button, instead of wrapping all content inside of the button,
13529 * because otherwise some elements may not be clickable inside of the button.
13530 *
13531 * ---
13532 * When using a secondary item inside of your list item, the `md-list-item` component will automatically create
13533 * a secondary container at the end of the `md-list-item`, which contains all secondary items.
13534 *
13535 * The secondary item container is not static, because otherwise the overflow will not work properly on the
13536 * list item.
13537 *
13538 */
13539function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
13540  var proxiedTypes = ['md-checkbox', 'md-switch', 'md-menu'];
13541  return {
13542    restrict: 'E',
13543    controller: 'MdListController',
13544    compile: function(tEl, tAttrs) {
13545
13546      // Check for proxy controls (no ng-click on parent, and a control inside)
13547      var secondaryItems = tEl[0].querySelectorAll('.md-secondary');
13548      var hasProxiedElement;
13549      var proxyElement;
13550      var itemContainer = tEl;
13551
13552      tEl[0].setAttribute('role', 'listitem');
13553
13554      if (tAttrs.ngClick || tAttrs.ngDblclick ||  tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref) {
13555        wrapIn('button');
13556      } else if (!tEl.hasClass('md-no-proxy')) {
13557
13558        for (var i = 0, type; type = proxiedTypes[i]; ++i) {
13559          if (proxyElement = tEl[0].querySelector(type)) {
13560            hasProxiedElement = true;
13561            break;
13562          }
13563        }
13564
13565        if (hasProxiedElement) {
13566          wrapIn('div');
13567        } else {
13568          tEl.addClass('md-no-proxy');
13569        }
13570
13571      }
13572
13573      wrapSecondaryItems();
13574      setupToggleAria();
13575
13576      if (hasProxiedElement && proxyElement.nodeName === "MD-MENU") {
13577        setupProxiedMenu();
13578      }
13579
13580      function setupToggleAria() {
13581        var toggleTypes = ['md-switch', 'md-checkbox'];
13582        var toggle;
13583
13584        for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) {
13585          if (toggle = tEl.find(toggleType)[0]) {
13586            if (!toggle.hasAttribute('aria-label')) {
13587              var p = tEl.find('p')[0];
13588              if (!p) return;
13589              toggle.setAttribute('aria-label', 'Toggle ' + p.textContent);
13590            }
13591          }
13592        }
13593      }
13594
13595      function setupProxiedMenu() {
13596        var menuEl = angular.element(proxyElement);
13597
13598        var isEndAligned = menuEl.parent().hasClass('md-secondary-container') ||
13599                           proxyElement.parentNode.firstElementChild !== proxyElement;
13600
13601        var xAxisPosition = 'left';
13602
13603        if (isEndAligned) {
13604          // When the proxy item is aligned at the end of the list, we have to set the origin to the end.
13605          xAxisPosition = 'right';
13606        }
13607
13608        // Set the position mode / origin of the proxied menu.
13609        if (!menuEl.attr('md-position-mode')) {
13610          menuEl.attr('md-position-mode', xAxisPosition + ' target');
13611        }
13612
13613        // Apply menu open binding to menu button
13614        var menuOpenButton = menuEl.children().eq(0);
13615        if (!hasClickEvent(menuOpenButton[0])) {
13616          menuOpenButton.attr('ng-click', '$mdMenu.open($event)');
13617        }
13618
13619        if (!menuOpenButton.attr('aria-label')) {
13620          menuOpenButton.attr('aria-label', 'Open List Menu');
13621        }
13622      }
13623
13624      function wrapIn(type) {
13625        if (type == 'div') {
13626          itemContainer = angular.element('<div class="md-no-style md-list-item-inner">');
13627          itemContainer.append(tEl.contents());
13628          tEl.addClass('md-proxy-focus');
13629        } else {
13630          // Element which holds the default list-item content.
13631          itemContainer = angular.element(
13632            '<div class="md-button md-no-style">'+
13633            '   <div class="md-list-item-inner"></div>'+
13634            '</div>'
13635          );
13636
13637          // Button which shows ripple and executes primary action.
13638          var buttonWrap = angular.element(
13639            '<md-button class="md-no-style"></md-button>'
13640          );
13641
13642          copyAttributes(tEl[0], buttonWrap[0]);
13643
13644          // If there is no aria-label set on the button (previously copied over if present)
13645          // we determine the label from the content and copy it to the button.
13646          if (!buttonWrap.attr('aria-label')) {
13647            buttonWrap.attr('aria-label', $mdAria.getText(tEl));
13648          }
13649
13650          // We allow developers to specify the `md-no-focus` class, to disable the focus style
13651          // on the button executor. Once more classes should be forwarded, we should probably make the
13652          // class forward more generic.
13653          if (tEl.hasClass('md-no-focus')) {
13654            buttonWrap.addClass('md-no-focus');
13655          }
13656
13657          // Append the button wrap before our list-item content, because it will overlay in relative.
13658          itemContainer.prepend(buttonWrap);
13659          itemContainer.children().eq(1).append(tEl.contents());
13660
13661          tEl.addClass('_md-button-wrap');
13662        }
13663
13664        tEl[0].setAttribute('tabindex', '-1');
13665        tEl.append(itemContainer);
13666      }
13667
13668      function wrapSecondaryItems() {
13669        var secondaryItemsWrapper = angular.element('<div class="md-secondary-container">');
13670
13671        angular.forEach(secondaryItems, function(secondaryItem) {
13672          wrapSecondaryItem(secondaryItem, secondaryItemsWrapper);
13673        });
13674
13675        itemContainer.append(secondaryItemsWrapper);
13676      }
13677
13678      function wrapSecondaryItem(secondaryItem, container) {
13679        // If the current secondary item is not a button, but contains a ng-click attribute,
13680        // the secondary item will be automatically wrapped inside of a button.
13681        if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) {
13682
13683          $mdAria.expect(secondaryItem, 'aria-label');
13684          var buttonWrapper = angular.element('<md-button class="md-secondary md-icon-button">');
13685
13686          // Copy the attributes from the secondary item to the generated button.
13687          // We also support some additional attributes from the secondary item,
13688          // because some developers may use a ngIf, ngHide, ngShow on their item.
13689          copyAttributes(secondaryItem, buttonWrapper[0], ['ng-if', 'ng-hide', 'ng-show']);
13690
13691          secondaryItem.setAttribute('tabindex', '-1');
13692          buttonWrapper.append(secondaryItem);
13693
13694          secondaryItem = buttonWrapper[0];
13695        }
13696
13697        if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) {
13698          // In this case we remove the secondary class, so we can identify it later, when we searching for the
13699          // proxy items.
13700          angular.element(secondaryItem).removeClass('md-secondary');
13701        }
13702
13703        tEl.addClass('md-with-secondary');
13704        container.append(secondaryItem);
13705      }
13706
13707      /**
13708       * Copies attributes from a source element to the destination element
13709       * By default the function will copy the most necessary attributes, supported
13710       * by the button executor for clickable list items.
13711       * @param source Element with the specified attributes
13712       * @param destination Element which will retrieve the attributes
13713       * @param extraAttrs Additional attributes, which will be copied over.
13714       */
13715      function copyAttributes(source, destination, extraAttrs) {
13716        var copiedAttrs = $mdUtil.prefixer([
13717          'ng-if', 'ng-click', 'ng-dblclick', 'aria-label', 'ng-disabled', 'ui-sref',
13718          'href', 'ng-href', 'target', 'ng-attr-ui-sref', 'ui-sref-opts'
13719        ]);
13720
13721        if (extraAttrs) {
13722          copiedAttrs = copiedAttrs.concat($mdUtil.prefixer(extraAttrs));
13723        }
13724
13725        angular.forEach(copiedAttrs, function(attr) {
13726          if (source.hasAttribute(attr)) {
13727            destination.setAttribute(attr, source.getAttribute(attr));
13728            source.removeAttribute(attr);
13729          }
13730        });
13731      }
13732
13733      function isProxiedElement(el) {
13734        return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1;
13735      }
13736
13737      function isButton(el) {
13738        var nodeName = el.nodeName.toUpperCase();
13739
13740        return nodeName == "MD-BUTTON" || nodeName == "BUTTON";
13741      }
13742
13743      function hasClickEvent (element) {
13744        var attr = element.attributes;
13745        for (var i = 0; i < attr.length; i++) {
13746          if (tAttrs.$normalize(attr[i].name) === 'ngClick') return true;
13747        }
13748        return false;
13749      }
13750
13751      return postLink;
13752
13753      function postLink($scope, $element, $attr, ctrl) {
13754        $element.addClass('_md');     // private md component indicator for styling
13755
13756        var proxies       = [],
13757            firstElement  = $element[0].firstElementChild,
13758            isButtonWrap  = $element.hasClass('_md-button-wrap'),
13759            clickChild    = isButtonWrap ? firstElement.firstElementChild : firstElement,
13760            hasClick      = clickChild && hasClickEvent(clickChild),
13761            noProxies     = $element.hasClass('md-no-proxy');
13762
13763        computeProxies();
13764        computeClickable();
13765
13766        if (proxies.length) {
13767          angular.forEach(proxies, function(proxy) {
13768            proxy = angular.element(proxy);
13769
13770            $scope.mouseActive = false;
13771            proxy.on('mousedown', function() {
13772              $scope.mouseActive = true;
13773              $timeout(function(){
13774                $scope.mouseActive = false;
13775              }, 100);
13776            })
13777            .on('focus', function() {
13778              if ($scope.mouseActive === false) { $element.addClass('md-focused'); }
13779              proxy.on('blur', function proxyOnBlur() {
13780                $element.removeClass('md-focused');
13781                proxy.off('blur', proxyOnBlur);
13782              });
13783            });
13784          });
13785        }
13786
13787
13788        function computeProxies() {
13789
13790          if (firstElement && firstElement.children && !hasClick && !noProxies) {
13791
13792            angular.forEach(proxiedTypes, function(type) {
13793
13794              // All elements which are not capable for being used a proxy have the .md-secondary class
13795              // applied. These items had been sorted out in the secondary wrap function.
13796              angular.forEach(firstElement.querySelectorAll(type + ':not(.md-secondary)'), function(child) {
13797                proxies.push(child);
13798              });
13799            });
13800
13801          }
13802        }
13803
13804        function computeClickable() {
13805          if (proxies.length == 1 || hasClick) {
13806            $element.addClass('md-clickable');
13807
13808            if (!hasClick) {
13809              ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style')));
13810            }
13811          }
13812        }
13813
13814        function isEventFromControl(event) {
13815          var forbiddenControls = ['md-slider'];
13816
13817          // If there is no path property in the event, then we can assume that the event was not bubbled.
13818          if (!event.path) {
13819            return forbiddenControls.indexOf(event.target.tagName.toLowerCase()) !== -1;
13820          }
13821
13822          // We iterate the event path up and check for a possible component.
13823          // Our maximum index to search, is the list item root.
13824          var maxPath = event.path.indexOf($element.children()[0]);
13825
13826          for (var i = 0; i < maxPath; i++) {
13827            if (forbiddenControls.indexOf(event.path[i].tagName.toLowerCase()) !== -1) {
13828              return true;
13829            }
13830          }
13831        }
13832
13833        var clickChildKeypressListener = function(e) {
13834          if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) {
13835            var keyCode = e.which || e.keyCode;
13836            if (keyCode == $mdConstant.KEY_CODE.SPACE) {
13837              if (clickChild) {
13838                clickChild.click();
13839                e.preventDefault();
13840                e.stopPropagation();
13841              }
13842            }
13843          }
13844        };
13845
13846        if (!hasClick && !proxies.length) {
13847          clickChild && clickChild.addEventListener('keypress', clickChildKeypressListener);
13848        }
13849
13850        $element.off('click');
13851        $element.off('keypress');
13852
13853        if (proxies.length == 1 && clickChild) {
13854          $element.children().eq(0).on('click', function(e) {
13855            // When the event is coming from an control and it should not trigger the proxied element
13856            // then we are skipping.
13857            if (isEventFromControl(e)) return;
13858
13859            var parentButton = $mdUtil.getClosest(e.target, 'BUTTON');
13860            if (!parentButton && clickChild.contains(e.target)) {
13861              angular.forEach(proxies, function(proxy) {
13862                if (e.target !== proxy && !proxy.contains(e.target)) {
13863                  if (proxy.nodeName === 'MD-MENU') {
13864                    proxy = proxy.children[0];
13865                  }
13866                  angular.element(proxy).triggerHandler('click');
13867                }
13868              });
13869            }
13870          });
13871        }
13872
13873        $scope.$on('$destroy', function () {
13874          clickChild && clickChild.removeEventListener('keypress', clickChildKeypressListener);
13875        });
13876      }
13877    }
13878  };
13879}
13880
13881/*
13882 * @private
13883 * @ngdoc controller
13884 * @name MdListController
13885 * @module material.components.list
13886 *
13887 */
13888function MdListController($scope, $element, $mdListInkRipple) {
13889  var ctrl = this;
13890  ctrl.attachRipple = attachRipple;
13891
13892  function attachRipple (scope, element) {
13893    var options = {};
13894    $mdListInkRipple.attach(scope, element, options);
13895  }
13896}
13897
13898})();
13899(function(){
13900"use strict";
13901
13902/**
13903 * @ngdoc module
13904 * @name material.components.menu
13905 */
13906
13907angular.module('material.components.menu', [
13908  'material.core',
13909  'material.components.backdrop'
13910]);
13911
13912})();
13913(function(){
13914"use strict";
13915
13916/**
13917 * @ngdoc module
13918 * @name material.components.menuBar
13919 */
13920
13921angular.module('material.components.menuBar', [
13922  'material.core',
13923  'material.components.icon',
13924  'material.components.menu'
13925]);
13926
13927})();
13928(function(){
13929"use strict";
13930
13931/**
13932 * @ngdoc module
13933 * @name material.components.panel
13934 */
13935MdPanelService.$inject = ["presets", "$rootElement", "$rootScope", "$injector", "$window"];
13936angular
13937  .module('material.components.panel', [
13938    'material.core',
13939    'material.components.backdrop'
13940  ])
13941  .provider('$mdPanel', MdPanelProvider);
13942
13943
13944/*****************************************************************************
13945 *                            PUBLIC DOCUMENTATION                           *
13946 *****************************************************************************/
13947
13948
13949/**
13950 * @ngdoc service
13951 * @name $mdPanelProvider
13952 * @module material.components.panel
13953 *
13954 * @description
13955 * `$mdPanelProvider` allows users to create configuration presets that will be
13956 * stored within a cached presets object. When the configuration is needed, the
13957 * user can request the preset by passing it as the first parameter in the
13958 * `$mdPanel.create` or `$mdPanel.open` methods.
13959 *
13960 * @usage
13961 * <hljs lang="js">
13962 * (function(angular, undefined) {
13963 *   'use strict';
13964 *
13965 *   angular
13966 *       .module('demoApp', ['ngMaterial'])
13967 *       .config(DemoConfig)
13968 *       .controller('DemoCtrl', DemoCtrl)
13969 *       .controller('DemoMenuCtrl', DemoMenuCtrl);
13970 *
13971 *   function DemoConfig($mdPanelProvider) {
13972 *     $mdPanelProvider.definePreset('demoPreset', {
13973 *       attachTo: angular.element(document.body),
13974 *       controller: DemoMenuCtrl,
13975 *       controllerAs: 'ctrl',
13976 *       template: '' +
13977 *           '<div class="menu-panel" md-whiteframe="4">' +
13978 *           '  <div class="menu-content">' +
13979 *           '    <div class="menu-item" ng-repeat="item in ctrl.items">' +
13980 *           '      <button class="md-button">' +
13981 *           '        <span>{{item}}</span>' +
13982 *           '      </button>' +
13983 *           '    </div>' +
13984 *           '    <md-divider></md-divider>' +
13985 *           '    <div class="menu-item">' +
13986 *           '      <button class="md-button" ng-click="ctrl.closeMenu()">' +
13987 *           '        <span>Close Menu</span>' +
13988 *           '      </button>' +
13989 *           '    </div>' +
13990 *           '  </div>' +
13991 *           '</div>',
13992 *       panelClass: 'menu-panel-container',
13993 *       focusOnOpen: false,
13994 *       zIndex: 100,
13995 *       propagateContainerEvents: true,
13996 *       groupName: 'menus'
13997 *     });
13998 *   }
13999 *
14000 *   function PanelProviderCtrl($mdPanel) {
14001 *     this.navigation = {
14002 *       name: 'navigation',
14003 *       items: [
14004 *         'Home',
14005 *         'About',
14006 *         'Contact'
14007 *       ]
14008 *     };
14009 *     this.favorites = {
14010 *       name: 'favorites',
14011 *       items: [
14012 *         'Add to Favorites'
14013 *       ]
14014 *     };
14015 *     this.more = {
14016 *       name: 'more',
14017 *       items: [
14018 *         'Account',
14019 *         'Sign Out'
14020 *       ]
14021 *     };
14022 *
14023 *     $mdPanel.newPanelGroup('menus', {
14024 *       maxOpen: 2
14025 *     });
14026 *
14027 *     this.showMenu = function($event, menu) {
14028 *       $mdPanel.open('demoPreset', {
14029 *         id: 'menu_' + menu.name,
14030 *         position: $mdPanel.newPanelPosition()
14031 *             .relativeTo($event.srcElement)
14032 *             .addPanelPosition(
14033 *               $mdPanel.xPosition.ALIGN_START,
14034 *               $mdPanel.yPosition.BELOW
14035 *             ),
14036 *         locals: {
14037 *           items: menu.items
14038 *         },
14039 *         openFrom: $event
14040 *       });
14041 *     };
14042 *   }
14043 *
14044 *   function PanelMenuCtrl(mdPanelRef) {
14045 *     this.closeMenu = function() {
14046 *       mdPanelRef && mdPanelRef.close();
14047 *     };
14048 *   }
14049 * })(angular);
14050 * </hljs>
14051 */
14052
14053/**
14054 * @ngdoc method
14055 * @name $mdPanelProvider#definePreset
14056 * @description
14057 * Takes the passed in preset name and preset configuration object and adds it
14058 * to the `_presets` object of the provider. This `_presets` object is then
14059 * passed along to the `$mdPanel` service.
14060 *
14061 * @param {string} name Preset name.
14062 * @param {!Object} preset Specific configuration object that can contain any
14063 *     and all of the parameters avaialble within the `$mdPanel.create` method.
14064 *     However, parameters that pertain to id, position, animation, and user
14065 *     interaction are not allowed and will be removed from the preset
14066 *     configuration.
14067 */
14068
14069
14070/*****************************************************************************
14071 *                               MdPanel Service                             *
14072 *****************************************************************************/
14073
14074
14075/**
14076 * @ngdoc service
14077 * @name $mdPanel
14078 * @module material.components.panel
14079 *
14080 * @description
14081 * `$mdPanel` is a robust, low-level service for creating floating panels on
14082 * the screen. It can be used to implement tooltips, dialogs, pop-ups, etc.
14083 *
14084 * @usage
14085 * <hljs lang="js">
14086 * (function(angular, undefined) {
14087 *   'use strict';
14088 *
14089 *   angular
14090 *       .module('demoApp', ['ngMaterial'])
14091 *       .controller('DemoDialogController', DialogController);
14092 *
14093 *   var panelRef;
14094 *
14095 *   function showPanel($event) {
14096 *     var panelPosition = $mdPanel.newPanelPosition()
14097 *         .absolute()
14098 *         .top('50%')
14099 *         .left('50%');
14100 *
14101 *     var panelAnimation = $mdPanel.newPanelAnimation()
14102 *         .targetEvent($event)
14103 *         .defaultAnimation('md-panel-animate-fly')
14104 *         .closeTo('.show-button');
14105 *
14106 *     var config = {
14107 *       attachTo: angular.element(document.body),
14108 *       controller: DialogController,
14109 *       controllerAs: 'ctrl',
14110 *       position: panelPosition,
14111 *       animation: panelAnimation,
14112 *       targetEvent: $event,
14113 *       templateUrl: 'dialog-template.html',
14114 *       clickOutsideToClose: true,
14115 *       escapeToClose: true,
14116 *       focusOnOpen: true
14117 *     }
14118 *
14119 *     $mdPanel.open(config)
14120 *         .then(function(result) {
14121 *           panelRef = result;
14122 *         });
14123 *   }
14124 *
14125 *   function DialogController(MdPanelRef) {
14126 *     function closeDialog() {
14127 *       if (MdPanelRef) MdPanelRef.close();
14128 *     }
14129 *   }
14130 * })(angular);
14131 * </hljs>
14132 */
14133
14134/**
14135 * @ngdoc method
14136 * @name $mdPanel#create
14137 * @description
14138 * Creates a panel with the specified options.
14139 *
14140 * @param config {!Object=} Specific configuration object that may contain the
14141 *     following properties:
14142 *
14143 *   - `id` - `{string=}`: An ID to track the panel by. When an ID is provided,
14144 *     the created panel is added to a tracked panels object. Any subsequent
14145 *     requests made to create a panel with that ID are ignored. This is useful
14146 *     in having the panel service not open multiple panels from the same user
14147 *     interaction when there is no backdrop and events are propagated. Defaults
14148 *     to an arbitrary string that is not tracked.
14149 *   - `template` - `{string=}`: HTML template to show in the panel. This
14150 *     **must** be trusted HTML with respect to Angular’s
14151 *     [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
14152 *   - `templateUrl` - `{string=}`: The URL that will be used as the content of
14153 *     the panel.
14154 *   - `contentElement` - `{(string|!angular.JQLite|!Element)=}`: Pre-compiled
14155 *     element to be used as the panel's content.
14156 *   - `controller` - `{(function|string)=}`: The controller to associate with
14157 *     the panel. The controller can inject a reference to the returned
14158 *     panelRef, which allows the panel to be closed, hidden, and shown. Any
14159 *     fields passed in through locals or resolve will be bound to the
14160 *     controller.
14161 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on
14162 *     the scope.
14163 *   - `bindToController` - `{boolean=}`: Binds locals to the controller
14164 *     instead of passing them in. Defaults to true, as this is a best
14165 *     practice.
14166 *   - `locals` - `{Object=}`: An object containing key/value pairs. The keys
14167 *     will be used as names of values to inject into the controller. For
14168 *     example, `locals: {three: 3}` would inject `three` into the controller,
14169 *     with the value 3.
14170 *   - `resolve` - `{Object=}`: Similar to locals, except it takes promises as
14171 *     values. The panel will not open until all of the promises resolve.
14172 *   - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to
14173 *     attach the panel to. Defaults to appending to the root element of the
14174 *     application.
14175 *   - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch
14176 *     events should be allowed to propagate 'go through' the container, aka the
14177 *     wrapper, of the panel. Defaults to false.
14178 *   - `panelClass` - `{string=}`: A css class to apply to the panel element.
14179 *     This class should define any borders, box-shadow, etc. for the panel.
14180 *   - `zIndex` - `{number=}`: The z-index to place the panel at.
14181 *     Defaults to 80.
14182 *   - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that
14183 *     specifies the alignment of the panel. For more information, see
14184 *     `MdPanelPosition`.
14185 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click
14186 *     outside the panel to close it. Defaults to false.
14187 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to
14188 *     close the panel. Defaults to false.
14189 *   - `onCloseSuccess` - `{function(!panelRef, string)=}`: Function that is
14190 *     called after the close successfully finishes. The first parameter passed
14191 *     into this function is the current panelRef and the 2nd is an optional
14192 *     string explaining the close reason. The currently supported closeReasons
14193 *     can be found in the MdPanelRef.closeReasons enum. These are by default
14194 *     passed along by the panel.
14195 *   - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the
14196 *     panel. If `trapFocus` is true, the user will not be able to interact
14197 *     with the rest of the page until the panel is dismissed. Defaults to
14198 *     false.
14199 *   - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on
14200 *     open. Only disable if focusing some other way, as focus management is
14201 *     required for panels to be accessible. Defaults to true.
14202 *   - `fullscreen` - `{boolean=}`: Whether the panel should be full screen.
14203 *     Applies the class `._md-panel-fullscreen` to the panel on open. Defaults
14204 *     to false.
14205 *   - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that
14206 *     specifies the animation of the panel. For more information, see
14207 *     `MdPanelAnimation`.
14208 *   - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop
14209 *     behind the panel. Defaults to false.
14210 *   - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the
14211 *     page behind the panel. Defaults to false.
14212 *   - `onDomAdded` - `{function=}`: Callback function used to announce when
14213 *     the panel is added to the DOM.
14214 *   - `onOpenComplete` - `{function=}`: Callback function used to announce
14215 *     when the open() action is finished.
14216 *   - `onRemoving` - `{function=}`: Callback function used to announce the
14217 *     close/hide() action is starting.
14218 *   - `onDomRemoved` - `{function=}`: Callback function used to announce when
14219 *     the panel is removed from the DOM.
14220 *   - `origin` - `{(string|!angular.JQLite|!Element)=}`: The element to focus
14221 *     on when the panel closes. This is commonly the element which triggered
14222 *     the opening of the panel. If you do not use `origin`, you need to control
14223 *     the focus manually.
14224 *   - `groupName` - `{(string|!Array<string>)=}`: A group name or an array of
14225 *     group names. The group name is used for creating a group of panels. The
14226 *     group is used for configuring the number of open panels and identifying
14227 *     specific behaviors for groups. For instance, all tooltips could be
14228 *     identified using the same groupName.
14229 *
14230 * @returns {!MdPanelRef} panelRef
14231 */
14232
14233/**
14234 * @ngdoc method
14235 * @name $mdPanel#open
14236 * @description
14237 * Calls the create method above, then opens the panel. This is a shortcut for
14238 * creating and then calling open manually. If custom methods need to be
14239 * called when the panel is added to the DOM or opened, do not use this method.
14240 * Instead create the panel, chain promises on the domAdded and openComplete
14241 * methods, and call open from the returned panelRef.
14242 *
14243 * @param {!Object=} config Specific configuration object that may contain
14244 *     the properties defined in `$mdPanel.create`.
14245 * @returns {!angular.$q.Promise<!MdPanelRef>} panelRef A promise that resolves
14246 *     to an instance of the panel.
14247 */
14248
14249/**
14250 * @ngdoc method
14251 * @name $mdPanel#newPanelPosition
14252 * @description
14253 * Returns a new instance of the MdPanelPosition object. Use this to create
14254 * the position config object.
14255 *
14256 * @returns {!MdPanelPosition} panelPosition
14257 */
14258
14259/**
14260 * @ngdoc method
14261 * @name $mdPanel#newPanelAnimation
14262 * @description
14263 * Returns a new instance of the MdPanelAnimation object. Use this to create
14264 * the animation config object.
14265 *
14266 * @returns {!MdPanelAnimation} panelAnimation
14267 */
14268
14269/**
14270 * @ngdoc method
14271 * @name $mdPanel#newPanelGroup
14272 * @description
14273 * Creates a panel group and adds it to a tracked list of panel groups.
14274 *
14275 * @param {string} groupName Name of the group to create.
14276 * @param {!Object=} config Specific configuration object that may contain the
14277 *     following properties:
14278 *
14279 *   - `maxOpen` - `{number=}`: The maximum number of panels that are allowed to
14280 *     be open within a defined panel group.
14281 *
14282 * @returns {!Object<string,
14283 *     {panels: !Array<!MdPanelRef>,
14284 *     openPanels: !Array<!MdPanelRef>,
14285 *     maxOpen: number}>} panelGroup
14286 */
14287
14288/**
14289 * @ngdoc method
14290 * @name $mdPanel#setGroupMaxOpen
14291 * @description
14292 * Sets the maximum number of panels in a group that can be opened at a given
14293 * time.
14294 *
14295 * @param {string} groupName The name of the group to configure.
14296 * @param {number} maxOpen The maximum number of panels that can be
14297 *     opened. Infinity can be passed in to remove the maxOpen limit.
14298 */
14299
14300
14301/*****************************************************************************
14302 *                                 MdPanelRef                                *
14303 *****************************************************************************/
14304
14305
14306/**
14307 * @ngdoc type
14308 * @name MdPanelRef
14309 * @module material.components.panel
14310 * @description
14311 * A reference to a created panel. This reference contains a unique id for the
14312 * panel, along with the following properties:
14313 *
14314 *   - `id` - `{string}`: The unique id for the panel. This id is used to track
14315 *     when a panel was interacted with.
14316 *   - `config` - `{!Object=}`: The entire config object that was used in
14317 *     create.
14318 *   - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM.
14319 *     Visibility to the user does not factor into isAttached.
14320 *   - `panelContainer` - `{angular.JQLite}`: The wrapper element containing the
14321 *     panel. This property is added in order to have access to the `addClass`,
14322 *     `removeClass`, `toggleClass`, etc methods.
14323 *   - `panelEl` - `{angular.JQLite}`: The panel element. This property is added
14324 *     in order to have access to the `addClass`, `removeClass`, `toggleClass`,
14325 *     etc methods.
14326 */
14327
14328/**
14329 * @ngdoc method
14330 * @name MdPanelRef#open
14331 * @description
14332 * Attaches and shows the panel.
14333 *
14334 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
14335 *     opened.
14336 */
14337
14338/**
14339 * @ngdoc method
14340 * @name MdPanelRef#close
14341 * @description
14342 * Hides and detaches the panel. Note that this will **not** destroy the panel.
14343 * If you don't intend on using the panel again, call the {@link #destroy
14344 * destroy} method afterwards.
14345 *
14346 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
14347 *     closed.
14348 */
14349
14350/**
14351 * @ngdoc method
14352 * @name MdPanelRef#attach
14353 * @description
14354 * Create the panel elements and attach them to the DOM. The panel will be
14355 * hidden by default.
14356 *
14357 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
14358 *     attached.
14359 */
14360
14361/**
14362 * @ngdoc method
14363 * @name MdPanelRef#detach
14364 * @description
14365 * Removes the panel from the DOM. This will NOT hide the panel before removing
14366 * it.
14367 *
14368 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
14369 *     detached.
14370 */
14371
14372/**
14373 * @ngdoc method
14374 * @name MdPanelRef#show
14375 * @description
14376 * Shows the panel.
14377 *
14378 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
14379 *     shown and animations are completed.
14380 */
14381
14382/**
14383 * @ngdoc method
14384 * @name MdPanelRef#hide
14385 * @description
14386 * Hides the panel.
14387 *
14388 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
14389 *     hidden and animations are completed.
14390 */
14391
14392/**
14393 * @ngdoc method
14394 * @name MdPanelRef#destroy
14395 * @description
14396 * Destroys the panel. The panel cannot be opened again after this is called.
14397 */
14398
14399/**
14400 * @ngdoc method
14401 * @name MdPanelRef#addClass
14402 * @deprecated
14403 * This method is in the process of being deprecated in favor of using the panel
14404 * and container JQLite elements that are referenced in the MdPanelRef object.
14405 * Full deprecation is scheduled for material 1.2.
14406 * @description
14407 * Adds a class to the panel. DO NOT use this hide/show the panel.
14408 *
14409 * @param {string} newClass class to be added.
14410 * @param {boolean} toElement Whether or not to add the class to the panel
14411 *     element instead of the container.
14412 */
14413
14414/**
14415 * @ngdoc method
14416 * @name MdPanelRef#removeClass
14417 * @deprecated
14418 * This method is in the process of being deprecated in favor of using the panel
14419 * and container JQLite elements that are referenced in the MdPanelRef object.
14420 * Full deprecation is scheduled for material 1.2.
14421 * @description
14422 * Removes a class from the panel. DO NOT use this to hide/show the panel.
14423 *
14424 * @param {string} oldClass Class to be removed.
14425 * @param {boolean} fromElement Whether or not to remove the class from the
14426 *     panel element instead of the container.
14427 */
14428
14429/**
14430 * @ngdoc method
14431 * @name MdPanelRef#toggleClass
14432 * @deprecated
14433 * This method is in the process of being deprecated in favor of using the panel
14434 * and container JQLite elements that are referenced in the MdPanelRef object.
14435 * Full deprecation is scheduled for material 1.2.
14436 * @description
14437 * Toggles a class on the panel. DO NOT use this to hide/show the panel.
14438 *
14439 * @param {string} toggleClass Class to be toggled.
14440 * @param {boolean} onElement Whether or not to remove the class from the panel
14441 *     element instead of the container.
14442 */
14443
14444/**
14445 * @ngdoc method
14446 * @name MdPanelRef#updatePosition
14447 * @description
14448 * Updates the position configuration of a panel. Use this to update the
14449 * position of a panel that is open, without having to close and re-open the
14450 * panel.
14451 *
14452 * @param {!MdPanelPosition} position
14453 */
14454
14455/**
14456 * @ngdoc method
14457 * @name MdPanelRef#addToGroup
14458 * @description
14459 * Adds a panel to a group if the panel does not exist within the group already.
14460 * A panel can only exist within a single group.
14461 *
14462 * @param {string} groupName The name of the group to add the panel to.
14463 */
14464
14465/**
14466 * @ngdoc method
14467 * @name MdPanelRef#removeFromGroup
14468 * @description
14469 * Removes a panel from a group if the panel exists within that group. The group
14470 * must be created ahead of time.
14471 *
14472 * @param {string} groupName The name of the group.
14473 */
14474
14475/**
14476 * @ngdoc method
14477 * @name MdPanelRef#registerInterceptor
14478 * @description
14479 * Registers an interceptor with the panel. The callback should return a promise,
14480 * which will allow the action to continue when it gets resolved, or will
14481 * prevent an action if it is rejected. The interceptors are called sequentially
14482 * and it reverse order. `type` must be one of the following
14483 * values available on `$mdPanel.interceptorTypes`:
14484 * * `CLOSE` - Gets called before the panel begins closing.
14485 *
14486 * @param {string} type Type of interceptor.
14487 * @param {!angular.$q.Promise<any>} callback Callback to be registered.
14488 * @returns {!MdPanelRef}
14489 */
14490
14491/**
14492 * @ngdoc method
14493 * @name MdPanelRef#removeInterceptor
14494 * @description
14495 * Removes a registered interceptor.
14496 *
14497 * @param {string} type Type of interceptor to be removed.
14498 * @param {function(): !angular.$q.Promise<any>} callback Interceptor to be removed.
14499 * @returns {!MdPanelRef}
14500 */
14501
14502/**
14503 * @ngdoc method
14504 * @name MdPanelRef#removeAllInterceptors
14505 * @description
14506 * Removes all interceptors. If a type is supplied, only the
14507 * interceptors of that type will be cleared.
14508 *
14509 * @param {string=} type Type of interceptors to be removed.
14510 * @returns {!MdPanelRef}
14511 */
14512
14513/**
14514 * @ngdoc method
14515 * @name MdPanelRef#updateAnimation
14516 * @description
14517 * Updates the animation configuration for a panel. You can use this to change
14518 * the panel's animation without having to re-create it.
14519 *
14520 * @param {!MdPanelAnimation} animation
14521 */
14522
14523
14524/*****************************************************************************
14525 *                               MdPanelPosition                            *
14526 *****************************************************************************/
14527
14528
14529/**
14530 * @ngdoc type
14531 * @name MdPanelPosition
14532 * @module material.components.panel
14533 * @description
14534 *
14535 * Object for configuring the position of the panel.
14536 *
14537 * @usage
14538 *
14539 * #### Centering the panel
14540 *
14541 * <hljs lang="js">
14542 * new MdPanelPosition().absolute().center();
14543 * </hljs>
14544 *
14545 * #### Overlapping the panel with an element
14546 *
14547 * <hljs lang="js">
14548 * new MdPanelPosition()
14549 *     .relativeTo(someElement)
14550 *     .addPanelPosition(
14551 *       $mdPanel.xPosition.ALIGN_START,
14552 *       $mdPanel.yPosition.ALIGN_TOPS
14553 *     );
14554 * </hljs>
14555 *
14556 * #### Aligning the panel with the bottom of an element
14557 *
14558 * <hljs lang="js">
14559 * new MdPanelPosition()
14560 *     .relativeTo(someElement)
14561 *     .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW);
14562 * </hljs>
14563 */
14564
14565/**
14566 * @ngdoc method
14567 * @name MdPanelPosition#absolute
14568 * @description
14569 * Positions the panel absolutely relative to the parent element. If the parent
14570 * is document.body, this is equivalent to positioning the panel absolutely
14571 * within the viewport.
14572 *
14573 * @returns {!MdPanelPosition}
14574 */
14575
14576/**
14577 * @ngdoc method
14578 * @name MdPanelPosition#relativeTo
14579 * @description
14580 * Positions the panel relative to a specific element.
14581 *
14582 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
14583 *     or angular element to position the panel with respect to.
14584 * @returns {!MdPanelPosition}
14585 */
14586
14587/**
14588 * @ngdoc method
14589 * @name MdPanelPosition#top
14590 * @description
14591 * Sets the value of `top` for the panel. Clears any previously set vertical
14592 * position.
14593 *
14594 * @param {string=} top Value of `top`. Defaults to '0'.
14595 * @returns {!MdPanelPosition}
14596 */
14597
14598/**
14599 * @ngdoc method
14600 * @name MdPanelPosition#bottom
14601 * @description
14602 * Sets the value of `bottom` for the panel. Clears any previously set vertical
14603 * position.
14604 *
14605 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
14606 * @returns {!MdPanelPosition}
14607 */
14608
14609/**
14610 * @ngdoc method
14611 * @name MdPanelPosition#start
14612 * @description
14613 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
14614 * `rtl`. Clears any previously set horizontal position.
14615 *
14616 * @param {string=} start Value of position. Defaults to '0'.
14617 * @returns {!MdPanelPosition}
14618 */
14619
14620/**
14621 * @ngdoc method
14622 * @name MdPanelPosition#end
14623 * @description
14624 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
14625 * Clears any previously set horizontal position.
14626 *
14627 * @param {string=} end Value of position. Defaults to '0'.
14628 * @returns {!MdPanelPosition}
14629 */
14630
14631/**
14632 * @ngdoc method
14633 * @name MdPanelPosition#left
14634 * @description
14635 * Sets the value of `left` for the panel. Clears any previously set
14636 * horizontal position.
14637 *
14638 * @param {string=} left Value of `left`. Defaults to '0'.
14639 * @returns {!MdPanelPosition}
14640 */
14641
14642/**
14643 * @ngdoc method
14644 * @name MdPanelPosition#right
14645 * @description
14646 * Sets the value of `right` for the panel. Clears any previously set
14647 * horizontal position.
14648 *
14649 * @param {string=} right Value of `right`. Defaults to '0'.
14650 * @returns {!MdPanelPosition}
14651 */
14652
14653/**
14654 * @ngdoc method
14655 * @name MdPanelPosition#centerHorizontally
14656 * @description
14657 * Centers the panel horizontally in the viewport. Clears any previously set
14658 * horizontal position.
14659 *
14660 * @returns {!MdPanelPosition}
14661 */
14662
14663/**
14664 * @ngdoc method
14665 * @name MdPanelPosition#centerVertically
14666 * @description
14667 * Centers the panel vertically in the viewport. Clears any previously set
14668 * vertical position.
14669 *
14670 * @returns {!MdPanelPosition}
14671 */
14672
14673/**
14674 * @ngdoc method
14675 * @name MdPanelPosition#center
14676 * @description
14677 * Centers the panel horizontally and vertically in the viewport. This is
14678 * equivalent to calling both `centerHorizontally` and `centerVertically`.
14679 * Clears any previously set horizontal and vertical positions.
14680 *
14681 * @returns {!MdPanelPosition}
14682 */
14683
14684/**
14685 * @ngdoc method
14686 * @name MdPanelPosition#addPanelPosition
14687 * @description
14688 * Sets the x and y position for the panel relative to another element. Can be
14689 * called multiple times to specify an ordered list of panel positions. The
14690 * first position which allows the panel to be completely on-screen will be
14691 * chosen; the last position will be chose whether it is on-screen or not.
14692 *
14693 * xPosition must be one of the following values available on
14694 * $mdPanel.xPosition:
14695 *
14696 *
14697 * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END
14698 *
14699 * <pre>
14700 *    *************
14701 *    *           *
14702 *    *   PANEL   *
14703 *    *           *
14704 *    *************
14705 *   A B    C    D E
14706 *
14707 * A: OFFSET_START (for LTR displays)
14708 * B: ALIGN_START (for LTR displays)
14709 * C: CENTER
14710 * D: ALIGN_END (for LTR displays)
14711 * E: OFFSET_END (for LTR displays)
14712 * </pre>
14713 *
14714 * yPosition must be one of the following values available on
14715 * $mdPanel.yPosition:
14716 *
14717 * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW
14718 *
14719 * <pre>
14720 *   F
14721 *   G *************
14722 *     *           *
14723 *   H *   PANEL   *
14724 *     *           *
14725 *   I *************
14726 *   J
14727 *
14728 * F: BELOW
14729 * G: ALIGN_TOPS
14730 * H: CENTER
14731 * I: ALIGN_BOTTOMS
14732 * J: ABOVE
14733 * </pre>
14734 *
14735 * @param {string} xPosition
14736 * @param {string} yPosition
14737 * @returns {!MdPanelPosition}
14738 */
14739
14740/**
14741 * @ngdoc method
14742 * @name MdPanelPosition#withOffsetX
14743 * @description
14744 * Sets the value of the offset in the x-direction.
14745 *
14746 * @param {string} offsetX
14747 * @returns {!MdPanelPosition}
14748 */
14749
14750/**
14751 * @ngdoc method
14752 * @name MdPanelPosition#withOffsetY
14753 * @description
14754 * Sets the value of the offset in the y-direction.
14755 *
14756 * @param {string} offsetY
14757 * @returns {!MdPanelPosition}
14758 */
14759
14760
14761/*****************************************************************************
14762 *                               MdPanelAnimation                            *
14763 *****************************************************************************/
14764
14765
14766/**
14767 * @ngdoc type
14768 * @name MdPanelAnimation
14769 * @module material.components.panel
14770 * @description
14771 * Animation configuration object. To use, create an MdPanelAnimation with the
14772 * desired properties, then pass the object as part of $mdPanel creation.
14773 *
14774 * @usage
14775 *
14776 * <hljs lang="js">
14777 * var panelAnimation = new MdPanelAnimation()
14778 *     .openFrom(myButtonEl)
14779 *     .duration(1337)
14780 *     .closeTo('.my-button')
14781 *     .withAnimation($mdPanel.animation.SCALE);
14782 *
14783 * $mdPanel.create({
14784 *   animation: panelAnimation
14785 * });
14786 * </hljs>
14787 */
14788
14789/**
14790 * @ngdoc method
14791 * @name MdPanelAnimation#openFrom
14792 * @description
14793 * Specifies where to start the open animation. `openFrom` accepts a
14794 * click event object, query selector, DOM element, or a Rect object that
14795 * is used to determine the bounds. When passed a click event, the location
14796 * of the click will be used as the position to start the animation.
14797 *
14798 * @param {string|!Element|!Event|{top: number, left: number}}
14799 * @returns {!MdPanelAnimation}
14800 */
14801
14802/**
14803 * @ngdoc method
14804 * @name MdPanelAnimation#closeTo
14805 * @description
14806 * Specifies where to animate the panel close. `closeTo` accepts a
14807 * query selector, DOM element, or a Rect object that is used to determine
14808 * the bounds.
14809 *
14810 * @param {string|!Element|{top: number, left: number}}
14811 * @returns {!MdPanelAnimation}
14812 */
14813
14814/**
14815 * @ngdoc method
14816 * @name MdPanelAnimation#withAnimation
14817 * @description
14818 * Specifies the animation class.
14819 *
14820 * There are several default animations that can be used:
14821 * ($mdPanel.animation)
14822 *   SLIDE: The panel slides in and out from the specified
14823 *       elements. It will not fade in or out.
14824 *   SCALE: The panel scales in and out. Slide and fade are
14825 *       included in this animation.
14826 *   FADE: The panel fades in and out.
14827 *
14828 * Custom classes will by default fade in and out unless
14829 * "transition: opacity 1ms" is added to the to custom class.
14830 *
14831 * @param {string|{open: string, close: string}} cssClass
14832 * @returns {!MdPanelAnimation}
14833 */
14834
14835/**
14836 * @ngdoc method
14837 * @name MdPanelAnimation#duration
14838 * @description
14839 * Specifies the duration of the animation in milliseconds. The `duration`
14840 * method accepts either a number or an object with separate open and close
14841 * durations.
14842 *
14843 * @param {number|{open: number, close: number}} duration
14844 * @returns {!MdPanelAnimation}
14845 */
14846
14847
14848/*****************************************************************************
14849 *                            PUBLIC DOCUMENTATION                           *
14850 *****************************************************************************/
14851
14852
14853var MD_PANEL_Z_INDEX = 80;
14854var MD_PANEL_HIDDEN = '_md-panel-hidden';
14855var FOCUS_TRAP_TEMPLATE = angular.element(
14856    '<div class="_md-panel-focus-trap" tabindex="0"></div>');
14857
14858var _presets = {};
14859
14860
14861/**
14862 * A provider that is used for creating presets for the panel API.
14863 * @final @constructor @ngInject
14864 */
14865function MdPanelProvider() {
14866  return {
14867    'definePreset': definePreset,
14868    'getAllPresets': getAllPresets,
14869    'clearPresets': clearPresets,
14870    '$get': $getProvider()
14871  };
14872}
14873
14874
14875/**
14876 * Takes the passed in panel configuration object and adds it to the `_presets`
14877 * object at the specified name.
14878 * @param {string} name Name of the preset to set.
14879 * @param {!Object} preset Specific configuration object that can contain any
14880 *     and all of the parameters avaialble within the `$mdPanel.create` method.
14881 *     However, parameters that pertain to id, position, animation, and user
14882 *     interaction are not allowed and will be removed from the preset
14883 *     configuration.
14884 */
14885function definePreset(name, preset) {
14886  if (!name || !preset) {
14887    throw new Error('mdPanelProvider: The panel preset definition is ' +
14888        'malformed. The name and preset object are required.');
14889  } else if (_presets.hasOwnProperty(name)) {
14890    throw new Error('mdPanelProvider: The panel preset you have requested ' +
14891        'has already been defined.');
14892  }
14893
14894  // Delete any property on the preset that is not allowed.
14895  delete preset.id;
14896  delete preset.position;
14897  delete preset.animation;
14898
14899  _presets[name] = preset;
14900}
14901
14902
14903/**
14904 * Gets a clone of the `_presets`.
14905 * @return {!Object}
14906 */
14907function getAllPresets() {
14908  return angular.copy(_presets);
14909}
14910
14911
14912/**
14913 * Clears all of the stored presets.
14914 */
14915function clearPresets() {
14916  _presets = {};
14917}
14918
14919
14920/**
14921 * Represents the `$get` method of the Angular provider. From here, a new
14922 * reference to the MdPanelService is returned where the needed arguments are
14923 * passed in including the MdPanelProvider `_presets`.
14924 * @param {!Object} _presets
14925 * @param {!angular.JQLite} $rootElement
14926 * @param {!angular.Scope} $rootScope
14927 * @param {!angular.$injector} $injector
14928 * @param {!angular.$window} $window
14929 */
14930function $getProvider() {
14931  return [
14932    '$rootElement', '$rootScope', '$injector', '$window',
14933    function($rootElement, $rootScope, $injector, $window) {
14934      return new MdPanelService(_presets, $rootElement, $rootScope,
14935          $injector, $window);
14936    }
14937  ];
14938}
14939
14940
14941/*****************************************************************************
14942 *                               MdPanel Service                             *
14943 *****************************************************************************/
14944
14945
14946/**
14947 * A service that is used for controlling/displaying panels on the screen.
14948 * @param {!Object} presets
14949 * @param {!angular.JQLite} $rootElement
14950 * @param {!angular.Scope} $rootScope
14951 * @param {!angular.$injector} $injector
14952 * @param {!angular.$window} $window
14953 * @final @constructor @ngInject
14954 */
14955function MdPanelService(presets, $rootElement, $rootScope, $injector, $window) {
14956  /**
14957   * Default config options for the panel.
14958   * Anything angular related needs to be done later. Therefore
14959   *     scope: $rootScope.$new(true),
14960   *     attachTo: $rootElement,
14961   * are added later.
14962   * @private {!Object}
14963   */
14964  this._defaultConfigOptions = {
14965    bindToController: true,
14966    clickOutsideToClose: false,
14967    disableParentScroll: false,
14968    escapeToClose: false,
14969    focusOnOpen: true,
14970    fullscreen: false,
14971    hasBackdrop: false,
14972    propagateContainerEvents: false,
14973    transformTemplate: angular.bind(this, this._wrapTemplate),
14974    trapFocus: false,
14975    zIndex: MD_PANEL_Z_INDEX
14976  };
14977
14978  /** @private {!Object} */
14979  this._config = {};
14980
14981  /** @private {!Object} */
14982  this._presets = presets;
14983
14984  /** @private @const */
14985  this._$rootElement = $rootElement;
14986
14987  /** @private @const */
14988  this._$rootScope = $rootScope;
14989
14990  /** @private @const */
14991  this._$injector = $injector;
14992
14993  /** @private @const */
14994  this._$window = $window;
14995
14996  /** @private @const */
14997  this._$mdUtil = this._$injector.get('$mdUtil');
14998
14999  /** @private {!Object<string, !MdPanelRef>} */
15000  this._trackedPanels = {};
15001
15002  /**
15003   * @private {!Object<string,
15004   *     {panels: !Array<!MdPanelRef>,
15005   *     openPanels: !Array<!MdPanelRef>,
15006   *     maxOpen: number}>}
15007   */
15008  this._groups = Object.create(null);
15009
15010  /**
15011   * Default animations that can be used within the panel.
15012   * @type {enum}
15013   */
15014  this.animation = MdPanelAnimation.animation;
15015
15016  /**
15017   * Possible values of xPosition for positioning the panel relative to
15018   * another element.
15019   * @type {enum}
15020   */
15021  this.xPosition = MdPanelPosition.xPosition;
15022
15023  /**
15024   * Possible values of yPosition for positioning the panel relative to
15025   * another element.
15026   * @type {enum}
15027   */
15028  this.yPosition = MdPanelPosition.yPosition;
15029
15030  /**
15031   * Possible values for the interceptors that can be registered on a panel.
15032   * @type {enum}
15033   */
15034  this.interceptorTypes = MdPanelRef.interceptorTypes;
15035
15036  /**
15037   * Possible values for closing of a panel.
15038   * @type {enum}
15039   */
15040  this.closeReasons = MdPanelRef.closeReasons;
15041
15042  /**
15043   * Possible values of absolute position.
15044   * @type {enum}
15045   */
15046  this.absPosition = MdPanelPosition.absPosition;
15047}
15048
15049
15050/**
15051 * Creates a panel with the specified options.
15052 * @param {string=} preset Name of a preset configuration that can be used to
15053 *     extend the panel configuration.
15054 * @param {!Object=} config Configuration object for the panel.
15055 * @returns {!MdPanelRef}
15056 */
15057MdPanelService.prototype.create = function(preset, config) {
15058  if (typeof preset === 'string') {
15059    preset = this._getPresetByName(preset);
15060  } else if (typeof preset === 'object' &&
15061      (angular.isUndefined(config) || !config)) {
15062    config = preset;
15063    preset = {};
15064  }
15065
15066  preset = preset || {};
15067  config = config || {};
15068
15069  // If the passed-in config contains an ID and the ID is within _trackedPanels,
15070  // return the tracked panel after updating its config with the passed-in
15071  // config.
15072  if (angular.isDefined(config.id) && this._trackedPanels[config.id]) {
15073    var trackedPanel = this._trackedPanels[config.id];
15074    angular.extend(trackedPanel.config, config);
15075    return trackedPanel;
15076  }
15077
15078  // Combine the passed-in config, the _defaultConfigOptions, and the preset
15079  // configuration into the `_config`.
15080  this._config = angular.extend({
15081    // If no ID is set within the passed-in config, then create an arbitrary ID.
15082    id: config.id || 'panel_' + this._$mdUtil.nextUid(),
15083    scope: this._$rootScope.$new(true),
15084    attachTo: this._$rootElement
15085  }, this._defaultConfigOptions, config, preset);
15086
15087  // Create the panelRef and add it to the `_trackedPanels` object.
15088  var panelRef = new MdPanelRef(this._config, this._$injector);
15089  this._trackedPanels[config.id] = panelRef;
15090
15091  // Add the panel to each of its requested groups.
15092  if (this._config.groupName) {
15093    if (angular.isString(this._config.groupName)) {
15094      this._config.groupName = [this._config.groupName];
15095    }
15096    angular.forEach(this._config.groupName, function(group) {
15097      panelRef.addToGroup(group);
15098    });
15099  }
15100
15101  this._config.scope.$on('$destroy', angular.bind(panelRef, panelRef.detach));
15102
15103  return panelRef;
15104};
15105
15106
15107/**
15108 * Creates and opens a panel with the specified options.
15109 * @param {string=} preset Name of a preset configuration that can be used to
15110 *     extend the panel configuration.
15111 * @param {!Object=} config Configuration object for the panel.
15112 * @returns {!angular.$q.Promise<!MdPanelRef>} The panel created from create.
15113 */
15114MdPanelService.prototype.open = function(preset, config) {
15115  var panelRef = this.create(preset, config);
15116  return panelRef.open().then(function() {
15117    return panelRef;
15118  });
15119};
15120
15121
15122/**
15123 * Gets a specific preset configuration object saved within `_presets`.
15124 * @param {string} preset Name of the preset to search for.
15125 * @returns {!Object} The preset configuration object.
15126 */
15127MdPanelService.prototype._getPresetByName = function(preset) {
15128  if (!this._presets[preset]) {
15129    throw new Error('mdPanel: The panel preset configuration that you ' +
15130        'requested does not exist. Use the $mdPanelProvider to create a ' +
15131        'preset before requesting one.');
15132  }
15133  return this._presets[preset];
15134};
15135
15136
15137/**
15138 * Returns a new instance of the MdPanelPosition. Use this to create the
15139 * positioning object.
15140 * @returns {!MdPanelPosition}
15141 */
15142MdPanelService.prototype.newPanelPosition = function() {
15143  return new MdPanelPosition(this._$injector);
15144};
15145
15146
15147/**
15148 * Returns a new instance of the MdPanelAnimation. Use this to create the
15149 * animation object.
15150 * @returns {!MdPanelAnimation}
15151 */
15152MdPanelService.prototype.newPanelAnimation = function() {
15153  return new MdPanelAnimation(this._$injector);
15154};
15155
15156
15157/**
15158 * Creates a panel group and adds it to a tracked list of panel groups.
15159 * @param groupName {string} Name of the group to create.
15160 * @param config {!Object=} Specific configuration object that may contain the
15161 *     following properties:
15162 *
15163 *   - `maxOpen` - `{number=}`: The maximum number of panels that are allowed
15164 *     open within a defined panel group.
15165 *
15166 * @returns {!Object<string,
15167 *     {panels: !Array<!MdPanelRef>,
15168 *     openPanels: !Array<!MdPanelRef>,
15169 *     maxOpen: number}>} panelGroup
15170 */
15171MdPanelService.prototype.newPanelGroup = function(groupName, config) {
15172  if (!this._groups[groupName]) {
15173    config = config || {};
15174    var group = {
15175      panels: [],
15176      openPanels: [],
15177      maxOpen: config.maxOpen > 0 ? config.maxOpen : Infinity
15178    };
15179    this._groups[groupName] = group;
15180  }
15181  return this._groups[groupName];
15182};
15183
15184
15185/**
15186 * Sets the maximum number of panels in a group that can be opened at a given
15187 * time.
15188 * @param {string} groupName The name of the group to configure.
15189 * @param {number} maxOpen The maximum number of panels that can be
15190 *     opened. Infinity can be passed in to remove the maxOpen limit.
15191 */
15192MdPanelService.prototype.setGroupMaxOpen = function(groupName, maxOpen) {
15193  if (this._groups[groupName]) {
15194    this._groups[groupName].maxOpen = maxOpen;
15195  } else {
15196    throw new Error('mdPanel: Group does not exist yet. Call newPanelGroup().');
15197  }
15198};
15199
15200
15201/**
15202 * Determines if the current number of open panels within a group exceeds the
15203 * limit of allowed open panels.
15204 * @param {string} groupName The name of the group to check.
15205 * @returns {boolean} true if open count does exceed maxOpen and false if not.
15206 * @private
15207 */
15208MdPanelService.prototype._openCountExceedsMaxOpen = function(groupName) {
15209  if (this._groups[groupName]) {
15210    var group = this._groups[groupName];
15211    return group.maxOpen > 0 && group.openPanels.length > group.maxOpen;
15212  }
15213  return false;
15214};
15215
15216
15217/**
15218 * Closes the first open panel within a specific group.
15219 * @param {string} groupName The name of the group.
15220 * @private
15221 */
15222MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) {
15223  this._groups[groupName].openPanels[0].close();
15224};
15225
15226
15227/**
15228 * Wraps the users template in two elements, md-panel-outer-wrapper, which
15229 * covers the entire attachTo element, and md-panel, which contains only the
15230 * template. This allows the panel control over positioning, animations,
15231 * and similar properties.
15232 * @param {string} origTemplate The original template.
15233 * @returns {string} The wrapped template.
15234 * @private
15235 */
15236MdPanelService.prototype._wrapTemplate = function(origTemplate) {
15237  var template = origTemplate || '';
15238
15239  // The panel should be initially rendered offscreen so we can calculate
15240  // height and width for positioning.
15241  return '' +
15242      '<div class="md-panel-outer-wrapper">' +
15243      '  <div class="md-panel" style="left: -9999px;">' + template + '</div>' +
15244      '</div>';
15245};
15246
15247
15248/**
15249 * Wraps a content element in a md-panel-outer wrapper and
15250 * positions it off-screen. Allows for proper control over positoning
15251 * and animations.
15252 * @param {!angular.JQLite} contentElement Element to be wrapped.
15253 * @return {!angular.JQLite} Wrapper element.
15254 * @private
15255 */
15256MdPanelService.prototype._wrapContentElement = function(contentElement) {
15257  var wrapper = angular.element('<div class="md-panel-outer-wrapper">');
15258
15259  contentElement.addClass('md-panel').css('left', '-9999px');
15260  wrapper.append(contentElement);
15261
15262  return wrapper;
15263};
15264
15265
15266/*****************************************************************************
15267 *                                 MdPanelRef                                *
15268 *****************************************************************************/
15269
15270
15271/**
15272 * A reference to a created panel. This reference contains a unique id for the
15273 * panel, along with properties/functions used to control the panel.
15274 * @param {!Object} config
15275 * @param {!angular.$injector} $injector
15276 * @final @constructor
15277 */
15278function MdPanelRef(config, $injector) {
15279  // Injected variables.
15280  /** @private @const {!angular.$q} */
15281  this._$q = $injector.get('$q');
15282
15283  /** @private @const {!angular.$mdCompiler} */
15284  this._$mdCompiler = $injector.get('$mdCompiler');
15285
15286  /** @private @const {!angular.$mdConstant} */
15287  this._$mdConstant = $injector.get('$mdConstant');
15288
15289  /** @private @const {!angular.$mdUtil} */
15290  this._$mdUtil = $injector.get('$mdUtil');
15291
15292  /** @private @const {!angular.$mdTheming} */
15293  this._$mdTheming = $injector.get('$mdTheming');
15294
15295  /** @private @const {!angular.Scope} */
15296  this._$rootScope = $injector.get('$rootScope');
15297
15298  /** @private @const {!angular.$animate} */
15299  this._$animate = $injector.get('$animate');
15300
15301  /** @private @const {!MdPanelRef} */
15302  this._$mdPanel = $injector.get('$mdPanel');
15303
15304  /** @private @const {!angular.$log} */
15305  this._$log = $injector.get('$log');
15306
15307  /** @private @const {!angular.$window} */
15308  this._$window = $injector.get('$window');
15309
15310  /** @private @const {!Function} */
15311  this._$$rAF = $injector.get('$$rAF');
15312
15313  // Public variables.
15314  /**
15315   * Unique id for the panelRef.
15316   * @type {string}
15317   */
15318  this.id = config.id;
15319
15320  /** @type {!Object} */
15321  this.config = config;
15322
15323  /** @type {!angular.JQLite|undefined} */
15324  this.panelContainer;
15325
15326  /** @type {!angular.JQLite|undefined} */
15327  this.panelEl;
15328
15329  /**
15330   * Whether the panel is attached. This is synchronous. When attach is called,
15331   * isAttached is set to true. When detach is called, isAttached is set to
15332   * false.
15333   * @type {boolean}
15334   */
15335  this.isAttached = false;
15336
15337  // Private variables.
15338  /** @private {Array<function()>} */
15339  this._removeListeners = [];
15340
15341  /** @private {!angular.JQLite|undefined} */
15342  this._topFocusTrap;
15343
15344  /** @private {!angular.JQLite|undefined} */
15345  this._bottomFocusTrap;
15346
15347  /** @private {!$mdPanel|undefined} */
15348  this._backdropRef;
15349
15350  /** @private {Function?} */
15351  this._restoreScroll = null;
15352
15353  /**
15354   * Keeps track of all the panel interceptors.
15355   * @private {!Object}
15356   */
15357  this._interceptors = Object.create(null);
15358
15359  /**
15360   * Cleanup function, provided by `$mdCompiler` and assigned after the element
15361   * has been compiled. When `contentElement` is used, the function is used to
15362   * restore the element to it's proper place in the DOM.
15363   * @private {!Function}
15364   */
15365  this._compilerCleanup = null;
15366
15367  /**
15368   * Cache for saving and restoring element inline styles, CSS classes etc.
15369   * @type {{styles: string, classes: string}}
15370   */
15371  this._restoreCache = {
15372    styles: '',
15373    classes: ''
15374  };
15375}
15376
15377
15378MdPanelRef.interceptorTypes = {
15379  CLOSE: 'onClose'
15380};
15381
15382
15383/**
15384 * Opens an already created and configured panel. If the panel is already
15385 * visible, does nothing.
15386 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
15387 *     the panel is opened and animations finish.
15388 */
15389MdPanelRef.prototype.open = function() {
15390  var self = this;
15391  return this._$q(function(resolve, reject) {
15392    var done = self._done(resolve, self);
15393    var show = self._simpleBind(self.show, self);
15394    var checkGroupMaxOpen = function() {
15395      if (self.config.groupName) {
15396        angular.forEach(self.config.groupName, function(group) {
15397          if (self._$mdPanel._openCountExceedsMaxOpen(group)) {
15398            self._$mdPanel._closeFirstOpenedPanel(group);
15399          }
15400        });
15401      }
15402    };
15403
15404    self.attach()
15405        .then(show)
15406        .then(checkGroupMaxOpen)
15407        .then(done)
15408        .catch(reject);
15409  });
15410};
15411
15412
15413/**
15414 * Closes the panel.
15415 * @param {string} closeReason The event type that triggered the close.
15416 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
15417 *     the panel is closed and animations finish.
15418 */
15419MdPanelRef.prototype.close = function(closeReason) {
15420  var self = this;
15421
15422  return this._$q(function(resolve, reject) {
15423    self._callInterceptors(MdPanelRef.interceptorTypes.CLOSE).then(function() {
15424      var done = self._done(resolve, self);
15425      var detach = self._simpleBind(self.detach, self);
15426      var onCloseSuccess = self.config['onCloseSuccess'] || angular.noop;
15427      onCloseSuccess = angular.bind(self, onCloseSuccess, self, closeReason);
15428
15429      self.hide()
15430          .then(detach)
15431          .then(done)
15432          .then(onCloseSuccess)
15433          .catch(reject);
15434    }, reject);
15435  });
15436};
15437
15438
15439/**
15440 * Attaches the panel. The panel will be hidden afterwards.
15441 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
15442 *     the panel is attached.
15443 */
15444MdPanelRef.prototype.attach = function() {
15445  if (this.isAttached && this.panelEl) {
15446    return this._$q.when(this);
15447  }
15448
15449  var self = this;
15450  return this._$q(function(resolve, reject) {
15451    var done = self._done(resolve, self);
15452    var onDomAdded = self.config['onDomAdded'] || angular.noop;
15453    var addListeners = function(response) {
15454      self.isAttached = true;
15455      self._addEventListeners();
15456      return response;
15457    };
15458
15459    self._$q.all([
15460        self._createBackdrop(),
15461        self._createPanel()
15462            .then(addListeners)
15463            .catch(reject)
15464    ]).then(onDomAdded)
15465      .then(done)
15466      .catch(reject);
15467  });
15468};
15469
15470
15471/**
15472 * Only detaches the panel. Will NOT hide the panel first.
15473 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
15474 *     the panel is detached.
15475 */
15476MdPanelRef.prototype.detach = function() {
15477  if (!this.isAttached) {
15478    return this._$q.when(this);
15479  }
15480
15481  var self = this;
15482  var onDomRemoved = self.config['onDomRemoved'] || angular.noop;
15483
15484  var detachFn = function() {
15485    self._removeEventListeners();
15486
15487    // Remove the focus traps that we added earlier for keeping focus within
15488    // the panel.
15489    if (self._topFocusTrap && self._topFocusTrap.parentNode) {
15490      self._topFocusTrap.parentNode.removeChild(self._topFocusTrap);
15491    }
15492
15493    if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) {
15494      self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap);
15495    }
15496
15497    if (self._restoreCache.classes) {
15498      self.panelEl[0].className = self._restoreCache.classes;
15499    }
15500
15501    // Either restore the saved styles or clear the ones set by mdPanel.
15502    self.panelEl[0].style.cssText = self._restoreCache.styles || '';
15503
15504    self._compilerCleanup();
15505    self.panelContainer.remove();
15506    self.isAttached = false;
15507    return self._$q.when(self);
15508  };
15509
15510  if (this._restoreScroll) {
15511    this._restoreScroll();
15512    this._restoreScroll = null;
15513  }
15514
15515  return this._$q(function(resolve, reject) {
15516    var done = self._done(resolve, self);
15517
15518    self._$q.all([
15519      detachFn(),
15520      self._backdropRef ? self._backdropRef.detach() : true
15521    ]).then(onDomRemoved)
15522      .then(done)
15523      .catch(reject);
15524  });
15525};
15526
15527
15528/**
15529 * Destroys the panel. The Panel cannot be opened again after this.
15530 */
15531MdPanelRef.prototype.destroy = function() {
15532  var self = this;
15533  if (this.config.groupName) {
15534    angular.forEach(this.config.groupName, function(group) {
15535      self.removeFromGroup(group);
15536    });
15537  }
15538  this.config.scope.$destroy();
15539  this.config.locals = null;
15540  this._interceptors = null;
15541};
15542
15543
15544/**
15545 * Shows the panel.
15546 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
15547 *     the panel has shown and animations finish.
15548 */
15549MdPanelRef.prototype.show = function() {
15550  if (!this.panelContainer) {
15551    return this._$q(function(resolve, reject) {
15552      reject('mdPanel: Panel does not exist yet. Call open() or attach().');
15553    });
15554  }
15555
15556  if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
15557    return this._$q.when(this);
15558  }
15559
15560  var self = this;
15561  var animatePromise = function() {
15562    self.panelContainer.removeClass(MD_PANEL_HIDDEN);
15563    return self._animateOpen();
15564  };
15565
15566  return this._$q(function(resolve, reject) {
15567    var done = self._done(resolve, self);
15568    var onOpenComplete = self.config['onOpenComplete'] || angular.noop;
15569    var addToGroupOpen = function() {
15570      if (self.config.groupName) {
15571        angular.forEach(self.config.groupName, function(group) {
15572          self._$mdPanel._groups[group].openPanels.push(self);
15573        });
15574      }
15575    };
15576
15577    self._$q.all([
15578      self._backdropRef ? self._backdropRef.show() : self,
15579      animatePromise().then(function() { self._focusOnOpen(); }, reject)
15580    ]).then(onOpenComplete)
15581      .then(addToGroupOpen)
15582      .then(done)
15583      .catch(reject);
15584  });
15585};
15586
15587
15588/**
15589 * Hides the panel.
15590 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
15591 *     the panel has hidden and animations finish.
15592 */
15593MdPanelRef.prototype.hide = function() {
15594  if (!this.panelContainer) {
15595    return this._$q(function(resolve, reject) {
15596      reject('mdPanel: Panel does not exist yet. Call open() or attach().');
15597    });
15598  }
15599
15600  if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
15601    return this._$q.when(this);
15602  }
15603
15604  var self = this;
15605
15606  return this._$q(function(resolve, reject) {
15607    var done = self._done(resolve, self);
15608    var onRemoving = self.config['onRemoving'] || angular.noop;
15609    var hidePanel = function() {
15610      self.panelContainer.addClass(MD_PANEL_HIDDEN);
15611    };
15612    var removeFromGroupOpen = function() {
15613      if (self.config.groupName) {
15614        var group, index;
15615        angular.forEach(self.config.groupName, function(group) {
15616          group = self._$mdPanel._groups[group];
15617          index = group.openPanels.indexOf(self);
15618          if (index > -1) {
15619            group.openPanels.splice(index, 1);
15620          }
15621        });
15622      }
15623    };
15624    var focusOnOrigin = function() {
15625      var origin = self.config['origin'];
15626      if (origin) {
15627        getElement(origin).focus();
15628      }
15629    };
15630
15631    self._$q.all([
15632      self._backdropRef ? self._backdropRef.hide() : self,
15633      self._animateClose()
15634          .then(onRemoving)
15635          .then(hidePanel)
15636          .then(removeFromGroupOpen)
15637          .then(focusOnOrigin)
15638          .catch(reject)
15639    ]).then(done, reject);
15640  });
15641};
15642
15643
15644/**
15645 * Add a class to the panel. DO NOT use this to hide/show the panel.
15646 * @deprecated
15647 * This method is in the process of being deprecated in favor of using the panel
15648 * and container JQLite elements that are referenced in the MdPanelRef object.
15649 * Full deprecation is scheduled for material 1.2.
15650 *
15651 * @param {string} newClass Class to be added.
15652 * @param {boolean} toElement Whether or not to add the class to the panel
15653 *     element instead of the container.
15654 */
15655MdPanelRef.prototype.addClass = function(newClass, toElement) {
15656  this._$log.warn(
15657      'mdPanel: The addClass method is in the process of being deprecated. ' +
15658      'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
15659      'To achieve the same results, use the panelContainer or panelEl ' +
15660      'JQLite elements that are referenced in MdPanelRef.');
15661
15662  if (!this.panelContainer) {
15663    throw new Error(
15664        'mdPanel: Panel does not exist yet. Call open() or attach().');
15665  }
15666
15667  if (!toElement && !this.panelContainer.hasClass(newClass)) {
15668    this.panelContainer.addClass(newClass);
15669  } else if (toElement && !this.panelEl.hasClass(newClass)) {
15670    this.panelEl.addClass(newClass);
15671  }
15672};
15673
15674
15675/**
15676 * Remove a class from the panel. DO NOT use this to hide/show the panel.
15677 * @deprecated
15678 * This method is in the process of being deprecated in favor of using the panel
15679 * and container JQLite elements that are referenced in the MdPanelRef object.
15680 * Full deprecation is scheduled for material 1.2.
15681 *
15682 * @param {string} oldClass Class to be removed.
15683 * @param {boolean} fromElement Whether or not to remove the class from the
15684 *     panel element instead of the container.
15685 */
15686MdPanelRef.prototype.removeClass = function(oldClass, fromElement) {
15687  this._$log.warn(
15688      'mdPanel: The removeClass method is in the process of being deprecated. ' +
15689      'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
15690      'To achieve the same results, use the panelContainer or panelEl ' +
15691      'JQLite elements that are referenced in MdPanelRef.');
15692
15693  if (!this.panelContainer) {
15694    throw new Error(
15695        'mdPanel: Panel does not exist yet. Call open() or attach().');
15696  }
15697
15698  if (!fromElement && this.panelContainer.hasClass(oldClass)) {
15699    this.panelContainer.removeClass(oldClass);
15700  } else if (fromElement && this.panelEl.hasClass(oldClass)) {
15701    this.panelEl.removeClass(oldClass);
15702  }
15703};
15704
15705
15706/**
15707 * Toggle a class on the panel. DO NOT use this to hide/show the panel.
15708 * @deprecated
15709 * This method is in the process of being deprecated in favor of using the panel
15710 * and container JQLite elements that are referenced in the MdPanelRef object.
15711 * Full deprecation is scheduled for material 1.2.
15712 *
15713 * @param {string} toggleClass The class to toggle.
15714 * @param {boolean} onElement Whether or not to toggle the class on the panel
15715 *     element instead of the container.
15716 */
15717MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) {
15718  this._$log.warn(
15719      'mdPanel: The toggleClass method is in the process of being deprecated. ' +
15720      'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
15721      'To achieve the same results, use the panelContainer or panelEl ' +
15722      'JQLite elements that are referenced in MdPanelRef.');
15723
15724  if (!this.panelContainer) {
15725    throw new Error(
15726        'mdPanel: Panel does not exist yet. Call open() or attach().');
15727  }
15728
15729  if (!onElement) {
15730    this.panelContainer.toggleClass(toggleClass);
15731  } else {
15732    this.panelEl.toggleClass(toggleClass);
15733  }
15734};
15735
15736
15737/**
15738 * Compiles the panel, according to the passed in config and appends it to
15739 * the DOM. Helps normalize differences in the compilation process between
15740 * using a string template and a content element.
15741 * @returns {!angular.$q.Promise<!MdPanelRef>} Promise that is resolved when
15742 *     the element has been compiled and added to the DOM.
15743 * @private
15744 */
15745MdPanelRef.prototype._compile = function() {
15746  var self = this;
15747
15748  // Compile the element via $mdCompiler. Note that when using a
15749  // contentElement, the element isn't actually being compiled, rather the
15750  // compiler saves it's place in the DOM and provides a way of restoring it.
15751  return self._$mdCompiler.compile(self.config).then(function(compileData) {
15752    var config = self.config;
15753
15754    if (config.contentElement) {
15755      var panelEl = compileData.element;
15756
15757      // Since mdPanel modifies the inline styles and CSS classes, we need
15758      // to save them in order to be able to restore on close.
15759      self._restoreCache.styles = panelEl[0].style.cssText;
15760      self._restoreCache.classes = panelEl[0].className;
15761
15762      self.panelContainer = self._$mdPanel._wrapContentElement(panelEl);
15763      self.panelEl = panelEl;
15764    } else {
15765      self.panelContainer = compileData.link(config['scope']);
15766      self.panelEl = angular.element(
15767        self.panelContainer[0].querySelector('.md-panel')
15768      );
15769    }
15770
15771    // Save a reference to the cleanup function from the compiler.
15772    self._compilerCleanup = compileData.cleanup;
15773
15774    // Attach the panel to the proper place in the DOM.
15775    getElement(self.config['attachTo']).append(self.panelContainer);
15776
15777    return self;
15778  });
15779};
15780
15781
15782/**
15783 * Creates a panel and adds it to the dom.
15784 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
15785 *     created.
15786 * @private
15787 */
15788MdPanelRef.prototype._createPanel = function() {
15789  var self = this;
15790
15791  return this._$q(function(resolve, reject) {
15792    if (!self.config.locals) {
15793      self.config.locals = {};
15794    }
15795
15796    self.config.locals.mdPanelRef = self;
15797
15798    self._compile().then(function() {
15799      if (self.config['disableParentScroll']) {
15800        self._restoreScroll = self._$mdUtil.disableScrollAround(
15801          null,
15802          self.panelContainer,
15803          { disableScrollMask: true }
15804        );
15805      }
15806
15807      // Add a custom CSS class to the panel element.
15808      if (self.config['panelClass']) {
15809        self.panelEl.addClass(self.config['panelClass']);
15810      }
15811
15812      // Handle click and touch events for the panel container.
15813      if (self.config['propagateContainerEvents']) {
15814        self.panelContainer.css('pointer-events', 'none');
15815      }
15816
15817      // Panel may be outside the $rootElement, tell ngAnimate to animate
15818      // regardless.
15819      if (self._$animate.pin) {
15820        self._$animate.pin(
15821          self.panelContainer,
15822          getElement(self.config['attachTo'])
15823        );
15824      }
15825
15826      self._configureTrapFocus();
15827      self._addStyles().then(function() {
15828        resolve(self);
15829      }, reject);
15830    }, reject);
15831
15832  });
15833};
15834
15835
15836/**
15837 * Adds the styles for the panel, such as positioning and z-index. Also,
15838 * themes the panel element and panel container using `$mdTheming`.
15839 * @returns {!angular.$q.Promise<!MdPanelRef>}
15840 * @private
15841 */
15842MdPanelRef.prototype._addStyles = function() {
15843  var self = this;
15844  return this._$q(function(resolve) {
15845    self.panelContainer.css('z-index', self.config['zIndex']);
15846    self.panelEl.css('z-index', self.config['zIndex'] + 1);
15847
15848    var hideAndResolve = function() {
15849      // Theme the element and container.
15850      self._setTheming();
15851
15852      // Remove left: -9999px and add hidden class.
15853      self.panelEl.css('left', '');
15854      self.panelContainer.addClass(MD_PANEL_HIDDEN);
15855
15856      resolve(self);
15857    };
15858
15859    if (self.config['fullscreen']) {
15860      self.panelEl.addClass('_md-panel-fullscreen');
15861      hideAndResolve();
15862      return; // Don't setup positioning.
15863    }
15864
15865    var positionConfig = self.config['position'];
15866    if (!positionConfig) {
15867      hideAndResolve();
15868      return; // Don't setup positioning.
15869    }
15870
15871    // Wait for angular to finish processing the template
15872    self._$rootScope['$$postDigest'](function() {
15873      // Position it correctly. This is necessary so that the panel will have a
15874      // defined height and width.
15875      self._updatePosition(true);
15876
15877      // Theme the element and container.
15878      self._setTheming();
15879
15880      resolve(self);
15881    });
15882  });
15883};
15884
15885
15886/**
15887 * Sets the `$mdTheming` classes on the `panelContainer` and `panelEl`.
15888 * @private
15889 */
15890MdPanelRef.prototype._setTheming = function() {
15891  this._$mdTheming(this.panelEl);
15892  this._$mdTheming(this.panelContainer);
15893};
15894
15895
15896/**
15897 * Updates the position configuration of a panel
15898 * @param {!MdPanelPosition} position
15899 */
15900MdPanelRef.prototype.updatePosition = function(position) {
15901  if (!this.panelContainer) {
15902    throw new Error(
15903        'mdPanel: Panel does not exist yet. Call open() or attach().');
15904  }
15905
15906  this.config['position'] = position;
15907  this._updatePosition();
15908};
15909
15910
15911/**
15912 * Calculates and updates the position of the panel.
15913 * @param {boolean=} init
15914 * @private
15915 */
15916MdPanelRef.prototype._updatePosition = function(init) {
15917  var positionConfig = this.config['position'];
15918
15919  if (positionConfig) {
15920    positionConfig._setPanelPosition(this.panelEl);
15921
15922    // Hide the panel now that position is known.
15923    if (init) {
15924      this.panelContainer.addClass(MD_PANEL_HIDDEN);
15925    }
15926
15927    this.panelEl.css(
15928      MdPanelPosition.absPosition.TOP,
15929      positionConfig.getTop()
15930    );
15931    this.panelEl.css(
15932      MdPanelPosition.absPosition.BOTTOM,
15933      positionConfig.getBottom()
15934    );
15935    this.panelEl.css(
15936      MdPanelPosition.absPosition.LEFT,
15937      positionConfig.getLeft()
15938    );
15939    this.panelEl.css(
15940      MdPanelPosition.absPosition.RIGHT,
15941      positionConfig.getRight()
15942    );
15943  }
15944};
15945
15946
15947/**
15948 * Focuses on the panel or the first focus target.
15949 * @private
15950 */
15951MdPanelRef.prototype._focusOnOpen = function() {
15952  if (this.config['focusOnOpen']) {
15953    // Wait for the template to finish rendering to guarantee md-autofocus has
15954    // finished adding the class md-autofocus, otherwise the focusable element
15955    // isn't available to focus.
15956    var self = this;
15957    this._$rootScope['$$postDigest'](function() {
15958      var target = self._$mdUtil.findFocusTarget(self.panelEl) ||
15959          self.panelEl;
15960      target.focus();
15961    });
15962  }
15963};
15964
15965
15966/**
15967 * Shows the backdrop.
15968 * @returns {!angular.$q.Promise} A promise that is resolved when the backdrop
15969 *     is created and attached.
15970 * @private
15971 */
15972MdPanelRef.prototype._createBackdrop = function() {
15973  if (this.config.hasBackdrop) {
15974    if (!this._backdropRef) {
15975      var backdropAnimation = this._$mdPanel.newPanelAnimation()
15976          .openFrom(this.config.attachTo)
15977          .withAnimation({
15978            open: '_md-opaque-enter',
15979            close: '_md-opaque-leave'
15980          });
15981
15982      if (this.config.animation) {
15983        backdropAnimation.duration(this.config.animation._rawDuration);
15984      }
15985
15986      var backdropConfig = {
15987        animation: backdropAnimation,
15988        attachTo: this.config.attachTo,
15989        focusOnOpen: false,
15990        panelClass: '_md-panel-backdrop',
15991        zIndex: this.config.zIndex - 1
15992      };
15993
15994      this._backdropRef = this._$mdPanel.create(backdropConfig);
15995    }
15996    if (!this._backdropRef.isAttached) {
15997      return this._backdropRef.attach();
15998    }
15999  }
16000};
16001
16002
16003/**
16004 * Listen for escape keys and outside clicks to auto close.
16005 * @private
16006 */
16007MdPanelRef.prototype._addEventListeners = function() {
16008  this._configureEscapeToClose();
16009  this._configureClickOutsideToClose();
16010  this._configureScrollListener();
16011};
16012
16013
16014/**
16015 * Remove event listeners added in _addEventListeners.
16016 * @private
16017 */
16018MdPanelRef.prototype._removeEventListeners = function() {
16019  this._removeListeners && this._removeListeners.forEach(function(removeFn) {
16020    removeFn();
16021  });
16022  this._removeListeners = [];
16023};
16024
16025
16026/**
16027 * Setup the escapeToClose event listeners.
16028 * @private
16029 */
16030MdPanelRef.prototype._configureEscapeToClose = function() {
16031  if (this.config['escapeToClose']) {
16032    var parentTarget = getElement(this.config['attachTo']);
16033    var self = this;
16034
16035    var keyHandlerFn = function(ev) {
16036      if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) {
16037        ev.stopPropagation();
16038        ev.preventDefault();
16039
16040        self.close(MdPanelRef.closeReasons.ESCAPE);
16041      }
16042    };
16043
16044    // Add keydown listeners
16045    this.panelContainer.on('keydown', keyHandlerFn);
16046    parentTarget.on('keydown', keyHandlerFn);
16047
16048    // Queue remove listeners function
16049    this._removeListeners.push(function() {
16050      self.panelContainer.off('keydown', keyHandlerFn);
16051      parentTarget.off('keydown', keyHandlerFn);
16052    });
16053  }
16054};
16055
16056
16057/**
16058 * Setup the clickOutsideToClose event listeners.
16059 * @private
16060 */
16061MdPanelRef.prototype._configureClickOutsideToClose = function() {
16062  if (this.config['clickOutsideToClose']) {
16063    var target = this.config['propagateContainerEvents'] ?
16064        angular.element(document.body) :
16065        this.panelContainer;
16066    var sourceEl;
16067
16068    // Keep track of the element on which the mouse originally went down
16069    // so that we can only close the backdrop when the 'click' started on it.
16070    // A simple 'click' handler does not work, it sets the target object as the
16071    // element the mouse went down on.
16072    var mousedownHandler = function(ev) {
16073      sourceEl = ev.target;
16074    };
16075
16076    // We check if our original element and the target is the backdrop
16077    // because if the original was the backdrop and the target was inside the
16078    // panel we don't want to panel to close.
16079    var self = this;
16080    var mouseupHandler = function(ev) {
16081      if (self.config['propagateContainerEvents']) {
16082
16083        // We check if the sourceEl of the event is the panel element or one
16084        // of it's children. If it is not, then close the panel.
16085        if (sourceEl !== self.panelEl[0] && !self.panelEl[0].contains(sourceEl)) {
16086          self.close();
16087        }
16088
16089      } else if (sourceEl === target[0] && ev.target === target[0]) {
16090        ev.stopPropagation();
16091        ev.preventDefault();
16092
16093        self.close(MdPanelRef.closeReasons.CLICK_OUTSIDE);
16094      }
16095    };
16096
16097    // Add listeners
16098    target.on('mousedown', mousedownHandler);
16099    target.on('mouseup', mouseupHandler);
16100
16101    // Queue remove listeners function
16102    this._removeListeners.push(function() {
16103      target.off('mousedown', mousedownHandler);
16104      target.off('mouseup', mouseupHandler);
16105    });
16106  }
16107};
16108
16109
16110/**
16111 * Configures the listeners for updating the panel position on scroll.
16112 * @private
16113*/
16114MdPanelRef.prototype._configureScrollListener = function() {
16115  // No need to bind the event if scrolling is disabled.
16116  if (!this.config['disableParentScroll']) {
16117    var updatePosition = angular.bind(this, this._updatePosition);
16118    var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition);
16119    var self = this;
16120
16121    var onScroll = function() {
16122      debouncedUpdatePosition();
16123    };
16124
16125    // Add listeners.
16126    this._$window.addEventListener('scroll', onScroll, true);
16127
16128    // Queue remove listeners function.
16129    this._removeListeners.push(function() {
16130      self._$window.removeEventListener('scroll', onScroll, true);
16131    });
16132  }
16133};
16134
16135
16136/**
16137 * Setup the focus traps. These traps will wrap focus when tabbing past the
16138 * panel. When shift-tabbing, the focus will stick in place.
16139 * @private
16140 */
16141MdPanelRef.prototype._configureTrapFocus = function() {
16142  // Focus doesn't remain inside of the panel without this.
16143  this.panelEl.attr('tabIndex', '-1');
16144  if (this.config['trapFocus']) {
16145    var element = this.panelEl;
16146    // Set up elements before and after the panel to capture focus and
16147    // redirect back into the panel.
16148    this._topFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
16149    this._bottomFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
16150
16151    // When focus is about to move out of the panel, we want to intercept it
16152    // and redirect it back to the panel element.
16153    var focusHandler = function() {
16154      element.focus();
16155    };
16156    this._topFocusTrap.addEventListener('focus', focusHandler);
16157    this._bottomFocusTrap.addEventListener('focus', focusHandler);
16158
16159    // Queue remove listeners function
16160    this._removeListeners.push(this._simpleBind(function() {
16161      this._topFocusTrap.removeEventListener('focus', focusHandler);
16162      this._bottomFocusTrap.removeEventListener('focus', focusHandler);
16163    }, this));
16164
16165    // The top focus trap inserted immediately before the md-panel element (as
16166    // a sibling). The bottom focus trap inserted immediately after the
16167    // md-panel element (as a sibling).
16168    element[0].parentNode.insertBefore(this._topFocusTrap, element[0]);
16169    element.after(this._bottomFocusTrap);
16170  }
16171};
16172
16173
16174/**
16175 * Updates the animation of a panel.
16176 * @param {!MdPanelAnimation} animation
16177 */
16178MdPanelRef.prototype.updateAnimation = function(animation) {
16179  this.config['animation'] = animation;
16180
16181  if (this._backdropRef) {
16182    this._backdropRef.config.animation.duration(animation._rawDuration);
16183  }
16184};
16185
16186
16187/**
16188 * Animate the panel opening.
16189 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
16190 *     animated open.
16191 * @private
16192 */
16193MdPanelRef.prototype._animateOpen = function() {
16194  this.panelContainer.addClass('md-panel-is-showing');
16195  var animationConfig = this.config['animation'];
16196  if (!animationConfig) {
16197    // Promise is in progress, return it.
16198    this.panelContainer.addClass('_md-panel-shown');
16199    return this._$q.when(this);
16200  }
16201
16202  var self = this;
16203  return this._$q(function(resolve) {
16204    var done = self._done(resolve, self);
16205    var warnAndOpen = function() {
16206      self._$log.warn(
16207          'mdPanel: MdPanel Animations failed. ' +
16208          'Showing panel without animating.');
16209      done();
16210    };
16211
16212    animationConfig.animateOpen(self.panelEl)
16213        .then(done, warnAndOpen);
16214  });
16215};
16216
16217
16218/**
16219 * Animate the panel closing.
16220 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
16221 *     animated closed.
16222 * @private
16223 */
16224MdPanelRef.prototype._animateClose = function() {
16225  var animationConfig = this.config['animation'];
16226  if (!animationConfig) {
16227    this.panelContainer.removeClass('md-panel-is-showing');
16228    this.panelContainer.removeClass('_md-panel-shown');
16229    return this._$q.when(this);
16230  }
16231
16232  var self = this;
16233  return this._$q(function(resolve) {
16234    var done = function() {
16235      self.panelContainer.removeClass('md-panel-is-showing');
16236      resolve(self);
16237    };
16238    var warnAndClose = function() {
16239      self._$log.warn(
16240          'mdPanel: MdPanel Animations failed. ' +
16241          'Hiding panel without animating.');
16242      done();
16243    };
16244
16245    animationConfig.animateClose(self.panelEl)
16246        .then(done, warnAndClose);
16247  });
16248};
16249
16250
16251/**
16252 * Registers a interceptor with the panel. The callback should return a promise,
16253 * which will allow the action to continue when it gets resolved, or will
16254 * prevent an action if it is rejected.
16255 * @param {string} type Type of interceptor.
16256 * @param {!angular.$q.Promise<!any>} callback Callback to be registered.
16257 * @returns {!MdPanelRef}
16258 */
16259MdPanelRef.prototype.registerInterceptor = function(type, callback) {
16260  var error = null;
16261
16262  if (!angular.isString(type)) {
16263    error = 'Interceptor type must be a string, instead got ' + typeof type;
16264  } else if (!angular.isFunction(callback)) {
16265    error = 'Interceptor callback must be a function, instead got ' + typeof callback;
16266  }
16267
16268  if (error) {
16269    throw new Error('MdPanel: ' + error);
16270  }
16271
16272  var interceptors = this._interceptors[type] = this._interceptors[type] || [];
16273
16274  if (interceptors.indexOf(callback) === -1) {
16275    interceptors.push(callback);
16276  }
16277
16278  return this;
16279};
16280
16281
16282/**
16283 * Removes a registered interceptor.
16284 * @param {string} type Type of interceptor to be removed.
16285 * @param {Function} callback Interceptor to be removed.
16286 * @returns {!MdPanelRef}
16287 */
16288MdPanelRef.prototype.removeInterceptor = function(type, callback) {
16289  var index = this._interceptors[type] ?
16290    this._interceptors[type].indexOf(callback) : -1;
16291
16292  if (index > -1) {
16293    this._interceptors[type].splice(index, 1);
16294  }
16295
16296  return this;
16297};
16298
16299
16300/**
16301 * Removes all interceptors.
16302 * @param {string=} type Type of interceptors to be removed.
16303 *     If ommited, all interceptors types will be removed.
16304 * @returns {!MdPanelRef}
16305 */
16306MdPanelRef.prototype.removeAllInterceptors = function(type) {
16307  if (type) {
16308    this._interceptors[type] = [];
16309  } else {
16310    this._interceptors = Object.create(null);
16311  }
16312
16313  return this;
16314};
16315
16316
16317/**
16318 * Invokes all the interceptors of a certain type sequantially in
16319 *     reverse order. Works in a similar way to `$q.all`, except it
16320 *     respects the order of the functions.
16321 * @param {string} type Type of interceptors to be invoked.
16322 * @returns {!angular.$q.Promise<!MdPanelRef>}
16323 * @private
16324 */
16325MdPanelRef.prototype._callInterceptors = function(type) {
16326  var self = this;
16327  var $q = self._$q;
16328  var interceptors = self._interceptors && self._interceptors[type] || [];
16329
16330  return interceptors.reduceRight(function(promise, interceptor) {
16331    var isPromiseLike = interceptor && angular.isFunction(interceptor.then);
16332    var response = isPromiseLike ? interceptor : null;
16333
16334    /**
16335    * For interceptors to reject/cancel subsequent portions of the chain, simply
16336    * return a `$q.reject(<value>)`
16337    */
16338    return promise.then(function() {
16339      if (!response) {
16340        try {
16341          response = interceptor(self);
16342        } catch(e) {
16343          response = $q.reject(e);
16344        }
16345      }
16346
16347     return response;
16348    });
16349  }, $q.resolve(self));
16350};
16351
16352
16353/**
16354 * Faster, more basic than angular.bind
16355 * http://jsperf.com/angular-bind-vs-custom-vs-native
16356 * @param {function} callback
16357 * @param {!Object} self
16358 * @return {function} Callback function with a bound self.
16359 */
16360MdPanelRef.prototype._simpleBind = function(callback, self) {
16361  return function(value) {
16362    return callback.apply(self, value);
16363  };
16364};
16365
16366
16367/**
16368 * @param {function} callback
16369 * @param {!Object} self
16370 * @return {function} Callback function with a self param.
16371 */
16372MdPanelRef.prototype._done = function(callback, self) {
16373  return function() {
16374    callback(self);
16375  };
16376};
16377
16378
16379/**
16380 * Adds a panel to a group if the panel does not exist within the group already.
16381 * A panel can only exist within a single group.
16382 * @param {string} groupName The name of the group.
16383 */
16384MdPanelRef.prototype.addToGroup = function(groupName) {
16385  if (!this._$mdPanel._groups[groupName]) {
16386    this._$mdPanel.newPanelGroup(groupName);
16387  }
16388
16389  var group = this._$mdPanel._groups[groupName];
16390  var index = group.panels.indexOf(this);
16391
16392  if (index < 0) {
16393    group.panels.push(this);
16394  }
16395};
16396
16397
16398/**
16399 * Removes a panel from a group if the panel exists within that group. The group
16400 * must be created ahead of time.
16401 * @param {string} groupName The name of the group.
16402 */
16403MdPanelRef.prototype.removeFromGroup = function(groupName) {
16404  if (!this._$mdPanel._groups[groupName]) {
16405    throw new Error('mdPanel: The group ' + groupName + ' does not exist.');
16406  }
16407
16408  var group = this._$mdPanel._groups[groupName];
16409  var index = group.panels.indexOf(this);
16410
16411  if (index > -1) {
16412    group.panels.splice(index, 1);
16413  }
16414};
16415
16416
16417/**
16418 * Possible default closeReasons for the close function.
16419 * @enum {string}
16420 */
16421MdPanelRef.closeReasons = {
16422  CLICK_OUTSIDE: 'clickOutsideToClose',
16423  ESCAPE: 'escapeToClose',
16424};
16425
16426
16427/*****************************************************************************
16428 *                               MdPanelPosition                             *
16429 *****************************************************************************/
16430
16431
16432/**
16433 * Position configuration object. To use, create an MdPanelPosition with the
16434 * desired properties, then pass the object as part of $mdPanel creation.
16435 *
16436 * Example:
16437 *
16438 * var panelPosition = new MdPanelPosition()
16439 *     .relativeTo(myButtonEl)
16440 *     .addPanelPosition(
16441 *       $mdPanel.xPosition.CENTER,
16442 *       $mdPanel.yPosition.ALIGN_TOPS
16443 *     );
16444 *
16445 * $mdPanel.create({
16446 *   position: panelPosition
16447 * });
16448 *
16449 * @param {!angular.$injector} $injector
16450 * @final @constructor
16451 */
16452function MdPanelPosition($injector) {
16453  /** @private @const {!angular.$window} */
16454  this._$window = $injector.get('$window');
16455
16456  /** @private {boolean} */
16457  this._isRTL = $injector.get('$mdUtil').bidi() === 'rtl';
16458
16459  /** @private @const {!angular.$mdConstant} */
16460  this._$mdConstant = $injector.get('$mdConstant');
16461
16462  /** @private {boolean} */
16463  this._absolute = false;
16464
16465  /** @private {!angular.JQLite} */
16466  this._relativeToEl;
16467
16468  /** @private {string} */
16469  this._top = '';
16470
16471  /** @private {string} */
16472  this._bottom = '';
16473
16474  /** @private {string} */
16475  this._left = '';
16476
16477  /** @private {string} */
16478  this._right = '';
16479
16480  /** @private {!Array<string>} */
16481  this._translateX = [];
16482
16483  /** @private {!Array<string>} */
16484  this._translateY = [];
16485
16486  /** @private {!Array<{x:string, y:string}>} */
16487  this._positions = [];
16488
16489  /** @private {?{x:string, y:string}} */
16490  this._actualPosition;
16491}
16492
16493
16494/**
16495 * Possible values of xPosition.
16496 * @enum {string}
16497 */
16498MdPanelPosition.xPosition = {
16499  CENTER: 'center',
16500  ALIGN_START: 'align-start',
16501  ALIGN_END: 'align-end',
16502  OFFSET_START: 'offset-start',
16503  OFFSET_END: 'offset-end'
16504};
16505
16506
16507/**
16508 * Possible values of yPosition.
16509 * @enum {string}
16510 */
16511MdPanelPosition.yPosition = {
16512  CENTER: 'center',
16513  ALIGN_TOPS: 'align-tops',
16514  ALIGN_BOTTOMS: 'align-bottoms',
16515  ABOVE: 'above',
16516  BELOW: 'below'
16517};
16518
16519
16520/**
16521 * Possible values of absolute position.
16522 * @enum {string}
16523 */
16524MdPanelPosition.absPosition = {
16525  TOP: 'top',
16526  RIGHT: 'right',
16527  BOTTOM: 'bottom',
16528  LEFT: 'left'
16529};
16530
16531/**
16532 * Margin between the edges of a panel and the viewport.
16533 * @const {number}
16534 */
16535MdPanelPosition.viewportMargin = 8;
16536
16537
16538/**
16539 * Sets absolute positioning for the panel.
16540 * @return {!MdPanelPosition}
16541 */
16542MdPanelPosition.prototype.absolute = function() {
16543  this._absolute = true;
16544  return this;
16545};
16546
16547
16548/**
16549 * Sets the value of a position for the panel. Clears any previously set
16550 * position.
16551 * @param {string} position Position to set
16552 * @param {string=} value Value of the position. Defaults to '0'.
16553 * @returns {!MdPanelPosition}
16554 * @private
16555 */
16556MdPanelPosition.prototype._setPosition = function(position, value) {
16557  if (position === MdPanelPosition.absPosition.RIGHT ||
16558      position === MdPanelPosition.absPosition.LEFT) {
16559    this._left = this._right = '';
16560  } else if (
16561      position === MdPanelPosition.absPosition.BOTTOM ||
16562      position === MdPanelPosition.absPosition.TOP) {
16563    this._top = this._bottom = '';
16564  } else {
16565    var positions = Object.keys(MdPanelPosition.absPosition).join()
16566        .toLowerCase();
16567
16568    throw new Error('mdPanel: Position must be one of ' + positions + '.');
16569  }
16570
16571  this['_' +  position] = angular.isString(value) ? value : '0';
16572
16573  return this;
16574};
16575
16576
16577/**
16578 * Sets the value of `top` for the panel. Clears any previously set vertical
16579 * position.
16580 * @param {string=} top Value of `top`. Defaults to '0'.
16581 * @returns {!MdPanelPosition}
16582 */
16583MdPanelPosition.prototype.top = function(top) {
16584  return this._setPosition(MdPanelPosition.absPosition.TOP, top);
16585};
16586
16587
16588/**
16589 * Sets the value of `bottom` for the panel. Clears any previously set vertical
16590 * position.
16591 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
16592 * @returns {!MdPanelPosition}
16593 */
16594MdPanelPosition.prototype.bottom = function(bottom) {
16595  return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom);
16596};
16597
16598
16599/**
16600 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
16601 * `rtl`. Clears any previously set horizontal position.
16602 * @param {string=} start Value of position. Defaults to '0'.
16603 * @returns {!MdPanelPosition}
16604 */
16605MdPanelPosition.prototype.start = function(start) {
16606  var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT;
16607  return this._setPosition(position, start);
16608};
16609
16610
16611/**
16612 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
16613 * Clears any previously set horizontal position.
16614 * @param {string=} end Value of position. Defaults to '0'.
16615 * @returns {!MdPanelPosition}
16616 */
16617MdPanelPosition.prototype.end = function(end) {
16618  var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT;
16619  return this._setPosition(position, end);
16620};
16621
16622
16623/**
16624 * Sets the value of `left` for the panel. Clears any previously set
16625 * horizontal position.
16626 * @param {string=} left Value of `left`. Defaults to '0'.
16627 * @returns {!MdPanelPosition}
16628 */
16629MdPanelPosition.prototype.left = function(left) {
16630  return this._setPosition(MdPanelPosition.absPosition.LEFT, left);
16631};
16632
16633
16634/**
16635 * Sets the value of `right` for the panel. Clears any previously set
16636 * horizontal position.
16637 * @param {string=} right Value of `right`. Defaults to '0'.
16638 * @returns {!MdPanelPosition}
16639*/
16640MdPanelPosition.prototype.right = function(right) {
16641  return this._setPosition(MdPanelPosition.absPosition.RIGHT, right);
16642};
16643
16644
16645/**
16646 * Centers the panel horizontally in the viewport. Clears any previously set
16647 * horizontal position.
16648 * @returns {!MdPanelPosition}
16649 */
16650MdPanelPosition.prototype.centerHorizontally = function() {
16651  this._left = '50%';
16652  this._right = '';
16653  this._translateX = ['-50%'];
16654  return this;
16655};
16656
16657
16658/**
16659 * Centers the panel vertically in the viewport. Clears any previously set
16660 * vertical position.
16661 * @returns {!MdPanelPosition}
16662 */
16663MdPanelPosition.prototype.centerVertically = function() {
16664  this._top = '50%';
16665  this._bottom = '';
16666  this._translateY = ['-50%'];
16667  return this;
16668};
16669
16670
16671/**
16672 * Centers the panel horizontally and vertically in the viewport. This is
16673 * equivalent to calling both `centerHorizontally` and `centerVertically`.
16674 * Clears any previously set horizontal and vertical positions.
16675 * @returns {!MdPanelPosition}
16676 */
16677MdPanelPosition.prototype.center = function() {
16678  return this.centerHorizontally().centerVertically();
16679};
16680
16681
16682/**
16683 * Sets element for relative positioning.
16684 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
16685 *     or angular element to set the panel relative to.
16686 * @returns {!MdPanelPosition}
16687 */
16688MdPanelPosition.prototype.relativeTo = function(element) {
16689  this._absolute = false;
16690  this._relativeToEl = getElement(element);
16691  return this;
16692};
16693
16694
16695/**
16696 * Sets the x and y positions for the panel relative to another element.
16697 * @param {string} xPosition must be one of the MdPanelPosition.xPosition
16698 *     values.
16699 * @param {string} yPosition must be one of the MdPanelPosition.yPosition
16700 *     values.
16701 * @returns {!MdPanelPosition}
16702 */
16703MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) {
16704  if (!this._relativeToEl) {
16705    throw new Error('mdPanel: addPanelPosition can only be used with ' +
16706        'relative positioning. Set relativeTo first.');
16707  }
16708
16709  this._validateXPosition(xPosition);
16710  this._validateYPosition(yPosition);
16711
16712  this._positions.push({
16713      x: xPosition,
16714      y: yPosition,
16715  });
16716  return this;
16717};
16718
16719
16720/**
16721 * Ensures that yPosition is a valid position name. Throw an exception if not.
16722 * @param {string} yPosition
16723 */
16724MdPanelPosition.prototype._validateYPosition = function(yPosition) {
16725  // empty is ok
16726  if (yPosition == null) {
16727      return;
16728  }
16729
16730  var positionKeys = Object.keys(MdPanelPosition.yPosition);
16731  var positionValues = [];
16732  for (var key, i = 0; key = positionKeys[i]; i++) {
16733    var position = MdPanelPosition.yPosition[key];
16734    positionValues.push(position);
16735
16736    if (position === yPosition) {
16737      return;
16738    }
16739  }
16740
16741  throw new Error('mdPanel: Panel y position only accepts the following ' +
16742      'values:\n' + positionValues.join(' | '));
16743};
16744
16745
16746/**
16747 * Ensures that xPosition is a valid position name. Throw an exception if not.
16748 * @param {string} xPosition
16749 */
16750MdPanelPosition.prototype._validateXPosition = function(xPosition) {
16751  // empty is ok
16752  if (xPosition == null) {
16753      return;
16754  }
16755
16756  var positionKeys = Object.keys(MdPanelPosition.xPosition);
16757  var positionValues = [];
16758  for (var key, i = 0; key = positionKeys[i]; i++) {
16759    var position = MdPanelPosition.xPosition[key];
16760    positionValues.push(position);
16761    if (position === xPosition) {
16762      return;
16763    }
16764  }
16765
16766  throw new Error('mdPanel: Panel x Position only accepts the following ' +
16767      'values:\n' + positionValues.join(' | '));
16768};
16769
16770
16771/**
16772 * Sets the value of the offset in the x-direction. This will add to any
16773 * previously set offsets.
16774 * @param {string|function(MdPanelPosition): string} offsetX
16775 * @returns {!MdPanelPosition}
16776 */
16777MdPanelPosition.prototype.withOffsetX = function(offsetX) {
16778  this._translateX.push(offsetX);
16779  return this;
16780};
16781
16782
16783/**
16784 * Sets the value of the offset in the y-direction. This will add to any
16785 * previously set offsets.
16786 * @param {string|function(MdPanelPosition): string} offsetY
16787 * @returns {!MdPanelPosition}
16788 */
16789MdPanelPosition.prototype.withOffsetY = function(offsetY) {
16790  this._translateY.push(offsetY);
16791  return this;
16792};
16793
16794
16795/**
16796 * Gets the value of `top` for the panel.
16797 * @returns {string}
16798 */
16799MdPanelPosition.prototype.getTop = function() {
16800  return this._top;
16801};
16802
16803
16804/**
16805 * Gets the value of `bottom` for the panel.
16806 * @returns {string}
16807 */
16808MdPanelPosition.prototype.getBottom = function() {
16809  return this._bottom;
16810};
16811
16812
16813/**
16814 * Gets the value of `left` for the panel.
16815 * @returns {string}
16816 */
16817MdPanelPosition.prototype.getLeft = function() {
16818  return this._left;
16819};
16820
16821
16822/**
16823 * Gets the value of `right` for the panel.
16824 * @returns {string}
16825 */
16826MdPanelPosition.prototype.getRight = function() {
16827  return this._right;
16828};
16829
16830
16831/**
16832 * Gets the value of `transform` for the panel.
16833 * @returns {string}
16834 */
16835MdPanelPosition.prototype.getTransform = function() {
16836  var translateX = this._reduceTranslateValues('translateX', this._translateX);
16837  var translateY = this._reduceTranslateValues('translateY', this._translateY);
16838
16839  // It's important to trim the result, because the browser will ignore the set
16840  // operation if the string contains only whitespace.
16841  return (translateX + ' ' + translateY).trim();
16842};
16843
16844
16845/**
16846 * Sets the `transform` value for a panel element.
16847 * @param {!angular.JQLite} panelEl
16848 * @returns {!angular.JQLite}
16849 * @private
16850 */
16851MdPanelPosition.prototype._setTransform = function(panelEl) {
16852  return panelEl.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
16853};
16854
16855
16856/**
16857 * True if the panel is completely on-screen with this positioning; false
16858 * otherwise.
16859 * @param {!angular.JQLite} panelEl
16860 * @return {boolean}
16861 * @private
16862 */
16863MdPanelPosition.prototype._isOnscreen = function(panelEl) {
16864  // this works because we always use fixed positioning for the panel,
16865  // which is relative to the viewport.
16866  var left = parseInt(this.getLeft());
16867  var top = parseInt(this.getTop());
16868
16869  if (this._translateX.length || this._translateY.length) {
16870    var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
16871    var offsets = getComputedTranslations(panelEl, prefixedTransform);
16872    left += offsets.x;
16873    top += offsets.y;
16874  }
16875
16876  var right = left + panelEl[0].offsetWidth;
16877  var bottom = top + panelEl[0].offsetHeight;
16878
16879  return (left >= 0) &&
16880    (top >= 0) &&
16881    (bottom <= this._$window.innerHeight) &&
16882    (right <= this._$window.innerWidth);
16883};
16884
16885
16886/**
16887 * Gets the first x/y position that can fit on-screen.
16888 * @returns {{x: string, y: string}}
16889 */
16890MdPanelPosition.prototype.getActualPosition = function() {
16891  return this._actualPosition;
16892};
16893
16894
16895/**
16896 * Reduces a list of translate values to a string that can be used within
16897 * transform.
16898 * @param {string} translateFn
16899 * @param {!Array<string>} values
16900 * @returns {string}
16901 * @private
16902 */
16903MdPanelPosition.prototype._reduceTranslateValues =
16904    function(translateFn, values) {
16905      return values.map(function(translation) {
16906        // TODO(crisbeto): this should add the units after #9609 is merged.
16907        var translationValue = angular.isFunction(translation) ?
16908            translation(this) : translation;
16909        return translateFn + '(' + translationValue + ')';
16910      }, this).join(' ');
16911    };
16912
16913
16914/**
16915 * Sets the panel position based on the created panel element and best x/y
16916 * positioning.
16917 * @param {!angular.JQLite} panelEl
16918 * @private
16919 */
16920MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
16921  // Remove the "position adjusted" class in case it has been added before.
16922  panelEl.removeClass('_md-panel-position-adjusted');
16923
16924  // Only calculate the position if necessary.
16925  if (this._absolute) {
16926    this._setTransform(panelEl);
16927    return;
16928  }
16929
16930  if (this._actualPosition) {
16931    this._calculatePanelPosition(panelEl, this._actualPosition);
16932    this._setTransform(panelEl);
16933    this._constrainToViewport(panelEl);
16934    return;
16935  }
16936
16937  for (var i = 0; i < this._positions.length; i++) {
16938    this._actualPosition = this._positions[i];
16939    this._calculatePanelPosition(panelEl, this._actualPosition);
16940    this._setTransform(panelEl);
16941
16942    if (this._isOnscreen(panelEl)) {
16943      return;
16944    }
16945  }
16946
16947  this._constrainToViewport(panelEl);
16948};
16949
16950
16951/**
16952 * Constrains a panel's position to the viewport.
16953 * @param {!angular.JQLite} panelEl
16954 * @private
16955 */
16956MdPanelPosition.prototype._constrainToViewport = function(panelEl) {
16957  var margin = MdPanelPosition.viewportMargin;
16958  var initialTop = this._top;
16959  var initialLeft = this._left;
16960
16961  if (this.getTop()) {
16962    var top = parseInt(this.getTop());
16963    var bottom = panelEl[0].offsetHeight + top;
16964    var viewportHeight = this._$window.innerHeight;
16965
16966    if (top < margin) {
16967      this._top = margin + 'px';
16968    } else if (bottom > viewportHeight) {
16969      this._top = top - (bottom - viewportHeight + margin) + 'px';
16970    }
16971  }
16972
16973  if (this.getLeft()) {
16974    var left = parseInt(this.getLeft());
16975    var right = panelEl[0].offsetWidth + left;
16976    var viewportWidth = this._$window.innerWidth;
16977
16978    if (left < margin) {
16979      this._left = margin + 'px';
16980    } else if (right > viewportWidth) {
16981      this._left = left - (right - viewportWidth + margin) + 'px';
16982    }
16983  }
16984
16985  // Class that can be used to re-style the panel if it was repositioned.
16986  panelEl.toggleClass(
16987    '_md-panel-position-adjusted',
16988    this._top !== initialTop || this._left !== initialLeft
16989  );
16990};
16991
16992
16993/**
16994 * Switches between 'start' and 'end'.
16995 * @param {string} position Horizontal position of the panel
16996 * @returns {string} Reversed position
16997 * @private
16998 */
16999MdPanelPosition.prototype._reverseXPosition = function(position) {
17000  if (position === MdPanelPosition.xPosition.CENTER) {
17001    return;
17002  }
17003
17004  var start = 'start';
17005  var end = 'end';
17006
17007  return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start);
17008};
17009
17010
17011/**
17012 * Handles horizontal positioning in rtl or ltr environments.
17013 * @param {string} position Horizontal position of the panel
17014 * @returns {string} The correct position according the page direction
17015 * @private
17016 */
17017MdPanelPosition.prototype._bidi = function(position) {
17018  return this._isRTL ? this._reverseXPosition(position) : position;
17019};
17020
17021
17022/**
17023 * Calculates the panel position based on the created panel element and the
17024 * provided positioning.
17025 * @param {!angular.JQLite} panelEl
17026 * @param {!{x:string, y:string}} position
17027 * @private
17028 */
17029MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) {
17030
17031  var panelBounds = panelEl[0].getBoundingClientRect();
17032  var panelWidth = panelBounds.width;
17033  var panelHeight = panelBounds.height;
17034
17035  var targetBounds = this._relativeToEl[0].getBoundingClientRect();
17036
17037  var targetLeft = targetBounds.left;
17038  var targetRight = targetBounds.right;
17039  var targetWidth = targetBounds.width;
17040
17041  switch (this._bidi(position.x)) {
17042    case MdPanelPosition.xPosition.OFFSET_START:
17043      this._left = targetLeft - panelWidth + 'px';
17044      break;
17045    case MdPanelPosition.xPosition.ALIGN_END:
17046      this._left = targetRight - panelWidth + 'px';
17047      break;
17048    case MdPanelPosition.xPosition.CENTER:
17049      var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth);
17050      this._left = left + 'px';
17051      break;
17052    case MdPanelPosition.xPosition.ALIGN_START:
17053      this._left = targetLeft + 'px';
17054      break;
17055    case MdPanelPosition.xPosition.OFFSET_END:
17056      this._left = targetRight + 'px';
17057      break;
17058  }
17059
17060  var targetTop = targetBounds.top;
17061  var targetBottom = targetBounds.bottom;
17062  var targetHeight = targetBounds.height;
17063
17064  switch (position.y) {
17065    case MdPanelPosition.yPosition.ABOVE:
17066      this._top = targetTop - panelHeight + 'px';
17067      break;
17068    case MdPanelPosition.yPosition.ALIGN_BOTTOMS:
17069      this._top = targetBottom - panelHeight + 'px';
17070      break;
17071    case MdPanelPosition.yPosition.CENTER:
17072      var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight);
17073      this._top = top + 'px';
17074      break;
17075    case MdPanelPosition.yPosition.ALIGN_TOPS:
17076      this._top = targetTop + 'px';
17077      break;
17078    case MdPanelPosition.yPosition.BELOW:
17079      this._top = targetBottom + 'px';
17080      break;
17081  }
17082};
17083
17084
17085/*****************************************************************************
17086 *                               MdPanelAnimation                            *
17087 *****************************************************************************/
17088
17089
17090/**
17091 * Animation configuration object. To use, create an MdPanelAnimation with the
17092 * desired properties, then pass the object as part of $mdPanel creation.
17093 *
17094 * Example:
17095 *
17096 * var panelAnimation = new MdPanelAnimation()
17097 *     .openFrom(myButtonEl)
17098 *     .closeTo('.my-button')
17099 *     .withAnimation($mdPanel.animation.SCALE);
17100 *
17101 * $mdPanel.create({
17102 *   animation: panelAnimation
17103 * });
17104 *
17105 * @param {!angular.$injector} $injector
17106 * @final @constructor
17107 */
17108function MdPanelAnimation($injector) {
17109  /** @private @const {!angular.$mdUtil} */
17110  this._$mdUtil = $injector.get('$mdUtil');
17111
17112  /**
17113   * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
17114   *     undefined}
17115   */
17116  this._openFrom;
17117
17118  /**
17119   * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
17120   *     undefined}
17121   */
17122  this._closeTo;
17123
17124  /** @private {string|{open: string, close: string}} */
17125  this._animationClass = '';
17126
17127  /** @private {number} */
17128  this._openDuration;
17129
17130  /** @private {number} */
17131  this._closeDuration;
17132
17133  /** @private {number|{open: number, close: number}} */
17134  this._rawDuration;
17135}
17136
17137
17138/**
17139 * Possible default animations.
17140 * @enum {string}
17141 */
17142MdPanelAnimation.animation = {
17143  SLIDE: 'md-panel-animate-slide',
17144  SCALE: 'md-panel-animate-scale',
17145  FADE: 'md-panel-animate-fade'
17146};
17147
17148
17149/**
17150 * Specifies where to start the open animation. `openFrom` accepts a
17151 * click event object, query selector, DOM element, or a Rect object that
17152 * is used to determine the bounds. When passed a click event, the location
17153 * of the click will be used as the position to start the animation.
17154 * @param {string|!Element|!Event|{top: number, left: number}} openFrom
17155 * @returns {!MdPanelAnimation}
17156 */
17157MdPanelAnimation.prototype.openFrom = function(openFrom) {
17158  // Check if 'openFrom' is an Event.
17159  openFrom = openFrom.target ? openFrom.target : openFrom;
17160
17161  this._openFrom = this._getPanelAnimationTarget(openFrom);
17162
17163  if (!this._closeTo) {
17164    this._closeTo = this._openFrom;
17165  }
17166  return this;
17167};
17168
17169
17170/**
17171 * Specifies where to animate the panel close. `closeTo` accepts a
17172 * query selector, DOM element, or a Rect object that is used to determine
17173 * the bounds.
17174 * @param {string|!Element|{top: number, left: number}} closeTo
17175 * @returns {!MdPanelAnimation}
17176 */
17177MdPanelAnimation.prototype.closeTo = function(closeTo) {
17178  this._closeTo = this._getPanelAnimationTarget(closeTo);
17179  return this;
17180};
17181
17182
17183/**
17184 * Specifies the duration of the animation in milliseconds.
17185 * @param {number|{open: number, close: number}} duration
17186 * @returns {!MdPanelAnimation}
17187 */
17188MdPanelAnimation.prototype.duration = function(duration) {
17189  if (duration) {
17190    if (angular.isNumber(duration)) {
17191      this._openDuration = this._closeDuration = toSeconds(duration);
17192    } else if (angular.isObject(duration)) {
17193      this._openDuration = toSeconds(duration.open);
17194      this._closeDuration = toSeconds(duration.close);
17195    }
17196  }
17197
17198  // Save the original value so it can be passed to the backdrop.
17199  this._rawDuration = duration;
17200
17201  return this;
17202
17203  function toSeconds(value) {
17204    if (angular.isNumber(value)) return value / 1000;
17205  }
17206};
17207
17208
17209/**
17210 * Returns the element and bounds for the animation target.
17211 * @param {string|!Element|{top: number, left: number}} location
17212 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
17213 * @private
17214 */
17215MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) {
17216  if (angular.isDefined(location.top) || angular.isDefined(location.left)) {
17217    return {
17218      element: undefined,
17219      bounds: {
17220        top: location.top || 0,
17221        left: location.left || 0
17222      }
17223    };
17224  } else {
17225    return this._getBoundingClientRect(getElement(location));
17226  }
17227};
17228
17229
17230/**
17231 * Specifies the animation class.
17232 *
17233 * There are several default animations that can be used:
17234 * (MdPanelAnimation.animation)
17235 *   SLIDE: The panel slides in and out from the specified
17236 *        elements.
17237 *   SCALE: The panel scales in and out.
17238 *   FADE: The panel fades in and out.
17239 *
17240 * @param {string|{open: string, close: string}} cssClass
17241 * @returns {!MdPanelAnimation}
17242 */
17243MdPanelAnimation.prototype.withAnimation = function(cssClass) {
17244  this._animationClass = cssClass;
17245  return this;
17246};
17247
17248
17249/**
17250 * Animate the panel open.
17251 * @param {!angular.JQLite} panelEl
17252 * @returns {!angular.$q.Promise} A promise that is resolved when the open
17253 *     animation is complete.
17254 */
17255MdPanelAnimation.prototype.animateOpen = function(panelEl) {
17256  var animator = this._$mdUtil.dom.animator;
17257
17258  this._fixBounds(panelEl);
17259  var animationOptions = {};
17260
17261  // Include the panel transformations when calculating the animations.
17262  var panelTransform = panelEl[0].style.transform || '';
17263
17264  var openFrom = animator.toTransformCss(panelTransform);
17265  var openTo = animator.toTransformCss(panelTransform);
17266
17267  switch (this._animationClass) {
17268    case MdPanelAnimation.animation.SLIDE:
17269      // Slide should start with opacity: 1.
17270      panelEl.css('opacity', '1');
17271
17272      animationOptions = {
17273        transitionInClass: '_md-panel-animate-enter'
17274      };
17275
17276      var openSlide = animator.calculateSlideToOrigin(
17277              panelEl, this._openFrom) || '';
17278      openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform);
17279      break;
17280
17281    case MdPanelAnimation.animation.SCALE:
17282      animationOptions = {
17283        transitionInClass: '_md-panel-animate-enter'
17284      };
17285
17286      var openScale = animator.calculateZoomToOrigin(
17287              panelEl, this._openFrom) || '';
17288      openFrom = animator.toTransformCss(openScale + ' ' + panelTransform);
17289      break;
17290
17291    case MdPanelAnimation.animation.FADE:
17292      animationOptions = {
17293        transitionInClass: '_md-panel-animate-enter'
17294      };
17295      break;
17296
17297    default:
17298      if (angular.isString(this._animationClass)) {
17299        animationOptions = {
17300          transitionInClass: this._animationClass
17301        };
17302      } else {
17303        animationOptions = {
17304          transitionInClass: this._animationClass['open'],
17305          transitionOutClass: this._animationClass['close'],
17306        };
17307      }
17308  }
17309
17310  animationOptions.duration = this._openDuration;
17311
17312  return animator
17313      .translate3d(panelEl, openFrom, openTo, animationOptions);
17314};
17315
17316
17317/**
17318 * Animate the panel close.
17319 * @param {!angular.JQLite} panelEl
17320 * @returns {!angular.$q.Promise} A promise that resolves when the close
17321 *     animation is complete.
17322 */
17323MdPanelAnimation.prototype.animateClose = function(panelEl) {
17324  var animator = this._$mdUtil.dom.animator;
17325  var reverseAnimationOptions = {};
17326
17327  // Include the panel transformations when calculating the animations.
17328  var panelTransform = panelEl[0].style.transform || '';
17329
17330  var closeFrom = animator.toTransformCss(panelTransform);
17331  var closeTo = animator.toTransformCss(panelTransform);
17332
17333  switch (this._animationClass) {
17334    case MdPanelAnimation.animation.SLIDE:
17335      // Slide should start with opacity: 1.
17336      panelEl.css('opacity', '1');
17337      reverseAnimationOptions = {
17338        transitionInClass: '_md-panel-animate-leave'
17339      };
17340
17341      var closeSlide = animator.calculateSlideToOrigin(
17342              panelEl, this._closeTo) || '';
17343      closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform);
17344      break;
17345
17346    case MdPanelAnimation.animation.SCALE:
17347      reverseAnimationOptions = {
17348        transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave'
17349      };
17350
17351      var closeScale = animator.calculateZoomToOrigin(
17352              panelEl, this._closeTo) || '';
17353      closeTo = animator.toTransformCss(closeScale + ' ' + panelTransform);
17354      break;
17355
17356    case MdPanelAnimation.animation.FADE:
17357      reverseAnimationOptions = {
17358        transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave'
17359      };
17360      break;
17361
17362    default:
17363      if (angular.isString(this._animationClass)) {
17364        reverseAnimationOptions = {
17365          transitionOutClass: this._animationClass
17366        };
17367      } else {
17368        reverseAnimationOptions = {
17369          transitionInClass: this._animationClass['close'],
17370          transitionOutClass: this._animationClass['open']
17371        };
17372      }
17373  }
17374
17375  reverseAnimationOptions.duration = this._closeDuration;
17376
17377  return animator
17378      .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions);
17379};
17380
17381
17382/**
17383 * Set the height and width to match the panel if not provided.
17384 * @param {!angular.JQLite} panelEl
17385 * @private
17386 */
17387MdPanelAnimation.prototype._fixBounds = function(panelEl) {
17388  var panelWidth = panelEl[0].offsetWidth;
17389  var panelHeight = panelEl[0].offsetHeight;
17390
17391  if (this._openFrom && this._openFrom.bounds.height == null) {
17392    this._openFrom.bounds.height = panelHeight;
17393  }
17394  if (this._openFrom && this._openFrom.bounds.width == null) {
17395    this._openFrom.bounds.width = panelWidth;
17396  }
17397  if (this._closeTo && this._closeTo.bounds.height == null) {
17398    this._closeTo.bounds.height = panelHeight;
17399  }
17400  if (this._closeTo && this._closeTo.bounds.width == null) {
17401    this._closeTo.bounds.width = panelWidth;
17402  }
17403};
17404
17405
17406/**
17407 * Identify the bounding RECT for the target element.
17408 * @param {!angular.JQLite} element
17409 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
17410 * @private
17411 */
17412MdPanelAnimation.prototype._getBoundingClientRect = function(element) {
17413  if (element instanceof angular.element) {
17414    return {
17415      element: element,
17416      bounds: element[0].getBoundingClientRect()
17417    };
17418  }
17419};
17420
17421
17422/*****************************************************************************
17423 *                                Util Methods                               *
17424 *****************************************************************************/
17425
17426
17427/**
17428 * Returns the angular element associated with a css selector or element.
17429 * @param el {string|!angular.JQLite|!Element}
17430 * @returns {!angular.JQLite}
17431 */
17432function getElement(el) {
17433  var queryResult = angular.isString(el) ?
17434      document.querySelector(el) : el;
17435  return angular.element(queryResult);
17436}
17437
17438
17439/**
17440 * Gets the computed values for an element's translateX and translateY in px.
17441 * @param {!angular.JQLite|!Element} el
17442 * @param {string} property
17443 * @return {{x: number, y: number}}
17444 */
17445function getComputedTranslations(el, property) {
17446  // The transform being returned by `getComputedStyle` is in the format:
17447  // `matrix(a, b, c, d, translateX, translateY)` if defined and `none`
17448  // if the element doesn't have a transform.
17449  var transform = getComputedStyle(el[0] || el)[property];
17450  var openIndex = transform.indexOf('(');
17451  var closeIndex = transform.lastIndexOf(')');
17452  var output = { x: 0, y: 0 };
17453
17454  if (openIndex > -1 && closeIndex > -1) {
17455    var parsedValues = transform
17456      .substring(openIndex + 1, closeIndex)
17457      .split(', ')
17458      .slice(-2);
17459
17460    output.x = parseInt(parsedValues[0]);
17461    output.y = parseInt(parsedValues[1]);
17462  }
17463
17464  return output;
17465}
17466
17467})();
17468(function(){
17469"use strict";
17470
17471/**
17472 * @ngdoc module
17473 * @name material.components.navBar
17474 */
17475
17476
17477MdNavBarController.$inject = ["$element", "$scope", "$timeout", "$mdConstant"];
17478MdNavItem.$inject = ["$mdAria", "$$rAF"];
17479MdNavItemController.$inject = ["$element"];
17480MdNavBar.$inject = ["$mdAria", "$mdTheming"];
17481angular.module('material.components.navBar', ['material.core'])
17482    .controller('MdNavBarController', MdNavBarController)
17483    .directive('mdNavBar', MdNavBar)
17484    .controller('MdNavItemController', MdNavItemController)
17485    .directive('mdNavItem', MdNavItem);
17486
17487
17488/*****************************************************************************
17489 *                            PUBLIC DOCUMENTATION                           *
17490 *****************************************************************************/
17491/**
17492 * @ngdoc directive
17493 * @name mdNavBar
17494 * @module material.components.navBar
17495 *
17496 * @restrict E
17497 *
17498 * @description
17499 * The `<md-nav-bar>` directive renders a list of material tabs that can be used
17500 * for top-level page navigation. Unlike `<md-tabs>`, it has no concept of a tab
17501 * body and no bar pagination.
17502 *
17503 * Because it deals with page navigation, certain routing concepts are built-in.
17504 * Route changes via via ng-href, ui-sref, or ng-click events are supported.
17505 * Alternatively, the user could simply watch currentNavItem for changes.
17506 *
17507 * Accessibility functionality is implemented as a site navigator with a
17508 * listbox, according to
17509 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style
17510 *
17511 * @param {string=} mdSelectedNavItem The name of the current tab; this must
17512 *     match the name attribute of `<md-nav-item>`
17513 * @param {boolean=} mdNoInkBar If set to true, the ink bar will be hidden.
17514 * @param {string=} navBarAriaLabel An aria-label for the nav-bar
17515 *
17516 * @usage
17517 * <hljs lang="html">
17518 *  <md-nav-bar md-selected-nav-item="currentNavItem">
17519 *    <md-nav-item md-nav-click="goto('page1')" name="page1">
17520 *      Page One
17521 *    </md-nav-item>
17522 *    <md-nav-item md-nav-href="#page2" name="page3">Page Two</md-nav-item>
17523 *    <md-nav-item md-nav-sref="page3" name="page2">Page Three</md-nav-item>
17524 *    <md-nav-item
17525 *      md-nav-sref="app.page4"
17526 *      sref-opts="{reload: true, notify: true}"
17527 *      name="page4">
17528 *      Page Four
17529 *    </md-nav-item>
17530 *  </md-nav-bar>
17531 *</hljs>
17532 * <hljs lang="js">
17533 * (function() {
17534 *   'use strict';
17535 *
17536 *    $rootScope.$on('$routeChangeSuccess', function(event, current) {
17537 *      $scope.currentLink = getCurrentLinkFromRoute(current);
17538 *    });
17539 * });
17540 * </hljs>
17541 */
17542
17543/*****************************************************************************
17544 *                            mdNavItem
17545 *****************************************************************************/
17546/**
17547 * @ngdoc directive
17548 * @name mdNavItem
17549 * @module material.components.navBar
17550 *
17551 * @restrict E
17552 *
17553 * @description
17554 * `<md-nav-item>` describes a page navigation link within the `<md-nav-bar>`
17555 * component. It renders an md-button as the actual link.
17556 *
17557 * Exactly one of the mdNavClick, mdNavHref, mdNavSref attributes are required
17558 * to be specified.
17559 *
17560 * @param {Function=} mdNavClick Function which will be called when the
17561 *     link is clicked to change the page. Renders as an `ng-click`.
17562 * @param {string=} mdNavHref url to transition to when this link is clicked.
17563 *     Renders as an `ng-href`.
17564 * @param {string=} mdNavSref Ui-router state to transition to when this link is
17565 *     clicked. Renders as a `ui-sref`.
17566 * @param {!Object=} srefOpts Ui-router options that are passed to the
17567 *     `$state.go()` function. See the [Ui-router documentation for details]
17568 *     (https://ui-router.github.io/docs/latest/interfaces/transition.transitionoptions.html).
17569 * @param {string=} name The name of this link. Used by the nav bar to know
17570 *     which link is currently selected.
17571 * @param {string=} aria-label Adds alternative text for accessibility
17572 *
17573 * @usage
17574 * See `<md-nav-bar>` for usage.
17575 */
17576
17577
17578/*****************************************************************************
17579 *                                IMPLEMENTATION                             *
17580 *****************************************************************************/
17581
17582function MdNavBar($mdAria, $mdTheming) {
17583  return {
17584    restrict: 'E',
17585    transclude: true,
17586    controller: MdNavBarController,
17587    controllerAs: 'ctrl',
17588    bindToController: true,
17589    scope: {
17590      'mdSelectedNavItem': '=?',
17591      'mdNoInkBar': '=?',
17592      'navBarAriaLabel': '@?',
17593    },
17594    template:
17595      '<div class="md-nav-bar">' +
17596        '<nav role="navigation">' +
17597          '<ul class="_md-nav-bar-list" ng-transclude role="listbox"' +
17598            'tabindex="0"' +
17599            'ng-focus="ctrl.onFocus()"' +
17600            'ng-keydown="ctrl.onKeydown($event)"' +
17601            'aria-label="{{ctrl.navBarAriaLabel}}">' +
17602          '</ul>' +
17603        '</nav>' +
17604        '<md-nav-ink-bar ng-hide="ctrl.mdNoInkBar"></md-nav-ink-bar>' +
17605      '</div>',
17606    link: function(scope, element, attrs, ctrl) {
17607      $mdTheming(element);
17608      if (!ctrl.navBarAriaLabel) {
17609        $mdAria.expectAsync(element, 'aria-label', angular.noop);
17610      }
17611    },
17612  };
17613}
17614
17615/**
17616 * Controller for the nav-bar component.
17617 *
17618 * Accessibility functionality is implemented as a site navigator with a
17619 * listbox, according to
17620 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style
17621 * @param {!angular.JQLite} $element
17622 * @param {!angular.Scope} $scope
17623 * @param {!angular.Timeout} $timeout
17624 * @param {!Object} $mdConstant
17625 * @constructor
17626 * @final
17627 * @ngInject
17628 */
17629function MdNavBarController($element, $scope, $timeout, $mdConstant) {
17630  // Injected variables
17631  /** @private @const {!angular.Timeout} */
17632  this._$timeout = $timeout;
17633
17634  /** @private @const {!angular.Scope} */
17635  this._$scope = $scope;
17636
17637  /** @private @const {!Object} */
17638  this._$mdConstant = $mdConstant;
17639
17640  // Data-bound variables.
17641  /** @type {string} */
17642  this.mdSelectedNavItem;
17643
17644  /** @type {string} */
17645  this.navBarAriaLabel;
17646
17647  // State variables.
17648
17649  /** @type {?angular.JQLite} */
17650  this._navBarEl = $element[0];
17651
17652  /** @type {?angular.JQLite} */
17653  this._inkbar;
17654
17655  var self = this;
17656  // need to wait for transcluded content to be available
17657  var deregisterTabWatch = this._$scope.$watch(function() {
17658    return self._navBarEl.querySelectorAll('._md-nav-button').length;
17659  },
17660  function(newLength) {
17661    if (newLength > 0) {
17662      self._initTabs();
17663      deregisterTabWatch();
17664    }
17665  });
17666}
17667
17668
17669
17670/**
17671 * Initializes the tab components once they exist.
17672 * @private
17673 */
17674MdNavBarController.prototype._initTabs = function() {
17675  this._inkbar = angular.element(this._navBarEl.querySelector('md-nav-ink-bar'));
17676
17677  var self = this;
17678  this._$timeout(function() {
17679    self._updateTabs(self.mdSelectedNavItem, undefined);
17680  });
17681
17682  this._$scope.$watch('ctrl.mdSelectedNavItem', function(newValue, oldValue) {
17683    // Wait a digest before update tabs for products doing
17684    // anything dynamic in the template.
17685    self._$timeout(function() {
17686      self._updateTabs(newValue, oldValue);
17687    });
17688  });
17689};
17690
17691/**
17692 * Set the current tab to be selected.
17693 * @param {string|undefined} newValue New current tab name.
17694 * @param {string|undefined} oldValue Previous tab name.
17695 * @private
17696 */
17697MdNavBarController.prototype._updateTabs = function(newValue, oldValue) {
17698  var self = this;
17699  var tabs = this._getTabs();
17700
17701  // this._getTabs can return null if nav-bar has not yet been initialized
17702  if(!tabs)
17703    return;
17704
17705  var oldIndex = -1;
17706  var newIndex = -1;
17707  var newTab = this._getTabByName(newValue);
17708  var oldTab = this._getTabByName(oldValue);
17709
17710  if (oldTab) {
17711    oldTab.setSelected(false);
17712    oldIndex = tabs.indexOf(oldTab);
17713  }
17714
17715  if (newTab) {
17716    newTab.setSelected(true);
17717    newIndex = tabs.indexOf(newTab);
17718  }
17719
17720  this._$timeout(function() {
17721    self._updateInkBarStyles(newTab, newIndex, oldIndex);
17722  });
17723};
17724
17725/**
17726 * Repositions the ink bar to the selected tab.
17727 * @private
17728 */
17729MdNavBarController.prototype._updateInkBarStyles = function(tab, newIndex, oldIndex) {
17730  this._inkbar.toggleClass('_md-left', newIndex < oldIndex)
17731      .toggleClass('_md-right', newIndex > oldIndex);
17732
17733  this._inkbar.css({display: newIndex < 0 ? 'none' : ''});
17734
17735  if (tab) {
17736    var tabEl = tab.getButtonEl();
17737    var left = tabEl.offsetLeft;
17738
17739    this._inkbar.css({left: left + 'px', width: tabEl.offsetWidth + 'px'});
17740  }
17741};
17742
17743/**
17744 * Returns an array of the current tabs.
17745 * @return {!Array<!NavItemController>}
17746 * @private
17747 */
17748MdNavBarController.prototype._getTabs = function() {
17749  var controllers = Array.prototype.slice.call(
17750    this._navBarEl.querySelectorAll('.md-nav-item'))
17751    .map(function(el) {
17752      return angular.element(el).controller('mdNavItem')
17753    });
17754  return controllers.indexOf(undefined) ? controllers : null;
17755};
17756
17757/**
17758 * Returns the tab with the specified name.
17759 * @param {string} name The name of the tab, found in its name attribute.
17760 * @return {!NavItemController|undefined}
17761 * @private
17762 */
17763MdNavBarController.prototype._getTabByName = function(name) {
17764  return this._findTab(function(tab) {
17765    return tab.getName() == name;
17766  });
17767};
17768
17769/**
17770 * Returns the selected tab.
17771 * @return {!NavItemController|undefined}
17772 * @private
17773 */
17774MdNavBarController.prototype._getSelectedTab = function() {
17775  return this._findTab(function(tab) {
17776    return tab.isSelected();
17777  });
17778};
17779
17780/**
17781 * Returns the focused tab.
17782 * @return {!NavItemController|undefined}
17783 */
17784MdNavBarController.prototype.getFocusedTab = function() {
17785  return this._findTab(function(tab) {
17786    return tab.hasFocus();
17787  });
17788};
17789
17790/**
17791 * Find a tab that matches the specified function.
17792 * @private
17793 */
17794MdNavBarController.prototype._findTab = function(fn) {
17795  var tabs = this._getTabs();
17796  for (var i = 0; i < tabs.length; i++) {
17797    if (fn(tabs[i])) {
17798      return tabs[i];
17799    }
17800  }
17801
17802  return null;
17803};
17804
17805/**
17806 * Direct focus to the selected tab when focus enters the nav bar.
17807 */
17808MdNavBarController.prototype.onFocus = function() {
17809  var tab = this._getSelectedTab();
17810  if (tab) {
17811    tab.setFocused(true);
17812  }
17813};
17814
17815/**
17816 * Move focus from oldTab to newTab.
17817 * @param {!NavItemController} oldTab
17818 * @param {!NavItemController} newTab
17819 * @private
17820 */
17821MdNavBarController.prototype._moveFocus = function(oldTab, newTab) {
17822  oldTab.setFocused(false);
17823  newTab.setFocused(true);
17824};
17825
17826/**
17827 * Responds to keypress events.
17828 * @param {!Event} e
17829 */
17830MdNavBarController.prototype.onKeydown = function(e) {
17831  var keyCodes = this._$mdConstant.KEY_CODE;
17832  var tabs = this._getTabs();
17833  var focusedTab = this.getFocusedTab();
17834  if (!focusedTab) return;
17835
17836  var focusedTabIndex = tabs.indexOf(focusedTab);
17837
17838  // use arrow keys to navigate between tabs
17839  switch (e.keyCode) {
17840    case keyCodes.UP_ARROW:
17841    case keyCodes.LEFT_ARROW:
17842      if (focusedTabIndex > 0) {
17843        this._moveFocus(focusedTab, tabs[focusedTabIndex - 1]);
17844      }
17845      break;
17846    case keyCodes.DOWN_ARROW:
17847    case keyCodes.RIGHT_ARROW:
17848      if (focusedTabIndex < tabs.length - 1) {
17849        this._moveFocus(focusedTab, tabs[focusedTabIndex + 1]);
17850      }
17851      break;
17852    case keyCodes.SPACE:
17853    case keyCodes.ENTER:
17854      // timeout to avoid a "digest already in progress" console error
17855      this._$timeout(function() {
17856        focusedTab.getButtonEl().click();
17857      });
17858      break;
17859  }
17860};
17861
17862/**
17863 * @ngInject
17864 */
17865function MdNavItem($mdAria, $$rAF) {
17866  return {
17867    restrict: 'E',
17868    require: ['mdNavItem', '^mdNavBar'],
17869    controller: MdNavItemController,
17870    bindToController: true,
17871    controllerAs: 'ctrl',
17872    replace: true,
17873    transclude: true,
17874    template: function(tElement, tAttrs) {
17875      var hasNavClick = tAttrs.mdNavClick;
17876      var hasNavHref = tAttrs.mdNavHref;
17877      var hasNavSref = tAttrs.mdNavSref;
17878      var hasSrefOpts = tAttrs.srefOpts;
17879      var navigationAttribute;
17880      var navigationOptions;
17881      var buttonTemplate;
17882
17883      // Cannot specify more than one nav attribute
17884      if ((hasNavClick ? 1:0) + (hasNavHref ? 1:0) + (hasNavSref ? 1:0) > 1) {
17885        throw Error(
17886          'Must not specify more than one of the md-nav-click, md-nav-href, ' +
17887          'or md-nav-sref attributes per nav-item directive.'
17888        );
17889      }
17890
17891      if (hasNavClick) {
17892        navigationAttribute = 'ng-click="ctrl.mdNavClick()"';
17893      } else if (hasNavHref) {
17894        navigationAttribute = 'ng-href="{{ctrl.mdNavHref}}"';
17895      } else if (hasNavSref) {
17896        navigationAttribute = 'ui-sref="{{ctrl.mdNavSref}}"';
17897      }
17898
17899      navigationOptions = hasSrefOpts ? 'ui-sref-opts="{{ctrl.srefOpts}}" ' : '';
17900
17901      if (navigationAttribute) {
17902        buttonTemplate = '' +
17903          '<md-button class="_md-nav-button md-accent" ' +
17904            'ng-class="ctrl.getNgClassMap()" ' +
17905            'ng-blur="ctrl.setFocused(false)" ' +
17906            'tabindex="-1" ' +
17907            navigationOptions +
17908            navigationAttribute + '>' +
17909            '<span ng-transclude class="_md-nav-button-text"></span>' +
17910          '</md-button>';
17911      }
17912
17913      return '' +
17914        '<li class="md-nav-item" ' +
17915          'role="option" ' +
17916          'aria-selected="{{ctrl.isSelected()}}">' +
17917          (buttonTemplate || '') +
17918        '</li>';
17919    },
17920    scope: {
17921      'mdNavClick': '&?',
17922      'mdNavHref': '@?',
17923      'mdNavSref': '@?',
17924      'srefOpts': '=?',
17925      'name': '@',
17926    },
17927    link: function(scope, element, attrs, controllers) {
17928      // When accessing the element's contents synchronously, they
17929      // may not be defined yet because of transclusion. There is a higher
17930      // chance that it will be accessible if we wait one frame.
17931      $$rAF(function() {
17932        var mdNavItem = controllers[0];
17933        var mdNavBar = controllers[1];
17934        var navButton = angular.element(element[0].querySelector('._md-nav-button'));
17935
17936        if (!mdNavItem.name) {
17937          mdNavItem.name = angular.element(element[0]
17938              .querySelector('._md-nav-button-text')).text().trim();
17939        }
17940
17941        navButton.on('click', function() {
17942          mdNavBar.mdSelectedNavItem = mdNavItem.name;
17943          scope.$apply();
17944        });
17945
17946        $mdAria.expectWithText(element, 'aria-label');
17947      });
17948    }
17949  };
17950}
17951
17952/**
17953 * Controller for the nav-item component.
17954 * @param {!angular.JQLite} $element
17955 * @constructor
17956 * @final
17957 * @ngInject
17958 */
17959function MdNavItemController($element) {
17960
17961  /** @private @const {!angular.JQLite} */
17962  this._$element = $element;
17963
17964  // Data-bound variables
17965
17966  /** @const {?Function} */
17967  this.mdNavClick;
17968
17969  /** @const {?string} */
17970  this.mdNavHref;
17971
17972  /** @const {?string} */
17973  this.mdNavSref;
17974  /** @const {?Object} */
17975  this.srefOpts;
17976  /** @const {?string} */
17977  this.name;
17978
17979  // State variables
17980  /** @private {boolean} */
17981  this._selected = false;
17982
17983  /** @private {boolean} */
17984  this._focused = false;
17985}
17986
17987/**
17988 * Returns a map of class names and values for use by ng-class.
17989 * @return {!Object<string,boolean>}
17990 */
17991MdNavItemController.prototype.getNgClassMap = function() {
17992  return {
17993    'md-active': this._selected,
17994    'md-primary': this._selected,
17995    'md-unselected': !this._selected,
17996    'md-focused': this._focused,
17997  };
17998};
17999
18000/**
18001 * Get the name attribute of the tab.
18002 * @return {string}
18003 */
18004MdNavItemController.prototype.getName = function() {
18005  return this.name;
18006};
18007
18008/**
18009 * Get the button element associated with the tab.
18010 * @return {!Element}
18011 */
18012MdNavItemController.prototype.getButtonEl = function() {
18013  return this._$element[0].querySelector('._md-nav-button');
18014};
18015
18016/**
18017 * Set the selected state of the tab.
18018 * @param {boolean} isSelected
18019 */
18020MdNavItemController.prototype.setSelected = function(isSelected) {
18021  this._selected = isSelected;
18022};
18023
18024/**
18025 * @return {boolean}
18026 */
18027MdNavItemController.prototype.isSelected = function() {
18028  return this._selected;
18029};
18030
18031/**
18032 * Set the focused state of the tab.
18033 * @param {boolean} isFocused
18034 */
18035MdNavItemController.prototype.setFocused = function(isFocused) {
18036  this._focused = isFocused;
18037
18038  if (isFocused) {
18039    this.getButtonEl().focus();
18040  }
18041};
18042
18043/**
18044 * @return {boolean}
18045 */
18046MdNavItemController.prototype.hasFocus = function() {
18047  return this._focused;
18048};
18049
18050})();
18051(function(){
18052"use strict";
18053
18054/**
18055 * @ngdoc module
18056 * @name material.components.progressCircular
18057 * @description Module for a circular progressbar
18058 */
18059
18060angular.module('material.components.progressCircular', ['material.core']);
18061
18062})();
18063(function(){
18064"use strict";
18065
18066/**
18067 * @ngdoc module
18068 * @name material.components.progressLinear
18069 * @description Linear Progress module!
18070 */
18071MdProgressLinearDirective.$inject = ["$mdTheming", "$mdUtil", "$log"];
18072angular.module('material.components.progressLinear', [
18073  'material.core'
18074])
18075  .directive('mdProgressLinear', MdProgressLinearDirective);
18076
18077/**
18078 * @ngdoc directive
18079 * @name mdProgressLinear
18080 * @module material.components.progressLinear
18081 * @restrict E
18082 *
18083 * @description
18084 * The linear progress directive is used to make loading content
18085 * in your app as delightful and painless as possible by minimizing
18086 * the amount of visual change a user sees before they can view
18087 * and interact with content.
18088 *
18089 * Each operation should only be represented by one activity indicator
18090 * For example: one refresh operation should not display both a
18091 * refresh bar and an activity circle.
18092 *
18093 * For operations where the percentage of the operation completed
18094 * can be determined, use a determinate indicator. They give users
18095 * a quick sense of how long an operation will take.
18096 *
18097 * For operations where the user is asked to wait a moment while
18098 * something finishes up, and it’s not necessary to expose what's
18099 * happening behind the scenes and how long it will take, use an
18100 * indeterminate indicator.
18101 *
18102 * @param {string} md-mode Select from one of four modes: determinate, indeterminate, buffer or query.
18103 *
18104 * Note: if the `md-mode` value is set as undefined or specified as 1 of the four (4) valid modes, then `indeterminate`
18105 * will be auto-applied as the mode.
18106 *
18107 * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute. If `value=""` is also specified, however,
18108 * then `md-mode="determinate"` would be auto-injected instead.
18109 * @param {number=} value In determinate and buffer modes, this number represents the percentage of the primary progress bar. Default: 0
18110 * @param {number=} md-buffer-value In the buffer mode, this number represents the percentage of the secondary progress bar. Default: 0
18111 * @param {boolean=} ng-disabled Determines whether to disable the progress element.
18112 *
18113 * @usage
18114 * <hljs lang="html">
18115 * <md-progress-linear md-mode="determinate" value="..."></md-progress-linear>
18116 *
18117 * <md-progress-linear md-mode="determinate" ng-value="..."></md-progress-linear>
18118 *
18119 * <md-progress-linear md-mode="indeterminate"></md-progress-linear>
18120 *
18121 * <md-progress-linear md-mode="buffer" value="..." md-buffer-value="..."></md-progress-linear>
18122 *
18123 * <md-progress-linear md-mode="query"></md-progress-linear>
18124 * </hljs>
18125 */
18126function MdProgressLinearDirective($mdTheming, $mdUtil, $log) {
18127  var MODE_DETERMINATE = "determinate";
18128  var MODE_INDETERMINATE = "indeterminate";
18129  var MODE_BUFFER = "buffer";
18130  var MODE_QUERY = "query";
18131  var DISABLED_CLASS = "_md-progress-linear-disabled";
18132
18133  return {
18134    restrict: 'E',
18135    template: '<div class="md-container">' +
18136      '<div class="md-dashed"></div>' +
18137      '<div class="md-bar md-bar1"></div>' +
18138      '<div class="md-bar md-bar2"></div>' +
18139      '</div>',
18140    compile: compile
18141  };
18142
18143  function compile(tElement, tAttrs, transclude) {
18144    tElement.attr('aria-valuemin', 0);
18145    tElement.attr('aria-valuemax', 100);
18146    tElement.attr('role', 'progressbar');
18147
18148    return postLink;
18149  }
18150  function postLink(scope, element, attr) {
18151    $mdTheming(element);
18152
18153    var lastMode;
18154    var isDisabled = attr.hasOwnProperty('disabled');
18155    var toVendorCSS = $mdUtil.dom.animator.toCss;
18156    var bar1 = angular.element(element[0].querySelector('.md-bar1'));
18157    var bar2 = angular.element(element[0].querySelector('.md-bar2'));
18158    var container = angular.element(element[0].querySelector('.md-container'));
18159
18160    element
18161      .attr('md-mode', mode())
18162      .toggleClass(DISABLED_CLASS, isDisabled);
18163
18164    validateMode();
18165    watchAttributes();
18166
18167    /**
18168     * Watch the value, md-buffer-value, and md-mode attributes
18169     */
18170    function watchAttributes() {
18171      attr.$observe('value', function(value) {
18172        var percentValue = clamp(value);
18173        element.attr('aria-valuenow', percentValue);
18174
18175        if (mode() != MODE_QUERY) animateIndicator(bar2, percentValue);
18176      });
18177
18178      attr.$observe('mdBufferValue', function(value) {
18179        animateIndicator(bar1, clamp(value));
18180      });
18181
18182      attr.$observe('disabled', function(value) {
18183        if (value === true || value === false) {
18184          isDisabled = !!value;
18185        } else {
18186          isDisabled = angular.isDefined(value);
18187        }
18188
18189        element.toggleClass(DISABLED_CLASS, isDisabled);
18190        container.toggleClass(lastMode, !isDisabled);
18191      });
18192
18193      attr.$observe('mdMode', function(mode) {
18194        if (lastMode) container.removeClass( lastMode );
18195
18196        switch( mode ) {
18197          case MODE_QUERY:
18198          case MODE_BUFFER:
18199          case MODE_DETERMINATE:
18200          case MODE_INDETERMINATE:
18201            container.addClass( lastMode = "md-mode-" + mode );
18202            break;
18203          default:
18204            container.addClass( lastMode = "md-mode-" + MODE_INDETERMINATE );
18205            break;
18206        }
18207      });
18208    }
18209
18210    /**
18211     * Auto-defaults the mode to either `determinate` or `indeterminate` mode; if not specified
18212     */
18213    function validateMode() {
18214      if ( angular.isUndefined(attr.mdMode) ) {
18215        var hasValue = angular.isDefined(attr.value);
18216        var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE;
18217        var info = "Auto-adding the missing md-mode='{0}' to the ProgressLinear element";
18218
18219        //$log.debug( $mdUtil.supplant(info, [mode]) );
18220
18221        element.attr("md-mode", mode);
18222        attr.mdMode = mode;
18223      }
18224    }
18225
18226    /**
18227     * Is the md-mode a valid option?
18228     */
18229    function mode() {
18230      var value = (attr.mdMode || "").trim();
18231      if ( value ) {
18232        switch(value) {
18233          case MODE_DETERMINATE:
18234          case MODE_INDETERMINATE:
18235          case MODE_BUFFER:
18236          case MODE_QUERY:
18237            break;
18238          default:
18239            value = MODE_INDETERMINATE;
18240            break;
18241        }
18242      }
18243      return value;
18244    }
18245
18246    /**
18247     * Manually set CSS to animate the Determinate indicator based on the specified
18248     * percentage value (0-100).
18249     */
18250    function animateIndicator(target, value) {
18251      if ( isDisabled || !mode() ) return;
18252
18253      var to = $mdUtil.supplant("translateX({0}%) scale({1},1)", [ (value-100)/2, value/100 ]);
18254      var styles = toVendorCSS({ transform : to });
18255      angular.element(target).css( styles );
18256    }
18257  }
18258
18259  /**
18260   * Clamps the value to be between 0 and 100.
18261   * @param {number} value The value to clamp.
18262   * @returns {number}
18263   */
18264  function clamp(value) {
18265    return Math.max(0, Math.min(value || 0, 100));
18266  }
18267}
18268
18269
18270})();
18271(function(){
18272"use strict";
18273
18274/**
18275 * @ngdoc module
18276 * @name material.components.radioButton
18277 * @description radioButton module!
18278 */
18279mdRadioGroupDirective.$inject = ["$mdUtil", "$mdConstant", "$mdTheming", "$timeout"];
18280mdRadioButtonDirective.$inject = ["$mdAria", "$mdUtil", "$mdTheming"];
18281angular.module('material.components.radioButton', [
18282  'material.core'
18283])
18284  .directive('mdRadioGroup', mdRadioGroupDirective)
18285  .directive('mdRadioButton', mdRadioButtonDirective);
18286
18287/**
18288 * @ngdoc directive
18289 * @module material.components.radioButton
18290 * @name mdRadioGroup
18291 *
18292 * @restrict E
18293 *
18294 * @description
18295 * The `<md-radio-group>` directive identifies a grouping
18296 * container for the 1..n grouped radio buttons; specified using nested
18297 * `<md-radio-button>` tags.
18298 *
18299 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
18300 * the radio button is in the accent color by default. The primary color palette may be used with
18301 * the `md-primary` class.
18302 *
18303 * Note: `<md-radio-group>` and `<md-radio-button>` handle tabindex differently
18304 * than the native `<input type='radio'>` controls. Whereas the native controls
18305 * force the user to tab through all the radio buttons, `<md-radio-group>`
18306 * is focusable, and by default the `<md-radio-button>`s are not.
18307 *
18308 * @param {string} ng-model Assignable angular expression to data-bind to.
18309 * @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects.
18310 *
18311 * @usage
18312 * <hljs lang="html">
18313 * <md-radio-group ng-model="selected">
18314 *
18315 *   <md-radio-button
18316 *        ng-repeat="d in colorOptions"
18317 *        ng-value="d.value" aria-label="{{ d.label }}">
18318 *
18319 *          {{ d.label }}
18320 *
18321 *   </md-radio-button>
18322 *
18323 * </md-radio-group>
18324 * </hljs>
18325 *
18326 */
18327function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) {
18328  RadioGroupController.prototype = createRadioGroupControllerProto();
18329
18330  return {
18331    restrict: 'E',
18332    controller: ['$element', RadioGroupController],
18333    require: ['mdRadioGroup', '?ngModel'],
18334    link: { pre: linkRadioGroup }
18335  };
18336
18337  function linkRadioGroup(scope, element, attr, ctrls) {
18338    element.addClass('_md');     // private md component indicator for styling
18339    $mdTheming(element);
18340
18341    var rgCtrl = ctrls[0];
18342    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
18343
18344    rgCtrl.init(ngModelCtrl);
18345
18346    scope.mouseActive = false;
18347
18348    element
18349      .attr({
18350        'role': 'radiogroup',
18351        'tabIndex': element.attr('tabindex') || '0'
18352      })
18353      .on('keydown', keydownListener)
18354      .on('mousedown', function(event) {
18355        scope.mouseActive = true;
18356        $timeout(function() {
18357          scope.mouseActive = false;
18358        }, 100);
18359      })
18360      .on('focus', function() {
18361        if(scope.mouseActive === false) {
18362          rgCtrl.$element.addClass('md-focused');
18363        }
18364      })
18365      .on('blur', function() {
18366        rgCtrl.$element.removeClass('md-focused');
18367      });
18368
18369    /**
18370     *
18371     */
18372    function setFocus() {
18373      if (!element.hasClass('md-focused')) { element.addClass('md-focused'); }
18374    }
18375
18376    /**
18377     *
18378     */
18379    function keydownListener(ev) {
18380      var keyCode = ev.which || ev.keyCode;
18381
18382      // Only listen to events that we originated ourselves
18383      // so that we don't trigger on things like arrow keys in
18384      // inputs.
18385
18386      if (keyCode != $mdConstant.KEY_CODE.ENTER &&
18387          ev.currentTarget != ev.target) {
18388        return;
18389      }
18390
18391      switch (keyCode) {
18392        case $mdConstant.KEY_CODE.LEFT_ARROW:
18393        case $mdConstant.KEY_CODE.UP_ARROW:
18394          ev.preventDefault();
18395          rgCtrl.selectPrevious();
18396          setFocus();
18397          break;
18398
18399        case $mdConstant.KEY_CODE.RIGHT_ARROW:
18400        case $mdConstant.KEY_CODE.DOWN_ARROW:
18401          ev.preventDefault();
18402          rgCtrl.selectNext();
18403          setFocus();
18404          break;
18405
18406        case $mdConstant.KEY_CODE.ENTER:
18407          var form = angular.element($mdUtil.getClosest(element[0], 'form'));
18408          if (form.length > 0) {
18409            form.triggerHandler('submit');
18410          }
18411          break;
18412      }
18413
18414    }
18415  }
18416
18417  function RadioGroupController($element) {
18418    this._radioButtonRenderFns = [];
18419    this.$element = $element;
18420  }
18421
18422  function createRadioGroupControllerProto() {
18423    return {
18424      init: function(ngModelCtrl) {
18425        this._ngModelCtrl = ngModelCtrl;
18426        this._ngModelCtrl.$render = angular.bind(this, this.render);
18427      },
18428      add: function(rbRender) {
18429        this._radioButtonRenderFns.push(rbRender);
18430      },
18431      remove: function(rbRender) {
18432        var index = this._radioButtonRenderFns.indexOf(rbRender);
18433        if (index !== -1) {
18434          this._radioButtonRenderFns.splice(index, 1);
18435        }
18436      },
18437      render: function() {
18438        this._radioButtonRenderFns.forEach(function(rbRender) {
18439          rbRender();
18440        });
18441      },
18442      setViewValue: function(value, eventType) {
18443        this._ngModelCtrl.$setViewValue(value, eventType);
18444        // update the other radio buttons as well
18445        this.render();
18446      },
18447      getViewValue: function() {
18448        return this._ngModelCtrl.$viewValue;
18449      },
18450      selectNext: function() {
18451        return changeSelectedButton(this.$element, 1);
18452      },
18453      selectPrevious: function() {
18454        return changeSelectedButton(this.$element, -1);
18455      },
18456      setActiveDescendant: function (radioId) {
18457        this.$element.attr('aria-activedescendant', radioId);
18458      },
18459      isDisabled: function() {
18460        return this.$element[0].hasAttribute('disabled');
18461      }
18462    };
18463  }
18464  /**
18465   * Change the radio group's selected button by a given increment.
18466   * If no button is selected, select the first button.
18467   */
18468  function changeSelectedButton(parent, increment) {
18469    // Coerce all child radio buttons into an array, then wrap then in an iterator
18470    var buttons = $mdUtil.iterator(parent[0].querySelectorAll('md-radio-button'), true);
18471
18472    if (buttons.count()) {
18473      var validate = function (button) {
18474        // If disabled, then NOT valid
18475        return !angular.element(button).attr("disabled");
18476      };
18477
18478      var selected = parent[0].querySelector('md-radio-button.md-checked');
18479      var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
18480
18481      // Activate radioButton's click listener (triggerHandler won't create a real click event)
18482      angular.element(target).triggerHandler('click');
18483    }
18484  }
18485
18486}
18487
18488/**
18489 * @ngdoc directive
18490 * @module material.components.radioButton
18491 * @name mdRadioButton
18492 *
18493 * @restrict E
18494 *
18495 * @description
18496 * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
18497 *
18498 * While similar to the `<input type="radio" ng-model="" value="">` directive,
18499 * the `<md-radio-button>` directive provides ink effects, ARIA support, and
18500 * supports use within named radio groups.
18501 *
18502 * @param {string} ngModel Assignable angular expression to data-bind to.
18503 * @param {string=} ngChange Angular expression to be executed when input changes due to user
18504 *    interaction with the input element.
18505 * @param {string} ngValue Angular expression which sets the value to which the expression should
18506 *    be set when selected.
18507 * @param {string} value The value to which the expression should be set when selected.
18508 * @param {string=} name Property name of the form under which the control is published.
18509 * @param {string=} aria-label Adds label to radio button for accessibility.
18510 * Defaults to radio button's text. If no text content is available, a warning will be logged.
18511 *
18512 * @usage
18513 * <hljs lang="html">
18514 *
18515 * <md-radio-button value="1" aria-label="Label 1">
18516 *   Label 1
18517 * </md-radio-button>
18518 *
18519 * <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
18520 *   Green
18521 * </md-radio-button>
18522 *
18523 * </hljs>
18524 *
18525 */
18526function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
18527
18528  var CHECKED_CSS = 'md-checked';
18529
18530  return {
18531    restrict: 'E',
18532    require: '^mdRadioGroup',
18533    transclude: true,
18534    template: '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
18535                '<div class="md-off"></div>' +
18536                '<div class="md-on"></div>' +
18537              '</div>' +
18538              '<div ng-transclude class="md-label"></div>',
18539    link: link
18540  };
18541
18542  function link(scope, element, attr, rgCtrl) {
18543    var lastChecked;
18544
18545    $mdTheming(element);
18546    configureAria(element, scope);
18547
18548    // ngAria overwrites the aria-checked inside a $watch for ngValue.
18549    // We should defer the initialization until all the watches have fired.
18550    // This can also be fixed by removing the `lastChecked` check, but that'll
18551    // cause more DOM manipulation on each digest.
18552    if (attr.ngValue) {
18553      $mdUtil.nextTick(initialize, false);
18554    } else {
18555      initialize();
18556    }
18557
18558    /**
18559     * Initializes the component.
18560     */
18561    function initialize() {
18562      if (!rgCtrl) {
18563        throw 'RadioButton: No RadioGroupController could be found.';
18564      }
18565
18566      rgCtrl.add(render);
18567      attr.$observe('value', render);
18568
18569      element
18570        .on('click', listener)
18571        .on('$destroy', function() {
18572          rgCtrl.remove(render);
18573        });
18574    }
18575
18576    /**
18577     * On click functionality.
18578     */
18579    function listener(ev) {
18580      if (element[0].hasAttribute('disabled') || rgCtrl.isDisabled()) return;
18581
18582      scope.$apply(function() {
18583        rgCtrl.setViewValue(attr.value, ev && ev.type);
18584      });
18585    }
18586
18587    /**
18588     *  Add or remove the `.md-checked` class from the RadioButton (and conditionally its parent).
18589     *  Update the `aria-activedescendant` attribute.
18590     */
18591    function render() {
18592      var checked = rgCtrl.getViewValue() == attr.value;
18593
18594      if (checked === lastChecked) return;
18595
18596      if (element[0].parentNode.nodeName.toLowerCase() !== 'md-radio-group') {
18597        // If the radioButton is inside a div, then add class so highlighting will work
18598        element.parent().toggleClass(CHECKED_CSS, checked);
18599      }
18600
18601      if (checked) {
18602        rgCtrl.setActiveDescendant(element.attr('id'));
18603      }
18604
18605      lastChecked = checked;
18606
18607      element
18608        .attr('aria-checked', checked)
18609        .toggleClass(CHECKED_CSS, checked);
18610    }
18611
18612    /**
18613     * Inject ARIA-specific attributes appropriate for each radio button
18614     */
18615    function configureAria(element, scope){
18616      element.attr({
18617        id: attr.id || 'radio_' + $mdUtil.nextUid(),
18618        role: 'radio',
18619        'aria-checked': 'false'
18620      });
18621
18622      $mdAria.expectWithText(element, 'aria-label');
18623    }
18624  }
18625}
18626
18627})();
18628(function(){
18629"use strict";
18630
18631/**
18632 * @ngdoc module
18633 * @name material.components.select
18634 */
18635
18636/***************************************************
18637
18638 ### TODO - POST RC1 ###
18639 - [ ] Abstract placement logic in $mdSelect service to $mdMenu service
18640
18641 ***************************************************/
18642
18643SelectDirective.$inject = ["$mdSelect", "$mdUtil", "$mdConstant", "$mdTheming", "$mdAria", "$parse", "$sce", "$injector"];
18644SelectMenuDirective.$inject = ["$parse", "$mdUtil", "$mdConstant", "$mdTheming"];
18645OptionDirective.$inject = ["$mdButtonInkRipple", "$mdUtil"];
18646SelectProvider.$inject = ["$$interimElementProvider"];
18647var SELECT_EDGE_MARGIN = 8;
18648var selectNextId = 0;
18649var CHECKBOX_SELECTION_INDICATOR =
18650  angular.element('<div class="md-container"><div class="md-icon"></div></div>');
18651
18652angular.module('material.components.select', [
18653    'material.core',
18654    'material.components.backdrop'
18655  ])
18656  .directive('mdSelect', SelectDirective)
18657  .directive('mdSelectMenu', SelectMenuDirective)
18658  .directive('mdOption', OptionDirective)
18659  .directive('mdOptgroup', OptgroupDirective)
18660  .directive('mdSelectHeader', SelectHeaderDirective)
18661  .provider('$mdSelect', SelectProvider);
18662
18663/**
18664 * @ngdoc directive
18665 * @name mdSelect
18666 * @restrict E
18667 * @module material.components.select
18668 *
18669 * @description Displays a select box, bound to an ng-model.
18670 *
18671 * When the select is required and uses a floating label, then the label will automatically contain
18672 * an asterisk (`*`). This behavior can be disabled by using the `md-no-asterisk` attribute.
18673 *
18674 * By default, the select will display with an underline to match other form elements. This can be
18675 * disabled by applying the `md-no-underline` CSS class.
18676 *
18677 * ### Option Params
18678 *
18679 * When applied, `md-option-empty` will mark the option as "empty" allowing the option to  clear the
18680 * select and put it back in it's default state. You may supply this attribute on any option you
18681 * wish, however, it is automatically applied to an option whose `value` or `ng-value` are not
18682 * defined.
18683 *
18684 * **Automatically Applied**
18685 *
18686 *  - `<md-option>`
18687 *  - `<md-option value>`
18688 *  - `<md-option value="">`
18689 *  - `<md-option ng-value>`
18690 *  - `<md-option ng-value="">`
18691 *
18692 * **NOT Automatically Applied**
18693 *
18694 *  - `<md-option ng-value="1">`
18695 *  - `<md-option ng-value="''">`
18696 *  - `<md-option ng-value="undefined">`
18697 *  - `<md-option value="undefined">` (this evaluates to the string `"undefined"`)
18698 *  - <code ng-non-bindable>&lt;md-option ng-value="{{someValueThatMightBeUndefined}}"&gt;</code>
18699 *
18700 * **Note:** A value of `undefined` ***is considered a valid value*** (and does not auto-apply this
18701 * attribute) since you may wish this to be your "Not Available" or "None" option.
18702 *
18703 * **Note:** Using the `value` attribute (as opposed to `ng-value`) always evaluates to a string, so
18704 * `value="null"` will require the test `ng-if="myValue != 'null'"` rather than `ng-if="!myValue"`.
18705 *
18706 * @param {expression} ng-model The model!
18707 * @param {boolean=} multiple When set to true, allows for more than one option to be selected. The model is an array with the selected choices.
18708 * @param {expression=} md-on-close Expression to be evaluated when the select is closed.
18709 * @param {expression=} md-on-open Expression to be evaluated when opening the select.
18710 * Will hide the select options and show a spinner until the evaluated promise resolves.
18711 * @param {expression=} md-selected-text Expression to be evaluated that will return a string
18712 * to be displayed as a placeholder in the select input box when it is closed. The value
18713 * will be treated as *text* (not html).
18714 * @param {expression=} md-selected-html Expression to be evaluated that will return a string
18715 * to be displayed as a placeholder in the select input box when it is closed. The value
18716 * will be treated as *html*. The value must either be explicitly marked as trustedHtml or
18717 * the ngSanitize module must be loaded.
18718 * @param {string=} placeholder Placeholder hint text.
18719 * @param md-no-asterisk {boolean=} When set to true, an asterisk will not be appended to the
18720 * floating label. **Note:** This attribute is only evaluated once; it is not watched.
18721 * @param {string=} aria-label Optional label for accessibility. Only necessary if no placeholder or
18722 * explicit label is present.
18723 * @param {string=} md-container-class Class list to get applied to the `.md-select-menu-container`
18724 * element (for custom styling).
18725 *
18726 * @usage
18727 * With a placeholder (label and aria-label are added dynamically)
18728 * <hljs lang="html">
18729 *   <md-input-container>
18730 *     <md-select
18731 *       ng-model="someModel"
18732 *       placeholder="Select a state">
18733 *       <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
18734 *     </md-select>
18735 *   </md-input-container>
18736 * </hljs>
18737 *
18738 * With an explicit label
18739 * <hljs lang="html">
18740 *   <md-input-container>
18741 *     <label>State</label>
18742 *     <md-select
18743 *       ng-model="someModel">
18744 *       <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
18745 *     </md-select>
18746 *   </md-input-container>
18747 * </hljs>
18748 *
18749 * With a select-header
18750 *
18751 * When a developer needs to put more than just a text label in the
18752 * md-select-menu, they should use the md-select-header.
18753 * The user can put custom HTML inside of the header and style it to their liking.
18754 * One common use case of this would be a sticky search bar.
18755 *
18756 * When using the md-select-header the labels that would previously be added to the
18757 * OptGroupDirective are ignored.
18758 *
18759 * <hljs lang="html">
18760 *   <md-input-container>
18761 *     <md-select ng-model="someModel">
18762 *       <md-select-header>
18763 *         <span> Neighborhoods - </span>
18764 *       </md-select-header>
18765 *       <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
18766 *     </md-select>
18767 *   </md-input-container>
18768 * </hljs>
18769 *
18770 * ## Selects and object equality
18771 * When using a `md-select` to pick from a list of objects, it is important to realize how javascript handles
18772 * equality. Consider the following example:
18773 * <hljs lang="js">
18774 * angular.controller('MyCtrl', function($scope) {
18775 *   $scope.users = [
18776 *     { id: 1, name: 'Bob' },
18777 *     { id: 2, name: 'Alice' },
18778 *     { id: 3, name: 'Steve' }
18779 *   ];
18780 *   $scope.selectedUser = { id: 1, name: 'Bob' };
18781 * });
18782 * </hljs>
18783 * <hljs lang="html">
18784 * <div ng-controller="MyCtrl">
18785 *   <md-select ng-model="selectedUser">
18786 *     <md-option ng-value="user" ng-repeat="user in users">{{ user.name }}</md-option>
18787 *   </md-select>
18788 * </div>
18789 * </hljs>
18790 *
18791 * At first one might expect that the select should be populated with "Bob" as the selected user. However,
18792 * this is not true. To determine whether something is selected,
18793 * `ngModelController` is looking at whether `$scope.selectedUser == (any user in $scope.users);`;
18794 *
18795 * Javascript's `==` operator does not check for deep equality (ie. that all properties
18796 * on the object are the same), but instead whether the objects are *the same object in memory*.
18797 * In this case, we have two instances of identical objects, but they exist in memory as unique
18798 * entities. Because of this, the select will have no value populated for a selected user.
18799 *
18800 * To get around this, `ngModelController` provides a `track by` option that allows us to specify a different
18801 * expression which will be used for the equality operator. As such, we can update our `html` to
18802 * make use of this by specifying the `ng-model-options="{trackBy: '$value.id'}"` on the `md-select`
18803 * element. This converts our equality expression to be
18804 * `$scope.selectedUser.id == (any id in $scope.users.map(function(u) { return u.id; }));`
18805 * which results in Bob being selected as desired.
18806 *
18807 * Working HTML:
18808 * <hljs lang="html">
18809 * <div ng-controller="MyCtrl">
18810 *   <md-select ng-model="selectedUser" ng-model-options="{trackBy: '$value.id'}">
18811 *     <md-option ng-value="user" ng-repeat="user in users">{{ user.name }}</md-option>
18812 *   </md-select>
18813 * </div>
18814 * </hljs>
18815 */
18816function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $parse, $sce,
18817    $injector) {
18818  var keyCodes = $mdConstant.KEY_CODE;
18819  var NAVIGATION_KEYS = [keyCodes.SPACE, keyCodes.ENTER, keyCodes.UP_ARROW, keyCodes.DOWN_ARROW];
18820
18821  return {
18822    restrict: 'E',
18823    require: ['^?mdInputContainer', 'mdSelect', 'ngModel', '?^form'],
18824    compile: compile,
18825    controller: function() {
18826    } // empty placeholder controller to be initialized in link
18827  };
18828
18829  function compile(element, attr) {
18830    // add the select value that will hold our placeholder or selected option value
18831    var valueEl = angular.element('<md-select-value><span></span></md-select-value>');
18832    valueEl.append('<span class="md-select-icon" aria-hidden="true"></span>');
18833    valueEl.addClass('md-select-value');
18834    if (!valueEl[0].hasAttribute('id')) {
18835      valueEl.attr('id', 'select_value_label_' + $mdUtil.nextUid());
18836    }
18837
18838    // There's got to be an md-content inside. If there's not one, let's add it.
18839    if (!element.find('md-content').length) {
18840      element.append(angular.element('<md-content>').append(element.contents()));
18841    }
18842
18843
18844    // Add progress spinner for md-options-loading
18845    if (attr.mdOnOpen) {
18846
18847      // Show progress indicator while loading async
18848      // Use ng-hide for `display:none` so the indicator does not interfere with the options list
18849      element
18850        .find('md-content')
18851        .prepend(angular.element(
18852          '<div>' +
18853          ' <md-progress-circular md-mode="indeterminate" ng-if="$$loadingAsyncDone === false" md-diameter="25px"></md-progress-circular>' +
18854          '</div>'
18855        ));
18856
18857      // Hide list [of item options] while loading async
18858      element
18859        .find('md-option')
18860        .attr('ng-show', '$$loadingAsyncDone');
18861    }
18862
18863    if (attr.name) {
18864      var autofillClone = angular.element('<select class="md-visually-hidden">');
18865      autofillClone.attr({
18866        'name': attr.name,
18867        'aria-hidden': 'true',
18868        'tabindex': '-1'
18869      });
18870      var opts = element.find('md-option');
18871      angular.forEach(opts, function(el) {
18872        var newEl = angular.element('<option>' + el.innerHTML + '</option>');
18873        if (el.hasAttribute('ng-value')) newEl.attr('ng-value', el.getAttribute('ng-value'));
18874        else if (el.hasAttribute('value')) newEl.attr('value', el.getAttribute('value'));
18875        autofillClone.append(newEl);
18876      });
18877
18878      // Adds an extra option that will hold the selected value for the
18879      // cases where the select is a part of a non-angular form. This can be done with a ng-model,
18880      // however if the `md-option` is being `ng-repeat`-ed, Angular seems to insert a similar
18881      // `option` node, but with a value of `? string: <value> ?` which would then get submitted.
18882      // This also goes around having to prepend a dot to the name attribute.
18883      autofillClone.append(
18884        '<option ng-value="' + attr.ngModel + '" selected></option>'
18885      );
18886
18887      element.parent().append(autofillClone);
18888    }
18889
18890    var isMultiple = $mdUtil.parseAttributeBoolean(attr.multiple);
18891
18892    // Use everything that's left inside element.contents() as the contents of the menu
18893    var multipleContent = isMultiple ? 'multiple' : '';
18894    var selectTemplate = '' +
18895      '<div class="md-select-menu-container" aria-hidden="true">' +
18896      '<md-select-menu {0}>{1}</md-select-menu>' +
18897      '</div>';
18898
18899    selectTemplate = $mdUtil.supplant(selectTemplate, [multipleContent, element.html()]);
18900    element.empty().append(valueEl);
18901    element.append(selectTemplate);
18902
18903    if(!attr.tabindex){
18904      attr.$set('tabindex', 0);
18905    }
18906
18907    return function postLink(scope, element, attr, ctrls) {
18908      var untouched = true;
18909      var isDisabled, ariaLabelBase;
18910
18911      var containerCtrl = ctrls[0];
18912      var mdSelectCtrl = ctrls[1];
18913      var ngModelCtrl = ctrls[2];
18914      var formCtrl = ctrls[3];
18915      // grab a reference to the select menu value label
18916      var valueEl = element.find('md-select-value');
18917      var isReadonly = angular.isDefined(attr.readonly);
18918      var disableAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
18919
18920      if (disableAsterisk) {
18921        element.addClass('md-no-asterisk');
18922      }
18923
18924      if (containerCtrl) {
18925        var isErrorGetter = containerCtrl.isErrorGetter || function() {
18926          return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (formCtrl && formCtrl.$submitted));
18927        };
18928
18929        if (containerCtrl.input) {
18930          // We ignore inputs that are in the md-select-header (one
18931          // case where this might be useful would be adding as searchbox)
18932          if (element.find('md-select-header').find('input')[0] !== containerCtrl.input[0]) {
18933            throw new Error("<md-input-container> can only have *one* child <input>, <textarea> or <select> element!");
18934          }
18935        }
18936
18937        containerCtrl.input = element;
18938        if (!containerCtrl.label) {
18939          $mdAria.expect(element, 'aria-label', element.attr('placeholder'));
18940        }
18941
18942        scope.$watch(isErrorGetter, containerCtrl.setInvalid);
18943      }
18944
18945      var selectContainer, selectScope, selectMenuCtrl;
18946
18947      findSelectContainer();
18948      $mdTheming(element);
18949
18950      if (formCtrl && angular.isDefined(attr.multiple)) {
18951        $mdUtil.nextTick(function() {
18952          var hasModelValue = ngModelCtrl.$modelValue || ngModelCtrl.$viewValue;
18953          if (hasModelValue) {
18954            formCtrl.$setPristine();
18955          }
18956        });
18957      }
18958
18959      var originalRender = ngModelCtrl.$render;
18960      ngModelCtrl.$render = function() {
18961        originalRender();
18962        syncLabelText();
18963        syncAriaLabel();
18964        inputCheckValue();
18965      };
18966
18967      attr.$observe('placeholder', ngModelCtrl.$render);
18968
18969      if (containerCtrl && containerCtrl.label) {
18970        attr.$observe('required', function (value) {
18971          // Toggle the md-required class on the input containers label, because the input container is automatically
18972          // applying the asterisk indicator on the label.
18973          containerCtrl.label.toggleClass('md-required', value && !disableAsterisk);
18974        });
18975      }
18976
18977      mdSelectCtrl.setLabelText = function(text) {
18978        mdSelectCtrl.setIsPlaceholder(!text);
18979
18980        // Whether the select label has been given via user content rather than the internal
18981        // template of <md-option>
18982        var isSelectLabelFromUser = false;
18983
18984        if (attr.mdSelectedText && attr.mdSelectedHtml) {
18985          throw Error('md-select cannot have both `md-selected-text` and `md-selected-html`');
18986        }
18987
18988        if (attr.mdSelectedText || attr.mdSelectedHtml) {
18989          text = $parse(attr.mdSelectedText || attr.mdSelectedHtml)(scope);
18990          isSelectLabelFromUser = true;
18991        } else if (!text) {
18992          // Use placeholder attribute, otherwise fallback to the md-input-container label
18993          var tmpPlaceholder = attr.placeholder ||
18994              (containerCtrl && containerCtrl.label ? containerCtrl.label.text() : '');
18995
18996          text = tmpPlaceholder || '';
18997          isSelectLabelFromUser = true;
18998        }
18999
19000        var target = valueEl.children().eq(0);
19001
19002        if (attr.mdSelectedHtml) {
19003            // Using getTrustedHtml will run the content through $sanitize if it is not already
19004            // explicitly trusted. If the ngSanitize module is not loaded, this will
19005            // *correctly* throw an sce error.
19006            target.html($sce.getTrustedHtml(text));
19007        } else if (isSelectLabelFromUser) {
19008          target.text(text);
19009        } else {
19010          // If we've reached this point, the text is not user-provided.
19011          target.html(text);
19012        }
19013      };
19014
19015      mdSelectCtrl.setIsPlaceholder = function(isPlaceholder) {
19016        if (isPlaceholder) {
19017          valueEl.addClass('md-select-placeholder');
19018          if (containerCtrl && containerCtrl.label) {
19019            containerCtrl.label.addClass('md-placeholder');
19020          }
19021        } else {
19022          valueEl.removeClass('md-select-placeholder');
19023          if (containerCtrl && containerCtrl.label) {
19024            containerCtrl.label.removeClass('md-placeholder');
19025          }
19026        }
19027      };
19028
19029      if (!isReadonly) {
19030        element
19031          .on('focus', function(ev) {
19032            // Always focus the container (if we have one) so floating labels and other styles are
19033            // applied properly
19034            containerCtrl && containerCtrl.setFocused(true);
19035          });
19036
19037        // Attach before ngModel's blur listener to stop propagation of blur event
19038        // to prevent from setting $touched.
19039        element.on('blur', function(event) {
19040          if (untouched) {
19041            untouched = false;
19042            if (selectScope._mdSelectIsOpen) {
19043              event.stopImmediatePropagation();
19044            }
19045          }
19046
19047          if (selectScope._mdSelectIsOpen) return;
19048          containerCtrl && containerCtrl.setFocused(false);
19049          inputCheckValue();
19050        });
19051      }
19052
19053      mdSelectCtrl.triggerClose = function() {
19054        $parse(attr.mdOnClose)(scope);
19055      };
19056
19057      scope.$$postDigest(function() {
19058        initAriaLabel();
19059        syncLabelText();
19060        syncAriaLabel();
19061      });
19062
19063      function initAriaLabel() {
19064        var labelText = element.attr('aria-label') || element.attr('placeholder');
19065        if (!labelText && containerCtrl && containerCtrl.label) {
19066          labelText = containerCtrl.label.text();
19067        }
19068        ariaLabelBase = labelText;
19069        $mdAria.expect(element, 'aria-label', labelText);
19070      }
19071
19072      scope.$watch(function() {
19073        return selectMenuCtrl.selectedLabels();
19074      }, syncLabelText);
19075
19076      function syncLabelText() {
19077        if (selectContainer) {
19078          selectMenuCtrl = selectMenuCtrl || selectContainer.find('md-select-menu').controller('mdSelectMenu');
19079          mdSelectCtrl.setLabelText(selectMenuCtrl.selectedLabels());
19080        }
19081      }
19082
19083      function syncAriaLabel() {
19084        if (!ariaLabelBase) return;
19085        var ariaLabels = selectMenuCtrl.selectedLabels({mode: 'aria'});
19086        element.attr('aria-label', ariaLabels.length ? ariaLabelBase + ': ' + ariaLabels : ariaLabelBase);
19087      }
19088
19089      var deregisterWatcher;
19090      attr.$observe('ngMultiple', function(val) {
19091        if (deregisterWatcher) deregisterWatcher();
19092        var parser = $parse(val);
19093        deregisterWatcher = scope.$watch(function() {
19094          return parser(scope);
19095        }, function(multiple, prevVal) {
19096          if (multiple === undefined && prevVal === undefined) return; // assume compiler did a good job
19097          if (multiple) {
19098            element.attr('multiple', 'multiple');
19099          } else {
19100            element.removeAttr('multiple');
19101          }
19102          element.attr('aria-multiselectable', multiple ? 'true' : 'false');
19103          if (selectContainer) {
19104            selectMenuCtrl.setMultiple(multiple);
19105            originalRender = ngModelCtrl.$render;
19106            ngModelCtrl.$render = function() {
19107              originalRender();
19108              syncLabelText();
19109              syncAriaLabel();
19110              inputCheckValue();
19111            };
19112            ngModelCtrl.$render();
19113          }
19114        });
19115      });
19116
19117      attr.$observe('disabled', function(disabled) {
19118        if (angular.isString(disabled)) {
19119          disabled = true;
19120        }
19121        // Prevent click event being registered twice
19122        if (isDisabled !== undefined && isDisabled === disabled) {
19123          return;
19124        }
19125        isDisabled = disabled;
19126        if (disabled) {
19127          element
19128            .attr({'aria-disabled': 'true'})
19129            .removeAttr('tabindex')
19130            .off('click', openSelect)
19131            .off('keydown', handleKeypress);
19132        } else {
19133          element
19134            .attr({'tabindex': attr.tabindex, 'aria-disabled': 'false'})
19135            .on('click', openSelect)
19136            .on('keydown', handleKeypress);
19137        }
19138      });
19139
19140      if (!attr.hasOwnProperty('disabled') && !attr.hasOwnProperty('ngDisabled')) {
19141        element.attr({'aria-disabled': 'false'});
19142        element.on('click', openSelect);
19143        element.on('keydown', handleKeypress);
19144      }
19145
19146      var ariaAttrs = {
19147        role: 'listbox',
19148        'aria-expanded': 'false',
19149        'aria-multiselectable': isMultiple && !attr.ngMultiple ? 'true' : 'false'
19150      };
19151
19152      if (!element[0].hasAttribute('id')) {
19153        ariaAttrs.id = 'select_' + $mdUtil.nextUid();
19154      }
19155
19156      var containerId = 'select_container_' + $mdUtil.nextUid();
19157      selectContainer.attr('id', containerId);
19158      ariaAttrs['aria-owns'] = containerId;
19159      element.attr(ariaAttrs);
19160
19161      scope.$on('$destroy', function() {
19162        $mdSelect
19163          .destroy()
19164          .finally(function() {
19165            if (containerCtrl) {
19166              containerCtrl.setFocused(false);
19167              containerCtrl.setHasValue(false);
19168              containerCtrl.input = null;
19169            }
19170            ngModelCtrl.$setTouched();
19171          });
19172      });
19173
19174
19175
19176      function inputCheckValue() {
19177        // The select counts as having a value if one or more options are selected,
19178        // or if the input's validity state says it has bad input (eg string in a number input)
19179        containerCtrl && containerCtrl.setHasValue(selectMenuCtrl.selectedLabels().length > 0 || (element[0].validity || {}).badInput);
19180      }
19181
19182      function findSelectContainer() {
19183        selectContainer = angular.element(
19184          element[0].querySelector('.md-select-menu-container')
19185        );
19186        selectScope = scope;
19187        if (attr.mdContainerClass) {
19188          var value = selectContainer[0].getAttribute('class') + ' ' + attr.mdContainerClass;
19189          selectContainer[0].setAttribute('class', value);
19190        }
19191        selectMenuCtrl = selectContainer.find('md-select-menu').controller('mdSelectMenu');
19192        selectMenuCtrl.init(ngModelCtrl, attr.ngModel);
19193        element.on('$destroy', function() {
19194          selectContainer.remove();
19195        });
19196      }
19197
19198      function handleKeypress(e) {
19199        if ($mdConstant.isNavigationKey(e)) {
19200          // prevent page scrolling on interaction
19201          e.preventDefault();
19202          openSelect(e);
19203        } else {
19204          if ($mdConstant.isInputKey(e) || $mdConstant.isNumPadKey(e)) {
19205            e.preventDefault();
19206
19207            var node = selectMenuCtrl.optNodeForKeyboardSearch(e);
19208            if (!node || node.hasAttribute('disabled')) return;
19209            var optionCtrl = angular.element(node).controller('mdOption');
19210            if (!selectMenuCtrl.isMultiple) {
19211              selectMenuCtrl.deselect(Object.keys(selectMenuCtrl.selected)[0]);
19212            }
19213            selectMenuCtrl.select(optionCtrl.hashKey, optionCtrl.value);
19214            selectMenuCtrl.refreshViewValue();
19215          }
19216        }
19217      }
19218
19219      function openSelect() {
19220        selectScope._mdSelectIsOpen = true;
19221        element.attr('aria-expanded', 'true');
19222
19223        $mdSelect.show({
19224          scope: selectScope,
19225          preserveScope: true,
19226          skipCompile: true,
19227          element: selectContainer,
19228          target: element[0],
19229          selectCtrl: mdSelectCtrl,
19230          preserveElement: true,
19231          hasBackdrop: true,
19232          loadingAsync: attr.mdOnOpen ? scope.$eval(attr.mdOnOpen) || true : false
19233        }).finally(function() {
19234          selectScope._mdSelectIsOpen = false;
19235          element.focus();
19236          element.attr('aria-expanded', 'false');
19237          ngModelCtrl.$setTouched();
19238        });
19239      }
19240
19241    };
19242  }
19243}
19244
19245function SelectMenuDirective($parse, $mdUtil, $mdConstant, $mdTheming) {
19246  // We want the scope to be set to 'false' so an isolated scope is not created
19247  // which would interfere with the md-select-header's access to the
19248  // parent scope.
19249  SelectMenuController.$inject = ["$scope", "$attrs", "$element"];
19250  return {
19251    restrict: 'E',
19252    require: ['mdSelectMenu'],
19253    scope: false,
19254    controller: SelectMenuController,
19255    link: {pre: preLink}
19256  };
19257
19258  // We use preLink instead of postLink to ensure that the select is initialized before
19259  // its child options run postLink.
19260  function preLink(scope, element, attr, ctrls) {
19261    var selectCtrl = ctrls[0];
19262
19263    element.addClass('_md');     // private md component indicator for styling
19264
19265    $mdTheming(element);
19266    element.on('click', clickListener);
19267    element.on('keypress', keyListener);
19268
19269    function keyListener(e) {
19270      if (e.keyCode == 13 || e.keyCode == 32) {
19271        clickListener(e);
19272      }
19273    }
19274
19275    function clickListener(ev) {
19276      var option = $mdUtil.getClosest(ev.target, 'md-option');
19277      var optionCtrl = option && angular.element(option).data('$mdOptionController');
19278      if (!option || !optionCtrl) return;
19279      if (option.hasAttribute('disabled')) {
19280        ev.stopImmediatePropagation();
19281        return false;
19282      }
19283
19284      var optionHashKey = selectCtrl.hashGetter(optionCtrl.value);
19285      var isSelected = angular.isDefined(selectCtrl.selected[optionHashKey]);
19286
19287      scope.$apply(function() {
19288        if (selectCtrl.isMultiple) {
19289          if (isSelected) {
19290            selectCtrl.deselect(optionHashKey);
19291          } else {
19292            selectCtrl.select(optionHashKey, optionCtrl.value);
19293          }
19294        } else {
19295          if (!isSelected) {
19296            selectCtrl.deselect(Object.keys(selectCtrl.selected)[0]);
19297            selectCtrl.select(optionHashKey, optionCtrl.value);
19298          }
19299        }
19300        selectCtrl.refreshViewValue();
19301      });
19302    }
19303  }
19304
19305  function SelectMenuController($scope, $attrs, $element) {
19306    var self = this;
19307    self.isMultiple = angular.isDefined($attrs.multiple);
19308    // selected is an object with keys matching all of the selected options' hashed values
19309    self.selected = {};
19310    // options is an object with keys matching every option's hash value,
19311    // and values matching every option's controller.
19312    self.options = {};
19313
19314    $scope.$watchCollection(function() {
19315      return self.options;
19316    }, function() {
19317      self.ngModel.$render();
19318    });
19319
19320    var deregisterCollectionWatch;
19321    var defaultIsEmpty;
19322    self.setMultiple = function(isMultiple) {
19323      var ngModel = self.ngModel;
19324      defaultIsEmpty = defaultIsEmpty || ngModel.$isEmpty;
19325
19326      self.isMultiple = isMultiple;
19327      if (deregisterCollectionWatch) deregisterCollectionWatch();
19328
19329      if (self.isMultiple) {
19330        ngModel.$validators['md-multiple'] = validateArray;
19331        ngModel.$render = renderMultiple;
19332
19333        // watchCollection on the model because by default ngModel only watches the model's
19334        // reference. This allowed the developer to also push and pop from their array.
19335        $scope.$watchCollection(self.modelBinding, function(value) {
19336          if (validateArray(value)) renderMultiple(value);
19337          self.ngModel.$setPristine();
19338        });
19339
19340        ngModel.$isEmpty = function(value) {
19341          return !value || value.length === 0;
19342        };
19343      } else {
19344        delete ngModel.$validators['md-multiple'];
19345        ngModel.$render = renderSingular;
19346      }
19347
19348      function validateArray(modelValue, viewValue) {
19349        // If a value is truthy but not an array, reject it.
19350        // If value is undefined/falsy, accept that it's an empty array.
19351        return angular.isArray(modelValue || viewValue || []);
19352      }
19353    };
19354
19355    var searchStr = '';
19356    var clearSearchTimeout, optNodes, optText;
19357    var CLEAR_SEARCH_AFTER = 300;
19358
19359    self.optNodeForKeyboardSearch = function(e) {
19360      clearSearchTimeout && clearTimeout(clearSearchTimeout);
19361      clearSearchTimeout = setTimeout(function() {
19362        clearSearchTimeout = undefined;
19363        searchStr = '';
19364        optText = undefined;
19365        optNodes = undefined;
19366      }, CLEAR_SEARCH_AFTER);
19367
19368      // Support 1-9 on numpad
19369      var keyCode = e.keyCode - ($mdConstant.isNumPadKey(e) ? 48 : 0);
19370
19371      searchStr += String.fromCharCode(keyCode);
19372      var search = new RegExp('^' + searchStr, 'i');
19373      if (!optNodes) {
19374        optNodes = $element.find('md-option');
19375        optText = new Array(optNodes.length);
19376        angular.forEach(optNodes, function(el, i) {
19377          optText[i] = el.textContent.trim();
19378        });
19379      }
19380      for (var i = 0; i < optText.length; ++i) {
19381        if (search.test(optText[i])) {
19382          return optNodes[i];
19383        }
19384      }
19385    };
19386
19387    self.init = function(ngModel, binding) {
19388      self.ngModel = ngModel;
19389      self.modelBinding = binding;
19390
19391      // Setup a more robust version of isEmpty to ensure value is a valid option
19392      self.ngModel.$isEmpty = function($viewValue) {
19393        // We have to transform the viewValue into the hashKey, because otherwise the
19394        // OptionCtrl may not exist. Developers may have specified a trackBy function.
19395        return !self.options[self.hashGetter($viewValue)];
19396      };
19397
19398      // Allow users to provide `ng-model="foo" ng-model-options="{trackBy: 'foo.id'}"` so
19399      // that we can properly compare objects set on the model to the available options
19400      var trackByOption = $mdUtil.getModelOption(ngModel, 'trackBy');
19401
19402      if (trackByOption) {
19403        var trackByLocals = {};
19404        var trackByParsed = $parse(trackByOption);
19405        self.hashGetter = function(value, valueScope) {
19406          trackByLocals.$value = value;
19407          return trackByParsed(valueScope || $scope, trackByLocals);
19408        };
19409        // If the user doesn't provide a trackBy, we automatically generate an id for every
19410        // value passed in
19411      } else {
19412        self.hashGetter = function getHashValue(value) {
19413          if (angular.isObject(value)) {
19414            return 'object_' + (value.$$mdSelectId || (value.$$mdSelectId = ++selectNextId));
19415          }
19416          return value;
19417        };
19418      }
19419      self.setMultiple(self.isMultiple);
19420    };
19421
19422    self.selectedLabels = function(opts) {
19423      opts = opts || {};
19424      var mode = opts.mode || 'html';
19425      var selectedOptionEls = $mdUtil.nodesToArray($element[0].querySelectorAll('md-option[selected]'));
19426      if (selectedOptionEls.length) {
19427        var mapFn;
19428
19429        if (mode == 'html') {
19430          // Map the given element to its innerHTML string. If the element has a child ripple
19431          // container remove it from the HTML string, before returning the string.
19432          mapFn = function(el) {
19433            // If we do not have a `value` or `ng-value`, assume it is an empty option which clears the select
19434            if (el.hasAttribute('md-option-empty')) {
19435              return '';
19436            }
19437
19438            var html = el.innerHTML;
19439
19440            // Remove the ripple container from the selected option, copying it would cause a CSP violation.
19441            var rippleContainer = el.querySelector('.md-ripple-container');
19442            if (rippleContainer) {
19443              html = html.replace(rippleContainer.outerHTML, '');
19444            }
19445
19446            // Remove the checkbox container, because it will cause the label to wrap inside of the placeholder.
19447            // It should be not displayed inside of the label element.
19448            var checkboxContainer = el.querySelector('.md-container');
19449            if (checkboxContainer) {
19450              html = html.replace(checkboxContainer.outerHTML, '');
19451            }
19452
19453            return html;
19454          };
19455        } else if (mode == 'aria') {
19456          mapFn = function(el) { return el.hasAttribute('aria-label') ? el.getAttribute('aria-label') : el.textContent; };
19457        }
19458
19459        // Ensure there are no duplicates; see https://github.com/angular/material/issues/9442
19460        return $mdUtil.uniq(selectedOptionEls.map(mapFn)).join(', ');
19461      } else {
19462        return '';
19463      }
19464    };
19465
19466    self.select = function(hashKey, hashedValue) {
19467      var option = self.options[hashKey];
19468      option && option.setSelected(true);
19469      self.selected[hashKey] = hashedValue;
19470    };
19471    self.deselect = function(hashKey) {
19472      var option = self.options[hashKey];
19473      option && option.setSelected(false);
19474      delete self.selected[hashKey];
19475    };
19476
19477    self.addOption = function(hashKey, optionCtrl) {
19478      if (angular.isDefined(self.options[hashKey])) {
19479        throw new Error('Duplicate md-option values are not allowed in a select. ' +
19480          'Duplicate value "' + optionCtrl.value + '" found.');
19481      }
19482
19483      self.options[hashKey] = optionCtrl;
19484
19485      // If this option's value was already in our ngModel, go ahead and select it.
19486      if (angular.isDefined(self.selected[hashKey])) {
19487        self.select(hashKey, optionCtrl.value);
19488
19489        // When the current $modelValue of the ngModel Controller is using the same hash as
19490        // the current option, which will be added, then we can be sure, that the validation
19491        // of the option has occurred before the option was added properly.
19492        // This means, that we have to manually trigger a new validation of the current option.
19493        if (angular.isDefined(self.ngModel.$modelValue) && self.hashGetter(self.ngModel.$modelValue) === hashKey) {
19494          self.ngModel.$validate();
19495        }
19496
19497        self.refreshViewValue();
19498      }
19499    };
19500    self.removeOption = function(hashKey) {
19501      delete self.options[hashKey];
19502      // Don't deselect an option when it's removed - the user's ngModel should be allowed
19503      // to have values that do not match a currently available option.
19504    };
19505
19506    self.refreshViewValue = function() {
19507      var values = [];
19508      var option;
19509      for (var hashKey in self.selected) {
19510        // If this hashKey has an associated option, push that option's value to the model.
19511        if ((option = self.options[hashKey])) {
19512          values.push(option.value);
19513        } else {
19514          // Otherwise, the given hashKey has no associated option, and we got it
19515          // from an ngModel value at an earlier time. Push the unhashed value of
19516          // this hashKey to the model.
19517          // This allows the developer to put a value in the model that doesn't yet have
19518          // an associated option.
19519          values.push(self.selected[hashKey]);
19520        }
19521      }
19522      var usingTrackBy = $mdUtil.getModelOption(self.ngModel, 'trackBy');
19523
19524      var newVal = self.isMultiple ? values : values[0];
19525      var prevVal = self.ngModel.$modelValue;
19526
19527      if (usingTrackBy ? !angular.equals(prevVal, newVal) : (prevVal + '') !== newVal) {
19528        self.ngModel.$setViewValue(newVal);
19529        self.ngModel.$render();
19530      }
19531    };
19532
19533    function renderMultiple() {
19534      var newSelectedValues = self.ngModel.$modelValue || self.ngModel.$viewValue || [];
19535      if (!angular.isArray(newSelectedValues)) return;
19536
19537      var oldSelected = Object.keys(self.selected);
19538
19539      var newSelectedHashes = newSelectedValues.map(self.hashGetter);
19540      var deselected = oldSelected.filter(function(hash) {
19541        return newSelectedHashes.indexOf(hash) === -1;
19542      });
19543
19544      deselected.forEach(self.deselect);
19545      newSelectedHashes.forEach(function(hashKey, i) {
19546        self.select(hashKey, newSelectedValues[i]);
19547      });
19548    }
19549
19550    function renderSingular() {
19551      var value = self.ngModel.$viewValue || self.ngModel.$modelValue;
19552      Object.keys(self.selected).forEach(self.deselect);
19553      self.select(self.hashGetter(value), value);
19554    }
19555  }
19556
19557}
19558
19559function OptionDirective($mdButtonInkRipple, $mdUtil) {
19560
19561  OptionController.$inject = ["$element"];
19562  return {
19563    restrict: 'E',
19564    require: ['mdOption', '^^mdSelectMenu'],
19565    controller: OptionController,
19566    compile: compile
19567  };
19568
19569  function compile(element, attr) {
19570    // Manual transclusion to avoid the extra inner <span> that ng-transclude generates
19571    element.append(angular.element('<div class="md-text">').append(element.contents()));
19572
19573    element.attr('tabindex', attr.tabindex || '0');
19574
19575    if (!hasDefinedValue(attr)) {
19576      element.attr('md-option-empty', '');
19577    }
19578
19579    return postLink;
19580  }
19581
19582  function hasDefinedValue(attr) {
19583    var value = attr.value;
19584    var ngValue = attr.ngValue;
19585
19586    return value || ngValue;
19587  }
19588
19589  function postLink(scope, element, attr, ctrls) {
19590    var optionCtrl = ctrls[0];
19591    var selectCtrl = ctrls[1];
19592
19593    if (selectCtrl.isMultiple) {
19594      element.addClass('md-checkbox-enabled');
19595      element.prepend(CHECKBOX_SELECTION_INDICATOR.clone());
19596    }
19597
19598    if (angular.isDefined(attr.ngValue)) {
19599      scope.$watch(attr.ngValue, setOptionValue);
19600    } else if (angular.isDefined(attr.value)) {
19601      setOptionValue(attr.value);
19602    } else {
19603      scope.$watch(function() {
19604        return element.text().trim();
19605      }, setOptionValue);
19606    }
19607
19608    attr.$observe('disabled', function(disabled) {
19609      if (disabled) {
19610        element.attr('tabindex', '-1');
19611      } else {
19612        element.attr('tabindex', '0');
19613      }
19614    });
19615
19616    scope.$$postDigest(function() {
19617      attr.$observe('selected', function(selected) {
19618        if (!angular.isDefined(selected)) return;
19619        if (typeof selected == 'string') selected = true;
19620        if (selected) {
19621          if (!selectCtrl.isMultiple) {
19622            selectCtrl.deselect(Object.keys(selectCtrl.selected)[0]);
19623          }
19624          selectCtrl.select(optionCtrl.hashKey, optionCtrl.value);
19625        } else {
19626          selectCtrl.deselect(optionCtrl.hashKey);
19627        }
19628        selectCtrl.refreshViewValue();
19629      });
19630    });
19631
19632    $mdButtonInkRipple.attach(scope, element);
19633    configureAria();
19634
19635    function setOptionValue(newValue, oldValue, prevAttempt) {
19636      if (!selectCtrl.hashGetter) {
19637        if (!prevAttempt) {
19638          scope.$$postDigest(function() {
19639            setOptionValue(newValue, oldValue, true);
19640          });
19641        }
19642        return;
19643      }
19644      var oldHashKey = selectCtrl.hashGetter(oldValue, scope);
19645      var newHashKey = selectCtrl.hashGetter(newValue, scope);
19646
19647      optionCtrl.hashKey = newHashKey;
19648      optionCtrl.value = newValue;
19649
19650      selectCtrl.removeOption(oldHashKey, optionCtrl);
19651      selectCtrl.addOption(newHashKey, optionCtrl);
19652    }
19653
19654    scope.$on('$destroy', function() {
19655      selectCtrl.removeOption(optionCtrl.hashKey, optionCtrl);
19656    });
19657
19658    function configureAria() {
19659      var ariaAttrs = {
19660        'role': 'option',
19661        'aria-selected': 'false'
19662      };
19663
19664      if (!element[0].hasAttribute('id')) {
19665        ariaAttrs.id = 'select_option_' + $mdUtil.nextUid();
19666      }
19667      element.attr(ariaAttrs);
19668    }
19669  }
19670
19671  function OptionController($element) {
19672    this.selected = false;
19673    this.setSelected = function(isSelected) {
19674      if (isSelected && !this.selected) {
19675        $element.attr({
19676          'selected': 'selected',
19677          'aria-selected': 'true'
19678        });
19679      } else if (!isSelected && this.selected) {
19680        $element.removeAttr('selected');
19681        $element.attr('aria-selected', 'false');
19682      }
19683      this.selected = isSelected;
19684    };
19685  }
19686
19687}
19688
19689function OptgroupDirective() {
19690  return {
19691    restrict: 'E',
19692    compile: compile
19693  };
19694  function compile(el, attrs) {
19695    // If we have a select header element, we don't want to add the normal label
19696    // header.
19697    if (!hasSelectHeader()) {
19698      setupLabelElement();
19699    }
19700
19701    function hasSelectHeader() {
19702      return el.parent().find('md-select-header').length;
19703    }
19704
19705    function setupLabelElement() {
19706      var labelElement = el.find('label');
19707      if (!labelElement.length) {
19708        labelElement = angular.element('<label>');
19709        el.prepend(labelElement);
19710      }
19711      labelElement.addClass('md-container-ignore');
19712      if (attrs.label) labelElement.text(attrs.label);
19713    }
19714  }
19715}
19716
19717function SelectHeaderDirective() {
19718  return {
19719    restrict: 'E',
19720  };
19721}
19722
19723function SelectProvider($$interimElementProvider) {
19724  selectDefaultOptions.$inject = ["$mdSelect", "$mdConstant", "$mdUtil", "$window", "$q", "$$rAF", "$animateCss", "$animate", "$document"];
19725  return $$interimElementProvider('$mdSelect')
19726    .setDefaults({
19727      methods: ['target'],
19728      options: selectDefaultOptions
19729    });
19730
19731  /* @ngInject */
19732  function selectDefaultOptions($mdSelect, $mdConstant, $mdUtil, $window, $q, $$rAF, $animateCss, $animate, $document) {
19733    var ERROR_TARGET_EXPECTED = "$mdSelect.show() expected a target element in options.target but got '{0}'!";
19734    var animator = $mdUtil.dom.animator;
19735    var keyCodes = $mdConstant.KEY_CODE;
19736
19737    return {
19738      parent: 'body',
19739      themable: true,
19740      onShow: onShow,
19741      onRemove: onRemove,
19742      hasBackdrop: true,
19743      disableParentScroll: true
19744    };
19745
19746    /**
19747     * Interim-element onRemove logic....
19748     */
19749    function onRemove(scope, element, opts) {
19750      opts = opts || { };
19751      opts.cleanupInteraction();
19752      opts.cleanupResizing();
19753      opts.hideBackdrop();
19754
19755      // For navigation $destroy events, do a quick, non-animated removal,
19756      // but for normal closes (from clicks, etc) animate the removal
19757
19758      return  (opts.$destroy === true) ? cleanElement() : animateRemoval().then( cleanElement );
19759
19760      /**
19761       * For normal closes (eg clicks), animate the removal.
19762       * For forced closes (like $destroy events from navigation),
19763       * skip the animations
19764       */
19765      function animateRemoval() {
19766        return $animateCss(element, {addClass: 'md-leave'}).start();
19767      }
19768
19769      /**
19770       * Restore the element to a closed state
19771       */
19772      function cleanElement() {
19773
19774        element.removeClass('md-active');
19775        element.attr('aria-hidden', 'true');
19776        element[0].style.display = 'none';
19777
19778        announceClosed(opts);
19779
19780        if (!opts.$destroy && opts.restoreFocus) {
19781          opts.target.focus();
19782        }
19783      }
19784
19785    }
19786
19787    /**
19788     * Interim-element onShow logic....
19789     */
19790    function onShow(scope, element, opts) {
19791
19792      watchAsyncLoad();
19793      sanitizeAndConfigure(scope, opts);
19794
19795      opts.hideBackdrop = showBackdrop(scope, element, opts);
19796
19797      return showDropDown(scope, element, opts)
19798        .then(function(response) {
19799          element.attr('aria-hidden', 'false');
19800          opts.alreadyOpen = true;
19801          opts.cleanupInteraction = activateInteraction();
19802          opts.cleanupResizing = activateResizing();
19803
19804          return response;
19805        }, opts.hideBackdrop);
19806
19807      // ************************************
19808      // Closure Functions
19809      // ************************************
19810
19811      /**
19812       *  Attach the select DOM element(s) and animate to the correct positions
19813       *  and scalings...
19814       */
19815      function showDropDown(scope, element, opts) {
19816        opts.parent.append(element);
19817
19818        return $q(function(resolve, reject) {
19819
19820          try {
19821
19822            $animateCss(element, {removeClass: 'md-leave', duration: 0})
19823              .start()
19824              .then(positionAndFocusMenu)
19825              .then(resolve);
19826
19827          } catch (e) {
19828            reject(e);
19829          }
19830
19831        });
19832      }
19833
19834      /**
19835       * Initialize container and dropDown menu positions/scale, then animate
19836       * to show... and autoFocus.
19837       */
19838      function positionAndFocusMenu() {
19839        return $q(function(resolve) {
19840          if (opts.isRemoved) return $q.reject(false);
19841
19842          var info = calculateMenuPositions(scope, element, opts);
19843
19844          info.container.element.css(animator.toCss(info.container.styles));
19845          info.dropDown.element.css(animator.toCss(info.dropDown.styles));
19846
19847          $$rAF(function() {
19848            element.addClass('md-active');
19849            info.dropDown.element.css(animator.toCss({transform: ''}));
19850
19851            autoFocus(opts.focusedNode);
19852            resolve();
19853          });
19854
19855        });
19856      }
19857
19858      /**
19859       * Show modal backdrop element...
19860       */
19861      function showBackdrop(scope, element, options) {
19862
19863        // If we are not within a dialog...
19864        if (options.disableParentScroll && !$mdUtil.getClosest(options.target, 'MD-DIALOG')) {
19865          // !! DO this before creating the backdrop; since disableScrollAround()
19866          //    configures the scroll offset; which is used by mdBackDrop postLink()
19867          options.restoreScroll = $mdUtil.disableScrollAround(options.element, options.parent);
19868        } else {
19869          options.disableParentScroll = false;
19870        }
19871
19872        if (options.hasBackdrop) {
19873          // Override duration to immediately show invisible backdrop
19874          options.backdrop = $mdUtil.createBackdrop(scope, "md-select-backdrop md-click-catcher");
19875          $animate.enter(options.backdrop, $document[0].body, null, {duration: 0});
19876        }
19877
19878        /**
19879         * Hide modal backdrop element...
19880         */
19881        return function hideBackdrop() {
19882          if (options.backdrop) options.backdrop.remove();
19883          if (options.disableParentScroll) options.restoreScroll();
19884
19885          delete options.restoreScroll;
19886        };
19887      }
19888
19889      /**
19890       *
19891       */
19892      function autoFocus(focusedNode) {
19893        if (focusedNode && !focusedNode.hasAttribute('disabled')) {
19894          focusedNode.focus();
19895        }
19896      }
19897
19898      /**
19899       * Check for valid opts and set some sane defaults
19900       */
19901      function sanitizeAndConfigure(scope, options) {
19902        var selectEl = element.find('md-select-menu');
19903
19904        if (!options.target) {
19905          throw new Error($mdUtil.supplant(ERROR_TARGET_EXPECTED, [options.target]));
19906        }
19907
19908        angular.extend(options, {
19909          isRemoved: false,
19910          target: angular.element(options.target), //make sure it's not a naked dom node
19911          parent: angular.element(options.parent),
19912          selectEl: selectEl,
19913          contentEl: element.find('md-content'),
19914          optionNodes: selectEl[0].getElementsByTagName('md-option')
19915        });
19916      }
19917
19918      /**
19919       * Configure various resize listeners for screen changes
19920       */
19921      function activateResizing() {
19922        var debouncedOnResize = (function(scope, target, options) {
19923
19924          return function() {
19925            if (options.isRemoved) return;
19926
19927            var updates = calculateMenuPositions(scope, target, options);
19928            var container = updates.container;
19929            var dropDown = updates.dropDown;
19930
19931            container.element.css(animator.toCss(container.styles));
19932            dropDown.element.css(animator.toCss(dropDown.styles));
19933          };
19934
19935        })(scope, element, opts);
19936
19937        var window = angular.element($window);
19938        window.on('resize', debouncedOnResize);
19939        window.on('orientationchange', debouncedOnResize);
19940
19941        // Publish deactivation closure...
19942        return function deactivateResizing() {
19943
19944          // Disable resizing handlers
19945          window.off('resize', debouncedOnResize);
19946          window.off('orientationchange', debouncedOnResize);
19947        };
19948      }
19949
19950      /**
19951       *  If asynchronously loading, watch and update internal
19952       *  '$$loadingAsyncDone' flag
19953       */
19954      function watchAsyncLoad() {
19955        if (opts.loadingAsync && !opts.isRemoved) {
19956          scope.$$loadingAsyncDone = false;
19957
19958          $q.when(opts.loadingAsync)
19959            .then(function() {
19960              scope.$$loadingAsyncDone = true;
19961              delete opts.loadingAsync;
19962            }).then(function() {
19963              $$rAF(positionAndFocusMenu);
19964            });
19965        }
19966      }
19967
19968      /**
19969       *
19970       */
19971      function activateInteraction() {
19972        if (opts.isRemoved) return;
19973
19974        var dropDown = opts.selectEl;
19975        var selectCtrl = dropDown.controller('mdSelectMenu') || {};
19976
19977        element.addClass('md-clickable');
19978
19979        // Close on backdrop click
19980        opts.backdrop && opts.backdrop.on('click', onBackdropClick);
19981
19982        // Escape to close
19983        // Cycling of options, and closing on enter
19984        dropDown.on('keydown', onMenuKeyDown);
19985        dropDown.on('click', checkCloseMenu);
19986
19987        return function cleanupInteraction() {
19988          opts.backdrop && opts.backdrop.off('click', onBackdropClick);
19989          dropDown.off('keydown', onMenuKeyDown);
19990          dropDown.off('click', checkCloseMenu);
19991
19992          element.removeClass('md-clickable');
19993          opts.isRemoved = true;
19994        };
19995
19996        // ************************************
19997        // Closure Functions
19998        // ************************************
19999
20000        function onBackdropClick(e) {
20001          e.preventDefault();
20002          e.stopPropagation();
20003          opts.restoreFocus = false;
20004          $mdUtil.nextTick($mdSelect.hide, true);
20005        }
20006
20007        function onMenuKeyDown(ev) {
20008          ev.preventDefault();
20009          ev.stopPropagation();
20010
20011          switch (ev.keyCode) {
20012            case keyCodes.UP_ARROW:
20013              return focusPrevOption();
20014            case keyCodes.DOWN_ARROW:
20015              return focusNextOption();
20016            case keyCodes.SPACE:
20017            case keyCodes.ENTER:
20018              var option = $mdUtil.getClosest(ev.target, 'md-option');
20019              if (option) {
20020                dropDown.triggerHandler({
20021                  type: 'click',
20022                  target: option
20023                });
20024                ev.preventDefault();
20025              }
20026              checkCloseMenu(ev);
20027              break;
20028            case keyCodes.TAB:
20029            case keyCodes.ESCAPE:
20030              ev.stopPropagation();
20031              ev.preventDefault();
20032              opts.restoreFocus = true;
20033              $mdUtil.nextTick($mdSelect.hide, true);
20034              break;
20035            default:
20036              if ($mdConstant.isInputKey(ev) || $mdConstant.isNumPadKey(ev)) {
20037                var optNode = dropDown.controller('mdSelectMenu').optNodeForKeyboardSearch(ev);
20038                opts.focusedNode = optNode || opts.focusedNode;
20039                optNode && optNode.focus();
20040              }
20041          }
20042        }
20043
20044        function focusOption(direction) {
20045          var optionsArray = $mdUtil.nodesToArray(opts.optionNodes);
20046          var index = optionsArray.indexOf(opts.focusedNode);
20047
20048          var newOption;
20049
20050          do {
20051            if (index === -1) {
20052              // We lost the previously focused element, reset to first option
20053              index = 0;
20054            } else if (direction === 'next' && index < optionsArray.length - 1) {
20055              index++;
20056            } else if (direction === 'prev' && index > 0) {
20057              index--;
20058            }
20059            newOption = optionsArray[index];
20060            if (newOption.hasAttribute('disabled')) newOption = undefined;
20061          } while (!newOption && index < optionsArray.length - 1 && index > 0);
20062
20063          newOption && newOption.focus();
20064          opts.focusedNode = newOption;
20065        }
20066
20067        function focusNextOption() {
20068          focusOption('next');
20069        }
20070
20071        function focusPrevOption() {
20072          focusOption('prev');
20073        }
20074
20075        function checkCloseMenu(ev) {
20076          if (ev && ( ev.type == 'click') && (ev.currentTarget != dropDown[0])) return;
20077          if ( mouseOnScrollbar() ) return;
20078
20079          var option = $mdUtil.getClosest(ev.target, 'md-option');
20080          if (option && option.hasAttribute && !option.hasAttribute('disabled')) {
20081            ev.preventDefault();
20082            ev.stopPropagation();
20083            if (!selectCtrl.isMultiple) {
20084              opts.restoreFocus = true;
20085
20086              $mdUtil.nextTick(function () {
20087                $mdSelect.hide(selectCtrl.ngModel.$viewValue);
20088              }, true);
20089            }
20090          }
20091          /**
20092           * check if the mouseup event was on a scrollbar
20093           */
20094          function mouseOnScrollbar() {
20095            var clickOnScrollbar = false;
20096            if (ev && (ev.currentTarget.children.length > 0)) {
20097              var child = ev.currentTarget.children[0];
20098              var hasScrollbar = child.scrollHeight > child.clientHeight;
20099              if (hasScrollbar && child.children.length > 0) {
20100                var relPosX = ev.pageX - ev.currentTarget.getBoundingClientRect().left;
20101                if (relPosX > child.querySelector('md-option').offsetWidth)
20102                  clickOnScrollbar = true;
20103              }
20104            }
20105            return clickOnScrollbar;
20106          }
20107        }
20108      }
20109
20110    }
20111
20112    /**
20113     * To notify listeners that the Select menu has closed,
20114     * trigger the [optional] user-defined expression
20115     */
20116    function announceClosed(opts) {
20117      var mdSelect = opts.selectCtrl;
20118      if (mdSelect) {
20119        var menuController = opts.selectEl.controller('mdSelectMenu');
20120        mdSelect.setLabelText(menuController ? menuController.selectedLabels() : '');
20121        mdSelect.triggerClose();
20122      }
20123    }
20124
20125
20126    /**
20127     * Calculate the
20128     */
20129    function calculateMenuPositions(scope, element, opts) {
20130      var
20131        containerNode = element[0],
20132        targetNode = opts.target[0].children[0], // target the label
20133        parentNode = $document[0].body,
20134        selectNode = opts.selectEl[0],
20135        contentNode = opts.contentEl[0],
20136        parentRect = parentNode.getBoundingClientRect(),
20137        targetRect = targetNode.getBoundingClientRect(),
20138        shouldOpenAroundTarget = false,
20139        bounds = {
20140          left: parentRect.left + SELECT_EDGE_MARGIN,
20141          top: SELECT_EDGE_MARGIN,
20142          bottom: parentRect.height - SELECT_EDGE_MARGIN,
20143          right: parentRect.width - SELECT_EDGE_MARGIN - ($mdUtil.floatingScrollbars() ? 16 : 0)
20144        },
20145        spaceAvailable = {
20146          top: targetRect.top - bounds.top,
20147          left: targetRect.left - bounds.left,
20148          right: bounds.right - (targetRect.left + targetRect.width),
20149          bottom: bounds.bottom - (targetRect.top + targetRect.height)
20150        },
20151        maxWidth = parentRect.width - SELECT_EDGE_MARGIN * 2,
20152        selectedNode = selectNode.querySelector('md-option[selected]'),
20153        optionNodes = selectNode.getElementsByTagName('md-option'),
20154        optgroupNodes = selectNode.getElementsByTagName('md-optgroup'),
20155        isScrollable = calculateScrollable(element, contentNode),
20156        centeredNode;
20157
20158      var loading = isPromiseLike(opts.loadingAsync);
20159      if (!loading) {
20160        // If a selected node, center around that
20161        if (selectedNode) {
20162          centeredNode = selectedNode;
20163          // If there are option groups, center around the first option group
20164        } else if (optgroupNodes.length) {
20165          centeredNode = optgroupNodes[0];
20166          // Otherwise - if we are not loading async - center around the first optionNode
20167        } else if (optionNodes.length) {
20168          centeredNode = optionNodes[0];
20169          // In case there are no options, center on whatever's in there... (eg progress indicator)
20170        } else {
20171          centeredNode = contentNode.firstElementChild || contentNode;
20172        }
20173      } else {
20174        // If loading, center on progress indicator
20175        centeredNode = contentNode.firstElementChild || contentNode;
20176      }
20177
20178      if (contentNode.offsetWidth > maxWidth) {
20179        contentNode.style['max-width'] = maxWidth + 'px';
20180      } else {
20181        contentNode.style.maxWidth = null;
20182      }
20183      if (shouldOpenAroundTarget) {
20184        contentNode.style['min-width'] = targetRect.width + 'px';
20185      }
20186
20187      // Remove padding before we compute the position of the menu
20188      if (isScrollable) {
20189        selectNode.classList.add('md-overflow');
20190      }
20191
20192      var focusedNode = centeredNode;
20193      if ((focusedNode.tagName || '').toUpperCase() === 'MD-OPTGROUP') {
20194        focusedNode = optionNodes[0] || contentNode.firstElementChild || contentNode;
20195        centeredNode = focusedNode;
20196      }
20197      // Cache for autoFocus()
20198      opts.focusedNode = focusedNode;
20199
20200      // Get the selectMenuRect *after* max-width is possibly set above
20201      containerNode.style.display = 'block';
20202      var selectMenuRect = selectNode.getBoundingClientRect();
20203      var centeredRect = getOffsetRect(centeredNode);
20204
20205      if (centeredNode) {
20206        var centeredStyle = $window.getComputedStyle(centeredNode);
20207        centeredRect.paddingLeft = parseInt(centeredStyle.paddingLeft, 10) || 0;
20208        centeredRect.paddingRight = parseInt(centeredStyle.paddingRight, 10) || 0;
20209      }
20210
20211      if (isScrollable) {
20212        var scrollBuffer = contentNode.offsetHeight / 2;
20213        contentNode.scrollTop = centeredRect.top + centeredRect.height / 2 - scrollBuffer;
20214
20215        if (spaceAvailable.top < scrollBuffer) {
20216          contentNode.scrollTop = Math.min(
20217            centeredRect.top,
20218            contentNode.scrollTop + scrollBuffer - spaceAvailable.top
20219          );
20220        } else if (spaceAvailable.bottom < scrollBuffer) {
20221          contentNode.scrollTop = Math.max(
20222            centeredRect.top + centeredRect.height - selectMenuRect.height,
20223            contentNode.scrollTop - scrollBuffer + spaceAvailable.bottom
20224          );
20225        }
20226      }
20227
20228      var left, top, transformOrigin, minWidth, fontSize;
20229      if (shouldOpenAroundTarget) {
20230        left = targetRect.left;
20231        top = targetRect.top + targetRect.height;
20232        transformOrigin = '50% 0';
20233        if (top + selectMenuRect.height > bounds.bottom) {
20234          top = targetRect.top - selectMenuRect.height;
20235          transformOrigin = '50% 100%';
20236        }
20237      } else {
20238        left = (targetRect.left + centeredRect.left - centeredRect.paddingLeft) + 2;
20239        top = Math.floor(targetRect.top + targetRect.height / 2 - centeredRect.height / 2 -
20240            centeredRect.top + contentNode.scrollTop) + 2;
20241
20242        transformOrigin = (centeredRect.left + targetRect.width / 2) + 'px ' +
20243          (centeredRect.top + centeredRect.height / 2 - contentNode.scrollTop) + 'px 0px';
20244
20245        minWidth = Math.min(targetRect.width + centeredRect.paddingLeft + centeredRect.paddingRight, maxWidth);
20246
20247        fontSize = window.getComputedStyle(targetNode)['font-size'];
20248      }
20249
20250      // Keep left and top within the window
20251      var containerRect = containerNode.getBoundingClientRect();
20252      var scaleX = Math.round(100 * Math.min(targetRect.width / selectMenuRect.width, 1.0)) / 100;
20253      var scaleY = Math.round(100 * Math.min(targetRect.height / selectMenuRect.height, 1.0)) / 100;
20254
20255      return {
20256        container: {
20257          element: angular.element(containerNode),
20258          styles: {
20259            left: Math.floor(clamp(bounds.left, left, bounds.right - containerRect.width)),
20260            top: Math.floor(clamp(bounds.top, top, bounds.bottom - containerRect.height)),
20261            'min-width': minWidth,
20262            'font-size': fontSize
20263          }
20264        },
20265        dropDown: {
20266          element: angular.element(selectNode),
20267          styles: {
20268            transformOrigin: transformOrigin,
20269            transform: !opts.alreadyOpen ? $mdUtil.supplant('scale({0},{1})', [scaleX, scaleY]) : ""
20270          }
20271        }
20272      };
20273
20274    }
20275
20276  }
20277
20278  function isPromiseLike(obj) {
20279    return obj && angular.isFunction(obj.then);
20280  }
20281
20282  function clamp(min, n, max) {
20283    return Math.max(min, Math.min(n, max));
20284  }
20285
20286  function getOffsetRect(node) {
20287    return node ? {
20288      left: node.offsetLeft,
20289      top: node.offsetTop,
20290      width: node.offsetWidth,
20291      height: node.offsetHeight
20292    } : {left: 0, top: 0, width: 0, height: 0};
20293  }
20294
20295  function calculateScrollable(element, contentNode) {
20296    var isScrollable = false;
20297
20298    try {
20299      var oldDisplay = element[0].style.display;
20300
20301      // Set the element's display to block so that this calculation is correct
20302      element[0].style.display = 'block';
20303
20304      isScrollable = contentNode.scrollHeight > contentNode.offsetHeight;
20305
20306      // Reset it back afterwards
20307      element[0].style.display = oldDisplay;
20308    } finally {
20309      // Nothing to do
20310    }
20311    return isScrollable;
20312  }
20313}
20314
20315})();
20316(function(){
20317"use strict";
20318
20319/**
20320 * @ngdoc module
20321 * @name material.components.showHide
20322 */
20323
20324// Add additional handlers to ng-show and ng-hide that notify directives
20325// contained within that they should recompute their size.
20326// These run in addition to Angular's built-in ng-hide and ng-show directives.
20327angular.module('material.components.showHide', [
20328  'material.core'
20329])
20330  .directive('ngShow', createDirective('ngShow', true))
20331  .directive('ngHide', createDirective('ngHide', false));
20332
20333
20334function createDirective(name, targetValue) {
20335  return ['$mdUtil', '$window', function($mdUtil, $window) {
20336    return {
20337      restrict: 'A',
20338      multiElement: true,
20339      link: function($scope, $element, $attr) {
20340        var unregister = $scope.$on('$md-resize-enable', function() {
20341          unregister();
20342
20343          var node = $element[0];
20344          var cachedTransitionStyles = node.nodeType === $window.Node.ELEMENT_NODE ?
20345            $window.getComputedStyle(node) : {};
20346
20347          $scope.$watch($attr[name], function(value) {
20348            if (!!value === targetValue) {
20349              $mdUtil.nextTick(function() {
20350                $scope.$broadcast('$md-resize');
20351              });
20352
20353              var opts = {
20354                cachedTransitionStyles: cachedTransitionStyles
20355              };
20356
20357              $mdUtil.dom.animator.waitTransitionEnd($element, opts).then(function() {
20358                $scope.$broadcast('$md-resize');
20359              });
20360            }
20361          });
20362        });
20363      }
20364    };
20365  }];
20366}
20367
20368})();
20369(function(){
20370"use strict";
20371
20372/**
20373 * @ngdoc module
20374 * @name material.components.sidenav
20375 *
20376 * @description
20377 * A Sidenav QP component.
20378 */
20379SidenavService.$inject = ["$mdComponentRegistry", "$mdUtil", "$q", "$log"];
20380SidenavDirective.$inject = ["$mdMedia", "$mdUtil", "$mdConstant", "$mdTheming", "$mdInteraction", "$animate", "$compile", "$parse", "$log", "$q", "$document", "$window", "$$rAF"];
20381SidenavController.$inject = ["$scope", "$attrs", "$mdComponentRegistry", "$q", "$interpolate"];
20382angular
20383  .module('material.components.sidenav', [
20384    'material.core',
20385    'material.components.backdrop'
20386  ])
20387  .factory('$mdSidenav', SidenavService )
20388  .directive('mdSidenav', SidenavDirective)
20389  .directive('mdSidenavFocus', SidenavFocusDirective)
20390  .controller('$mdSidenavController', SidenavController);
20391
20392
20393/**
20394 * @ngdoc service
20395 * @name $mdSidenav
20396 * @module material.components.sidenav
20397 *
20398 * @description
20399 * `$mdSidenav` makes it easy to interact with multiple sidenavs
20400 * in an app. When looking up a sidenav instance, you can either look
20401 * it up synchronously or wait for it to be initializied asynchronously.
20402 * This is done by passing the second argument to `$mdSidenav`.
20403 *
20404 * @usage
20405 * <hljs lang="js">
20406 * // Async lookup for sidenav instance; will resolve when the instance is available
20407 * $mdSidenav(componentId, true).then(function(instance) {
20408 *   $log.debug( componentId + "is now ready" );
20409 * });
20410 * // Sync lookup for sidenav instance; this will resolve immediately.
20411 * $mdSidenav(componentId).then(function(instance) {
20412 *   $log.debug( componentId + "is now ready" );
20413 * });
20414 * // Async toggle the given sidenav;
20415 * // when instance is known ready and lazy lookup is not needed.
20416 * $mdSidenav(componentId)
20417 *    .toggle()
20418 *    .then(function(){
20419 *      $log.debug('toggled');
20420 *    });
20421 * // Async open the given sidenav
20422 * $mdSidenav(componentId)
20423 *    .open()
20424 *    .then(function(){
20425 *      $log.debug('opened');
20426 *    });
20427 * // Async close the given sidenav
20428 * $mdSidenav(componentId)
20429 *    .close()
20430 *    .then(function(){
20431 *      $log.debug('closed');
20432 *    });
20433 * // Sync check to see if the specified sidenav is set to be open
20434 * $mdSidenav(componentId).isOpen();
20435 * // Sync check to whether given sidenav is locked open
20436 * // If this is true, the sidenav will be open regardless of close()
20437 * $mdSidenav(componentId).isLockedOpen();
20438 * // On close callback to handle close, backdrop click or escape key pressed
20439 * // Callback happens BEFORE the close action occurs.
20440 * $mdSidenav(componentId).onClose(function () {
20441 *   $log.debug('closing');
20442 * });
20443 * </hljs>
20444 */
20445function SidenavService($mdComponentRegistry, $mdUtil, $q, $log) {
20446  var errorMsg = "SideNav '{0}' is not available! Did you use md-component-id='{0}'?";
20447  var service = {
20448        find    : findInstance,     //  sync  - returns proxy API
20449        waitFor : waitForInstance   //  async - returns promise
20450      };
20451
20452  /**
20453   * Service API that supports three (3) usages:
20454   *   $mdSidenav().find("left")                       // sync (must already exist) or returns undefined
20455   *   $mdSidenav("left").toggle();                    // sync (must already exist) or returns reject promise;
20456   *   $mdSidenav("left",true).then( function(left){   // async returns instance when available
20457   *    left.toggle();
20458   *   });
20459   */
20460  return function(handle, enableWait) {
20461    if ( angular.isUndefined(handle) ) return service;
20462
20463    var shouldWait = enableWait === true;
20464    var instance = service.find(handle, shouldWait);
20465    return  !instance && shouldWait ? service.waitFor(handle) :
20466            !instance && angular.isUndefined(enableWait) ? addLegacyAPI(service, handle) : instance;
20467  };
20468
20469  /**
20470   * For failed instance/handle lookups, older-clients expect an response object with noops
20471   * that include `rejected promise APIs`
20472   */
20473  function addLegacyAPI(service, handle) {
20474      var falseFn  = function() { return false; };
20475      var rejectFn = function() {
20476            return $q.when($mdUtil.supplant(errorMsg, [handle || ""]));
20477          };
20478
20479      return angular.extend({
20480        isLockedOpen : falseFn,
20481        isOpen       : falseFn,
20482        toggle       : rejectFn,
20483        open         : rejectFn,
20484        close        : rejectFn,
20485        onClose      : angular.noop,
20486        then : function(callback) {
20487          return waitForInstance(handle)
20488            .then(callback || angular.noop);
20489        }
20490       }, service);
20491    }
20492    /**
20493     * Synchronously lookup the controller instance for the specified sidNav instance which has been
20494     * registered with the markup `md-component-id`
20495     */
20496    function findInstance(handle, shouldWait) {
20497      var instance = $mdComponentRegistry.get(handle);
20498
20499      if (!instance && !shouldWait) {
20500
20501        // Report missing instance
20502        $log.error( $mdUtil.supplant(errorMsg, [handle || ""]) );
20503
20504        // The component has not registered itself... most like NOT yet created
20505        // return null to indicate that the Sidenav is not in the DOM
20506        return undefined;
20507      }
20508      return instance;
20509    }
20510
20511    /**
20512     * Asynchronously wait for the component instantiation,
20513     * Deferred lookup of component instance using $component registry
20514     */
20515    function waitForInstance(handle) {
20516      return $mdComponentRegistry.when(handle).catch($log.error);
20517    }
20518}
20519/**
20520 * @ngdoc directive
20521 * @name mdSidenavFocus
20522 * @module material.components.sidenav
20523 *
20524 * @restrict A
20525 *
20526 * @description
20527 * `mdSidenavFocus` provides a way to specify the focused element when a sidenav opens.
20528 * This is completely optional, as the sidenav itself is focused by default.
20529 *
20530 * @usage
20531 * <hljs lang="html">
20532 * <md-sidenav>
20533 *   <form>
20534 *     <md-input-container>
20535 *       <label for="testInput">Label</label>
20536 *       <input id="testInput" type="text" md-sidenav-focus>
20537 *     </md-input-container>
20538 *   </form>
20539 * </md-sidenav>
20540 * </hljs>
20541 **/
20542function SidenavFocusDirective() {
20543  return {
20544    restrict: 'A',
20545    require: '^mdSidenav',
20546    link: function(scope, element, attr, sidenavCtrl) {
20547      // @see $mdUtil.findFocusTarget(...)
20548    }
20549  };
20550}
20551/**
20552 * @ngdoc directive
20553 * @name mdSidenav
20554 * @module material.components.sidenav
20555 * @restrict E
20556 *
20557 * @description
20558 *
20559 * A Sidenav component that can be opened and closed programatically.
20560 *
20561 * By default, upon opening it will slide out on top of the main content area.
20562 *
20563 * For keyboard and screen reader accessibility, focus is sent to the sidenav wrapper by default.
20564 * It can be overridden with the `md-autofocus` directive on the child element you want focused.
20565 *
20566 * @usage
20567 * <hljs lang="html">
20568 * <div layout="row" ng-controller="MyController">
20569 *   <md-sidenav md-component-id="left" class="md-sidenav-left">
20570 *     Left Nav!
20571 *   </md-sidenav>
20572 *
20573 *   <md-content>
20574 *     Center Content
20575 *     <md-button ng-click="openLeftMenu()">
20576 *       Open Left Menu
20577 *     </md-button>
20578 *   </md-content>
20579 *
20580 *   <md-sidenav md-component-id="right"
20581 *     md-is-locked-open="$mdMedia('min-width: 333px')"
20582 *     class="md-sidenav-right">
20583 *     <form>
20584 *       <md-input-container>
20585 *         <label for="testInput">Test input</label>
20586 *         <input id="testInput" type="text"
20587 *                ng-model="data" md-autofocus>
20588 *       </md-input-container>
20589 *     </form>
20590 *   </md-sidenav>
20591 * </div>
20592 * </hljs>
20593 *
20594 * <hljs lang="js">
20595 * var app = angular.module('myApp', ['ngMaterial']);
20596 * app.controller('MyController', function($scope, $mdSidenav) {
20597 *   $scope.openLeftMenu = function() {
20598 *     $mdSidenav('left').toggle();
20599 *   };
20600 * });
20601 * </hljs>
20602 *
20603 * @param {expression=} md-is-open A model bound to whether the sidenav is opened.
20604 * @param {boolean=} md-disable-backdrop When present in the markup, the sidenav will not show a backdrop.
20605 * @param {string=} md-component-id componentId to use with $mdSidenav service.
20606 * @param {expression=} md-is-locked-open When this expression evaluates to true,
20607 * the sidenav 'locks open': it falls into the content's flow instead
20608 * of appearing over it. This overrides the `md-is-open` attribute.
20609 * @param {string=} md-disable-scroll-target Selector, pointing to an element, whose scrolling will
20610 * be disabled when the sidenav is opened. By default this is the sidenav's direct parent.
20611 *
20612* The $mdMedia() service is exposed to the is-locked-open attribute, which
20613 * can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
20614 * Examples:
20615 *
20616 *   - `<md-sidenav md-is-locked-open="shouldLockOpen"></md-sidenav>`
20617 *   - `<md-sidenav md-is-locked-open="$mdMedia('min-width: 1000px')"></md-sidenav>`
20618 *   - `<md-sidenav md-is-locked-open="$mdMedia('sm')"></md-sidenav>` (locks open on small screens)
20619 */
20620function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $mdInteraction, $animate,
20621                          $compile, $parse, $log, $q, $document, $window, $$rAF) {
20622  return {
20623    restrict: 'E',
20624    scope: {
20625      isOpen: '=?mdIsOpen'
20626    },
20627    controller: '$mdSidenavController',
20628    compile: function(element) {
20629      element.addClass('md-closed').attr('tabIndex', '-1');
20630      return postLink;
20631    }
20632  };
20633
20634  /**
20635   * Directive Post Link function...
20636   */
20637  function postLink(scope, element, attr, sidenavCtrl) {
20638    var lastParentOverFlow;
20639    var backdrop;
20640    var disableScrollTarget = null;
20641    var triggeringInteractionType;
20642    var triggeringElement = null;
20643    var previousContainerStyles;
20644    var promise = $q.when(true);
20645    var isLockedOpenParsed = $parse(attr.mdIsLockedOpen);
20646    var ngWindow = angular.element($window);
20647    var isLocked = function() {
20648      return isLockedOpenParsed(scope.$parent, {
20649        $media: function(arg) {
20650          $log.warn("$media is deprecated for is-locked-open. Use $mdMedia instead.");
20651          return $mdMedia(arg);
20652        },
20653        $mdMedia: $mdMedia
20654      });
20655    };
20656
20657    if (attr.mdDisableScrollTarget) {
20658      disableScrollTarget = $document[0].querySelector(attr.mdDisableScrollTarget);
20659
20660      if (disableScrollTarget) {
20661        disableScrollTarget = angular.element(disableScrollTarget);
20662      } else {
20663        $log.warn($mdUtil.supplant('mdSidenav: couldn\'t find element matching ' +
20664          'selector "{selector}". Falling back to parent.', { selector: attr.mdDisableScrollTarget }));
20665      }
20666    }
20667
20668    if (!disableScrollTarget) {
20669      disableScrollTarget = element.parent();
20670    }
20671
20672    // Only create the backdrop if the backdrop isn't disabled.
20673    if (!attr.hasOwnProperty('mdDisableBackdrop')) {
20674      backdrop = $mdUtil.createBackdrop(scope, "md-sidenav-backdrop md-opaque ng-enter");
20675    }
20676
20677    element.addClass('_md');     // private md component indicator for styling
20678    $mdTheming(element);
20679
20680    // The backdrop should inherit the sidenavs theme,
20681    // because the backdrop will take its parent theme by default.
20682    if ( backdrop ) $mdTheming.inherit(backdrop, element);
20683
20684    element.on('$destroy', function() {
20685      backdrop && backdrop.remove();
20686      sidenavCtrl.destroy();
20687    });
20688
20689    scope.$on('$destroy', function(){
20690      backdrop && backdrop.remove();
20691    });
20692
20693    scope.$watch(isLocked, updateIsLocked);
20694    scope.$watch('isOpen', updateIsOpen);
20695
20696
20697    // Publish special accessor for the Controller instance
20698    sidenavCtrl.$toggleOpen = toggleOpen;
20699
20700    /**
20701     * Toggle the DOM classes to indicate `locked`
20702     * @param isLocked
20703     */
20704    function updateIsLocked(isLocked, oldValue) {
20705      scope.isLockedOpen = isLocked;
20706      if (isLocked === oldValue) {
20707        element.toggleClass('md-locked-open', !!isLocked);
20708      } else {
20709        $animate[isLocked ? 'addClass' : 'removeClass'](element, 'md-locked-open');
20710      }
20711      if (backdrop) {
20712        backdrop.toggleClass('md-locked-open', !!isLocked);
20713      }
20714    }
20715
20716    /**
20717     * Toggle the SideNav view and attach/detach listeners
20718     * @param isOpen
20719     */
20720    function updateIsOpen(isOpen) {
20721      // Support deprecated md-sidenav-focus attribute as fallback
20722      var focusEl = $mdUtil.findFocusTarget(element) || $mdUtil.findFocusTarget(element,'[md-sidenav-focus]') || element;
20723      var parent = element.parent();
20724
20725      parent[isOpen ? 'on' : 'off']('keydown', onKeyDown);
20726      if (backdrop) backdrop[isOpen ? 'on' : 'off']('click', close);
20727
20728      var restorePositioning = updateContainerPositions(parent, isOpen);
20729
20730      if ( isOpen ) {
20731        // Capture upon opening..
20732        triggeringElement = $document[0].activeElement;
20733        triggeringInteractionType = $mdInteraction.getLastInteractionType();
20734      }
20735
20736      disableParentScroll(isOpen);
20737
20738      return promise = $q.all([
20739        isOpen && backdrop ? $animate.enter(backdrop, parent) : backdrop ?
20740                             $animate.leave(backdrop) : $q.when(true),
20741        $animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed')
20742      ]).then(function() {
20743        // Perform focus when animations are ALL done...
20744        if (scope.isOpen) {
20745          $$rAF(function() {
20746            // Notifies child components that the sidenav was opened. Should wait
20747            // a frame in order to allow for the element height to be computed.
20748            ngWindow.triggerHandler('resize');
20749          });
20750
20751          focusEl && focusEl.focus();
20752        }
20753
20754        // Restores the positioning on the sidenav and backdrop.
20755        restorePositioning && restorePositioning();
20756      });
20757    }
20758
20759    function updateContainerPositions(parent, willOpen) {
20760      var drawerEl = element[0];
20761      var scrollTop = parent[0].scrollTop;
20762
20763      if (willOpen && scrollTop) {
20764        previousContainerStyles = {
20765          top: drawerEl.style.top,
20766          bottom: drawerEl.style.bottom,
20767          height: drawerEl.style.height
20768        };
20769
20770        // When the parent is scrolled down, then we want to be able to show the sidenav at the current scroll
20771        // position. We're moving the sidenav down to the correct scroll position and apply the height of the
20772        // parent, to increase the performance. Using 100% as height, will impact the performance heavily.
20773        var positionStyle = {
20774          top: scrollTop + 'px',
20775          bottom: 'auto',
20776          height: parent[0].clientHeight + 'px'
20777        };
20778
20779        // Apply the new position styles to the sidenav and backdrop.
20780        element.css(positionStyle);
20781        backdrop.css(positionStyle);
20782      }
20783
20784      // When the sidenav is closing and we have previous defined container styles,
20785      // then we return a restore function, which resets the sidenav and backdrop.
20786      if (!willOpen && previousContainerStyles) {
20787        return function() {
20788          drawerEl.style.top = previousContainerStyles.top;
20789          drawerEl.style.bottom = previousContainerStyles.bottom;
20790          drawerEl.style.height = previousContainerStyles.height;
20791
20792          backdrop[0].style.top = null;
20793          backdrop[0].style.bottom = null;
20794          backdrop[0].style.height = null;
20795
20796          previousContainerStyles = null;
20797        };
20798      }
20799    }
20800
20801    /**
20802     * Prevent parent scrolling (when the SideNav is open)
20803     */
20804    function disableParentScroll(disabled) {
20805      if ( disabled && !lastParentOverFlow ) {
20806        lastParentOverFlow = disableScrollTarget.css('overflow');
20807        disableScrollTarget.css('overflow', 'hidden');
20808      } else if (angular.isDefined(lastParentOverFlow)) {
20809        disableScrollTarget.css('overflow', lastParentOverFlow);
20810        lastParentOverFlow = undefined;
20811      }
20812    }
20813
20814    /**
20815     * Toggle the sideNav view and publish a promise to be resolved when
20816     * the view animation finishes.
20817     *
20818     * @param isOpen
20819     * @returns {*}
20820     */
20821    function toggleOpen( isOpen ) {
20822      if (scope.isOpen == isOpen ) {
20823
20824        return $q.when(true);
20825
20826      } else {
20827        if (scope.isOpen && sidenavCtrl.onCloseCb) sidenavCtrl.onCloseCb();
20828
20829        return $q(function(resolve){
20830          // Toggle value to force an async `updateIsOpen()` to run
20831          scope.isOpen = isOpen;
20832
20833          $mdUtil.nextTick(function() {
20834            // When the current `updateIsOpen()` animation finishes
20835            promise.then(function(result) {
20836
20837              if ( !scope.isOpen && triggeringElement && triggeringInteractionType === 'keyboard') {
20838                // reset focus to originating element (if available) upon close
20839                triggeringElement.focus();
20840                triggeringElement = null;
20841              }
20842
20843              resolve(result);
20844            });
20845          });
20846
20847        });
20848
20849      }
20850    }
20851
20852    /**
20853     * Auto-close sideNav when the `escape` key is pressed.
20854     * @param evt
20855     */
20856    function onKeyDown(ev) {
20857      var isEscape = (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE);
20858      return isEscape ? close(ev) : $q.when(true);
20859    }
20860
20861    /**
20862     * With backdrop `clicks` or `escape` key-press, immediately
20863     * apply the CSS close transition... Then notify the controller
20864     * to close() and perform its own actions.
20865     */
20866    function close(ev) {
20867      ev.preventDefault();
20868
20869      return sidenavCtrl.close();
20870    }
20871
20872  }
20873}
20874
20875/*
20876 * @private
20877 * @ngdoc controller
20878 * @name SidenavController
20879 * @module material.components.sidenav
20880 */
20881function SidenavController($scope, $attrs, $mdComponentRegistry, $q, $interpolate) {
20882
20883  var self = this;
20884
20885  // Use Default internal method until overridden by directive postLink
20886
20887  // Synchronous getters
20888  self.isOpen = function() { return !!$scope.isOpen; };
20889  self.isLockedOpen = function() { return !!$scope.isLockedOpen; };
20890
20891  // Synchronous setters
20892  self.onClose = function (callback) {
20893    self.onCloseCb = callback;
20894    return self;
20895  };
20896
20897  // Async actions
20898  self.open   = function() { return self.$toggleOpen( true );  };
20899  self.close  = function() { return self.$toggleOpen( false ); };
20900  self.toggle = function() { return self.$toggleOpen( !$scope.isOpen );  };
20901  self.$toggleOpen = function(value) { return $q.when($scope.isOpen = value); };
20902
20903  // Evaluate the component id.
20904  var rawId = $attrs.mdComponentId;
20905  var hasDataBinding = rawId && rawId.indexOf($interpolate.startSymbol()) > -1;
20906  var componentId = hasDataBinding ? $interpolate(rawId)($scope.$parent) : rawId;
20907
20908  // Register the component.
20909  self.destroy = $mdComponentRegistry.register(self, componentId);
20910
20911  // Watch and update the component, if the id has changed.
20912  if (hasDataBinding) {
20913    $attrs.$observe('mdComponentId', function(id) {
20914      if (id && id !== self.$$mdHandle) {
20915        self.destroy(); // `destroy` only deregisters the old component id so we can add the new one.
20916        self.destroy = $mdComponentRegistry.register(self, id);
20917      }
20918    });
20919  }
20920}
20921
20922})();
20923(function(){
20924"use strict";
20925
20926  /**
20927   * @ngdoc module
20928   * @name material.components.slider
20929   */
20930SliderDirective.$inject = ["$$rAF", "$window", "$mdAria", "$mdUtil", "$mdConstant", "$mdTheming", "$mdGesture", "$parse", "$log", "$timeout"];
20931  angular.module('material.components.slider', [
20932    'material.core'
20933  ])
20934  .directive('mdSlider', SliderDirective)
20935  .directive('mdSliderContainer', SliderContainerDirective);
20936
20937/**
20938 * @ngdoc directive
20939 * @name mdSliderContainer
20940 * @module material.components.slider
20941 * @restrict E
20942 * @description
20943 * The `<md-slider-container>` contains slider with two other elements.
20944 *
20945 *
20946 * @usage
20947 * <h4>Normal Mode</h4>
20948 * <hljs lang="html">
20949 * </hljs>
20950 */
20951function SliderContainerDirective() {
20952  return {
20953    controller: function () {},
20954    compile: function (elem) {
20955      var slider = elem.find('md-slider');
20956
20957      if (!slider) {
20958        return;
20959      }
20960
20961      var vertical = slider.attr('md-vertical');
20962
20963      if (vertical !== undefined) {
20964        elem.attr('md-vertical', '');
20965      }
20966
20967      if(!slider.attr('flex')) {
20968        slider.attr('flex', '');
20969      }
20970
20971      return function postLink(scope, element, attr, ctrl) {
20972        element.addClass('_md');     // private md component indicator for styling
20973
20974        // We have to manually stop the $watch on ngDisabled because it exists
20975        // on the parent scope, and won't be automatically destroyed when
20976        // the component is destroyed.
20977        function setDisable(value) {
20978          element.children().attr('disabled', value);
20979          element.find('input').attr('disabled', value);
20980        }
20981
20982        var stopDisabledWatch = angular.noop;
20983
20984        if (attr.disabled) {
20985          setDisable(true);
20986        }
20987        else if (attr.ngDisabled) {
20988          stopDisabledWatch = scope.$watch(attr.ngDisabled, function (value) {
20989            setDisable(value);
20990          });
20991        }
20992
20993        scope.$on('$destroy', function () {
20994          stopDisabledWatch();
20995        });
20996
20997        var initialMaxWidth;
20998
20999        ctrl.fitInputWidthToTextLength = function (length) {
21000          var input = element[0].querySelector('md-input-container');
21001
21002          if (input) {
21003            var computedStyle = getComputedStyle(input);
21004            var minWidth = parseInt(computedStyle.minWidth);
21005            var padding = parseInt(computedStyle.padding) * 2;
21006
21007            initialMaxWidth = initialMaxWidth || parseInt(computedStyle.maxWidth);
21008            var newMaxWidth = Math.max(initialMaxWidth, minWidth + padding + (minWidth / 2 * length));
21009
21010            input.style.maxWidth = newMaxWidth + 'px';
21011          }
21012        };
21013      };
21014    }
21015  };
21016}
21017
21018/**
21019 * @ngdoc directive
21020 * @name mdSlider
21021 * @module material.components.slider
21022 * @restrict E
21023 * @description
21024 * The `<md-slider>` component allows the user to choose from a range of
21025 * values.
21026 *
21027 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
21028 * the slider is in the accent color by default. The primary color palette may be used with
21029 * the `md-primary` class.
21030 *
21031 * It has two modes: 'normal' mode, where the user slides between a wide range
21032 * of values, and 'discrete' mode, where the user slides between only a few
21033 * select values.
21034 *
21035 * To enable discrete mode, add the `md-discrete` attribute to a slider,
21036 * and use the `step` attribute to change the distance between
21037 * values the user is allowed to pick.
21038 *
21039 * @usage
21040 * <h4>Normal Mode</h4>
21041 * <hljs lang="html">
21042 * <md-slider ng-model="myValue" min="5" max="500">
21043 * </md-slider>
21044 * </hljs>
21045 * <h4>Discrete Mode</h4>
21046 * <hljs lang="html">
21047 * <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
21048 * </md-slider>
21049 * </hljs>
21050 * <h4>Invert Mode</h4>
21051 * <hljs lang="html">
21052 * <md-slider md-invert ng-model="myValue" step="10" min="10" max="130">
21053 * </md-slider>
21054 * </hljs>
21055 *
21056 * @param {boolean=} md-discrete Whether to enable discrete mode.
21057 * @param {boolean=} md-invert Whether to enable invert mode.
21058 * @param {number=} step The distance between values the user is allowed to pick. Default 1.
21059 * @param {number=} min The minimum value the user is allowed to pick. Default 0.
21060 * @param {number=} max The maximum value the user is allowed to pick. Default 100.
21061 * @param {number=} round The amount of numbers after the decimal point, maximum is 6 to prevent scientific notation. Default 3.
21062 */
21063function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdTheming, $mdGesture, $parse, $log, $timeout) {
21064  return {
21065    scope: {},
21066    require: ['?ngModel', '?^mdSliderContainer'],
21067    template:
21068      '<div class="md-slider-wrapper">' +
21069        '<div class="md-slider-content">' +
21070          '<div class="md-track-container">' +
21071            '<div class="md-track"></div>' +
21072            '<div class="md-track md-track-fill"></div>' +
21073            '<div class="md-track-ticks"></div>' +
21074          '</div>' +
21075          '<div class="md-thumb-container">' +
21076            '<div class="md-thumb"></div>' +
21077            '<div class="md-focus-thumb"></div>' +
21078            '<div class="md-focus-ring"></div>' +
21079            '<div class="md-sign">' +
21080              '<span class="md-thumb-text"></span>' +
21081            '</div>' +
21082            '<div class="md-disabled-thumb"></div>' +
21083          '</div>' +
21084        '</div>' +
21085      '</div>',
21086    compile: compile
21087  };
21088
21089  // **********************************************************
21090  // Private Methods
21091  // **********************************************************
21092
21093  function compile (tElement, tAttrs) {
21094    var wrapper = angular.element(tElement[0].getElementsByClassName('md-slider-wrapper'));
21095
21096    var tabIndex = tAttrs.tabindex || 0;
21097    wrapper.attr('tabindex', tabIndex);
21098
21099    if (tAttrs.disabled || tAttrs.ngDisabled) wrapper.attr('tabindex', -1);
21100
21101    wrapper.attr('role', 'slider');
21102
21103    $mdAria.expect(tElement, 'aria-label');
21104
21105    return postLink;
21106  }
21107
21108  function postLink(scope, element, attr, ctrls) {
21109    $mdTheming(element);
21110    var ngModelCtrl = ctrls[0] || {
21111      // Mock ngModelController if it doesn't exist to give us
21112      // the minimum functionality needed
21113      $setViewValue: function(val) {
21114        this.$viewValue = val;
21115        this.$viewChangeListeners.forEach(function(cb) { cb(); });
21116      },
21117      $parsers: [],
21118      $formatters: [],
21119      $viewChangeListeners: []
21120    };
21121
21122    var containerCtrl = ctrls[1];
21123    var container = angular.element($mdUtil.getClosest(element, '_md-slider-container', true));
21124    var isDisabled = attr.ngDisabled ? angular.bind(null, $parse(attr.ngDisabled), scope.$parent) : function () {
21125          return element[0].hasAttribute('disabled');
21126        };
21127
21128    var thumb = angular.element(element[0].querySelector('.md-thumb'));
21129    var thumbText = angular.element(element[0].querySelector('.md-thumb-text'));
21130    var thumbContainer = thumb.parent();
21131    var trackContainer = angular.element(element[0].querySelector('.md-track-container'));
21132    var activeTrack = angular.element(element[0].querySelector('.md-track-fill'));
21133    var tickContainer = angular.element(element[0].querySelector('.md-track-ticks'));
21134    var wrapper = angular.element(element[0].getElementsByClassName('md-slider-wrapper'));
21135    var content = angular.element(element[0].getElementsByClassName('md-slider-content'));
21136    var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
21137
21138    // Default values, overridable by attrs
21139    var DEFAULT_ROUND = 3;
21140    var vertical = angular.isDefined(attr.mdVertical);
21141    var discrete = angular.isDefined(attr.mdDiscrete);
21142    var invert = angular.isDefined(attr.mdInvert);
21143    angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(0);
21144    angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(100);
21145    angular.isDefined(attr.step)? attr.$observe('step', updateStep) : updateStep(1);
21146    angular.isDefined(attr.round)? attr.$observe('round', updateRound) : updateRound(DEFAULT_ROUND);
21147
21148    // We have to manually stop the $watch on ngDisabled because it exists
21149    // on the parent scope, and won't be automatically destroyed when
21150    // the component is destroyed.
21151    var stopDisabledWatch = angular.noop;
21152    if (attr.ngDisabled) {
21153      stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
21154    }
21155
21156    $mdGesture.register(wrapper, 'drag', { horizontal: !vertical });
21157
21158    scope.mouseActive = false;
21159
21160    wrapper
21161      .on('keydown', keydownListener)
21162      .on('mousedown', mouseDownListener)
21163      .on('focus', focusListener)
21164      .on('blur', blurListener)
21165      .on('$md.pressdown', onPressDown)
21166      .on('$md.pressup', onPressUp)
21167      .on('$md.dragstart', onDragStart)
21168      .on('$md.drag', onDrag)
21169      .on('$md.dragend', onDragEnd);
21170
21171    // On resize, recalculate the slider's dimensions and re-render
21172    function updateAll() {
21173      refreshSliderDimensions();
21174      ngModelRender();
21175    }
21176    setTimeout(updateAll, 0);
21177
21178    var debouncedUpdateAll = $$rAF.throttle(updateAll);
21179    angular.element($window).on('resize', debouncedUpdateAll);
21180
21181    scope.$on('$destroy', function() {
21182      angular.element($window).off('resize', debouncedUpdateAll);
21183    });
21184
21185    ngModelCtrl.$render = ngModelRender;
21186    ngModelCtrl.$viewChangeListeners.push(ngModelRender);
21187    ngModelCtrl.$formatters.push(minMaxValidator);
21188    ngModelCtrl.$formatters.push(stepValidator);
21189
21190    /**
21191     * Attributes
21192     */
21193    var min;
21194    var max;
21195    var step;
21196    var round;
21197    function updateMin(value) {
21198      min = parseFloat(value);
21199      element.attr('aria-valuemin', value);
21200      updateAll();
21201    }
21202    function updateMax(value) {
21203      max = parseFloat(value);
21204      element.attr('aria-valuemax', value);
21205      updateAll();
21206    }
21207    function updateStep(value) {
21208      step = parseFloat(value);
21209    }
21210    function updateRound(value) {
21211      // Set max round digits to 6, after 6 the input uses scientific notation
21212      round = minMaxValidator(parseInt(value), 0, 6);
21213    }
21214    function updateAriaDisabled() {
21215      element.attr('aria-disabled', !!isDisabled());
21216    }
21217
21218    // Draw the ticks with canvas.
21219    // The alternative to drawing ticks with canvas is to draw one element for each tick,
21220    // which could quickly become a performance bottleneck.
21221    var tickCanvas, tickCtx;
21222    function redrawTicks() {
21223      if (!discrete || isDisabled()) return;
21224      if ( angular.isUndefined(step) )         return;
21225
21226      if ( step <= 0 ) {
21227        var msg = 'Slider step value must be greater than zero when in discrete mode';
21228        $log.error(msg);
21229        throw new Error(msg);
21230      }
21231
21232      var numSteps = Math.floor( (max - min) / step );
21233      if (!tickCanvas) {
21234        tickCanvas = angular.element('<canvas>').css('position', 'absolute');
21235        tickContainer.append(tickCanvas);
21236
21237        tickCtx = tickCanvas[0].getContext('2d');
21238      }
21239
21240      var dimensions = getSliderDimensions();
21241
21242      // If `dimensions` doesn't have height and width it might be the first attempt so we will refresh dimensions
21243      if (dimensions && !dimensions.height && !dimensions.width) {
21244        refreshSliderDimensions();
21245        dimensions = sliderDimensions;
21246      }
21247
21248      tickCanvas[0].width = dimensions.width;
21249      tickCanvas[0].height = dimensions.height;
21250
21251      var distance;
21252      for (var i = 0; i <= numSteps; i++) {
21253        var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
21254        tickCtx.fillStyle = trackTicksStyle.color || 'black';
21255
21256        distance = Math.floor((vertical ? dimensions.height : dimensions.width) * (i / numSteps));
21257
21258        tickCtx.fillRect(vertical ? 0 : distance - 1,
21259          vertical ? distance - 1 : 0,
21260          vertical ? dimensions.width : 2,
21261          vertical ? 2 : dimensions.height);
21262      }
21263    }
21264
21265    function clearTicks() {
21266      if(tickCanvas && tickCtx) {
21267        var dimensions = getSliderDimensions();
21268        tickCtx.clearRect(0, 0, dimensions.width, dimensions.height);
21269      }
21270    }
21271
21272    /**
21273     * Refreshing Dimensions
21274     */
21275    var sliderDimensions = {};
21276    refreshSliderDimensions();
21277    function refreshSliderDimensions() {
21278      sliderDimensions = trackContainer[0].getBoundingClientRect();
21279    }
21280    function getSliderDimensions() {
21281      throttledRefreshDimensions();
21282      return sliderDimensions;
21283    }
21284
21285    /**
21286     * left/right/up/down arrow listener
21287     */
21288    function keydownListener(ev) {
21289      if (isDisabled()) return;
21290
21291      var changeAmount;
21292      if (vertical ? ev.keyCode === $mdConstant.KEY_CODE.DOWN_ARROW : ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
21293        changeAmount = -step;
21294      } else if (vertical ? ev.keyCode === $mdConstant.KEY_CODE.UP_ARROW : ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
21295        changeAmount = step;
21296      }
21297      changeAmount = invert ? -changeAmount : changeAmount;
21298      if (changeAmount) {
21299        if (ev.metaKey || ev.ctrlKey || ev.altKey) {
21300          changeAmount *= 4;
21301        }
21302        ev.preventDefault();
21303        ev.stopPropagation();
21304        scope.$evalAsync(function() {
21305          setModelValue(ngModelCtrl.$viewValue + changeAmount);
21306        });
21307      }
21308    }
21309
21310    function mouseDownListener() {
21311      redrawTicks();
21312
21313      scope.mouseActive = true;
21314      wrapper.removeClass('md-focused');
21315
21316      $timeout(function() {
21317        scope.mouseActive = false;
21318      }, 100);
21319    }
21320
21321    function focusListener() {
21322      if (scope.mouseActive === false) {
21323        wrapper.addClass('md-focused');
21324      }
21325    }
21326
21327    function blurListener() {
21328      wrapper.removeClass('md-focused');
21329      element.removeClass('md-active');
21330      clearTicks();
21331    }
21332
21333    /**
21334     * ngModel setters and validators
21335     */
21336    function setModelValue(value) {
21337      ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
21338    }
21339    function ngModelRender() {
21340      if (isNaN(ngModelCtrl.$viewValue)) {
21341        ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
21342      }
21343
21344      ngModelCtrl.$viewValue = minMaxValidator(ngModelCtrl.$viewValue);
21345
21346      var percent = valueToPercent(ngModelCtrl.$viewValue);
21347      scope.modelValue = ngModelCtrl.$viewValue;
21348      element.attr('aria-valuenow', ngModelCtrl.$viewValue);
21349      setSliderPercent(percent);
21350      thumbText.text( ngModelCtrl.$viewValue );
21351    }
21352
21353    function minMaxValidator(value, minValue, maxValue) {
21354      if (angular.isNumber(value)) {
21355        minValue = angular.isNumber(minValue) ? minValue : min;
21356        maxValue = angular.isNumber(maxValue) ? maxValue : max;
21357
21358        return Math.max(minValue, Math.min(maxValue, value));
21359      }
21360    }
21361
21362    function stepValidator(value) {
21363      if (angular.isNumber(value)) {
21364        var formattedValue = (Math.round((value - min) / step) * step + min);
21365        formattedValue = (Math.round(formattedValue * Math.pow(10, round)) / Math.pow(10, round));
21366
21367        if (containerCtrl && containerCtrl.fitInputWidthToTextLength){
21368          $mdUtil.debounce(function () {
21369            containerCtrl.fitInputWidthToTextLength(formattedValue.toString().length);
21370          }, 100)();
21371        }
21372
21373        return formattedValue;
21374      }
21375    }
21376
21377    /**
21378     * @param percent 0-1
21379     */
21380    function setSliderPercent(percent) {
21381
21382      percent = clamp(percent);
21383
21384      var thumbPosition = (percent * 100) + '%';
21385      var activeTrackPercent = invert ? (1 - percent) * 100 + '%' : thumbPosition;
21386
21387      if (vertical) {
21388        thumbContainer.css('bottom', thumbPosition);
21389      }
21390      else {
21391        $mdUtil.bidiProperty(thumbContainer, 'left', 'right', thumbPosition);
21392      }
21393
21394      
21395      activeTrack.css(vertical ? 'height' : 'width', activeTrackPercent);
21396
21397      element.toggleClass((invert ? 'md-max' : 'md-min'), percent === 0);
21398      element.toggleClass((invert ? 'md-min' : 'md-max'), percent === 1);
21399    }
21400
21401    /**
21402     * Slide listeners
21403     */
21404    var isDragging = false;
21405
21406    function onPressDown(ev) {
21407      if (isDisabled()) return;
21408
21409      element.addClass('md-active');
21410      element[0].focus();
21411      refreshSliderDimensions();
21412
21413      var exactVal = percentToValue( positionToPercent( vertical ? ev.pointer.y : ev.pointer.x ));
21414      var closestVal = minMaxValidator( stepValidator(exactVal) );
21415      scope.$apply(function() {
21416        setModelValue( closestVal );
21417        setSliderPercent( valueToPercent(closestVal));
21418      });
21419    }
21420    function onPressUp(ev) {
21421      if (isDisabled()) return;
21422
21423      element.removeClass('md-dragging');
21424
21425      var exactVal = percentToValue( positionToPercent( vertical ? ev.pointer.y : ev.pointer.x ));
21426      var closestVal = minMaxValidator( stepValidator(exactVal) );
21427      scope.$apply(function() {
21428        setModelValue(closestVal);
21429        ngModelRender();
21430      });
21431    }
21432    function onDragStart(ev) {
21433      if (isDisabled()) return;
21434      isDragging = true;
21435
21436      ev.stopPropagation();
21437
21438      element.addClass('md-dragging');
21439      setSliderFromEvent(ev);
21440    }
21441    function onDrag(ev) {
21442      if (!isDragging) return;
21443      ev.stopPropagation();
21444      setSliderFromEvent(ev);
21445    }
21446    function onDragEnd(ev) {
21447      if (!isDragging) return;
21448      ev.stopPropagation();
21449      isDragging = false;
21450    }
21451
21452    function setSliderFromEvent(ev) {
21453      // While panning discrete, update only the
21454      // visual positioning but not the model value.
21455      if ( discrete ) adjustThumbPosition( vertical ? ev.pointer.y : ev.pointer.x );
21456      else            doSlide( vertical ? ev.pointer.y : ev.pointer.x );
21457    }
21458
21459    /**
21460     * Slide the UI by changing the model value
21461     * @param x
21462     */
21463    function doSlide( x ) {
21464      scope.$evalAsync( function() {
21465        setModelValue( percentToValue( positionToPercent(x) ));
21466      });
21467    }
21468
21469    /**
21470     * Slide the UI without changing the model (while dragging/panning)
21471     * @param x
21472     */
21473    function adjustThumbPosition( x ) {
21474      var exactVal = percentToValue( positionToPercent( x ));
21475      var closestVal = minMaxValidator( stepValidator(exactVal) );
21476      setSliderPercent( positionToPercent(x) );
21477      thumbText.text( closestVal );
21478    }
21479
21480    /**
21481    * Clamps the value to be between 0 and 1.
21482    * @param {number} value The value to clamp.
21483    * @returns {number}
21484    */
21485    function clamp(value) {
21486      return Math.max(0, Math.min(value || 0, 1));
21487    }
21488
21489    /**
21490     * Convert position on slider to percentage value of offset from beginning...
21491     * @param position
21492     * @returns {number}
21493     */
21494    function positionToPercent( position ) {
21495      var offset = vertical ? sliderDimensions.top : sliderDimensions.left;
21496      var size = vertical ? sliderDimensions.height : sliderDimensions.width;
21497      var calc = (position - offset) / size;
21498
21499      if (!vertical && $mdUtil.bidi() === 'rtl') {
21500        calc = 1 - calc;
21501      }
21502
21503      return Math.max(0, Math.min(1, vertical ? 1 - calc : calc));
21504    }
21505
21506    /**
21507     * Convert percentage offset on slide to equivalent model value
21508     * @param percent
21509     * @returns {*}
21510     */
21511    function percentToValue( percent ) {
21512      var adjustedPercent = invert ? (1 - percent) : percent;
21513      return (min + adjustedPercent * (max - min));
21514    }
21515
21516    function valueToPercent( val ) {
21517      var percent = (val - min) / (max - min);
21518      return invert ? (1 - percent) : percent;
21519    }
21520  }
21521}
21522
21523})();
21524(function(){
21525"use strict";
21526
21527/**
21528 * @ngdoc module
21529 * @name material.components.sticky
21530 * @description
21531 * Sticky effects for md
21532 *
21533 */
21534MdSticky.$inject = ["$mdConstant", "$$rAF", "$mdUtil", "$compile"];
21535angular
21536  .module('material.components.sticky', [
21537    'material.core',
21538    'material.components.content'
21539  ])
21540  .factory('$mdSticky', MdSticky);
21541
21542/**
21543 * @ngdoc service
21544 * @name $mdSticky
21545 * @module material.components.sticky
21546 *
21547 * @description
21548 * The `$mdSticky`service provides a mixin to make elements sticky.
21549 *
21550 * Whenever the current browser supports stickiness natively, the `$mdSticky` service will just
21551 * use the native browser stickiness.
21552 *
21553 * By default the `$mdSticky` service compiles the cloned element, when not specified through the `elementClone`
21554 * parameter, in the same scope as the actual element lives.
21555 *
21556 *
21557 * <h3>Notes</h3>
21558 * When using an element which is containing a compiled directive, which changed its DOM structure during compilation,
21559 * you should compile the clone yourself using the plain template.<br/><br/>
21560 * See the right usage below:
21561 * <hljs lang="js">
21562 *   angular.module('myModule')
21563 *     .directive('stickySelect', function($mdSticky, $compile) {
21564 *       var SELECT_TEMPLATE =
21565 *         '<md-select ng-model="selected">' +
21566 *           '<md-option>Option 1</md-option>' +
21567 *         '</md-select>';
21568 *
21569 *       return {
21570 *         restrict: 'E',
21571 *         replace: true,
21572 *         template: SELECT_TEMPLATE,
21573 *         link: function(scope,element) {
21574 *           $mdSticky(scope, element, $compile(SELECT_TEMPLATE)(scope));
21575 *         }
21576 *       };
21577 *     });
21578 * </hljs>
21579 *
21580 * @usage
21581 * <hljs lang="js">
21582 *   angular.module('myModule')
21583 *     .directive('stickyText', function($mdSticky, $compile) {
21584 *       return {
21585 *         restrict: 'E',
21586 *         template: '<span>Sticky Text</span>',
21587 *         link: function(scope,element) {
21588 *           $mdSticky(scope, element);
21589 *         }
21590 *       };
21591 *     });
21592 * </hljs>
21593 *
21594 * @returns A `$mdSticky` function that takes three arguments:
21595 *   - `scope`
21596 *   - `element`: The element that will be 'sticky'
21597 *   - `elementClone`: A clone of the element, that will be shown
21598 *     when the user starts scrolling past the original element.
21599 *     If not provided, it will use the result of `element.clone()` and compiles it in the given scope.
21600 */
21601function MdSticky($mdConstant, $$rAF, $mdUtil, $compile) {
21602
21603  var browserStickySupport = $mdUtil.checkStickySupport();
21604
21605  /**
21606   * Registers an element as sticky, used internally by directives to register themselves
21607   */
21608  return function registerStickyElement(scope, element, stickyClone) {
21609    var contentCtrl = element.controller('mdContent');
21610    if (!contentCtrl) return;
21611
21612    if (browserStickySupport) {
21613      element.css({
21614        position: browserStickySupport,
21615        top: 0,
21616        'z-index': 2
21617      });
21618    } else {
21619      var $$sticky = contentCtrl.$element.data('$$sticky');
21620      if (!$$sticky) {
21621        $$sticky = setupSticky(contentCtrl);
21622        contentCtrl.$element.data('$$sticky', $$sticky);
21623      }
21624
21625      // Compile our cloned element, when cloned in this service, into the given scope.
21626      var cloneElement = stickyClone || $compile(element.clone())(scope);
21627
21628      var deregister = $$sticky.add(element, cloneElement);
21629      scope.$on('$destroy', deregister);
21630    }
21631  };
21632
21633  function setupSticky(contentCtrl) {
21634    var contentEl = contentCtrl.$element;
21635
21636    // Refresh elements is very expensive, so we use the debounced
21637    // version when possible.
21638    var debouncedRefreshElements = $$rAF.throttle(refreshElements);
21639
21640    // setupAugmentedScrollEvents gives us `$scrollstart` and `$scroll`,
21641    // more reliable than `scroll` on android.
21642    setupAugmentedScrollEvents(contentEl);
21643    contentEl.on('$scrollstart', debouncedRefreshElements);
21644    contentEl.on('$scroll', onScroll);
21645
21646    var self;
21647    return self = {
21648      prev: null,
21649      current: null, //the currently stickied item
21650      next: null,
21651      items: [],
21652      add: add,
21653      refreshElements: refreshElements
21654    };
21655
21656    /***************
21657     * Public
21658     ***************/
21659    // Add an element and its sticky clone to this content's sticky collection
21660    function add(element, stickyClone) {
21661      stickyClone.addClass('md-sticky-clone');
21662
21663      var item = {
21664        element: element,
21665        clone: stickyClone
21666      };
21667      self.items.push(item);
21668
21669      $mdUtil.nextTick(function() {
21670        contentEl.prepend(item.clone);
21671      });
21672
21673      debouncedRefreshElements();
21674
21675      return function remove() {
21676        self.items.forEach(function(item, index) {
21677          if (item.element[0] === element[0]) {
21678            self.items.splice(index, 1);
21679            item.clone.remove();
21680          }
21681        });
21682        debouncedRefreshElements();
21683      };
21684    }
21685
21686    function refreshElements() {
21687      // Sort our collection of elements by their current position in the DOM.
21688      // We need to do this because our elements' order of being added may not
21689      // be the same as their order of display.
21690      self.items.forEach(refreshPosition);
21691      self.items = self.items.sort(function(a, b) {
21692        return a.top < b.top ? -1 : 1;
21693      });
21694
21695      // Find which item in the list should be active, 
21696      // based upon the content's current scroll position
21697      var item;
21698      var currentScrollTop = contentEl.prop('scrollTop');
21699      for (var i = self.items.length - 1; i >= 0; i--) {
21700        if (currentScrollTop > self.items[i].top) {
21701          item = self.items[i];
21702          break;
21703        }
21704      }
21705      setCurrentItem(item);
21706    }
21707
21708    /***************
21709     * Private
21710     ***************/
21711
21712    // Find the `top` of an item relative to the content element,
21713    // and also the height.
21714    function refreshPosition(item) {
21715      // Find the top of an item by adding to the offsetHeight until we reach the 
21716      // content element.
21717      var current = item.element[0];
21718      item.top = 0;
21719      item.left = 0;
21720      item.right = 0;
21721      while (current && current !== contentEl[0]) {
21722        item.top += current.offsetTop;
21723        item.left += current.offsetLeft;
21724        if ( current.offsetParent ){
21725          item.right += current.offsetParent.offsetWidth - current.offsetWidth - current.offsetLeft; //Compute offsetRight
21726        }
21727        current = current.offsetParent;
21728      }
21729      item.height = item.element.prop('offsetHeight');
21730
21731      var defaultVal = $mdUtil.floatingScrollbars() ? '0' : undefined;
21732      $mdUtil.bidi(item.clone, 'margin-left', item.left, defaultVal);
21733      $mdUtil.bidi(item.clone, 'margin-right', defaultVal, item.right);
21734    }
21735
21736    // As we scroll, push in and select the correct sticky element.
21737    function onScroll() {
21738      var scrollTop = contentEl.prop('scrollTop');
21739      var isScrollingDown = scrollTop > (onScroll.prevScrollTop || 0);
21740
21741      // Store the previous scroll so we know which direction we are scrolling
21742      onScroll.prevScrollTop = scrollTop;
21743
21744      //
21745      // AT TOP (not scrolling)
21746      //
21747      if (scrollTop === 0) {
21748        // If we're at the top, just clear the current item and return
21749        setCurrentItem(null);
21750        return;
21751      }
21752
21753      //
21754      // SCROLLING DOWN (going towards the next item)
21755      //
21756      if (isScrollingDown) {
21757
21758        // If we've scrolled down past the next item's position, sticky it and return
21759        if (self.next && self.next.top <= scrollTop) {
21760          setCurrentItem(self.next);
21761          return;
21762        }
21763
21764        // If the next item is close to the current one, push the current one up out of the way
21765        if (self.current && self.next && self.next.top - scrollTop <= self.next.height) {
21766          translate(self.current, scrollTop + (self.next.top - self.next.height - scrollTop));
21767          return;
21768        }
21769      }
21770
21771      //
21772      // SCROLLING UP (not at the top & not scrolling down; must be scrolling up)
21773      //
21774      if (!isScrollingDown) {
21775
21776        // If we've scrolled up past the previous item's position, sticky it and return
21777        if (self.current && self.prev && scrollTop < self.current.top) {
21778          setCurrentItem(self.prev);
21779          return;
21780        }
21781
21782        // If the next item is close to the current one, pull the current one down into view
21783        if (self.next && self.current && (scrollTop >= (self.next.top - self.current.height))) {
21784          translate(self.current, scrollTop + (self.next.top - scrollTop - self.current.height));
21785          return;
21786        }
21787      }
21788
21789      //
21790      // Otherwise, just move the current item to the proper place (scrolling up or down)
21791      //
21792      if (self.current) {
21793        translate(self.current, scrollTop);
21794      }
21795    }
21796
21797    function setCurrentItem(item) {
21798      if (self.current === item) return;
21799      // Deactivate currently active item
21800      if (self.current) {
21801        translate(self.current, null);
21802        setStickyState(self.current, null);
21803      }
21804
21805      // Activate new item if given
21806      if (item) {
21807        setStickyState(item, 'active');
21808      }
21809
21810      self.current = item;
21811      var index = self.items.indexOf(item);
21812      // If index === -1, index + 1 = 0. It works out.
21813      self.next = self.items[index + 1];
21814      self.prev = self.items[index - 1];
21815      setStickyState(self.next, 'next');
21816      setStickyState(self.prev, 'prev');
21817    }
21818
21819    function setStickyState(item, state) {
21820      if (!item || item.state === state) return;
21821      if (item.state) {
21822        item.clone.attr('sticky-prev-state', item.state);
21823        item.element.attr('sticky-prev-state', item.state);
21824      }
21825      item.clone.attr('sticky-state', state);
21826      item.element.attr('sticky-state', state);
21827      item.state = state;
21828    }
21829
21830    function translate(item, amount) {
21831      if (!item) return;
21832      if (amount === null || amount === undefined) {
21833        if (item.translateY) {
21834          item.translateY = null;
21835          item.clone.css($mdConstant.CSS.TRANSFORM, '');
21836        }
21837      } else {
21838        item.translateY = amount;
21839
21840        $mdUtil.bidi( item.clone, $mdConstant.CSS.TRANSFORM,
21841          'translate3d(' + item.left + 'px,' + amount + 'px,0)',
21842          'translateY(' + amount + 'px)'
21843        );
21844      }
21845    }
21846  }
21847
21848
21849  // Android 4.4 don't accurately give scroll events.
21850  // To fix this problem, we setup a fake scroll event. We say:
21851  // > If a scroll or touchmove event has happened in the last DELAY milliseconds, 
21852  //   then send a `$scroll` event every animationFrame.
21853  // Additionally, we add $scrollstart and $scrollend events.
21854  function setupAugmentedScrollEvents(element) {
21855    var SCROLL_END_DELAY = 200;
21856    var isScrolling;
21857    var lastScrollTime;
21858    element.on('scroll touchmove', function() {
21859      if (!isScrolling) {
21860        isScrolling = true;
21861        $$rAF.throttle(loopScrollEvent);
21862        element.triggerHandler('$scrollstart');
21863      }
21864      element.triggerHandler('$scroll');
21865      lastScrollTime = +$mdUtil.now();
21866    });
21867
21868    function loopScrollEvent() {
21869      if (+$mdUtil.now() - lastScrollTime > SCROLL_END_DELAY) {
21870        isScrolling = false;
21871        element.triggerHandler('$scrollend');
21872      } else {
21873        element.triggerHandler('$scroll');
21874        $$rAF.throttle(loopScrollEvent);
21875      }
21876    }
21877  }
21878
21879}
21880
21881})();
21882(function(){
21883"use strict";
21884
21885/**
21886 * @ngdoc module
21887 * @name material.components.subheader
21888 * @description
21889 * SubHeader module
21890 *
21891 *  Subheaders are special list tiles that delineate distinct sections of a
21892 *  list or grid list and are typically related to the current filtering or
21893 *  sorting criteria. Subheader tiles are either displayed inline with tiles or
21894 *  can be associated with content, for example, in an adjacent column.
21895 *
21896 *  Upon scrolling, subheaders remain pinned to the top of the screen and remain
21897 *  pinned until pushed on or off screen by the next subheader. @see [Material
21898 *  Design Specifications](https://www.google.com/design/spec/components/subheaders.html)
21899 *
21900 *  > To improve the visual grouping of content, use the system color for your subheaders.
21901 *
21902 */
21903MdSubheaderDirective.$inject = ["$mdSticky", "$compile", "$mdTheming", "$mdUtil", "$mdAria"];
21904angular
21905  .module('material.components.subheader', [
21906    'material.core',
21907    'material.components.sticky'
21908  ])
21909  .directive('mdSubheader', MdSubheaderDirective);
21910
21911/**
21912 * @ngdoc directive
21913 * @name mdSubheader
21914 * @module material.components.subheader
21915 *
21916 * @restrict E
21917 *
21918 * @description
21919 * The `md-subheader` directive creates a sticky subheader for a section.
21920 *
21921 * Developers are able to disable the stickiness of the subheader by using the following markup
21922 *
21923 * <hljs lang="html">
21924 *   <md-subheader class="md-no-sticky">Not Sticky</md-subheader>
21925 * </hljs>
21926 *
21927 * ### Notes
21928 * - The `md-subheader` directive uses the <a ng-href="api/service/$mdSticky">$mdSticky</a> service
21929 * to make the subheader sticky.
21930 *
21931 * > Whenever the current browser doesn't support stickiness natively, the subheader
21932 * will be compiled twice to create a sticky clone of the subheader.
21933 *
21934 * @usage
21935 * <hljs lang="html">
21936 * <md-subheader>Online Friends</md-subheader>
21937 * </hljs>
21938 */
21939
21940function MdSubheaderDirective($mdSticky, $compile, $mdTheming, $mdUtil, $mdAria) {
21941  return {
21942    restrict: 'E',
21943    replace: true,
21944    transclude: true,
21945    template: (
21946    '<div class="md-subheader _md">' +
21947    '  <div class="md-subheader-inner">' +
21948    '    <div class="md-subheader-content"></div>' +
21949    '  </div>' +
21950    '</div>'
21951    ),
21952    link: function postLink(scope, element, attr, controllers, transclude) {
21953      $mdTheming(element);
21954      element.addClass('_md');
21955
21956      // Remove the ngRepeat attribute from the root element, because we don't want to compile
21957      // the ngRepeat for the sticky clone again.
21958      $mdUtil.prefixer().removeAttribute(element, 'ng-repeat');
21959
21960      var outerHTML = element[0].outerHTML;
21961
21962      function getContent(el) {
21963        return angular.element(el[0].querySelector('.md-subheader-content'));
21964      }
21965
21966      // Set the ARIA attributes on the original element since it keeps it's original place in
21967      // the DOM, whereas the clones are in reverse order. Should be done after the outerHTML,
21968      // in order to avoid having multiple element be marked as headers.
21969      attr.$set('role', 'heading');
21970      $mdAria.expect(element, 'aria-level', '2');
21971
21972      // Transclude the user-given contents of the subheader
21973      // the conventional way.
21974      transclude(scope, function(clone) {
21975        getContent(element).append(clone);
21976      });
21977
21978      // Create another clone, that uses the outer and inner contents
21979      // of the element, that will be 'stickied' as the user scrolls.
21980      if (!element.hasClass('md-no-sticky')) {
21981        transclude(scope, function(clone) {
21982          // If the user adds an ng-if or ng-repeat directly to the md-subheader element, the
21983          // compiled clone below will only be a comment tag (since they replace their elements with
21984          // a comment) which cannot be properly passed to the $mdSticky; so we wrap it in our own
21985          // DIV to ensure we have something $mdSticky can use
21986          var wrapper = $compile('<div class="md-subheader-wrapper" aria-hidden="true">' + outerHTML + '</div>')(scope);
21987
21988          // Delay initialization until after any `ng-if`/`ng-repeat`/etc has finished before
21989          // attempting to create the clone
21990          $mdUtil.nextTick(function() {
21991            // Append our transcluded clone into the wrapper.
21992            // We don't have to recompile the element again, because the clone is already
21993            // compiled in it's transclusion scope. If we recompile the outerHTML of the new clone, we would lose
21994            // our ngIf's and other previous registered bindings / properties.
21995            getContent(wrapper).append(clone);
21996          });
21997
21998          // Make the element sticky and provide the stickyClone our self, to avoid recompilation of the subheader
21999          // element.
22000          $mdSticky(scope, element, wrapper);
22001        });
22002      }
22003    }
22004  };
22005}
22006
22007})();
22008(function(){
22009"use strict";
22010
22011/**
22012 * @ngdoc module
22013 * @name material.components.swipe
22014 * @description Swipe module!
22015 */
22016/**
22017 * @ngdoc directive
22018 * @module material.components.swipe
22019 * @name mdSwipeLeft
22020 *
22021 * @restrict A
22022 *
22023 * @description
22024 * The md-swipe-left directive allows you to specify custom behavior when an element is swiped
22025 * left.
22026 *
22027 * @usage
22028 * <hljs lang="html">
22029 * <div md-swipe-left="onSwipeLeft()">Swipe me left!</div>
22030 * </hljs>
22031 */
22032/**
22033 * @ngdoc directive
22034 * @module material.components.swipe
22035 * @name mdSwipeRight
22036 *
22037 * @restrict A
22038 *
22039 * @description
22040 * The md-swipe-right directive allows you to specify custom behavior when an element is swiped
22041 * right.
22042 *
22043 * @usage
22044 * <hljs lang="html">
22045 * <div md-swipe-right="onSwipeRight()">Swipe me right!</div>
22046 * </hljs>
22047 */
22048/**
22049 * @ngdoc directive
22050 * @module material.components.swipe
22051 * @name mdSwipeUp
22052 *
22053 * @restrict A
22054 *
22055 * @description
22056 * The md-swipe-up directive allows you to specify custom behavior when an element is swiped
22057 * up.
22058 *
22059 * @usage
22060 * <hljs lang="html">
22061 * <div md-swipe-up="onSwipeUp()">Swipe me up!</div>
22062 * </hljs>
22063 */
22064/**
22065 * @ngdoc directive
22066 * @module material.components.swipe
22067 * @name mdSwipeDown
22068 *
22069 * @restrict A
22070 *
22071 * @description
22072 * The md-swipe-down directive allows you to specify custom behavior when an element is swiped
22073 * down.
22074 *
22075 * @usage
22076 * <hljs lang="html">
22077 * <div md-swipe-down="onSwipDown()">Swipe me down!</div>
22078 * </hljs>
22079 */
22080
22081angular.module('material.components.swipe', ['material.core'])
22082    .directive('mdSwipeLeft', getDirective('SwipeLeft'))
22083    .directive('mdSwipeRight', getDirective('SwipeRight'))
22084    .directive('mdSwipeUp', getDirective('SwipeUp'))
22085    .directive('mdSwipeDown', getDirective('SwipeDown'));
22086
22087function getDirective(name) {
22088    DirectiveFactory.$inject = ["$parse"];
22089  var directiveName = 'md' + name;
22090  var eventName = '$md.' + name.toLowerCase();
22091
22092  return DirectiveFactory;
22093
22094  /* @ngInject */
22095  function DirectiveFactory($parse) {
22096      return { restrict: 'A', link: postLink };
22097      function postLink(scope, element, attr) {
22098        element.css('touch-action', 'none');
22099
22100        var fn = $parse(attr[directiveName]);
22101        element.on(eventName, function(ev) {
22102          scope.$applyAsync(function() { fn(scope, { $event: ev }); });
22103        });
22104      }
22105    }
22106}
22107
22108
22109
22110})();
22111(function(){
22112"use strict";
22113
22114/**
22115 * @ngdoc module
22116 * @name material.components.switch
22117 */
22118
22119MdSwitch.$inject = ["mdCheckboxDirective", "$mdUtil", "$mdConstant", "$parse", "$$rAF", "$mdGesture", "$timeout"];
22120angular.module('material.components.switch', [
22121  'material.core',
22122  'material.components.checkbox'
22123])
22124  .directive('mdSwitch', MdSwitch);
22125
22126/**
22127 * @ngdoc directive
22128 * @module material.components.switch
22129 * @name mdSwitch
22130 * @restrict E
22131 *
22132 * The switch directive is used very much like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
22133 *
22134 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
22135 * the switch is in the accent color by default. The primary color palette may be used with
22136 * the `md-primary` class.
22137 *
22138 * @param {string} ng-model Assignable angular expression to data-bind to.
22139 * @param {string=} name Property name of the form under which the control is published.
22140 * @param {expression=} ng-true-value The value to which the expression should be set when selected.
22141 * @param {expression=} ng-false-value The value to which the expression should be set when not selected.
22142 * @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element.
22143 * @param {expression=} ng-disabled En/Disable based on the expression.
22144 * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects.
22145 * @param {string=} aria-label Publish the button label used by screen-readers for accessibility. Defaults to the switch's text.
22146 * @param {boolean=} md-invert When set to true, the switch will be inverted.
22147 *
22148 * @usage
22149 * <hljs lang="html">
22150 * <md-switch ng-model="isActive" aria-label="Finished?">
22151 *   Finished ?
22152 * </md-switch>
22153 *
22154 * <md-switch md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
22155 *   No Ink Effects
22156 * </md-switch>
22157 *
22158 * <md-switch ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
22159 *   Disabled
22160 * </md-switch>
22161 *
22162 * </hljs>
22163 */
22164function MdSwitch(mdCheckboxDirective, $mdUtil, $mdConstant, $parse, $$rAF, $mdGesture, $timeout) {
22165  var checkboxDirective = mdCheckboxDirective[0];
22166
22167  return {
22168    restrict: 'E',
22169    priority: $mdConstant.BEFORE_NG_ARIA,
22170    transclude: true,
22171    template:
22172      '<div class="md-container">' +
22173        '<div class="md-bar"></div>' +
22174        '<div class="md-thumb-container">' +
22175          '<div class="md-thumb" md-ink-ripple md-ink-ripple-checkbox></div>' +
22176        '</div>'+
22177      '</div>' +
22178      '<div ng-transclude class="md-label"></div>',
22179    require: ['^?mdInputContainer', '?ngModel', '?^form'],
22180    compile: mdSwitchCompile
22181  };
22182
22183  function mdSwitchCompile(element, attr) {
22184    var checkboxLink = checkboxDirective.compile(element, attr).post;
22185    // No transition on initial load.
22186    element.addClass('md-dragging');
22187
22188    return function (scope, element, attr, ctrls) {
22189      var containerCtrl = ctrls[0];
22190      var ngModel = ctrls[1] || $mdUtil.fakeNgModel();
22191      var formCtrl = ctrls[2];
22192
22193      var disabledGetter = null;
22194      if (attr.disabled != null) {
22195        disabledGetter = function() { return true; };
22196      } else if (attr.ngDisabled) {
22197        disabledGetter = $parse(attr.ngDisabled);
22198      }
22199
22200      var thumbContainer = angular.element(element[0].querySelector('.md-thumb-container'));
22201      var switchContainer = angular.element(element[0].querySelector('.md-container'));
22202      var labelContainer = angular.element(element[0].querySelector('.md-label'));
22203
22204      // no transition on initial load
22205      $$rAF(function() {
22206        element.removeClass('md-dragging');
22207      });
22208
22209      checkboxLink(scope, element, attr, ctrls);
22210
22211      if (disabledGetter) {
22212        scope.$watch(disabledGetter, function(isDisabled) {
22213          element.attr('tabindex', isDisabled ? -1 : 0);
22214        });
22215      }
22216
22217      attr.$observe('mdInvert', function(newValue) {
22218        var isInverted = $mdUtil.parseAttributeBoolean(newValue);
22219
22220        isInverted ? element.prepend(labelContainer) : element.prepend(switchContainer);
22221
22222        // Toggle a CSS class to update the margin.
22223        element.toggleClass('md-inverted', isInverted);
22224      });
22225
22226      // These events are triggered by setup drag
22227      $mdGesture.register(switchContainer, 'drag');
22228      switchContainer
22229        .on('$md.dragstart', onDragStart)
22230        .on('$md.drag', onDrag)
22231        .on('$md.dragend', onDragEnd);
22232
22233      var drag;
22234      function onDragStart(ev) {
22235        // Don't go if the switch is disabled.
22236        if (disabledGetter && disabledGetter(scope)) return;
22237        ev.stopPropagation();
22238
22239        element.addClass('md-dragging');
22240        drag = {width: thumbContainer.prop('offsetWidth')};
22241      }
22242
22243      function onDrag(ev) {
22244        if (!drag) return;
22245        ev.stopPropagation();
22246        ev.srcEvent && ev.srcEvent.preventDefault();
22247
22248        var percent = ev.pointer.distanceX / drag.width;
22249
22250        //if checked, start from right. else, start from left
22251        var translate = ngModel.$viewValue ?  1 + percent : percent;
22252        // Make sure the switch stays inside its bounds, 0-1%
22253        translate = Math.max(0, Math.min(1, translate));
22254
22255        thumbContainer.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + (100*translate) + '%,0,0)');
22256        drag.translate = translate;
22257      }
22258
22259      function onDragEnd(ev) {
22260        if (!drag) return;
22261        ev.stopPropagation();
22262
22263        element.removeClass('md-dragging');
22264        thumbContainer.css($mdConstant.CSS.TRANSFORM, '');
22265
22266        // We changed if there is no distance (this is a click a click),
22267        // or if the drag distance is >50% of the total.
22268        var isChanged = ngModel.$viewValue ? drag.translate < 0.5 : drag.translate > 0.5;
22269        if (isChanged) {
22270          applyModelValue(!ngModel.$viewValue);
22271        }
22272        drag = null;
22273
22274        // Wait for incoming mouse click
22275        scope.skipToggle = true;
22276        $timeout(function() {
22277          scope.skipToggle = false;
22278        }, 1);
22279      }
22280
22281      function applyModelValue(newValue) {
22282        scope.$apply(function() {
22283          ngModel.$setViewValue(newValue);
22284          ngModel.$render();
22285        });
22286      }
22287
22288    };
22289  }
22290
22291
22292}
22293
22294})();
22295(function(){
22296"use strict";
22297
22298/**
22299  * @ngdoc module
22300  * @name material.components.toast
22301  * @description
22302  * Toast
22303  */
22304MdToastDirective.$inject = ["$mdToast"];
22305MdToastProvider.$inject = ["$$interimElementProvider"];
22306angular.module('material.components.toast', [
22307  'material.core',
22308  'material.components.button'
22309])
22310  .directive('mdToast', MdToastDirective)
22311  .provider('$mdToast', MdToastProvider);
22312
22313/* @ngInject */
22314function MdToastDirective($mdToast) {
22315  return {
22316    restrict: 'E',
22317    link: function postLink(scope, element) {
22318      element.addClass('_md');     // private md component indicator for styling
22319      
22320      // When navigation force destroys an interimElement, then
22321      // listen and $destroy() that interim instance...
22322      scope.$on('$destroy', function() {
22323        $mdToast.destroy();
22324      });
22325    }
22326  };
22327}
22328
22329/**
22330  * @ngdoc service
22331  * @name $mdToast
22332  * @module material.components.toast
22333  *
22334  * @description
22335  * `$mdToast` is a service to build a toast notification on any position
22336  * on the screen with an optional duration, and provides a simple promise API.
22337  *
22338  * The toast will be always positioned at the `bottom`, when the screen size is
22339  * between `600px` and `959px` (`sm` breakpoint)
22340  *
22341  * ## Restrictions on custom toasts
22342  * - The toast's template must have an outer `<md-toast>` element.
22343  * - For a toast action, use element with class `md-action`.
22344  * - Add the class `md-capsule` for curved corners.
22345  *
22346  * ### Custom Presets
22347  * Developers are also able to create their own preset, which can be easily used without repeating
22348  * their options each time.
22349  *
22350  * <hljs lang="js">
22351  *   $mdToastProvider.addPreset('testPreset', {
22352  *     options: function() {
22353  *       return {
22354  *         template:
22355  *           '<md-toast>' +
22356  *             '<div class="md-toast-content">' +
22357  *               'This is a custom preset' +
22358  *             '</div>' +
22359  *           '</md-toast>',
22360  *         controllerAs: 'toast',
22361  *         bindToController: true
22362  *       };
22363  *     }
22364  *   });
22365  * </hljs>
22366  *
22367  * After you created your preset at config phase, you can easily access it.
22368  *
22369  * <hljs lang="js">
22370  *   $mdToast.show(
22371  *     $mdToast.testPreset()
22372  *   );
22373  * </hljs>
22374  *
22375  * ## Parent container notes
22376  *
22377  * The toast is positioned using absolute positioning relative to its first non-static parent
22378  * container. Thus, if the requested parent container uses static positioning, we will temporarily
22379  * set its positioning to `relative` while the toast is visible and reset it when the toast is
22380  * hidden.
22381  *
22382  * Because of this, it is usually best to ensure that the parent container has a fixed height and
22383  * prevents scrolling by setting the `overflow: hidden;` style. Since the position is based off of
22384  * the parent's height, the toast may be mispositioned if you allow the parent to scroll.
22385  *
22386  * You can, however, have a scrollable element inside of the container; just make sure the
22387  * container itself does not scroll.
22388  *
22389  * <hljs lang="html">
22390  * <div layout-fill id="toast-container">
22391  *   <md-content>
22392  *     I can have lots of content and scroll!
22393  *   </md-content>
22394  * </div>
22395  * </hljs>
22396  *
22397  * @usage
22398  * <hljs lang="html">
22399  * <div ng-controller="MyController">
22400  *   <md-button ng-click="openToast()">
22401  *     Open a Toast!
22402  *   </md-button>
22403  * </div>
22404  * </hljs>
22405  *
22406  * <hljs lang="js">
22407  * var app = angular.module('app', ['ngMaterial']);
22408  * app.controller('MyController', function($scope, $mdToast) {
22409  *   $scope.openToast = function($event) {
22410  *     $mdToast.show($mdToast.simple().textContent('Hello!'));
22411  *     // Could also do $mdToast.showSimple('Hello');
22412  *   };
22413  * });
22414  * </hljs>
22415  */
22416
22417/**
22418 * @ngdoc method
22419 * @name $mdToast#showSimple
22420 * 
22421 * @param {string} message The message to display inside the toast
22422 * @description
22423 * Convenience method which builds and shows a simple toast.
22424 *
22425 * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
22426 * rejected with `$mdToast.cancel()`.
22427 *
22428 */
22429
22430 /**
22431  * @ngdoc method
22432  * @name $mdToast#simple
22433  *
22434  * @description
22435  * Builds a preconfigured toast.
22436  *
22437  * @returns {obj} a `$mdToastPreset` with the following chainable configuration methods.
22438  *
22439  * _**Note:** These configuration methods are provided in addition to the methods provided by
22440  * the `build()` and `show()` methods below._
22441  *
22442  * <table class="md-api-table methods">
22443  *    <thead>
22444  *      <tr>
22445  *        <th>Method</th>
22446  *        <th>Description</th>
22447  *      </tr>
22448  *    </thead>
22449  *    <tbody>
22450  *      <tr>
22451  *        <td>`.textContent(string)`</td>
22452  *        <td>Sets the toast content to the specified string</td>
22453  *      </tr>
22454  *      <tr>
22455  *        <td>`.action(string)`</td>
22456  *        <td>
22457  *          Adds an action button. <br/>
22458  *          If clicked, the promise (returned from `show()`)
22459  *          will resolve with the value `'ok'`; otherwise, it is resolved with `true` after a `hideDelay`
22460  *          timeout
22461  *        </td>
22462  *      </tr>
22463  *      <tr>
22464  *        <td>`.highlightAction(boolean)`</td>
22465  *        <td>
22466  *          Whether or not the action button will have an additional highlight class.<br/>
22467  *          By default the `accent` color will be applied to the action button.
22468  *        </td>
22469  *      </tr>
22470  *      <tr>
22471  *        <td>`.highlightClass(string)`</td>
22472  *        <td>
22473  *          If set, the given class will be applied to the highlighted action button.<br/>
22474  *          This allows you to specify the highlight color easily. Highlight classes are `md-primary`, `md-warn`
22475  *          and `md-accent`
22476  *        </td>
22477  *      </tr>
22478  *      <tr>
22479  *        <td>`.capsule(boolean)`</td>
22480  *        <td>Whether or not to add the `md-capsule` class to the toast to provide rounded corners</td>
22481  *      </tr>
22482  *      <tr>
22483  *        <td>`.theme(string)`</td>
22484  *        <td>Sets the theme on the toast to the requested theme. Default is `$mdThemingProvider`'s default.</td>
22485  *      </tr>
22486  *      <tr>
22487  *        <td>`.toastClass(string)`</td>
22488  *        <td>Sets a class on the toast element</td>
22489  *      </tr>
22490  *    </tbody>
22491  * </table>
22492  *
22493  */
22494
22495/**
22496  * @ngdoc method
22497  * @name $mdToast#updateTextContent
22498  *
22499  * @description
22500  * Updates the content of an existing toast. Useful for updating things like counts, etc.
22501  *
22502  */
22503
22504 /**
22505  * @ngdoc method
22506  * @name $mdToast#build
22507  *
22508  * @description
22509  * Creates a custom `$mdToastPreset` that you can configure.
22510  *
22511  * @returns {obj} a `$mdToastPreset` with the chainable configuration methods for shows' options (see below).
22512  */
22513
22514 /**
22515  * @ngdoc method
22516  * @name $mdToast#show
22517  *
22518  * @description Shows the toast.
22519  *
22520  * @param {object} optionsOrPreset Either provide an `$mdToastPreset` returned from `simple()`
22521  * and `build()`, or an options object with the following properties:
22522  *
22523  *   - `templateUrl` - `{string=}`: The url of an html template file that will
22524  *     be used as the content of the toast. Restrictions: the template must
22525  *     have an outer `md-toast` element.
22526  *   - `template` - `{string=}`: Same as templateUrl, except this is an actual
22527  *     template string.
22528  *   - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template content with a
22529  *     `<div class="md-toast-content">` if one is not provided. Defaults to true. Can be disabled if you provide a
22530  *     custom toast directive.
22531  *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
22532  *     This scope will be destroyed when the toast is removed unless `preserveScope` is set to true.
22533  *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
22534  *   - `hideDelay` - `{number=}`: How many milliseconds the toast should stay
22535  *     active before automatically closing.  Set to 0 or false to have the toast stay open until
22536  *     closed manually. Default: 3000.
22537  *   - `position` - `{string=}`: Sets the position of the toast. <br/>
22538  *     Available: any combination of `'bottom'`, `'left'`, `'top'`, `'right'`, `'end'` and `'start'`.
22539  *     The properties `'end'` and `'start'` are dynamic and can be used for RTL support.<br/>
22540  *     Default combination: `'bottom left'`.
22541  *   - `toastClass` - `{string=}`: A class to set on the toast element.
22542  *   - `controller` - `{string=}`: The controller to associate with this toast.
22543  *     The controller will be injected the local `$mdToast.hide( )`, which is a function
22544  *     used to hide the toast.
22545  *   - `locals` - `{string=}`: An object containing key/value pairs. The keys will
22546  *     be used as names of values to inject into the controller. For example,
22547  *     `locals: {three: 3}` would inject `three` into the controller with the value
22548  *     of 3.
22549  *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
22550  *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
22551  *     and the toast will not open until the promises resolve.
22552  *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
22553  *   - `parent` - `{element=}`: The element to append the toast to. Defaults to appending
22554  *     to the root element of the application.
22555  *
22556  * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
22557  * rejected with `$mdToast.cancel()`. `$mdToast.hide()` will resolve either with a Boolean
22558  * value == 'true' or the value passed as an argument to `$mdToast.hide()`.
22559  * And `$mdToast.cancel()` will resolve the promise with a Boolean value == 'false'
22560  */
22561
22562/**
22563  * @ngdoc method
22564  * @name $mdToast#hide
22565  *
22566  * @description
22567  * Hide an existing toast and resolve the promise returned from `$mdToast.show()`.
22568  *
22569  * @param {*=} response An argument for the resolved promise.
22570  *
22571  * @returns {promise} a promise that is called when the existing element is removed from the DOM.
22572  * The promise is resolved with either a Boolean value == 'true' or the value passed as the
22573  * argument to `.hide()`.
22574  *
22575  */
22576
22577/**
22578  * @ngdoc method
22579  * @name $mdToast#cancel
22580  *
22581  * @description
22582  * `DEPRECATED` - The promise returned from opening a toast is used only to notify about the closing of the toast.
22583  * As such, there isn't any reason to also allow that promise to be rejected,
22584  * since it's not clear what the difference between resolve and reject would be.
22585  *
22586  * Hide the existing toast and reject the promise returned from
22587  * `$mdToast.show()`.
22588  *
22589  * @param {*=} response An argument for the rejected promise.
22590  *
22591  * @returns {promise} a promise that is called when the existing element is removed from the DOM
22592  * The promise is resolved with a Boolean value == 'false'.
22593  *
22594  */
22595
22596function MdToastProvider($$interimElementProvider) {
22597  // Differentiate promise resolves: hide timeout (value == true) and hide action clicks (value == ok).
22598  toastDefaultOptions.$inject = ["$animate", "$mdToast", "$mdUtil", "$mdMedia"];
22599  var ACTION_RESOLVE = 'ok';
22600
22601  var activeToastContent;
22602  var $mdToast = $$interimElementProvider('$mdToast')
22603    .setDefaults({
22604      methods: ['position', 'hideDelay', 'capsule', 'parent', 'position', 'toastClass'],
22605      options: toastDefaultOptions
22606    })
22607    .addPreset('simple', {
22608      argOption: 'textContent',
22609      methods: ['textContent', 'content', 'action', 'highlightAction', 'highlightClass', 'theme', 'parent' ],
22610      options: /* @ngInject */ ["$mdToast", "$mdTheming", function($mdToast, $mdTheming) {
22611        return {
22612          template:
22613            '<md-toast md-theme="{{ toast.theme }}" ng-class="{\'md-capsule\': toast.capsule}">' +
22614            '  <div class="md-toast-content">' +
22615            '    <span class="md-toast-text" role="alert" aria-relevant="all" aria-atomic="true">' +
22616            '      {{ toast.content }}' +
22617            '    </span>' +
22618            '    <md-button class="md-action" ng-if="toast.action" ng-click="toast.resolve()" ' +
22619            '        ng-class="highlightClasses">' +
22620            '      {{ toast.action }}' +
22621            '    </md-button>' +
22622            '  </div>' +
22623            '</md-toast>',
22624          controller: /* @ngInject */ ["$scope", function mdToastCtrl($scope) {
22625            var self = this;
22626
22627            if (self.highlightAction) {
22628              $scope.highlightClasses = [
22629                'md-highlight',
22630                self.highlightClass
22631              ]
22632            }
22633
22634            $scope.$watch(function() { return activeToastContent; }, function() {
22635              self.content = activeToastContent;
22636            });
22637
22638            this.resolve = function() {
22639              $mdToast.hide( ACTION_RESOLVE );
22640            };
22641          }],
22642          theme: $mdTheming.defaultTheme(),
22643          controllerAs: 'toast',
22644          bindToController: true
22645        };
22646      }]
22647    })
22648    .addMethod('updateTextContent', updateTextContent)
22649    .addMethod('updateContent', updateTextContent);
22650
22651    function updateTextContent(newContent) {
22652      activeToastContent = newContent;
22653    }
22654
22655    return $mdToast;
22656
22657  /* @ngInject */
22658  function toastDefaultOptions($animate, $mdToast, $mdUtil, $mdMedia) {
22659    var SWIPE_EVENTS = '$md.swipeleft $md.swiperight $md.swipeup $md.swipedown';
22660    return {
22661      onShow: onShow,
22662      onRemove: onRemove,
22663      toastClass: '',
22664      position: 'bottom left',
22665      themable: true,
22666      hideDelay: 3000,
22667      autoWrap: true,
22668      transformTemplate: function(template, options) {
22669        var shouldAddWrapper = options.autoWrap && template && !/md-toast-content/g.test(template);
22670
22671        if (shouldAddWrapper) {
22672          // Root element of template will be <md-toast>. We need to wrap all of its content inside of
22673          // of <div class="md-toast-content">. All templates provided here should be static, developer-controlled
22674          // content (meaning we're not attempting to guard against XSS).
22675          var templateRoot = document.createElement('md-template');
22676          templateRoot.innerHTML = template;
22677
22678          // Iterate through all root children, to detect possible md-toast directives.
22679          for (var i = 0; i < templateRoot.children.length; i++) {
22680            if (templateRoot.children[i].nodeName === 'MD-TOAST') {
22681              var wrapper = angular.element('<div class="md-toast-content">');
22682
22683              // Wrap the children of the `md-toast` directive in jqLite, to be able to append multiple
22684              // nodes with the same execution.
22685              wrapper.append(angular.element(templateRoot.children[i].childNodes));
22686
22687              // Append the new wrapped element to the `md-toast` directive.
22688              templateRoot.children[i].appendChild(wrapper[0]);
22689            }
22690          }
22691
22692          // We have to return the innerHTMl, because we do not want to have the `md-template` element to be
22693          // the root element of our interimElement.
22694          return templateRoot.innerHTML;
22695        }
22696
22697        return template || '';
22698      }
22699    };
22700
22701    function onShow(scope, element, options) {
22702      activeToastContent = options.textContent || options.content; // support deprecated #content method
22703
22704      var isSmScreen = !$mdMedia('gt-sm');
22705
22706      element = $mdUtil.extractElementByName(element, 'md-toast', true);
22707      options.element = element;
22708
22709      options.onSwipe = function(ev, gesture) {
22710        //Add the relevant swipe class to the element so it can animate correctly
22711        var swipe = ev.type.replace('$md.','');
22712        var direction = swipe.replace('swipe', '');
22713
22714        // If the swipe direction is down/up but the toast came from top/bottom don't fade away
22715        // Unless the screen is small, then the toast always on bottom
22716        if ((direction === 'down' && options.position.indexOf('top') != -1 && !isSmScreen) ||
22717            (direction === 'up' && (options.position.indexOf('bottom') != -1 || isSmScreen))) {
22718          return;
22719        }
22720
22721        if ((direction === 'left' || direction === 'right') && isSmScreen) {
22722          return;
22723        }
22724
22725        element.addClass('md-' + swipe);
22726        $mdUtil.nextTick($mdToast.cancel);
22727      };
22728      options.openClass = toastOpenClass(options.position);
22729
22730      element.addClass(options.toastClass);
22731
22732      // 'top left' -> 'md-top md-left'
22733      options.parent.addClass(options.openClass);
22734
22735      // static is the default position
22736      if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {
22737        options.parent.css('position', 'relative');
22738      }
22739
22740      element.on(SWIPE_EVENTS, options.onSwipe);
22741      element.addClass(isSmScreen ? 'md-bottom' : options.position.split(' ').map(function(pos) {
22742        return 'md-' + pos;
22743      }).join(' '));
22744
22745      if (options.parent) options.parent.addClass('md-toast-animating');
22746      return $animate.enter(element, options.parent).then(function() {
22747        if (options.parent) options.parent.removeClass('md-toast-animating');
22748      });
22749    }
22750
22751    function onRemove(scope, element, options) {
22752      element.off(SWIPE_EVENTS, options.onSwipe);
22753      if (options.parent) options.parent.addClass('md-toast-animating');
22754      if (options.openClass) options.parent.removeClass(options.openClass);
22755
22756      return ((options.$destroy == true) ? element.remove() : $animate.leave(element))
22757        .then(function () {
22758          if (options.parent) options.parent.removeClass('md-toast-animating');
22759          if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {
22760            options.parent.css('position', '');
22761          }
22762        });
22763    }
22764
22765    function toastOpenClass(position) {
22766      // For mobile, always open full-width on bottom
22767      if (!$mdMedia('gt-xs')) {
22768        return 'md-toast-open-bottom';
22769      }
22770
22771      return 'md-toast-open-' +
22772        (position.indexOf('top') > -1 ? 'top' : 'bottom');
22773    }
22774  }
22775
22776}
22777
22778})();
22779(function(){
22780"use strict";
22781
22782/**
22783 * @ngdoc module
22784 * @name material.components.tabs
22785 * @description
22786 *
22787 *  Tabs, created with the `<md-tabs>` directive provide *tabbed* navigation with different styles.
22788 *  The Tabs component consists of clickable tabs that are aligned horizontally side-by-side.
22789 *
22790 *  Features include support for:
22791 *
22792 *  - static or dynamic tabs,
22793 *  - responsive designs,
22794 *  - accessibility support (ARIA),
22795 *  - tab pagination,
22796 *  - external or internal tab content,
22797 *  - focus indicators and arrow-key navigations,
22798 *  - programmatic lookup and access to tab controllers, and
22799 *  - dynamic transitions through different tab contents.
22800 *
22801 */
22802/*
22803 * @see js folder for tabs implementation
22804 */
22805angular.module('material.components.tabs', [
22806  'material.core',
22807  'material.components.icon'
22808]);
22809
22810})();
22811(function(){
22812"use strict";
22813
22814/**
22815 * @ngdoc module
22816 * @name material.components.toolbar
22817 */
22818mdToolbarDirective.$inject = ["$$rAF", "$mdConstant", "$mdUtil", "$mdTheming", "$animate"];
22819angular.module('material.components.toolbar', [
22820  'material.core',
22821  'material.components.content'
22822])
22823  .directive('mdToolbar', mdToolbarDirective);
22824
22825/**
22826 * @ngdoc directive
22827 * @name mdToolbar
22828 * @module material.components.toolbar
22829 * @restrict E
22830 * @description
22831 * `md-toolbar` is used to place a toolbar in your app.
22832 *
22833 * Toolbars are usually used above a content area to display the title of the
22834 * current page, and show relevant action buttons for that page.
22835 *
22836 * You can change the height of the toolbar by adding either the
22837 * `md-medium-tall` or `md-tall` class to the toolbar.
22838 *
22839 * @usage
22840 * <hljs lang="html">
22841 * <div layout="column" layout-fill>
22842 *   <md-toolbar>
22843 *
22844 *     <div class="md-toolbar-tools">
22845 *       <h2 md-truncate flex>My App's Title</h2>
22846 *
22847 *       <md-button>
22848 *         Right Bar Button
22849 *       </md-button>
22850 *     </div>
22851 *
22852 *   </md-toolbar>
22853 *   <md-content>
22854 *     Hello!
22855 *   </md-content>
22856 * </div>
22857 * </hljs>
22858 *
22859 * <i><b>Note:</b> The code above shows usage with the `md-truncate` component which provides an
22860 * ellipsis if the title is longer than the width of the Toolbar.</i>
22861 *
22862 * ## CSS & Styles
22863 *
22864 * The `<md-toolbar>` provides a few custom CSS classes that you may use to enhance the
22865 * functionality of your toolbar.
22866 *
22867 * <div>
22868 * <docs-css-api-table>
22869 *
22870 *   <docs-css-selector code="md-toolbar .md-toolbar-tools">
22871 *     The `md-toolbar-tools` class provides quite a bit of automatic styling for your toolbar
22872 *     buttons and text. When applied, it will center the buttons and text vertically for you.
22873 *   </docs-css-selector>
22874 *
22875 * </docs-css-api-table>
22876 * </div>
22877 *
22878 * ### Private Classes
22879 *
22880 * Currently, the only private class is the `md-toolbar-transitions` class. All other classes are
22881 * considered public.
22882 *
22883 * @param {boolean=} md-scroll-shrink Whether the header should shrink away as
22884 * the user scrolls down, and reveal itself as the user scrolls up.
22885 *
22886 * _**Note (1):** for scrollShrink to work, the toolbar must be a sibling of a
22887 * `md-content` element, placed before it. See the scroll shrink demo._
22888 *
22889 * _**Note (2):** The `md-scroll-shrink` attribute is only parsed on component
22890 * initialization, it does not watch for scope changes._
22891 *
22892 *
22893 * @param {number=} md-shrink-speed-factor How much to change the speed of the toolbar's
22894 * shrinking by. For example, if 0.25 is given then the toolbar will shrink
22895 * at one fourth the rate at which the user scrolls down. Default 0.5.
22896 *
22897 */
22898
22899function mdToolbarDirective($$rAF, $mdConstant, $mdUtil, $mdTheming, $animate) {
22900  var translateY = angular.bind(null, $mdUtil.supplant, 'translate3d(0,{0}px,0)');
22901
22902  return {
22903    template: '',
22904    restrict: 'E',
22905
22906    link: function(scope, element, attr) {
22907
22908      element.addClass('_md');     // private md component indicator for styling
22909      $mdTheming(element);
22910
22911      $mdUtil.nextTick(function () {
22912        element.addClass('_md-toolbar-transitions');     // adding toolbar transitions after digest
22913      }, false);
22914
22915      if (angular.isDefined(attr.mdScrollShrink)) {
22916        setupScrollShrink();
22917      }
22918
22919      function setupScrollShrink() {
22920
22921        var toolbarHeight;
22922        var contentElement;
22923        var disableScrollShrink = angular.noop;
22924
22925        // Current "y" position of scroll
22926        // Store the last scroll top position
22927        var y = 0;
22928        var prevScrollTop = 0;
22929        var shrinkSpeedFactor = attr.mdShrinkSpeedFactor || 0.5;
22930
22931        var debouncedContentScroll = $$rAF.throttle(onContentScroll);
22932        var debouncedUpdateHeight = $mdUtil.debounce(updateToolbarHeight, 5 * 1000);
22933
22934        // Wait for $mdContentLoaded event from mdContent directive.
22935        // If the mdContent element is a sibling of our toolbar, hook it up
22936        // to scroll events.
22937
22938        scope.$on('$mdContentLoaded', onMdContentLoad);
22939
22940        // If the toolbar is used inside an ng-if statement, we may miss the
22941        // $mdContentLoaded event, so we attempt to fake it if we have a
22942        // md-content close enough.
22943
22944        attr.$observe('mdScrollShrink', onChangeScrollShrink);
22945
22946        // If the toolbar has ngShow or ngHide we need to update height immediately as it changed
22947        // and not wait for $mdUtil.debounce to happen
22948
22949        if (attr.ngShow) { scope.$watch(attr.ngShow, updateToolbarHeight); }
22950        if (attr.ngHide) { scope.$watch(attr.ngHide, updateToolbarHeight); }
22951
22952        // If the scope is destroyed (which could happen with ng-if), make sure
22953        // to disable scroll shrinking again
22954
22955        scope.$on('$destroy', disableScrollShrink);
22956
22957        /**
22958         *
22959         */
22960        function onChangeScrollShrink(shrinkWithScroll) {
22961          var closestContent = element.parent().find('md-content');
22962
22963          // If we have a content element, fake the call; this might still fail
22964          // if the content element isn't a sibling of the toolbar
22965
22966          if (!contentElement && closestContent.length) {
22967            onMdContentLoad(null, closestContent);
22968          }
22969
22970          // Evaluate the expression
22971          shrinkWithScroll = scope.$eval(shrinkWithScroll);
22972
22973          // Disable only if the attribute's expression evaluates to false
22974          if (shrinkWithScroll === false) {
22975            disableScrollShrink();
22976          } else {
22977            disableScrollShrink = enableScrollShrink();
22978          }
22979        }
22980
22981        /**
22982         *
22983         */
22984        function onMdContentLoad($event, newContentEl) {
22985          // Toolbar and content must be siblings
22986          if (newContentEl && element.parent()[0] === newContentEl.parent()[0]) {
22987            // unhook old content event listener if exists
22988            if (contentElement) {
22989              contentElement.off('scroll', debouncedContentScroll);
22990            }
22991
22992            contentElement = newContentEl;
22993            disableScrollShrink = enableScrollShrink();
22994          }
22995        }
22996
22997        /**
22998         *
22999         */
23000        function onContentScroll(e) {
23001          var scrollTop = e ? e.target.scrollTop : prevScrollTop;
23002
23003          debouncedUpdateHeight();
23004
23005          y = Math.min(
23006            toolbarHeight / shrinkSpeedFactor,
23007            Math.max(0, y + scrollTop - prevScrollTop)
23008          );
23009
23010          element.css($mdConstant.CSS.TRANSFORM, translateY([-y * shrinkSpeedFactor]));
23011          contentElement.css($mdConstant.CSS.TRANSFORM, translateY([(toolbarHeight - y) * shrinkSpeedFactor]));
23012
23013          prevScrollTop = scrollTop;
23014
23015          $mdUtil.nextTick(function() {
23016            var hasWhiteFrame = element.hasClass('md-whiteframe-z1');
23017
23018            if (hasWhiteFrame && !y) {
23019              $animate.removeClass(element, 'md-whiteframe-z1');
23020            } else if (!hasWhiteFrame && y) {
23021              $animate.addClass(element, 'md-whiteframe-z1');
23022            }
23023          });
23024
23025        }
23026
23027        /**
23028         *
23029         */
23030        function enableScrollShrink() {
23031          if (!contentElement)     return angular.noop;           // no md-content
23032
23033          contentElement.on('scroll', debouncedContentScroll);
23034          contentElement.attr('scroll-shrink', 'true');
23035
23036          $mdUtil.nextTick(updateToolbarHeight, false);
23037
23038          return function disableScrollShrink() {
23039            contentElement.off('scroll', debouncedContentScroll);
23040            contentElement.attr('scroll-shrink', 'false');
23041
23042            updateToolbarHeight();
23043          };
23044        }
23045
23046        /**
23047         *
23048         */
23049        function updateToolbarHeight() {
23050          toolbarHeight = element.prop('offsetHeight');
23051          // Add a negative margin-top the size of the toolbar to the content el.
23052          // The content will start transformed down the toolbarHeight amount,
23053          // so everything looks normal.
23054          //
23055          // As the user scrolls down, the content will be transformed up slowly
23056          // to put the content underneath where the toolbar was.
23057          var margin = (-toolbarHeight * shrinkSpeedFactor) + 'px';
23058
23059          contentElement.css({
23060            "margin-top": margin,
23061            "margin-bottom": margin
23062          });
23063
23064          onContentScroll();
23065        }
23066
23067      }
23068
23069    }
23070  };
23071
23072}
23073
23074})();
23075(function(){
23076"use strict";
23077
23078/**
23079 * @ngdoc module
23080 * @name material.components.tooltip
23081 */
23082MdTooltipDirective.$inject = ["$timeout", "$window", "$$rAF", "$document", "$interpolate", "$mdUtil", "$mdPanel", "$$mdTooltipRegistry"];
23083angular
23084    .module('material.components.tooltip', [
23085      'material.core',
23086      'material.components.panel'
23087    ])
23088    .directive('mdTooltip', MdTooltipDirective)
23089    .service('$$mdTooltipRegistry', MdTooltipRegistry);
23090
23091
23092/**
23093 * @ngdoc directive
23094 * @name mdTooltip
23095 * @module material.components.tooltip
23096 * @description
23097 * Tooltips are used to describe elements that are interactive and primarily
23098 * graphical (not textual).
23099 *
23100 * Place a `<md-tooltip>` as a child of the element it describes.
23101 *
23102 * A tooltip will activate when the user hovers over, focuses, or touches the
23103 * parent element.
23104 *
23105 * @usage
23106 * <hljs lang="html">
23107 *   <md-button class="md-fab md-accent" aria-label="Play">
23108 *     <md-tooltip>Play Music</md-tooltip>
23109 *     <md-icon md-svg-src="img/icons/ic_play_arrow_24px.svg"></md-icon>
23110 *   </md-button>
23111 * </hljs>
23112 *
23113 * @param {number=} md-z-index The visual level that the tooltip will appear
23114 *     in comparison with the rest of the elements of the application.
23115 * @param {expression=} md-visible Boolean bound to whether the tooltip is
23116 *     currently visible.
23117 * @param {number=} md-delay How many milliseconds to wait to show the tooltip
23118 *     after the user hovers over, focuses, or touches the parent element.
23119 *     Defaults to 0ms on non-touch devices and 75ms on touch.
23120 * @param {boolean=} md-autohide If present or provided with a boolean value,
23121 *     the tooltip will hide on mouse leave, regardless of focus.
23122 * @param {string=} md-direction The direction that the tooltip is shown,
23123 *     relative to the parent element. Supports top, right, bottom, and left.
23124 *     Defaults to bottom.
23125 */
23126function MdTooltipDirective($timeout, $window, $$rAF, $document, $interpolate,
23127    $mdUtil, $mdPanel, $$mdTooltipRegistry) {
23128
23129  var ENTER_EVENTS = 'focus touchstart mouseenter';
23130  var LEAVE_EVENTS = 'blur touchcancel mouseleave';
23131  var TOOLTIP_DEFAULT_Z_INDEX = 100;
23132  var TOOLTIP_DEFAULT_SHOW_DELAY = 0;
23133  var TOOLTIP_DEFAULT_DIRECTION = 'bottom';
23134  var TOOLTIP_DIRECTIONS = {
23135    top: { x: $mdPanel.xPosition.CENTER, y: $mdPanel.yPosition.ABOVE },
23136    right: { x: $mdPanel.xPosition.OFFSET_END, y: $mdPanel.yPosition.CENTER },
23137    bottom: { x: $mdPanel.xPosition.CENTER, y: $mdPanel.yPosition.BELOW },
23138    left: { x: $mdPanel.xPosition.OFFSET_START, y: $mdPanel.yPosition.CENTER }
23139  };
23140
23141  return {
23142    restrict: 'E',
23143    priority: 210, // Before ngAria
23144    scope: {
23145      mdZIndex: '=?mdZIndex',
23146      mdDelay: '=?mdDelay',
23147      mdVisible: '=?mdVisible',
23148      mdAutohide: '=?mdAutohide',
23149      mdDirection: '@?mdDirection' // Do not expect expressions.
23150    },
23151    link: linkFunc
23152  };
23153
23154  function linkFunc(scope, element, attr) {
23155    // Set constants.
23156    var parent = $mdUtil.getParentWithPointerEvents(element);
23157    var debouncedOnResize = $$rAF.throttle(updatePosition);
23158    var mouseActive = false;
23159    var origin, position, panelPosition, panelRef, autohide, showTimeout,
23160        elementFocusedOnWindowBlur = null;
23161
23162    // Set defaults
23163    setDefaults();
23164
23165    // Set parent aria-label.
23166    addAriaLabel();
23167
23168    // Remove the element from its current DOM position.
23169    element.detach();
23170
23171    updatePosition();
23172    bindEvents();
23173    configureWatchers();
23174
23175    function setDefaults() {
23176      scope.mdZIndex = scope.mdZIndex || TOOLTIP_DEFAULT_Z_INDEX;
23177      scope.mdDelay = scope.mdDelay || TOOLTIP_DEFAULT_SHOW_DELAY;
23178      if (!TOOLTIP_DIRECTIONS[scope.mdDirection]) {
23179        scope.mdDirection = TOOLTIP_DEFAULT_DIRECTION;
23180      }
23181    }
23182
23183    function addAriaLabel(override) {
23184      if (override || !parent.attr('aria-label')) {
23185        // Only interpolate the text from the HTML element because otherwise the custom text
23186        // could be interpolated twice and cause XSS violations.
23187        var interpolatedText = override || $interpolate(element.text().trim())(scope.$parent);
23188        parent.attr('aria-label', interpolatedText);
23189      }
23190    }
23191
23192    function updatePosition() {
23193      setDefaults();
23194
23195      // If the panel has already been created, remove the current origin
23196      // class from the panel element.
23197      if (panelRef && panelRef.panelEl) {
23198        panelRef.panelEl.removeClass(origin);
23199      }
23200
23201      // Set the panel element origin class based off of the current
23202      // mdDirection.
23203      origin = 'md-origin-' + scope.mdDirection;
23204
23205      // Create the position of the panel based off of the mdDirection.
23206      position = TOOLTIP_DIRECTIONS[scope.mdDirection];
23207
23208      // Using the newly created position object, use the MdPanel
23209      // panelPosition API to build the panel's position.
23210      panelPosition = $mdPanel.newPanelPosition()
23211          .relativeTo(parent)
23212          .addPanelPosition(position.x, position.y);
23213
23214      // If the panel has already been created, add the new origin class to
23215      // the panel element and update it's position with the panelPosition.
23216      if (panelRef && panelRef.panelEl) {
23217        panelRef.panelEl.addClass(origin);
23218        panelRef.updatePosition(panelPosition);
23219      }
23220    }
23221
23222    function bindEvents() {
23223      // Add a mutationObserver where there is support for it and the need
23224      // for it in the form of viable host(parent[0]).
23225      if (parent[0] && 'MutationObserver' in $window) {
23226        // Use a mutationObserver to tackle #2602.
23227        var attributeObserver = new MutationObserver(function(mutations) {
23228          if (isDisabledMutation(mutations)) {
23229            $mdUtil.nextTick(function() {
23230              setVisible(false);
23231            });
23232          }
23233        });
23234
23235        attributeObserver.observe(parent[0], {
23236          attributes: true
23237        });
23238      }
23239
23240      elementFocusedOnWindowBlur = false;
23241
23242      $$mdTooltipRegistry.register('scroll', windowScrollEventHandler, true);
23243      $$mdTooltipRegistry.register('blur', windowBlurEventHandler);
23244      $$mdTooltipRegistry.register('resize', debouncedOnResize);
23245
23246      scope.$on('$destroy', onDestroy);
23247
23248      // To avoid 'synthetic clicks', we listen to mousedown instead of
23249      // 'click'.
23250      parent.on('mousedown', mousedownEventHandler);
23251      parent.on(ENTER_EVENTS, enterEventHandler);
23252
23253      function isDisabledMutation(mutations) {
23254        mutations.some(function(mutation) {
23255          return mutation.attributeName === 'disabled' && parent[0].disabled;
23256        });
23257        return false;
23258      }
23259
23260      function windowScrollEventHandler() {
23261        setVisible(false);
23262      }
23263
23264      function windowBlurEventHandler() {
23265        elementFocusedOnWindowBlur = document.activeElement === parent[0];
23266      }
23267
23268      function enterEventHandler($event) {
23269        // Prevent the tooltip from showing when the window is receiving
23270        // focus.
23271        if ($event.type === 'focus' && elementFocusedOnWindowBlur) {
23272          elementFocusedOnWindowBlur = false;
23273        } else if (!scope.mdVisible) {
23274          parent.on(LEAVE_EVENTS, leaveEventHandler);
23275          setVisible(true);
23276
23277          // If the user is on a touch device, we should bind the tap away
23278          // after the 'touched' in order to prevent the tooltip being
23279          // removed immediately.
23280          if ($event.type === 'touchstart') {
23281            parent.one('touchend', function() {
23282              $mdUtil.nextTick(function() {
23283                $document.one('touchend', leaveEventHandler);
23284              }, false);
23285            });
23286          }
23287        }
23288      }
23289
23290      function leaveEventHandler() {
23291        autohide = scope.hasOwnProperty('mdAutohide') ?
23292            scope.mdAutohide :
23293            attr.hasOwnProperty('mdAutohide');
23294
23295        if (autohide || mouseActive ||
23296            $document[0].activeElement !== parent[0]) {
23297          // When a show timeout is currently in progress, then we have
23298          // to cancel it, otherwise the tooltip will remain showing
23299          // without focus or hover.
23300          if (showTimeout) {
23301            $timeout.cancel(showTimeout);
23302            setVisible.queued = false;
23303            showTimeout = null;
23304          }
23305
23306          parent.off(LEAVE_EVENTS, leaveEventHandler);
23307          parent.triggerHandler('blur');
23308          setVisible(false);
23309        }
23310        mouseActive = false;
23311      }
23312
23313      function mousedownEventHandler() {
23314        mouseActive = true;
23315      }
23316
23317      function onDestroy() {
23318        $$mdTooltipRegistry.deregister('scroll', windowScrollEventHandler, true);
23319        $$mdTooltipRegistry.deregister('blur', windowBlurEventHandler);
23320        $$mdTooltipRegistry.deregister('resize', debouncedOnResize);
23321
23322        parent
23323            .off(ENTER_EVENTS, enterEventHandler)
23324            .off(LEAVE_EVENTS, leaveEventHandler)
23325            .off('mousedown', mousedownEventHandler);
23326
23327        // Trigger the handler in case any of the tooltips are
23328        // still visible.
23329        leaveEventHandler();
23330        attributeObserver && attributeObserver.disconnect();
23331      }
23332    }
23333
23334    function configureWatchers() {
23335      if (element[0] && 'MutationObserver' in $window) {
23336        var attributeObserver = new MutationObserver(function(mutations) {
23337          mutations.forEach(function(mutation) {
23338            if (mutation.attributeName === 'md-visible' &&
23339                !scope.visibleWatcher ) {
23340              scope.visibleWatcher = scope.$watch('mdVisible',
23341                  onVisibleChanged);
23342            }
23343          });
23344        });
23345
23346        attributeObserver.observe(element[0], {
23347          attributes: true
23348        });
23349
23350        // Build watcher only if mdVisible is being used.
23351        if (attr.hasOwnProperty('mdVisible')) {
23352          scope.visibleWatcher = scope.$watch('mdVisible',
23353              onVisibleChanged);
23354        }
23355      } else {
23356        // MutationObserver not supported
23357        scope.visibleWatcher = scope.$watch('mdVisible', onVisibleChanged);
23358      }
23359
23360      // Direction watcher
23361      scope.$watch('mdDirection', updatePosition);
23362
23363      // Clean up if the element or parent was removed via jqLite's .remove.
23364      // A couple of notes:
23365      //   - In these cases the scope might not have been destroyed, which
23366      //     is why we destroy it manually. An example of this can be having
23367      //     `md-visible="false"` and adding tooltips while they're
23368      //     invisible. If `md-visible` becomes true, at some point, you'd
23369      //     usually get a lot of tooltips.
23370      //   - We use `.one`, not `.on`, because this only needs to fire once.
23371      //     If we were using `.on`, it would get thrown into an infinite
23372      //     loop.
23373      //   - This kicks off the scope's `$destroy` event which finishes the
23374      //     cleanup.
23375      element.one('$destroy', onElementDestroy);
23376      parent.one('$destroy', onElementDestroy);
23377      scope.$on('$destroy', function() {
23378        setVisible(false);
23379        panelRef && panelRef.destroy();
23380        attributeObserver && attributeObserver.disconnect();
23381        element.remove();
23382      });
23383
23384      // Updates the aria-label when the element text changes. This watch
23385      // doesn't need to be set up if the element doesn't have any data
23386      // bindings.
23387      if (element.text().indexOf($interpolate.startSymbol()) > -1) {
23388        scope.$watch(function() {
23389          return element.text().trim();
23390        }, addAriaLabel);
23391      }
23392
23393      function onElementDestroy() {
23394        scope.$destroy();
23395      }
23396    }
23397
23398    function setVisible(value) {
23399      // Break if passed value is already in queue or there is no queue and
23400      // passed value is current in the controller.
23401      if (setVisible.queued && setVisible.value === !!value ||
23402          !setVisible.queued && scope.mdVisible === !!value) {
23403        return;
23404      }
23405      setVisible.value = !!value;
23406
23407      if (!setVisible.queued) {
23408        if (value) {
23409          setVisible.queued = true;
23410          showTimeout = $timeout(function() {
23411            scope.mdVisible = setVisible.value;
23412            setVisible.queued = false;
23413            showTimeout = null;
23414            if (!scope.visibleWatcher) {
23415              onVisibleChanged(scope.mdVisible);
23416            }
23417          }, scope.mdDelay);
23418        } else {
23419          $mdUtil.nextTick(function() {
23420            scope.mdVisible = false;
23421            if (!scope.visibleWatcher) {
23422              onVisibleChanged(false);
23423            }
23424          });
23425        }
23426      }
23427    }
23428
23429    function onVisibleChanged(isVisible) {
23430      isVisible ? showTooltip() : hideTooltip();
23431    }
23432
23433    function showTooltip() {
23434      // Do not show the tooltip if the text is empty.
23435      if (!element[0].textContent.trim()) {
23436        throw new Error('Text for the tooltip has not been provided. ' +
23437            'Please include text within the mdTooltip element.');
23438      }
23439
23440      if (!panelRef) {
23441        var id = 'tooltip-' + $mdUtil.nextUid();
23442        var attachTo = angular.element(document.body);
23443        var panelAnimation = $mdPanel.newPanelAnimation()
23444            .openFrom(parent)
23445            .closeTo(parent)
23446            .withAnimation({
23447              open: 'md-show',
23448              close: 'md-hide'
23449            });
23450
23451        var panelConfig = {
23452          id: id,
23453          attachTo: attachTo,
23454          contentElement: element,
23455          propagateContainerEvents: true,
23456          panelClass: 'md-tooltip ' + origin,
23457          animation: panelAnimation,
23458          position: panelPosition,
23459          zIndex: scope.mdZIndex,
23460          focusOnOpen: false
23461        };
23462
23463        panelRef = $mdPanel.create(panelConfig);
23464      }
23465
23466      panelRef.open().then(function() {
23467        panelRef.panelEl.attr('role', 'tooltip');
23468      });
23469    }
23470
23471    function hideTooltip() {
23472      panelRef && panelRef.close();
23473    }
23474  }
23475
23476}
23477
23478
23479/**
23480 * Service that is used to reduce the amount of listeners that are being
23481 * registered on the `window` by the tooltip component. Works by collecting
23482 * the individual event handlers and dispatching them from a global handler.
23483 *
23484 * @ngInject
23485 */
23486function MdTooltipRegistry() {
23487  var listeners = {};
23488  var ngWindow = angular.element(window);
23489
23490  return {
23491    register: register,
23492    deregister: deregister
23493  };
23494
23495  /**
23496   * Global event handler that dispatches the registered handlers in the
23497   * service.
23498   * @param {!Event} event Event object passed in by the browser
23499   */
23500  function globalEventHandler(event) {
23501    if (listeners[event.type]) {
23502      listeners[event.type].forEach(function(currentHandler) {
23503        currentHandler.call(this, event);
23504      }, this);
23505    }
23506  }
23507
23508  /**
23509   * Registers a new handler with the service.
23510   * @param {string} type Type of event to be registered.
23511   * @param {!Function} handler Event handler.
23512   * @param {boolean} useCapture Whether to use event capturing.
23513   */
23514  function register(type, handler, useCapture) {
23515    var handlers = listeners[type] = listeners[type] || [];
23516
23517    if (!handlers.length) {
23518      useCapture ? window.addEventListener(type, globalEventHandler, true) :
23519          ngWindow.on(type, globalEventHandler);
23520    }
23521
23522    if (handlers.indexOf(handler) === -1) {
23523      handlers.push(handler);
23524    }
23525  }
23526
23527  /**
23528   * Removes an event handler from the service.
23529   * @param {string} type Type of event handler.
23530   * @param {!Function} handler The event handler itself.
23531   * @param {boolean} useCapture Whether the event handler used event capturing.
23532   */
23533  function deregister(type, handler, useCapture) {
23534    var handlers = listeners[type];
23535    var index = handlers ? handlers.indexOf(handler) : -1;
23536
23537    if (index > -1) {
23538      handlers.splice(index, 1);
23539
23540      if (handlers.length === 0) {
23541        useCapture ? window.removeEventListener(type, globalEventHandler, true) :
23542            ngWindow.off(type, globalEventHandler);
23543      }
23544    }
23545  }
23546}
23547
23548})();
23549(function(){
23550"use strict";
23551
23552/**
23553 * @ngdoc module
23554 * @name material.components.truncate
23555 */
23556MdTruncateController.$inject = ["$element"];
23557angular.module('material.components.truncate', ['material.core'])
23558  .directive('mdTruncate', MdTruncateDirective);
23559
23560/**
23561 * @ngdoc directive
23562 * @name mdTruncate
23563 * @module material.components.truncate
23564 * @restrict AE
23565 * @description
23566 *
23567 * The `md-truncate` component displays a label that will automatically clip text which is wider
23568 * than the component. By default, it displays an ellipsis, but you may apply the `md-clip` CSS
23569 * class to override this default and use a standard "clipping" approach.
23570 *
23571 * <i><b>Note:</b> The `md-truncate` component does not automatically adjust it's width. You must
23572 * provide the `flex` attribute, or some other CSS-based width management. See the
23573 * <a ng-href="./demo/truncate">demos</a> for examples.</i>
23574 *
23575 * @usage
23576 *
23577 * ### As an Element
23578 *
23579 * <hljs lang="html">
23580 *   <div layout="row">
23581 *     <md-button>Back</md-button>
23582 *
23583 *     <md-truncate flex>Chapter 1 - The Way of the Old West</md-truncate>
23584 *
23585 *     <md-button>Forward</md-button>
23586 *   </div>
23587 * </hljs>
23588 *
23589 * ### As an Attribute
23590 *
23591 * <hljs lang="html">
23592 *   <h2 md-truncate style="max-width: 100px;">Some Title With a Lot of Text</h2>
23593 * </hljs>
23594 *
23595 * ## CSS & Styles
23596 *
23597 * `<md-truncate>` provides two CSS classes that you may use to control the type of clipping.
23598 *
23599 * <i><b>Note:</b> The `md-truncate` also applies a setting of `width: 0;` when used with the `flex`
23600 * attribute to fix an issue with the flex element not shrinking properly.</i>
23601 *
23602 * <div>
23603 * <docs-css-api-table>
23604 *
23605 *   <docs-css-selector code=".md-ellipsis">
23606 *     Assigns the "ellipsis" behavior (default) which will cut off mid-word and append an ellipsis
23607 *     (&hellip;) to the end of the text.
23608 *   </docs-css-selector>
23609 *
23610 *   <docs-css-selector code=".md-clip">
23611 *     Assigns the "clipping" behavior which will simply chop off the text. This may happen
23612 *     mid-word or even mid-character.
23613 *   </docs-css-selector>
23614 *
23615 * </docs-css-api-table>
23616 * </div>
23617 */
23618function MdTruncateDirective() {
23619  return {
23620    restrict: 'AE',
23621
23622    controller: MdTruncateController,
23623    controllerAs: '$ctrl',
23624    bindToController: true
23625  }
23626}
23627
23628/**
23629 * Controller for the <md-truncate> component.
23630 *
23631 * @param $element The md-truncate element.
23632 *
23633 * @constructor
23634 * @ngInject
23635 */
23636function MdTruncateController($element) {
23637  $element.addClass('md-truncate');
23638}
23639
23640})();
23641(function(){
23642"use strict";
23643
23644/**
23645 * @ngdoc module
23646 * @name material.components.virtualRepeat
23647 */
23648VirtualRepeatContainerController.$inject = ["$$rAF", "$mdUtil", "$mdConstant", "$parse", "$rootScope", "$window", "$scope", "$element", "$attrs"];
23649VirtualRepeatController.$inject = ["$scope", "$element", "$attrs", "$browser", "$document", "$rootScope", "$$rAF", "$mdUtil"];
23650VirtualRepeatDirective.$inject = ["$parse"];
23651angular.module('material.components.virtualRepeat', [
23652  'material.core',
23653  'material.components.showHide'
23654])
23655.directive('mdVirtualRepeatContainer', VirtualRepeatContainerDirective)
23656.directive('mdVirtualRepeat', VirtualRepeatDirective);
23657
23658
23659/**
23660 * @ngdoc directive
23661 * @name mdVirtualRepeatContainer
23662 * @module material.components.virtualRepeat
23663 * @restrict E
23664 * @description
23665 * `md-virtual-repeat-container` provides the scroll container for md-virtual-repeat.
23666 *
23667 * VirtualRepeat is a limited substitute for ng-repeat that renders only
23668 * enough DOM nodes to fill the container and recycling them as the user scrolls.
23669 *
23670 * Once an element is not visible anymore, the VirtualRepeat recycles it and will reuse it for
23671 * another visible item by replacing the previous dataset with the new one.
23672 *
23673 * ### Common Issues
23674 *
23675 * - When having one-time bindings inside of the view template, the VirtualRepeat will not properly
23676 *   update the bindings for new items, since the view will be recycled.
23677 *
23678 * - Directives inside of a VirtualRepeat will be only compiled (linked) once, because those
23679 *   are will be recycled items and used for other items.
23680 *   The VirtualRepeat just updates the scope bindings.
23681 *
23682 *
23683 * ### Notes
23684 *
23685 * > The VirtualRepeat is a similar implementation to the Android
23686 * [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)
23687 *
23688 * <!-- This comment forces a break between blockquotes //-->
23689 *
23690 * > Please also review the <a ng-href="api/directive/mdVirtualRepeat">VirtualRepeat</a>
23691 * documentation for more information.
23692 *
23693 *
23694 * @usage
23695 * <hljs lang="html">
23696 *
23697 * <md-virtual-repeat-container md-top-index="topIndex">
23698 *   <div md-virtual-repeat="i in items" md-item-size="20">Hello {{i}}!</div>
23699 * </md-virtual-repeat-container>
23700 * </hljs>
23701 *
23702 * @param {number=} md-top-index Binds the index of the item that is at the top of the scroll
23703 *     container to $scope. It can both read and set the scroll position.
23704 * @param {boolean=} md-orient-horizontal Whether the container should scroll horizontally
23705 *     (defaults to orientation and scrolling vertically).
23706 * @param {boolean=} md-auto-shrink When present, the container will shrink to fit
23707 *     the number of items when that number is less than its original size.
23708 * @param {number=} md-auto-shrink-min Minimum number of items that md-auto-shrink
23709 *     will shrink to (default: 0).
23710 */
23711function VirtualRepeatContainerDirective() {
23712  return {
23713    controller: VirtualRepeatContainerController,
23714    template: virtualRepeatContainerTemplate,
23715    compile: function virtualRepeatContainerCompile($element, $attrs) {
23716      $element
23717          .addClass('md-virtual-repeat-container')
23718          .addClass($attrs.hasOwnProperty('mdOrientHorizontal')
23719              ? 'md-orient-horizontal'
23720              : 'md-orient-vertical');
23721    }
23722  };
23723}
23724
23725
23726function virtualRepeatContainerTemplate($element) {
23727  return '<div class="md-virtual-repeat-scroller">' +
23728    '<div class="md-virtual-repeat-sizer"></div>' +
23729    '<div class="md-virtual-repeat-offsetter">' +
23730      $element[0].innerHTML +
23731    '</div></div>';
23732}
23733
23734/**
23735 * Number of additional elements to render above and below the visible area inside
23736 * of the virtual repeat container. A higher number results in less flicker when scrolling
23737 * very quickly in Safari, but comes with a higher rendering and dirty-checking cost.
23738 * @const {number}
23739 */
23740var NUM_EXTRA = 3;
23741
23742/** @ngInject */
23743function VirtualRepeatContainerController($$rAF, $mdUtil, $mdConstant, $parse, $rootScope, $window, $scope,
23744                                          $element, $attrs) {
23745  this.$rootScope = $rootScope;
23746  this.$scope = $scope;
23747  this.$element = $element;
23748  this.$attrs = $attrs;
23749
23750  /** @type {number} The width or height of the container */
23751  this.size = 0;
23752  /** @type {number} The scroll width or height of the scroller */
23753  this.scrollSize = 0;
23754  /** @type {number} The scrollLeft or scrollTop of the scroller */
23755  this.scrollOffset = 0;
23756  /** @type {boolean} Whether the scroller is oriented horizontally */
23757  this.horizontal = this.$attrs.hasOwnProperty('mdOrientHorizontal');
23758  /** @type {!VirtualRepeatController} The repeater inside of this container */
23759  this.repeater = null;
23760  /** @type {boolean} Whether auto-shrink is enabled */
23761  this.autoShrink = this.$attrs.hasOwnProperty('mdAutoShrink');
23762  /** @type {number} Minimum number of items to auto-shrink to */
23763  this.autoShrinkMin = parseInt(this.$attrs.mdAutoShrinkMin, 10) || 0;
23764  /** @type {?number} Original container size when shrank */
23765  this.originalSize = null;
23766  /** @type {number} Amount to offset the total scroll size by. */
23767  this.offsetSize = parseInt(this.$attrs.mdOffsetSize, 10) || 0;
23768  /** @type {?string} height or width element style on the container prior to auto-shrinking. */
23769  this.oldElementSize = null;
23770  /** @type {!number} Maximum amount of pixels allowed for a single DOM element */
23771  this.maxElementPixels = $mdConstant.ELEMENT_MAX_PIXELS;
23772
23773  if (this.$attrs.mdTopIndex) {
23774    /** @type {function(angular.Scope): number} Binds to topIndex on Angular scope */
23775    this.bindTopIndex = $parse(this.$attrs.mdTopIndex);
23776    /** @type {number} The index of the item that is at the top of the scroll container */
23777    this.topIndex = this.bindTopIndex(this.$scope);
23778
23779    if (!angular.isDefined(this.topIndex)) {
23780      this.topIndex = 0;
23781      this.bindTopIndex.assign(this.$scope, 0);
23782    }
23783
23784    this.$scope.$watch(this.bindTopIndex, angular.bind(this, function(newIndex) {
23785      if (newIndex !== this.topIndex) {
23786        this.scrollToIndex(newIndex);
23787      }
23788    }));
23789  } else {
23790    this.topIndex = 0;
23791  }
23792
23793  this.scroller = $element[0].querySelector('.md-virtual-repeat-scroller');
23794  this.sizer = this.scroller.querySelector('.md-virtual-repeat-sizer');
23795  this.offsetter = this.scroller.querySelector('.md-virtual-repeat-offsetter');
23796
23797  // After the dom stablizes, measure the initial size of the container and
23798  // make a best effort at re-measuring as it changes.
23799  var boundUpdateSize = angular.bind(this, this.updateSize);
23800
23801  $$rAF(angular.bind(this, function() {
23802    boundUpdateSize();
23803
23804    var debouncedUpdateSize = $mdUtil.debounce(boundUpdateSize, 10, null, false);
23805    var jWindow = angular.element($window);
23806
23807    // Make one more attempt to get the size if it is 0.
23808    // This is not by any means a perfect approach, but there's really no
23809    // silver bullet here.
23810    if (!this.size) {
23811      debouncedUpdateSize();
23812    }
23813
23814    jWindow.on('resize', debouncedUpdateSize);
23815    $scope.$on('$destroy', function() {
23816      jWindow.off('resize', debouncedUpdateSize);
23817    });
23818
23819    $scope.$emit('$md-resize-enable');
23820    $scope.$on('$md-resize', boundUpdateSize);
23821  }));
23822}
23823
23824
23825/** Called by the md-virtual-repeat inside of the container at startup. */
23826VirtualRepeatContainerController.prototype.register = function(repeaterCtrl) {
23827  this.repeater = repeaterCtrl;
23828
23829  angular.element(this.scroller)
23830      .on('scroll wheel touchmove touchend', angular.bind(this, this.handleScroll_));
23831};
23832
23833
23834/** @return {boolean} Whether the container is configured for horizontal scrolling. */
23835VirtualRepeatContainerController.prototype.isHorizontal = function() {
23836  return this.horizontal;
23837};
23838
23839
23840/** @return {number} The size (width or height) of the container. */
23841VirtualRepeatContainerController.prototype.getSize = function() {
23842  return this.size;
23843};
23844
23845
23846/**
23847 * Resizes the container.
23848 * @private
23849 * @param {number} size The new size to set.
23850 */
23851VirtualRepeatContainerController.prototype.setSize_ = function(size) {
23852  var dimension = this.getDimensionName_();
23853
23854  this.size = size;
23855  this.$element[0].style[dimension] = size + 'px';
23856};
23857
23858
23859VirtualRepeatContainerController.prototype.unsetSize_ = function() {
23860  this.$element[0].style[this.getDimensionName_()] = this.oldElementSize;
23861  this.oldElementSize = null;
23862};
23863
23864
23865/** Instructs the container to re-measure its size. */
23866VirtualRepeatContainerController.prototype.updateSize = function() {
23867  // If the original size is already determined, we can skip the update.
23868  if (this.originalSize) return;
23869
23870  this.size = this.isHorizontal()
23871      ? this.$element[0].clientWidth
23872      : this.$element[0].clientHeight;
23873
23874  // Recheck the scroll position after updating the size. This resolves
23875  // problems that can result if the scroll position was measured while the
23876  // element was display: none or detached from the document.
23877  this.handleScroll_();
23878
23879  this.repeater && this.repeater.containerUpdated();
23880};
23881
23882
23883/** @return {number} The container's scrollHeight or scrollWidth. */
23884VirtualRepeatContainerController.prototype.getScrollSize = function() {
23885  return this.scrollSize;
23886};
23887
23888
23889VirtualRepeatContainerController.prototype.getDimensionName_ = function() {
23890  return this.isHorizontal() ? 'width' : 'height';
23891};
23892
23893
23894/**
23895 * Sets the scroller element to the specified size.
23896 * @private
23897 * @param {number} size The new size.
23898 */
23899VirtualRepeatContainerController.prototype.sizeScroller_ = function(size) {
23900  var dimension =  this.getDimensionName_();
23901  var crossDimension = this.isHorizontal() ? 'height' : 'width';
23902
23903  // Clear any existing dimensions.
23904  this.sizer.innerHTML = '';
23905
23906  // If the size falls within the browser's maximum explicit size for a single element, we can
23907  // set the size and be done. Otherwise, we have to create children that add up the the desired
23908  // size.
23909  if (size < this.maxElementPixels) {
23910    this.sizer.style[dimension] = size + 'px';
23911  } else {
23912    this.sizer.style[dimension] = 'auto';
23913    this.sizer.style[crossDimension] = 'auto';
23914
23915    // Divide the total size we have to render into N max-size pieces.
23916    var numChildren = Math.floor(size / this.maxElementPixels);
23917
23918    // Element template to clone for each max-size piece.
23919    var sizerChild = document.createElement('div');
23920    sizerChild.style[dimension] = this.maxElementPixels + 'px';
23921    sizerChild.style[crossDimension] = '1px';
23922
23923    for (var i = 0; i < numChildren; i++) {
23924      this.sizer.appendChild(sizerChild.cloneNode(false));
23925    }
23926
23927    // Re-use the element template for the remainder.
23928    sizerChild.style[dimension] = (size - (numChildren * this.maxElementPixels)) + 'px';
23929    this.sizer.appendChild(sizerChild);
23930  }
23931};
23932
23933
23934/**
23935 * If auto-shrinking is enabled, shrinks or unshrinks as appropriate.
23936 * @private
23937 * @param {number} size The new size.
23938 */
23939VirtualRepeatContainerController.prototype.autoShrink_ = function(size) {
23940  var shrinkSize = Math.max(size, this.autoShrinkMin * this.repeater.getItemSize());
23941
23942  if (this.autoShrink && shrinkSize !== this.size) {
23943    if (this.oldElementSize === null) {
23944      this.oldElementSize = this.$element[0].style[this.getDimensionName_()];
23945    }
23946
23947    var currentSize = this.originalSize || this.size;
23948
23949    if (!currentSize || shrinkSize < currentSize) {
23950      if (!this.originalSize) {
23951        this.originalSize = this.size;
23952      }
23953
23954      // Now we update the containers size, because shrinking is enabled.
23955      this.setSize_(shrinkSize);
23956    } else if (this.originalSize !== null) {
23957      // Set the size back to our initial size.
23958      this.unsetSize_();
23959
23960      var _originalSize = this.originalSize;
23961      this.originalSize = null;
23962
23963      // We determine the repeaters size again, if the original size was zero.
23964      // The originalSize needs to be null, to be able to determine the size.
23965      if (!_originalSize) this.updateSize();
23966
23967      // Apply the original size or the determined size back to the container, because
23968      // it has been overwritten before, in the shrink block.
23969      this.setSize_(_originalSize || this.size);
23970    }
23971
23972    this.repeater.containerUpdated();
23973  }
23974};
23975
23976
23977/**
23978 * Sets the scrollHeight or scrollWidth. Called by the repeater based on
23979 * its item count and item size.
23980 * @param {number} itemsSize The total size of the items.
23981 */
23982VirtualRepeatContainerController.prototype.setScrollSize = function(itemsSize) {
23983  var size = itemsSize + this.offsetSize;
23984  if (this.scrollSize === size) return;
23985
23986  this.sizeScroller_(size);
23987  this.autoShrink_(size);
23988  this.scrollSize = size;
23989};
23990
23991
23992/** @return {number} The container's current scroll offset. */
23993VirtualRepeatContainerController.prototype.getScrollOffset = function() {
23994  return this.scrollOffset;
23995};
23996
23997/**
23998 * Scrolls to a given scrollTop position.
23999 * @param {number} position
24000 */
24001VirtualRepeatContainerController.prototype.scrollTo = function(position) {
24002  this.scroller[this.isHorizontal() ? 'scrollLeft' : 'scrollTop'] = position;
24003  this.handleScroll_();
24004};
24005
24006/**
24007 * Scrolls the item with the given index to the top of the scroll container.
24008 * @param {number} index
24009 */
24010VirtualRepeatContainerController.prototype.scrollToIndex = function(index) {
24011  var itemSize = this.repeater.getItemSize();
24012  var itemsLength = this.repeater.itemsLength;
24013  if(index > itemsLength) {
24014    index = itemsLength - 1;
24015  }
24016  this.scrollTo(itemSize * index);
24017};
24018
24019VirtualRepeatContainerController.prototype.resetScroll = function() {
24020  this.scrollTo(0);
24021};
24022
24023
24024VirtualRepeatContainerController.prototype.handleScroll_ = function() {
24025  var ltr = document.dir != 'rtl' && document.body.dir != 'rtl';
24026  if(!ltr && !this.maxSize) {
24027    this.scroller.scrollLeft = this.scrollSize;
24028    this.maxSize = this.scroller.scrollLeft;
24029  }
24030  var offset = this.isHorizontal() ?
24031      (ltr?this.scroller.scrollLeft : this.maxSize - this.scroller.scrollLeft)
24032      : this.scroller.scrollTop;
24033  if (offset === this.scrollOffset || offset > this.scrollSize - this.size) return;
24034
24035  var itemSize = this.repeater.getItemSize();
24036  if (!itemSize) return;
24037
24038  var numItems = Math.max(0, Math.floor(offset / itemSize) - NUM_EXTRA);
24039
24040  var transform = (this.isHorizontal() ? 'translateX(' : 'translateY(') +
24041      (!this.isHorizontal() || ltr ? (numItems * itemSize) : - (numItems * itemSize))  + 'px)';
24042
24043  this.scrollOffset = offset;
24044  this.offsetter.style.webkitTransform = transform;
24045  this.offsetter.style.transform = transform;
24046
24047  if (this.bindTopIndex) {
24048    var topIndex = Math.floor(offset / itemSize);
24049    if (topIndex !== this.topIndex && topIndex < this.repeater.getItemCount()) {
24050      this.topIndex = topIndex;
24051      this.bindTopIndex.assign(this.$scope, topIndex);
24052      if (!this.$rootScope.$$phase) this.$scope.$digest();
24053    }
24054  }
24055
24056  this.repeater.containerUpdated();
24057};
24058
24059
24060/**
24061 * @ngdoc directive
24062 * @name mdVirtualRepeat
24063 * @module material.components.virtualRepeat
24064 * @restrict A
24065 * @priority 1000
24066 * @description
24067 * `md-virtual-repeat` specifies an element to repeat using virtual scrolling.
24068 *
24069 * Virtual repeat is a limited substitute for ng-repeat that renders only
24070 * enough DOM nodes to fill the container and recycling them as the user scrolls.
24071 *
24072 * Arrays, but not objects are supported for iteration.
24073 * Track by, as alias, and (key, value) syntax are not supported.
24074 *
24075 * ### On-Demand Async Item Loading
24076 *
24077 * When using the `md-on-demand` attribute and loading some asynchronous data, the `getItemAtIndex` function will
24078 * mostly return nothing.
24079 *
24080 * <hljs lang="js">
24081 *   DynamicItems.prototype.getItemAtIndex = function(index) {
24082 *     if (this.pages[index]) {
24083 *       return this.pages[index];
24084 *     } else {
24085 *       // This is an asynchronous action and does not return any value.
24086 *       this.loadPage(index);
24087 *     }
24088 *   };
24089 * </hljs>
24090 *
24091 * This means that the VirtualRepeat will not have any value for the given index.<br/>
24092 * After the data loading completed, the user expects the VirtualRepeat to recognize the change.
24093 *
24094 * To make sure that the VirtualRepeat properly detects any change, you need to run the operation
24095 * in another digest.
24096 *
24097 * <hljs lang="js">
24098 *   DynamicItems.prototype.loadPage = function(index) {
24099 *     var self = this;
24100 *
24101 *     // Trigger a new digest by using $timeout
24102 *     $timeout(function() {
24103 *       self.pages[index] = Data;
24104 *     });
24105 *   };
24106 * </hljs>
24107 *
24108 * > <b>Note:</b> Please also review the
24109 *   <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeatContainer</a> documentation
24110 *   for more information.
24111 *
24112 * @usage
24113 * <hljs lang="html">
24114 * <md-virtual-repeat-container>
24115 *   <div md-virtual-repeat="i in items">Hello {{i}}!</div>
24116 * </md-virtual-repeat-container>
24117 *
24118 * <md-virtual-repeat-container md-orient-horizontal>
24119 *   <div md-virtual-repeat="i in items" md-item-size="20">Hello {{i}}!</div>
24120 * </md-virtual-repeat-container>
24121 * </hljs>
24122 *
24123 * @param {number=} md-item-size The height or width of the repeated elements (which must be
24124 *   identical for each element). Optional. Will attempt to read the size from the dom if missing,
24125 *   but still assumes that all repeated nodes have same height or width.
24126 * @param {string=} md-extra-name Evaluates to an additional name to which the current iterated item
24127 *   can be assigned on the repeated scope (needed for use in `md-autocomplete`).
24128 * @param {boolean=} md-on-demand When present, treats the md-virtual-repeat argument as an object
24129 *   that can fetch rows rather than an array.
24130 *
24131 *   **NOTE:** This object must implement the following interface with two (2) methods:
24132 *
24133 *   - `getItemAtIndex: function(index) [object]` The item at that index or null if it is not yet
24134 *     loaded (it should start downloading the item in that case).
24135 *   - `getLength: function() [number]` The data length to which the repeater container
24136 *     should be sized. Ideally, when the count is known, this method should return it.
24137 *     Otherwise, return a higher number than the currently loaded items to produce an
24138 *     infinite-scroll behavior.
24139 */
24140function VirtualRepeatDirective($parse) {
24141  return {
24142    controller: VirtualRepeatController,
24143    priority: 1000,
24144    require: ['mdVirtualRepeat', '^^mdVirtualRepeatContainer'],
24145    restrict: 'A',
24146    terminal: true,
24147    transclude: 'element',
24148    compile: function VirtualRepeatCompile($element, $attrs) {
24149      var expression = $attrs.mdVirtualRepeat;
24150      var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/);
24151      var repeatName = match[1];
24152      var repeatListExpression = $parse(match[2]);
24153      var extraName = $attrs.mdExtraName && $parse($attrs.mdExtraName);
24154
24155      return function VirtualRepeatLink($scope, $element, $attrs, ctrl, $transclude) {
24156        ctrl[0].link_(ctrl[1], $transclude, repeatName, repeatListExpression, extraName);
24157      };
24158    }
24159  };
24160}
24161
24162
24163/** @ngInject */
24164function VirtualRepeatController($scope, $element, $attrs, $browser, $document, $rootScope,
24165    $$rAF, $mdUtil) {
24166  this.$scope = $scope;
24167  this.$element = $element;
24168  this.$attrs = $attrs;
24169  this.$browser = $browser;
24170  this.$document = $document;
24171  this.$rootScope = $rootScope;
24172  this.$$rAF = $$rAF;
24173
24174  /** @type {boolean} Whether we are in on-demand mode. */
24175  this.onDemand = $mdUtil.parseAttributeBoolean($attrs.mdOnDemand);
24176  /** @type {!Function} Backup reference to $browser.$$checkUrlChange */
24177  this.browserCheckUrlChange = $browser.$$checkUrlChange;
24178  /** @type {number} Most recent starting repeat index (based on scroll offset) */
24179  this.newStartIndex = 0;
24180  /** @type {number} Most recent ending repeat index (based on scroll offset) */
24181  this.newEndIndex = 0;
24182  /** @type {number} Most recent end visible index (based on scroll offset) */
24183  this.newVisibleEnd = 0;
24184  /** @type {number} Previous starting repeat index (based on scroll offset) */
24185  this.startIndex = 0;
24186  /** @type {number} Previous ending repeat index (based on scroll offset) */
24187  this.endIndex = 0;
24188  // TODO: measure width/height of first element from dom if not provided.
24189  // getComputedStyle?
24190  /** @type {?number} Height/width of repeated elements. */
24191  this.itemSize = $scope.$eval($attrs.mdItemSize) || null;
24192
24193  /** @type {boolean} Whether this is the first time that items are rendered. */
24194  this.isFirstRender = true;
24195
24196  /**
24197   * @private {boolean} Whether the items in the list are already being updated. Used to prevent
24198   *     nested calls to virtualRepeatUpdate_.
24199   */
24200  this.isVirtualRepeatUpdating_ = false;
24201
24202  /** @type {number} Most recently seen length of items. */
24203  this.itemsLength = 0;
24204
24205  /**
24206   * @type {!Function} Unwatch callback for item size (when md-items-size is
24207   *     not specified), or angular.noop otherwise.
24208   */
24209  this.unwatchItemSize_ = angular.noop;
24210
24211  /**
24212   * Presently rendered blocks by repeat index.
24213   * @type {Object<number, !VirtualRepeatController.Block}
24214   */
24215  this.blocks = {};
24216  /** @type {Array<!VirtualRepeatController.Block>} A pool of presently unused blocks. */
24217  this.pooledBlocks = [];
24218
24219  $scope.$on('$destroy', angular.bind(this, this.cleanupBlocks_));
24220}
24221
24222
24223/**
24224 * An object representing a repeated item.
24225 * @typedef {{element: !jqLite, new: boolean, scope: !angular.Scope}}
24226 */
24227VirtualRepeatController.Block;
24228
24229
24230/**
24231 * Called at startup by the md-virtual-repeat postLink function.
24232 * @param {!VirtualRepeatContainerController} container The container's controller.
24233 * @param {!Function} transclude The repeated element's bound transclude function.
24234 * @param {string} repeatName The left hand side of the repeat expression, indicating
24235 *     the name for each item in the array.
24236 * @param {!Function} repeatListExpression A compiled expression based on the right hand side
24237 *     of the repeat expression. Points to the array to repeat over.
24238 * @param {string|undefined} extraName The optional extra repeatName.
24239 */
24240VirtualRepeatController.prototype.link_ =
24241    function(container, transclude, repeatName, repeatListExpression, extraName) {
24242  this.container = container;
24243  this.transclude = transclude;
24244  this.repeatName = repeatName;
24245  this.rawRepeatListExpression = repeatListExpression;
24246  this.extraName = extraName;
24247  this.sized = false;
24248
24249  this.repeatListExpression = angular.bind(this, this.repeatListExpression_);
24250
24251  this.container.register(this);
24252};
24253
24254
24255/** @private Cleans up unused blocks. */
24256VirtualRepeatController.prototype.cleanupBlocks_ = function() {
24257  angular.forEach(this.pooledBlocks, function cleanupBlock(block) {
24258    block.element.remove();
24259  });
24260};
24261
24262
24263/** @private Attempts to set itemSize by measuring a repeated element in the dom */
24264VirtualRepeatController.prototype.readItemSize_ = function() {
24265  if (this.itemSize) {
24266    // itemSize was successfully read in a different asynchronous call.
24267    return;
24268  }
24269
24270  this.items = this.repeatListExpression(this.$scope);
24271  this.parentNode = this.$element[0].parentNode;
24272  var block = this.getBlock_(0);
24273  if (!block.element[0].parentNode) {
24274    this.parentNode.appendChild(block.element[0]);
24275  }
24276
24277  this.itemSize = block.element[0][
24278      this.container.isHorizontal() ? 'offsetWidth' : 'offsetHeight'] || null;
24279
24280  this.blocks[0] = block;
24281  this.poolBlock_(0);
24282
24283  if (this.itemSize) {
24284    this.containerUpdated();
24285  }
24286};
24287
24288
24289/**
24290 * Returns the user-specified repeat list, transforming it into an array-like
24291 * object in the case of infinite scroll/dynamic load mode.
24292 * @param {!angular.Scope} The scope.
24293 * @return {!Array|!Object} An array or array-like object for iteration.
24294 */
24295VirtualRepeatController.prototype.repeatListExpression_ = function(scope) {
24296  var repeatList = this.rawRepeatListExpression(scope);
24297
24298  if (this.onDemand && repeatList) {
24299    var virtualList = new VirtualRepeatModelArrayLike(repeatList);
24300    virtualList.$$includeIndexes(this.newStartIndex, this.newVisibleEnd);
24301    return virtualList;
24302  } else {
24303    return repeatList;
24304  }
24305};
24306
24307
24308/**
24309 * Called by the container. Informs us that the containers scroll or size has
24310 * changed.
24311 */
24312VirtualRepeatController.prototype.containerUpdated = function() {
24313  // If itemSize is unknown, attempt to measure it.
24314  if (!this.itemSize) {
24315    // Make sure to clean up watchers if we can (see #8178)
24316    if(this.unwatchItemSize_ && this.unwatchItemSize_ !== angular.noop){
24317      this.unwatchItemSize_();
24318    }
24319    this.unwatchItemSize_ = this.$scope.$watchCollection(
24320        this.repeatListExpression,
24321        angular.bind(this, function(items) {
24322          if (items && items.length) {
24323            this.readItemSize_();
24324          }
24325        }));
24326    if (!this.$rootScope.$$phase) this.$scope.$digest();
24327
24328    return;
24329  } else if (!this.sized) {
24330    this.items = this.repeatListExpression(this.$scope);
24331  }
24332
24333  if (!this.sized) {
24334    this.unwatchItemSize_();
24335    this.sized = true;
24336    this.$scope.$watchCollection(this.repeatListExpression,
24337        angular.bind(this, function(items, oldItems) {
24338          if (!this.isVirtualRepeatUpdating_) {
24339            this.virtualRepeatUpdate_(items, oldItems);
24340          }
24341        }));
24342  }
24343
24344  this.updateIndexes_();
24345
24346  if (this.newStartIndex !== this.startIndex ||
24347      this.newEndIndex !== this.endIndex ||
24348      this.container.getScrollOffset() > this.container.getScrollSize()) {
24349    if (this.items instanceof VirtualRepeatModelArrayLike) {
24350      this.items.$$includeIndexes(this.newStartIndex, this.newEndIndex);
24351    }
24352    this.virtualRepeatUpdate_(this.items, this.items);
24353  }
24354};
24355
24356
24357/**
24358 * Called by the container. Returns the size of a single repeated item.
24359 * @return {?number} Size of a repeated item.
24360 */
24361VirtualRepeatController.prototype.getItemSize = function() {
24362  return this.itemSize;
24363};
24364
24365
24366/**
24367 * Called by the container. Returns the size of a single repeated item.
24368 * @return {?number} Size of a repeated item.
24369 */
24370VirtualRepeatController.prototype.getItemCount = function() {
24371  return this.itemsLength;
24372};
24373
24374
24375/**
24376 * Updates the order and visible offset of repeated blocks in response to scrolling
24377 * or items updates.
24378 * @private
24379 */
24380VirtualRepeatController.prototype.virtualRepeatUpdate_ = function(items, oldItems) {
24381  this.isVirtualRepeatUpdating_ = true;
24382
24383  var itemsLength = items && items.length || 0;
24384  var lengthChanged = false;
24385
24386  // If the number of items shrank, keep the scroll position.
24387  if (this.items && itemsLength < this.items.length && this.container.getScrollOffset() !== 0) {
24388    this.items = items;
24389    var previousScrollOffset = this.container.getScrollOffset();
24390    this.container.resetScroll();
24391    this.container.scrollTo(previousScrollOffset);
24392  }
24393
24394  if (itemsLength !== this.itemsLength) {
24395    lengthChanged = true;
24396    this.itemsLength = itemsLength;
24397  }
24398
24399  this.items = items;
24400  if (items !== oldItems || lengthChanged) {
24401    this.updateIndexes_();
24402  }
24403
24404  this.parentNode = this.$element[0].parentNode;
24405
24406  if (lengthChanged) {
24407    this.container.setScrollSize(itemsLength * this.itemSize);
24408  }
24409
24410  if (this.isFirstRender) {
24411    this.isFirstRender = false;
24412    var startIndex = this.$attrs.mdStartIndex ?
24413      this.$scope.$eval(this.$attrs.mdStartIndex) :
24414      this.container.topIndex;
24415    this.container.scrollToIndex(startIndex);
24416  }
24417
24418  // Detach and pool any blocks that are no longer in the viewport.
24419  Object.keys(this.blocks).forEach(function(blockIndex) {
24420    var index = parseInt(blockIndex, 10);
24421    if (index < this.newStartIndex || index >= this.newEndIndex) {
24422      this.poolBlock_(index);
24423    }
24424  }, this);
24425
24426  // Add needed blocks.
24427  // For performance reasons, temporarily block browser url checks as we digest
24428  // the restored block scopes ($$checkUrlChange reads window.location to
24429  // check for changes and trigger route change, etc, which we don't need when
24430  // trying to scroll at 60fps).
24431  this.$browser.$$checkUrlChange = angular.noop;
24432
24433  var i, block,
24434      newStartBlocks = [],
24435      newEndBlocks = [];
24436
24437  // Collect blocks at the top.
24438  for (i = this.newStartIndex; i < this.newEndIndex && this.blocks[i] == null; i++) {
24439    block = this.getBlock_(i);
24440    this.updateBlock_(block, i);
24441    newStartBlocks.push(block);
24442  }
24443
24444  // Update blocks that are already rendered.
24445  for (; this.blocks[i] != null; i++) {
24446    this.updateBlock_(this.blocks[i], i);
24447  }
24448  var maxIndex = i - 1;
24449
24450  // Collect blocks at the end.
24451  for (; i < this.newEndIndex; i++) {
24452    block = this.getBlock_(i);
24453    this.updateBlock_(block, i);
24454    newEndBlocks.push(block);
24455  }
24456
24457  // Attach collected blocks to the document.
24458  if (newStartBlocks.length) {
24459    this.parentNode.insertBefore(
24460        this.domFragmentFromBlocks_(newStartBlocks),
24461        this.$element[0].nextSibling);
24462  }
24463  if (newEndBlocks.length) {
24464    this.parentNode.insertBefore(
24465        this.domFragmentFromBlocks_(newEndBlocks),
24466        this.blocks[maxIndex] && this.blocks[maxIndex].element[0].nextSibling);
24467  }
24468
24469  // Restore $$checkUrlChange.
24470  this.$browser.$$checkUrlChange = this.browserCheckUrlChange;
24471
24472  this.startIndex = this.newStartIndex;
24473  this.endIndex = this.newEndIndex;
24474
24475  this.isVirtualRepeatUpdating_ = false;
24476};
24477
24478
24479/**
24480 * @param {number} index Where the block is to be in the repeated list.
24481 * @return {!VirtualRepeatController.Block} A new or pooled block to place at the specified index.
24482 * @private
24483 */
24484VirtualRepeatController.prototype.getBlock_ = function(index) {
24485  if (this.pooledBlocks.length) {
24486    return this.pooledBlocks.pop();
24487  }
24488
24489  var block;
24490  this.transclude(angular.bind(this, function(clone, scope) {
24491    block = {
24492      element: clone,
24493      new: true,
24494      scope: scope
24495    };
24496
24497    this.updateScope_(scope, index);
24498    this.parentNode.appendChild(clone[0]);
24499  }));
24500
24501  return block;
24502};
24503
24504
24505/**
24506 * Updates and if not in a digest cycle, digests the specified block's scope to the data
24507 * at the specified index.
24508 * @param {!VirtualRepeatController.Block} block The block whose scope should be updated.
24509 * @param {number} index The index to set.
24510 * @private
24511 */
24512VirtualRepeatController.prototype.updateBlock_ = function(block, index) {
24513  this.blocks[index] = block;
24514
24515  if (!block.new &&
24516      (block.scope.$index === index && block.scope[this.repeatName] === this.items[index])) {
24517    return;
24518  }
24519  block.new = false;
24520
24521  // Update and digest the block's scope.
24522  this.updateScope_(block.scope, index);
24523
24524  // Perform digest before reattaching the block.
24525  // Any resulting synchronous dom mutations should be much faster as a result.
24526  // This might break some directives, but I'm going to try it for now.
24527  if (!this.$rootScope.$$phase) {
24528    block.scope.$digest();
24529  }
24530};
24531
24532
24533/**
24534 * Updates scope to the data at the specified index.
24535 * @param {!angular.Scope} scope The scope which should be updated.
24536 * @param {number} index The index to set.
24537 * @private
24538 */
24539VirtualRepeatController.prototype.updateScope_ = function(scope, index) {
24540  scope.$index = index;
24541  scope[this.repeatName] = this.items && this.items[index];
24542  if (this.extraName) scope[this.extraName(this.$scope)] = this.items[index];
24543};
24544
24545
24546/**
24547 * Pools the block at the specified index (Pulls its element out of the dom and stores it).
24548 * @param {number} index The index at which the block to pool is stored.
24549 * @private
24550 */
24551VirtualRepeatController.prototype.poolBlock_ = function(index) {
24552  this.pooledBlocks.push(this.blocks[index]);
24553  this.parentNode.removeChild(this.blocks[index].element[0]);
24554  delete this.blocks[index];
24555};
24556
24557
24558/**
24559 * Produces a dom fragment containing the elements from the list of blocks.
24560 * @param {!Array<!VirtualRepeatController.Block>} blocks The blocks whose elements
24561 *     should be added to the document fragment.
24562 * @return {DocumentFragment}
24563 * @private
24564 */
24565VirtualRepeatController.prototype.domFragmentFromBlocks_ = function(blocks) {
24566  var fragment = this.$document[0].createDocumentFragment();
24567  blocks.forEach(function(block) {
24568    fragment.appendChild(block.element[0]);
24569  });
24570  return fragment;
24571};
24572
24573
24574/**
24575 * Updates start and end indexes based on length of repeated items and container size.
24576 * @private
24577 */
24578VirtualRepeatController.prototype.updateIndexes_ = function() {
24579  var itemsLength = this.items ? this.items.length : 0;
24580  var containerLength = Math.ceil(this.container.getSize() / this.itemSize);
24581
24582  this.newStartIndex = Math.max(0, Math.min(
24583      itemsLength - containerLength,
24584      Math.floor(this.container.getScrollOffset() / this.itemSize)));
24585  this.newVisibleEnd = this.newStartIndex + containerLength + NUM_EXTRA;
24586  this.newEndIndex = Math.min(itemsLength, this.newVisibleEnd);
24587  this.newStartIndex = Math.max(0, this.newStartIndex - NUM_EXTRA);
24588};
24589
24590/**
24591 * This VirtualRepeatModelArrayLike class enforces the interface requirements
24592 * for infinite scrolling within a mdVirtualRepeatContainer. An object with this
24593 * interface must implement the following interface with two (2) methods:
24594 *
24595 * getItemAtIndex: function(index) -> item at that index or null if it is not yet
24596 *     loaded (It should start downloading the item in that case).
24597 *
24598 * getLength: function() -> number The data legnth to which the repeater container
24599 *     should be sized. Ideally, when the count is known, this method should return it.
24600 *     Otherwise, return a higher number than the currently loaded items to produce an
24601 *     infinite-scroll behavior.
24602 *
24603 * @usage
24604 * <hljs lang="html">
24605 *  <md-virtual-repeat-container md-orient-horizontal>
24606 *    <div md-virtual-repeat="i in items" md-on-demand>
24607 *      Hello {{i}}!
24608 *    </div>
24609 *  </md-virtual-repeat-container>
24610 * </hljs>
24611 *
24612 */
24613function VirtualRepeatModelArrayLike(model) {
24614  if (!angular.isFunction(model.getItemAtIndex) ||
24615      !angular.isFunction(model.getLength)) {
24616    throw Error('When md-on-demand is enabled, the Object passed to md-virtual-repeat must implement ' +
24617        'functions getItemAtIndex() and getLength() ');
24618  }
24619
24620  this.model = model;
24621}
24622
24623
24624VirtualRepeatModelArrayLike.prototype.$$includeIndexes = function(start, end) {
24625  for (var i = start; i < end; i++) {
24626    if (!this.hasOwnProperty(i)) {
24627      this[i] = this.model.getItemAtIndex(i);
24628    }
24629  }
24630  this.length = this.model.getLength();
24631};
24632
24633})();
24634(function(){
24635"use strict";
24636
24637/**
24638 * @ngdoc module
24639 * @name material.components.whiteframe
24640 */
24641MdWhiteframeDirective.$inject = ["$log"];
24642angular
24643  .module('material.components.whiteframe', ['material.core'])
24644  .directive('mdWhiteframe', MdWhiteframeDirective);
24645
24646/**
24647 * @ngdoc directive
24648 * @module material.components.whiteframe
24649 * @name mdWhiteframe
24650 *
24651 * @description
24652 * The md-whiteframe directive allows you to apply an elevation shadow to an element.
24653 *
24654 * The attribute values needs to be a number between 1 and 24 or -1.
24655 * When set to -1 no style is applied.
24656 *
24657 * ### Notes
24658 * - If there is no value specified it defaults to 4dp.
24659 * - If the value is not valid it defaults to 4dp.
24660
24661 * @usage
24662 * <hljs lang="html">
24663 * <div md-whiteframe="3">
24664 *   <span>Elevation of 3dp</span>
24665 * </div>
24666 * </hljs>
24667 *
24668 * <hljs lang="html">
24669 * <div md-whiteframe="-1">
24670 *   <span>No elevation shadow applied</span>
24671 * </div>
24672 * </hljs>
24673 *
24674 * <hljs lang="html">
24675 * <div ng-init="elevation = 5" md-whiteframe="{{elevation}}">
24676 *   <span>Elevation of 5dp with an interpolated value</span>
24677 * </div>
24678 * </hljs>
24679 */
24680function MdWhiteframeDirective($log) {
24681  var DISABLE_DP = -1;
24682  var MIN_DP = 1;
24683  var MAX_DP = 24;
24684  var DEFAULT_DP = 4;
24685
24686  return {
24687    link: postLink
24688  };
24689
24690  function postLink(scope, element, attr) {
24691    var oldClass = '';
24692
24693    attr.$observe('mdWhiteframe', function(elevation) {
24694      elevation = parseInt(elevation, 10) || DEFAULT_DP;
24695
24696      if (elevation != DISABLE_DP && (elevation > MAX_DP || elevation < MIN_DP)) {
24697        $log.warn('md-whiteframe attribute value is invalid. It should be a number between ' + MIN_DP + ' and ' + MAX_DP, element[0]);
24698        elevation = DEFAULT_DP;
24699      }
24700
24701      var newClass = elevation == DISABLE_DP ? '' : 'md-whiteframe-' + elevation + 'dp';
24702      attr.$updateClass(newClass, oldClass);
24703      oldClass = newClass;
24704    });
24705  }
24706}
24707
24708
24709})();
24710(function(){
24711"use strict";
24712
24713
24714MdAutocompleteCtrl.$inject = ["$scope", "$element", "$mdUtil", "$mdConstant", "$mdTheming", "$window", "$animate", "$rootElement", "$attrs", "$q", "$log", "$mdLiveAnnouncer"];angular
24715    .module('material.components.autocomplete')
24716    .controller('MdAutocompleteCtrl', MdAutocompleteCtrl);
24717
24718var ITEM_HEIGHT   = 48,
24719    MAX_ITEMS     = 5,
24720    MENU_PADDING  = 8,
24721    INPUT_PADDING = 2; // Padding provided by `md-input-container`
24722
24723function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, $window,
24724                             $animate, $rootElement, $attrs, $q, $log, $mdLiveAnnouncer) {
24725
24726  // Internal Variables.
24727  var ctrl                 = this,
24728      itemParts            = $scope.itemsExpr.split(/ in /i),
24729      itemExpr             = itemParts[ 1 ],
24730      elements             = null,
24731      cache                = {},
24732      noBlur               = false,
24733      selectedItemWatchers = [],
24734      hasFocus             = false,
24735      fetchesInProgress    = 0,
24736      enableWrapScroll     = null,
24737      inputModelCtrl       = null,
24738      debouncedOnResize    = $mdUtil.debounce(onWindowResize);
24739
24740  // Public Exported Variables with handlers
24741  defineProperty('hidden', handleHiddenChange, true);
24742
24743  // Public Exported Variables
24744  ctrl.scope      = $scope;
24745  ctrl.parent     = $scope.$parent;
24746  ctrl.itemName   = itemParts[ 0 ];
24747  ctrl.matches    = [];
24748  ctrl.loading    = false;
24749  ctrl.hidden     = true;
24750  ctrl.index      = null;
24751  ctrl.id         = $mdUtil.nextUid();
24752  ctrl.isDisabled = null;
24753  ctrl.isRequired = null;
24754  ctrl.isReadonly = null;
24755  ctrl.hasNotFound = false;
24756
24757  // Public Exported Methods
24758  ctrl.keydown                       = keydown;
24759  ctrl.blur                          = blur;
24760  ctrl.focus                         = focus;
24761  ctrl.clear                         = clearValue;
24762  ctrl.select                        = select;
24763  ctrl.listEnter                     = onListEnter;
24764  ctrl.listLeave                     = onListLeave;
24765  ctrl.mouseUp                       = onMouseup;
24766  ctrl.getCurrentDisplayValue        = getCurrentDisplayValue;
24767  ctrl.registerSelectedItemWatcher   = registerSelectedItemWatcher;
24768  ctrl.unregisterSelectedItemWatcher = unregisterSelectedItemWatcher;
24769  ctrl.notFoundVisible               = notFoundVisible;
24770  ctrl.loadingIsVisible              = loadingIsVisible;
24771  ctrl.positionDropdown              = positionDropdown;
24772
24773  /**
24774   * Report types to be used for the $mdLiveAnnouncer
24775   * @enum {number} Unique flag id.
24776   */
24777  var ReportType = {
24778    Count: 1,
24779    Selected: 2
24780  };
24781
24782  return init();
24783
24784  //-- initialization methods
24785
24786  /**
24787   * Initialize the controller, setup watchers, gather elements
24788   */
24789  function init () {
24790
24791    $mdUtil.initOptionalProperties($scope, $attrs, {
24792      searchText: '',
24793      selectedItem: null,
24794      clearButton: false
24795    });
24796
24797    $mdTheming($element);
24798    configureWatchers();
24799    $mdUtil.nextTick(function () {
24800
24801      gatherElements();
24802      moveDropdown();
24803
24804      // Forward all focus events to the input element when autofocus is enabled
24805      if ($scope.autofocus) {
24806        $element.on('focus', focusInputElement);
24807      }
24808    });
24809  }
24810
24811  function updateModelValidators() {
24812    if (!$scope.requireMatch || !inputModelCtrl) return;
24813
24814    inputModelCtrl.$setValidity('md-require-match', !!$scope.selectedItem || !$scope.searchText);
24815  }
24816
24817  /**
24818   * Calculates the dropdown's position and applies the new styles to the menu element
24819   * @returns {*}
24820   */
24821  function positionDropdown () {
24822    if (!elements) {
24823      return $mdUtil.nextTick(positionDropdown, false, $scope);
24824    }
24825
24826    var dropdownHeight = ($scope.dropdownItems || MAX_ITEMS) * ITEM_HEIGHT;
24827
24828    var hrect  = elements.wrap.getBoundingClientRect(),
24829        vrect  = elements.snap.getBoundingClientRect(),
24830        root   = elements.root.getBoundingClientRect(),
24831        top    = vrect.bottom - root.top,
24832        bot    = root.bottom - vrect.top,
24833        left   = hrect.left - root.left,
24834        width  = hrect.width,
24835        offset = getVerticalOffset(),
24836        position = $scope.dropdownPosition,
24837        styles;
24838
24839    // Automatically determine dropdown placement based on available space in viewport.
24840    if (!position) {
24841      position = (top > bot && root.height - hrect.bottom - MENU_PADDING < dropdownHeight) ? 'top' : 'bottom';
24842    }
24843    // Adjust the width to account for the padding provided by `md-input-container`
24844    if ($attrs.mdFloatingLabel) {
24845      left += INPUT_PADDING;
24846      width -= INPUT_PADDING * 2;
24847    }
24848    styles = {
24849      left:     left + 'px',
24850      minWidth: width + 'px',
24851      maxWidth: Math.max(hrect.right - root.left, root.right - hrect.left) - MENU_PADDING + 'px'
24852    };
24853
24854    if (position === 'top') {
24855      styles.top       = 'auto';
24856      styles.bottom    = bot + 'px';
24857      styles.maxHeight = Math.min(dropdownHeight, hrect.top - root.top - MENU_PADDING) + 'px';
24858    } else {
24859      var bottomSpace = root.bottom - hrect.bottom - MENU_PADDING + $mdUtil.getViewportTop();
24860
24861      styles.top       = (top - offset) + 'px';
24862      styles.bottom    = 'auto';
24863      styles.maxHeight = Math.min(dropdownHeight, bottomSpace) + 'px';
24864    }
24865
24866    elements.$.scrollContainer.css(styles);
24867    $mdUtil.nextTick(correctHorizontalAlignment, false);
24868
24869    /**
24870     * Calculates the vertical offset for floating label examples to account for ngMessages
24871     * @returns {number}
24872     */
24873    function getVerticalOffset () {
24874      var offset = 0;
24875      var inputContainer = $element.find('md-input-container');
24876      if (inputContainer.length) {
24877        var input = inputContainer.find('input');
24878        offset = inputContainer.prop('offsetHeight');
24879        offset -= input.prop('offsetTop');
24880        offset -= input.prop('offsetHeight');
24881        // add in the height left up top for the floating label text
24882        offset += inputContainer.prop('offsetTop');
24883      }
24884      return offset;
24885    }
24886
24887    /**
24888     * Makes sure that the menu doesn't go off of the screen on either side.
24889     */
24890    function correctHorizontalAlignment () {
24891      var dropdown = elements.scrollContainer.getBoundingClientRect(),
24892          styles   = {};
24893      if (dropdown.right > root.right - MENU_PADDING) {
24894        styles.left = (hrect.right - dropdown.width) + 'px';
24895      }
24896      elements.$.scrollContainer.css(styles);
24897    }
24898  }
24899
24900  /**
24901   * Moves the dropdown menu to the body tag in order to avoid z-index and overflow issues.
24902   */
24903  function moveDropdown () {
24904    if (!elements.$.root.length) return;
24905    $mdTheming(elements.$.scrollContainer);
24906    elements.$.scrollContainer.detach();
24907    elements.$.root.append(elements.$.scrollContainer);
24908    if ($animate.pin) $animate.pin(elements.$.scrollContainer, $rootElement);
24909  }
24910
24911  /**
24912   * Sends focus to the input element.
24913   */
24914  function focusInputElement () {
24915    elements.input.focus();
24916  }
24917
24918  /**
24919   * Sets up any watchers used by autocomplete
24920   */
24921  function configureWatchers () {
24922    var wait = parseInt($scope.delay, 10) || 0;
24923
24924    $attrs.$observe('disabled', function (value) { ctrl.isDisabled = $mdUtil.parseAttributeBoolean(value, false); });
24925    $attrs.$observe('required', function (value) { ctrl.isRequired = $mdUtil.parseAttributeBoolean(value, false); });
24926    $attrs.$observe('readonly', function (value) { ctrl.isReadonly = $mdUtil.parseAttributeBoolean(value, false); });
24927
24928    $scope.$watch('searchText', wait ? $mdUtil.debounce(handleSearchText, wait) : handleSearchText);
24929    $scope.$watch('selectedItem', selectedItemChange);
24930
24931    angular.element($window).on('resize', debouncedOnResize);
24932
24933    $scope.$on('$destroy', cleanup);
24934  }
24935
24936  /**
24937   * Removes any events or leftover elements created by this controller
24938   */
24939  function cleanup () {
24940    if (!ctrl.hidden) {
24941      $mdUtil.enableScrolling();
24942    }
24943
24944    angular.element($window).off('resize', debouncedOnResize);
24945
24946    if ( elements ){
24947      var items = ['ul', 'scroller', 'scrollContainer', 'input'];
24948      angular.forEach(items, function(key){
24949        elements.$[key].remove();
24950      });
24951    }
24952  }
24953
24954  /**
24955   * Event handler to be called whenever the window resizes.
24956   */
24957  function onWindowResize() {
24958    if (!ctrl.hidden) {
24959      positionDropdown();
24960    }
24961  }
24962
24963  /**
24964   * Gathers all of the elements needed for this controller
24965   */
24966  function gatherElements () {
24967
24968    var snapWrap = gatherSnapWrap();
24969
24970    elements = {
24971      main:  $element[0],
24972      scrollContainer: $element[0].querySelector('.md-virtual-repeat-container'),
24973      scroller: $element[0].querySelector('.md-virtual-repeat-scroller'),
24974      ul:    $element.find('ul')[0],
24975      input: $element.find('input')[0],
24976      wrap:  snapWrap.wrap,
24977      snap:  snapWrap.snap,
24978      root:  document.body
24979    };
24980
24981    elements.li   = elements.ul.getElementsByTagName('li');
24982    elements.$    = getAngularElements(elements);
24983
24984    inputModelCtrl = elements.$.input.controller('ngModel');
24985  }
24986
24987  /**
24988   * Gathers the snap and wrap elements
24989   *
24990   */
24991  function gatherSnapWrap() {
24992    var element;
24993    var value;
24994    for (element = $element; element.length; element = element.parent()) {
24995      value = element.attr('md-autocomplete-snap');
24996      if (angular.isDefined(value)) break;
24997    }
24998
24999    if (element.length) {
25000      return {
25001        snap: element[0],
25002        wrap: (value.toLowerCase() === 'width') ? element[0] : $element.find('md-autocomplete-wrap')[0]
25003      };
25004    }
25005
25006    var wrap = $element.find('md-autocomplete-wrap')[0];
25007    return {
25008      snap: wrap,
25009      wrap: wrap
25010    };
25011  }
25012
25013  /**
25014   * Gathers angular-wrapped versions of each element
25015   * @param elements
25016   * @returns {{}}
25017   */
25018  function getAngularElements (elements) {
25019    var obj = {};
25020    for (var key in elements) {
25021      if (elements.hasOwnProperty(key)) obj[ key ] = angular.element(elements[ key ]);
25022    }
25023    return obj;
25024  }
25025
25026  //-- event/change handlers
25027
25028  /**
25029   * Handles changes to the `hidden` property.
25030   * @param hidden
25031   * @param oldHidden
25032   */
25033  function handleHiddenChange (hidden, oldHidden) {
25034    if (!hidden && oldHidden) {
25035      positionDropdown();
25036
25037      // Report in polite mode, because the screenreader should finish the default description of
25038      // the input. element.
25039      reportMessages(true, ReportType.Count | ReportType.Selected);
25040
25041      if (elements) {
25042        $mdUtil.disableScrollAround(elements.ul);
25043        enableWrapScroll = disableElementScrollEvents(angular.element(elements.wrap));
25044      }
25045    } else if (hidden && !oldHidden) {
25046      $mdUtil.enableScrolling();
25047
25048      if (enableWrapScroll) {
25049        enableWrapScroll();
25050        enableWrapScroll = null;
25051      }
25052    }
25053  }
25054
25055  /**
25056   * Disables scrolling for a specific element
25057   */
25058  function disableElementScrollEvents(element) {
25059
25060    function preventDefault(e) {
25061      e.preventDefault();
25062    }
25063
25064    element.on('wheel', preventDefault);
25065    element.on('touchmove', preventDefault);
25066
25067    return function() {
25068      element.off('wheel', preventDefault);
25069      element.off('touchmove', preventDefault);
25070    };
25071  }
25072
25073  /**
25074   * When the user mouses over the dropdown menu, ignore blur events.
25075   */
25076  function onListEnter () {
25077    noBlur = true;
25078  }
25079
25080  /**
25081   * When the user's mouse leaves the menu, blur events may hide the menu again.
25082   */
25083  function onListLeave () {
25084    if (!hasFocus && !ctrl.hidden) elements.input.focus();
25085    noBlur = false;
25086    ctrl.hidden = shouldHide();
25087  }
25088
25089  /**
25090   * When the mouse button is released, send focus back to the input field.
25091   */
25092  function onMouseup () {
25093    elements.input.focus();
25094  }
25095
25096  /**
25097   * Handles changes to the selected item.
25098   * @param selectedItem
25099   * @param previousSelectedItem
25100   */
25101  function selectedItemChange (selectedItem, previousSelectedItem) {
25102
25103    updateModelValidators();
25104
25105    if (selectedItem) {
25106      getDisplayValue(selectedItem).then(function (val) {
25107        $scope.searchText = val;
25108        handleSelectedItemChange(selectedItem, previousSelectedItem);
25109      });
25110    } else if (previousSelectedItem && $scope.searchText) {
25111      getDisplayValue(previousSelectedItem).then(function(displayValue) {
25112        // Clear the searchText, when the selectedItem is set to null.
25113        // Do not clear the searchText, when the searchText isn't matching with the previous
25114        // selected item.
25115        if (angular.isString($scope.searchText)
25116          && displayValue.toString().toLowerCase() === $scope.searchText.toLowerCase()) {
25117          $scope.searchText = '';
25118        }
25119      });
25120    }
25121
25122    if (selectedItem !== previousSelectedItem) announceItemChange();
25123  }
25124
25125  /**
25126   * Use the user-defined expression to announce changes each time a new item is selected
25127   */
25128  function announceItemChange () {
25129    angular.isFunction($scope.itemChange) && $scope.itemChange(getItemAsNameVal($scope.selectedItem));
25130  }
25131
25132  /**
25133   * Use the user-defined expression to announce changes each time the search text is changed
25134   */
25135  function announceTextChange () {
25136    angular.isFunction($scope.textChange) && $scope.textChange();
25137  }
25138
25139  /**
25140   * Calls any external watchers listening for the selected item.  Used in conjunction with
25141   * `registerSelectedItemWatcher`.
25142   * @param selectedItem
25143   * @param previousSelectedItem
25144   */
25145  function handleSelectedItemChange (selectedItem, previousSelectedItem) {
25146    selectedItemWatchers.forEach(function (watcher) { watcher(selectedItem, previousSelectedItem); });
25147  }
25148
25149  /**
25150   * Register a function to be called when the selected item changes.
25151   * @param cb
25152   */
25153  function registerSelectedItemWatcher (cb) {
25154    if (selectedItemWatchers.indexOf(cb) == -1) {
25155      selectedItemWatchers.push(cb);
25156    }
25157  }
25158
25159  /**
25160   * Unregister a function previously registered for selected item changes.
25161   * @param cb
25162   */
25163  function unregisterSelectedItemWatcher (cb) {
25164    var i = selectedItemWatchers.indexOf(cb);
25165    if (i != -1) {
25166      selectedItemWatchers.splice(i, 1);
25167    }
25168  }
25169
25170  /**
25171   * Handles changes to the searchText property.
25172   * @param searchText
25173   * @param previousSearchText
25174   */
25175  function handleSearchText (searchText, previousSearchText) {
25176    ctrl.index = getDefaultIndex();
25177
25178    // do nothing on init
25179    if (searchText === previousSearchText) return;
25180
25181    updateModelValidators();
25182
25183    getDisplayValue($scope.selectedItem).then(function (val) {
25184      // clear selected item if search text no longer matches it
25185      if (searchText !== val) {
25186        $scope.selectedItem = null;
25187
25188
25189        // trigger change event if available
25190        if (searchText !== previousSearchText) announceTextChange();
25191
25192        // cancel results if search text is not long enough
25193        if (!isMinLengthMet()) {
25194          ctrl.matches = [];
25195
25196          setLoading(false);
25197          reportMessages(false, ReportType.Count);
25198
25199        } else {
25200          handleQuery();
25201        }
25202      }
25203    });
25204
25205  }
25206
25207  /**
25208   * Handles input blur event, determines if the dropdown should hide.
25209   */
25210  function blur($event) {
25211    hasFocus = false;
25212
25213    if (!noBlur) {
25214      ctrl.hidden = shouldHide();
25215      evalAttr('ngBlur', { $event: $event });
25216    }
25217  }
25218
25219  /**
25220   * Force blur on input element
25221   * @param forceBlur
25222   */
25223  function doBlur(forceBlur) {
25224    if (forceBlur) {
25225      noBlur = false;
25226      hasFocus = false;
25227    }
25228    elements.input.blur();
25229  }
25230
25231  /**
25232   * Handles input focus event, determines if the dropdown should show.
25233   */
25234  function focus($event) {
25235    hasFocus = true;
25236
25237    if (isSearchable() && isMinLengthMet()) {
25238      handleQuery();
25239    }
25240
25241    ctrl.hidden = shouldHide();
25242
25243    evalAttr('ngFocus', { $event: $event });
25244  }
25245
25246  /**
25247   * Handles keyboard input.
25248   * @param event
25249   */
25250  function keydown (event) {
25251    switch (event.keyCode) {
25252      case $mdConstant.KEY_CODE.DOWN_ARROW:
25253        if (ctrl.loading) return;
25254        event.stopPropagation();
25255        event.preventDefault();
25256        ctrl.index   = Math.min(ctrl.index + 1, ctrl.matches.length - 1);
25257        updateScroll();
25258        reportMessages(false, ReportType.Selected);
25259        break;
25260      case $mdConstant.KEY_CODE.UP_ARROW:
25261        if (ctrl.loading) return;
25262        event.stopPropagation();
25263        event.preventDefault();
25264        ctrl.index   = ctrl.index < 0 ? ctrl.matches.length - 1 : Math.max(0, ctrl.index - 1);
25265        updateScroll();
25266        reportMessages(false, ReportType.Selected);
25267        break;
25268      case $mdConstant.KEY_CODE.TAB:
25269        // If we hit tab, assume that we've left the list so it will close
25270        onListLeave();
25271
25272        if (ctrl.hidden || ctrl.loading || ctrl.index < 0 || ctrl.matches.length < 1) return;
25273        select(ctrl.index);
25274        break;
25275      case $mdConstant.KEY_CODE.ENTER:
25276        if (ctrl.hidden || ctrl.loading || ctrl.index < 0 || ctrl.matches.length < 1) return;
25277        if (hasSelection()) return;
25278        event.stopPropagation();
25279        event.preventDefault();
25280        select(ctrl.index);
25281        break;
25282      case $mdConstant.KEY_CODE.ESCAPE:
25283        event.preventDefault(); // Prevent browser from always clearing input
25284        if (!shouldProcessEscape()) return;
25285        event.stopPropagation();
25286
25287        clearSelectedItem();
25288        if ($scope.searchText && hasEscapeOption('clear')) {
25289          clearSearchText();
25290        }
25291
25292        // Manually hide (needed for mdNotFound support)
25293        ctrl.hidden = true;
25294
25295        if (hasEscapeOption('blur')) {
25296          // Force the component to blur if they hit escape
25297          doBlur(true);
25298        }
25299
25300        break;
25301      default:
25302    }
25303  }
25304
25305  //-- getters
25306
25307  /**
25308   * Returns the minimum length needed to display the dropdown.
25309   * @returns {*}
25310   */
25311  function getMinLength () {
25312    return angular.isNumber($scope.minLength) ? $scope.minLength : 1;
25313  }
25314
25315  /**
25316   * Returns the display value for an item.
25317   * @param item
25318   * @returns {*}
25319   */
25320  function getDisplayValue (item) {
25321    return $q.when(getItemText(item) || item).then(function(itemText) {
25322      if (itemText && !angular.isString(itemText)) {
25323        $log.warn('md-autocomplete: Could not resolve display value to a string. ' +
25324          'Please check the `md-item-text` attribute.');
25325      }
25326
25327      return itemText;
25328    });
25329
25330    /**
25331     * Getter function to invoke user-defined expression (in the directive)
25332     * to convert your object to a single string.
25333     */
25334    function getItemText (item) {
25335      return (item && $scope.itemText) ? $scope.itemText(getItemAsNameVal(item)) : null;
25336    }
25337  }
25338
25339  /**
25340   * Returns the locals object for compiling item templates.
25341   * @param item
25342   * @returns {{}}
25343   */
25344  function getItemAsNameVal (item) {
25345    if (!item) return undefined;
25346
25347    var locals = {};
25348    if (ctrl.itemName) locals[ ctrl.itemName ] = item;
25349
25350    return locals;
25351  }
25352
25353  /**
25354   * Returns the default index based on whether or not autoselect is enabled.
25355   * @returns {number}
25356   */
25357  function getDefaultIndex () {
25358    return $scope.autoselect ? 0 : -1;
25359  }
25360
25361  /**
25362   * Sets the loading parameter and updates the hidden state.
25363   * @param value {boolean} Whether or not the component is currently loading.
25364   */
25365  function setLoading(value) {
25366    if (ctrl.loading != value) {
25367      ctrl.loading = value;
25368    }
25369
25370    // Always refresh the hidden variable as something else might have changed
25371    ctrl.hidden = shouldHide();
25372  }
25373
25374  /**
25375   * Determines if the menu should be hidden.
25376   * @returns {boolean}
25377   */
25378  function shouldHide () {
25379    if (!isSearchable()) return true;    // Hide when not able to query
25380    else return !shouldShow();            // Hide when the dropdown is not able to show.
25381  }
25382
25383  /**
25384   * Determines whether the autocomplete is able to query within the current state.
25385   * @returns {boolean}
25386   */
25387  function isSearchable() {
25388    if (ctrl.loading && !hasMatches()) return false; // No query when query is in progress.
25389    else if (hasSelection()) return false;           // No query if there is already a selection
25390    else if (!hasFocus) return false;                // No query if the input does not have focus
25391    return true;
25392  }
25393
25394  /**
25395   * Determines if the escape keydown should be processed
25396   * @returns {boolean}
25397   */
25398  function shouldProcessEscape() {
25399    return hasEscapeOption('blur') || !ctrl.hidden || ctrl.loading || hasEscapeOption('clear') && $scope.searchText;
25400  }
25401
25402  /**
25403   * Determines if an escape option is set
25404   * @returns {boolean}
25405   */
25406  function hasEscapeOption(option) {
25407    return !$scope.escapeOptions || $scope.escapeOptions.toLowerCase().indexOf(option) !== -1;
25408  }
25409
25410  /**
25411   * Determines if the menu should be shown.
25412   * @returns {boolean}
25413   */
25414  function shouldShow() {
25415    return (isMinLengthMet() && hasMatches()) || notFoundVisible();
25416  }
25417
25418  /**
25419   * Returns true if the search text has matches.
25420   * @returns {boolean}
25421   */
25422  function hasMatches() {
25423    return ctrl.matches.length ? true : false;
25424  }
25425
25426  /**
25427   * Returns true if the autocomplete has a valid selection.
25428   * @returns {boolean}
25429   */
25430  function hasSelection() {
25431    return ctrl.scope.selectedItem ? true : false;
25432  }
25433
25434  /**
25435   * Returns true if the loading indicator is, or should be, visible.
25436   * @returns {boolean}
25437   */
25438  function loadingIsVisible() {
25439    return ctrl.loading && !hasSelection();
25440  }
25441
25442  /**
25443   * Returns the display value of the current item.
25444   * @returns {*}
25445   */
25446  function getCurrentDisplayValue () {
25447    return getDisplayValue(ctrl.matches[ ctrl.index ]);
25448  }
25449
25450  /**
25451   * Determines if the minimum length is met by the search text.
25452   * @returns {*}
25453   */
25454  function isMinLengthMet () {
25455    return ($scope.searchText || '').length >= getMinLength();
25456  }
25457
25458  //-- actions
25459
25460  /**
25461   * Defines a public property with a handler and a default value.
25462   * @param key
25463   * @param handler
25464   * @param value
25465   */
25466  function defineProperty (key, handler, value) {
25467    Object.defineProperty(ctrl, key, {
25468      get: function () { return value; },
25469      set: function (newValue) {
25470        var oldValue = value;
25471        value        = newValue;
25472        handler(newValue, oldValue);
25473      }
25474    });
25475  }
25476
25477  /**
25478   * Selects the item at the given index.
25479   * @param index
25480   */
25481  function select (index) {
25482    //-- force form to update state for validation
25483    $mdUtil.nextTick(function () {
25484      getDisplayValue(ctrl.matches[ index ]).then(function (val) {
25485        var ngModel = elements.$.input.controller('ngModel');
25486        ngModel.$setViewValue(val);
25487        ngModel.$render();
25488      }).finally(function () {
25489        $scope.selectedItem = ctrl.matches[ index ];
25490        setLoading(false);
25491      });
25492    }, false);
25493  }
25494
25495  /**
25496   * Clears the searchText value and selected item.
25497   */
25498  function clearValue () {
25499    clearSelectedItem();
25500    clearSearchText();
25501  }
25502
25503  /**
25504   * Clears the selected item
25505   */
25506  function clearSelectedItem () {
25507    // Reset our variables
25508    ctrl.index = 0;
25509    ctrl.matches = [];
25510  }
25511
25512  /**
25513   * Clears the searchText value
25514   */
25515  function clearSearchText () {
25516    // Set the loading to true so we don't see flashes of content.
25517    // The flashing will only occur when an async request is running.
25518    // So the loading process will stop when the results had been retrieved.
25519    setLoading(true);
25520
25521    $scope.searchText = '';
25522
25523    // Normally, triggering the change / input event is unnecessary, because the browser detects it properly.
25524    // But some browsers are not detecting it properly, which means that we have to trigger the event.
25525    // Using the `input` is not working properly, because for example IE11 is not supporting the `input` event.
25526    // The `change` event is a good alternative and is supported by all supported browsers.
25527    var eventObj = document.createEvent('CustomEvent');
25528    eventObj.initCustomEvent('change', true, true, { value: '' });
25529    elements.input.dispatchEvent(eventObj);
25530
25531    // For some reason, firing the above event resets the value of $scope.searchText if
25532    // $scope.searchText has a space character at the end, so we blank it one more time and then
25533    // focus.
25534    elements.input.blur();
25535    $scope.searchText = '';
25536    elements.input.focus();
25537  }
25538
25539  /**
25540   * Fetches the results for the provided search text.
25541   * @param searchText
25542   */
25543  function fetchResults (searchText) {
25544    var items = $scope.$parent.$eval(itemExpr),
25545        term  = searchText.toLowerCase(),
25546        isList = angular.isArray(items),
25547        isPromise = !!items.then; // Every promise should contain a `then` property
25548
25549    if (isList) onResultsRetrieved(items);
25550    else if (isPromise) handleAsyncResults(items);
25551
25552    function handleAsyncResults(items) {
25553      if ( !items ) return;
25554
25555      items = $q.when(items);
25556      fetchesInProgress++;
25557      setLoading(true);
25558
25559      $mdUtil.nextTick(function () {
25560          items
25561            .then(onResultsRetrieved)
25562            .finally(function(){
25563              if (--fetchesInProgress === 0) {
25564                setLoading(false);
25565              }
25566            });
25567      },true, $scope);
25568    }
25569
25570    function onResultsRetrieved(matches) {
25571      cache[term] = matches;
25572
25573      // Just cache the results if the request is now outdated.
25574      // The request becomes outdated, when the new searchText has changed during the result fetching.
25575      if ((searchText || '') !== ($scope.searchText || '')) {
25576        return;
25577      }
25578
25579      handleResults(matches);
25580    }
25581  }
25582
25583
25584  /**
25585   * Reports given message types to supported screenreaders.
25586   * @param {boolean} isPolite Whether the announcement should be polite.
25587   * @param {!number} types Message flags to be reported to the screenreader.
25588   */
25589  function reportMessages(isPolite, types) {
25590
25591    var politeness = isPolite ? 'polite' : 'assertive';
25592    var messages = [];
25593
25594    if (types & ReportType.Selected && ctrl.index !== -1) {
25595      messages.push(getCurrentDisplayValue());
25596    }
25597
25598    if (types & ReportType.Count) {
25599      messages.push($q.resolve(getCountMessage()));
25600    }
25601
25602    $q.all(messages).then(function(data) {
25603      $mdLiveAnnouncer.announce(data.join(' '), politeness);
25604    });
25605
25606  }
25607
25608  /**
25609   * Returns the ARIA message for how many results match the current query.
25610   * @returns {*}
25611   */
25612  function getCountMessage () {
25613    switch (ctrl.matches.length) {
25614      case 0:
25615        return 'There are no matches available.';
25616      case 1:
25617        return 'There is 1 match available.';
25618      default:
25619        return 'There are ' + ctrl.matches.length + ' matches available.';
25620    }
25621  }
25622
25623  /**
25624   * Makes sure that the focused element is within view.
25625   */
25626  function updateScroll () {
25627    if (!elements.li[0]) return;
25628    var height = elements.li[0].offsetHeight,
25629        top = height * ctrl.index,
25630        bot = top + height,
25631        hgt = elements.scroller.clientHeight,
25632        scrollTop = elements.scroller.scrollTop;
25633    if (top < scrollTop) {
25634      scrollTo(top);
25635    } else if (bot > scrollTop + hgt) {
25636      scrollTo(bot - hgt);
25637    }
25638  }
25639
25640  function isPromiseFetching() {
25641    return fetchesInProgress !== 0;
25642  }
25643
25644  function scrollTo (offset) {
25645    elements.$.scrollContainer.controller('mdVirtualRepeatContainer').scrollTo(offset);
25646  }
25647
25648  function notFoundVisible () {
25649    var textLength = (ctrl.scope.searchText || '').length;
25650
25651    return ctrl.hasNotFound && !hasMatches() && (!ctrl.loading || isPromiseFetching()) && textLength >= getMinLength() && (hasFocus || noBlur) && !hasSelection();
25652  }
25653
25654  /**
25655   * Starts the query to gather the results for the current searchText.  Attempts to return cached
25656   * results first, then forwards the process to `fetchResults` if necessary.
25657   */
25658  function handleQuery () {
25659    var searchText = $scope.searchText || '';
25660    var term = searchText.toLowerCase();
25661
25662    // If caching is enabled and the current searchText is stored in the cache
25663    if (!$scope.noCache && cache[term]) {
25664      // The results should be handled as same as a normal un-cached request does.
25665      handleResults(cache[term]);
25666    } else {
25667      fetchResults(searchText);
25668    }
25669
25670    ctrl.hidden = shouldHide();
25671  }
25672
25673  /**
25674   * Handles the retrieved results by showing them in the autocompletes dropdown.
25675   * @param results Retrieved results
25676   */
25677  function handleResults(results) {
25678    ctrl.matches = results;
25679    ctrl.hidden  = shouldHide();
25680
25681    // If loading is in progress, then we'll end the progress. This is needed for example,
25682    // when the `clear` button was clicked, because there we always show the loading process, to prevent flashing.
25683    if (ctrl.loading) setLoading(false);
25684
25685    if ($scope.selectOnMatch) selectItemOnMatch();
25686
25687    positionDropdown();
25688    reportMessages(true, ReportType.Count);
25689  }
25690
25691  /**
25692   * If there is only one matching item and the search text matches its display value exactly,
25693   * automatically select that item.  Note: This function is only called if the user uses the
25694   * `md-select-on-match` flag.
25695   */
25696  function selectItemOnMatch () {
25697    var searchText = $scope.searchText,
25698        matches    = ctrl.matches,
25699        item       = matches[ 0 ];
25700    if (matches.length === 1) getDisplayValue(item).then(function (displayValue) {
25701      var isMatching = searchText == displayValue;
25702      if ($scope.matchInsensitive && !isMatching) {
25703        isMatching = searchText.toLowerCase() == displayValue.toLowerCase();
25704      }
25705
25706      if (isMatching) select(0);
25707    });
25708  }
25709
25710  /**
25711   * Evaluates an attribute expression against the parent scope.
25712   * @param {String} attr Name of the attribute to be evaluated.
25713   * @param {Object?} locals Properties to be injected into the evaluation context.
25714   */
25715 function evalAttr(attr, locals) {
25716    if ($attrs[attr]) {
25717      $scope.$parent.$eval($attrs[attr], locals || {});
25718    }
25719  }
25720
25721}
25722
25723})();
25724(function(){
25725"use strict";
25726
25727
25728MdAutocomplete.$inject = ["$$mdSvgRegistry"];angular
25729    .module('material.components.autocomplete')
25730    .directive('mdAutocomplete', MdAutocomplete);
25731
25732/**
25733 * @ngdoc directive
25734 * @name mdAutocomplete
25735 * @module material.components.autocomplete
25736 *
25737 * @description
25738 * `<md-autocomplete>` is a special input component with a drop-down of all possible matches to a
25739 *     custom query. This component allows you to provide real-time suggestions as the user types
25740 *     in the input area.
25741 *
25742 * To start, you will need to specify the required parameters and provide a template for your
25743 *     results. The content inside `md-autocomplete` will be treated as a template.
25744 *
25745 * In more complex cases, you may want to include other content such as a message to display when
25746 *     no matches were found.  You can do this by wrapping your template in `md-item-template` and
25747 *     adding a tag for `md-not-found`.  An example of this is shown below.
25748 *
25749 * To reset the displayed value you must clear both values for `md-search-text` and `md-selected-item`.
25750 *
25751 * ### Validation
25752 *
25753 * You can use `ng-messages` to include validation the same way that you would normally validate;
25754 *     however, if you want to replicate a standard input with a floating label, you will have to
25755 *     do the following:
25756 *
25757 * - Make sure that your template is wrapped in `md-item-template`
25758 * - Add your `ng-messages` code inside of `md-autocomplete`
25759 * - Add your validation properties to `md-autocomplete` (ie. `required`)
25760 * - Add a `name` to `md-autocomplete` (to be used on the generated `input`)
25761 *
25762 * There is an example below of how this should look.
25763 *
25764 * ### Snapping Drop-Down
25765 *
25766 * You can cause the autocomplete drop-down to snap to an ancestor element by applying the
25767 *     `md-autocomplete-snap` attribute to that element. You can also snap to the width of
25768 *     the `md-autocomplete-snap` element by setting the attribute's value to `width`
25769 *     (ie. `md-autocomplete-snap="width"`).
25770 *
25771 * ### Notes
25772 *
25773 * **Autocomplete Dropdown Items Rendering**
25774 *
25775 * The `md-autocomplete` uses the the <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeat</a>
25776 * directive for displaying the results inside of the dropdown.<br/>
25777 *
25778 * > When encountering issues regarding the item template please take a look at the
25779 *   <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeatContainer</a> documentation.
25780 *
25781 * **Autocomplete inside of a Virtual Repeat**
25782 *
25783 * When using the `md-autocomplete` directive inside of a
25784 * <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeatContainer</a> the dropdown items might
25785 * not update properly, because caching of the results is enabled by default.
25786 *
25787 * The autocomplete will then show invalid dropdown items, because the VirtualRepeat only updates the
25788 * scope bindings, rather than re-creating the `md-autocomplete` and the previous cached results will be used.
25789 *
25790 * > To avoid such problems ensure that the autocomplete does not cache any results.
25791 *
25792 * <hljs lang="html">
25793 *   <md-autocomplete
25794 *       md-no-cache="true"
25795 *       md-selected-item="selectedItem"
25796 *       md-items="item in items"
25797 *       md-search-text="searchText"
25798 *       md-item-text="item.display">
25799 *     <span>{{ item.display }}</span>
25800 *   </md-autocomplete>
25801 * </hljs>
25802 *
25803 *
25804 *
25805 * @param {expression} md-items An expression in the format of `item in results` to iterate over
25806 *     matches for your search.<br/><br/>
25807 *     The `results` expression can be also a function, which returns the results synchronously
25808 *     or asynchronously (per Promise)
25809 * @param {expression=} md-selected-item-change An expression to be run each time a new item is
25810 *     selected
25811 * @param {expression=} md-search-text-change An expression to be run each time the search text
25812 *     updates
25813 * @param {expression=} md-search-text A model to bind the search query text to
25814 * @param {object=} md-selected-item A model to bind the selected item to
25815 * @param {expression=} md-item-text An expression that will convert your object to a single string.
25816 * @param {string=} placeholder Placeholder text that will be forwarded to the input.
25817 * @param {boolean=} md-no-cache Disables the internal caching that happens in autocomplete
25818 * @param {boolean=} ng-disabled Determines whether or not to disable the input field
25819 * @param {boolean=} md-require-match When set to true, the autocomplete will add a validator,
25820 *     which will evaluate to false, when no item is currently selected.
25821 * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will
25822 *     make suggestions
25823 * @param {number=} md-delay Specifies the amount of time (in milliseconds) to wait before looking
25824 *     for results
25825 * @param {boolean=} md-clear-button Whether the clear button for the autocomplete input should show up or not.
25826 * @param {boolean=} md-autofocus If true, the autocomplete will be automatically focused when a `$mdDialog`,
25827 *     `$mdBottomsheet` or `$mdSidenav`, which contains the autocomplete, is opening. <br/><br/>
25828 *     Also the autocomplete will immediately focus the input element.
25829 * @param {boolean=} md-no-asterisk When present, asterisk will not be appended to the floating label
25830 * @param {boolean=} md-autoselect If set to true, the first item will be automatically selected
25831 *     in the dropdown upon open.
25832 * @param {string=} md-menu-class This will be applied to the dropdown menu for styling
25833 * @param {string=} md-floating-label This will add a floating label to autocomplete and wrap it in
25834 *     `md-input-container`
25835 * @param {string=} md-input-name The name attribute given to the input element to be used with
25836 *     FormController
25837 * @param {string=} md-select-on-focus When present the inputs text will be automatically selected
25838 *     on focus.
25839 * @param {string=} md-input-id An ID to be added to the input element
25840 * @param {number=} md-input-minlength The minimum length for the input's value for validation
25841 * @param {number=} md-input-maxlength The maximum length for the input's value for validation
25842 * @param {boolean=} md-select-on-match When set, autocomplete will automatically select exact
25843 *     the item if the search text is an exact match. <br/><br/>
25844 *     Exact match means that there is only one match showing up.
25845 * @param {boolean=} md-match-case-insensitive When set and using `md-select-on-match`, autocomplete
25846 *     will select on case-insensitive match
25847 * @param {string=} md-escape-options Override escape key logic. Default is `blur clear`.<br/>
25848 *     Options: `blur | clear`, `none`
25849 * @param {string=} md-dropdown-items Specifies the maximum amount of items to be shown in
25850 *     the dropdown.<br/><br/>
25851 *     When the dropdown doesn't fit into the viewport, the dropdown will shrink
25852 *     as less as possible.
25853 * @param {string=} md-dropdown-position Overrides the default dropdown position. Options: `top`, `bottom`.
25854 * @param {string=} ng-trim If set to false, the search text will be not trimmed automatically.
25855 *     Defaults to true.
25856 * @param {string=} ng-pattern Adds the pattern validator to the ngModel of the search text.
25857 *     [ngPattern Directive](https://docs.angularjs.org/api/ng/directive/ngPattern)
25858 *
25859 * @usage
25860 * ### Basic Example
25861 * <hljs lang="html">
25862 *   <md-autocomplete
25863 *       md-selected-item="selectedItem"
25864 *       md-search-text="searchText"
25865 *       md-items="item in getMatches(searchText)"
25866 *       md-item-text="item.display">
25867 *     <span md-highlight-text="searchText">{{item.display}}</span>
25868 *   </md-autocomplete>
25869 * </hljs>
25870 *
25871 * ### Example with "not found" message
25872 * <hljs lang="html">
25873 * <md-autocomplete
25874 *     md-selected-item="selectedItem"
25875 *     md-search-text="searchText"
25876 *     md-items="item in getMatches(searchText)"
25877 *     md-item-text="item.display">
25878 *   <md-item-template>
25879 *     <span md-highlight-text="searchText">{{item.display}}</span>
25880 *   </md-item-template>
25881 *   <md-not-found>
25882 *     No matches found.
25883 *   </md-not-found>
25884 * </md-autocomplete>
25885 * </hljs>
25886 *
25887 * In this example, our code utilizes `md-item-template` and `md-not-found` to specify the
25888 *     different parts that make up our component.
25889 *
25890 * ### Clear button for the input
25891 * By default, for floating label autocomplete's the clear button is not showing up
25892 * ([See specs](https://material.google.com/components/text-fields.html#text-fields-auto-complete-text-field))
25893 *
25894 * Nevertheless, developers are able to explicitly toggle the clear button for all types of autocomplete's.
25895 *
25896 * <hljs lang="html">
25897 *   <md-autocomplete ... md-clear-button="true"></md-autocomplete>
25898 *   <md-autocomplete ... md-clear-button="false"></md-autocomplete>
25899 * </hljs>
25900 *
25901 * ### Example with validation
25902 * <hljs lang="html">
25903 * <form name="autocompleteForm">
25904 *   <md-autocomplete
25905 *       required
25906 *       md-input-name="autocomplete"
25907 *       md-selected-item="selectedItem"
25908 *       md-search-text="searchText"
25909 *       md-items="item in getMatches(searchText)"
25910 *       md-item-text="item.display">
25911 *     <md-item-template>
25912 *       <span md-highlight-text="searchText">{{item.display}}</span>
25913 *     </md-item-template>
25914 *     <div ng-messages="autocompleteForm.autocomplete.$error">
25915 *       <div ng-message="required">This field is required</div>
25916 *     </div>
25917 *   </md-autocomplete>
25918 * </form>
25919 * </hljs>
25920 *
25921 * In this example, our code utilizes `md-item-template` and `ng-messages` to specify
25922 *     input validation for the field.
25923 *
25924 * ### Asynchronous Results
25925 * The autocomplete items expression also supports promises, which will resolve with the query results.
25926 *
25927 * <hljs lang="js">
25928 *   function AppController($scope, $http) {
25929 *     $scope.query = function(searchText) {
25930 *       return $http
25931 *         .get(BACKEND_URL + '/items/' + searchText)
25932 *         .then(function(data) {
25933 *           // Map the response object to the data object.
25934 *           return data;
25935 *         });
25936 *     };
25937 *   }
25938 * </hljs>
25939 *
25940 * <hljs lang="html">
25941 *   <md-autocomplete
25942 *       md-selected-item="selectedItem"
25943 *       md-search-text="searchText"
25944 *       md-items="item in query(searchText)">
25945 *     <md-item-template>
25946 *       <span md-highlight-text="searchText">{{item}}</span>
25947 *     </md-item-template>
25948 * </md-autocomplete>
25949 * </hljs>
25950 *
25951 */
25952
25953function MdAutocomplete ($$mdSvgRegistry) {
25954
25955  return {
25956    controller:   'MdAutocompleteCtrl',
25957    controllerAs: '$mdAutocompleteCtrl',
25958    scope:        {
25959      inputName:        '@mdInputName',
25960      inputMinlength:   '@mdInputMinlength',
25961      inputMaxlength:   '@mdInputMaxlength',
25962      searchText:       '=?mdSearchText',
25963      selectedItem:     '=?mdSelectedItem',
25964      itemsExpr:        '@mdItems',
25965      itemText:         '&mdItemText',
25966      placeholder:      '@placeholder',
25967      noCache:          '=?mdNoCache',
25968      requireMatch:     '=?mdRequireMatch',
25969      selectOnMatch:    '=?mdSelectOnMatch',
25970      matchInsensitive: '=?mdMatchCaseInsensitive',
25971      itemChange:       '&?mdSelectedItemChange',
25972      textChange:       '&?mdSearchTextChange',
25973      minLength:        '=?mdMinLength',
25974      delay:            '=?mdDelay',
25975      autofocus:        '=?mdAutofocus',
25976      floatingLabel:    '@?mdFloatingLabel',
25977      autoselect:       '=?mdAutoselect',
25978      menuClass:        '@?mdMenuClass',
25979      inputId:          '@?mdInputId',
25980      escapeOptions:    '@?mdEscapeOptions',
25981      dropdownItems:    '=?mdDropdownItems',
25982      dropdownPosition: '@?mdDropdownPosition',
25983      clearButton:      '=?mdClearButton'
25984    },
25985    compile: function(tElement, tAttrs) {
25986      var attributes = ['md-select-on-focus', 'md-no-asterisk', 'ng-trim', 'ng-pattern'];
25987      var input = tElement.find('input');
25988
25989      attributes.forEach(function(attribute) {
25990        var attrValue = tAttrs[tAttrs.$normalize(attribute)];
25991
25992        if (attrValue !== null) {
25993          input.attr(attribute, attrValue);
25994        }
25995      });
25996
25997      return function(scope, element, attrs, ctrl) {
25998        // Retrieve the state of using a md-not-found template by using our attribute, which will
25999        // be added to the element in the template function.
26000        ctrl.hasNotFound = !!element.attr('md-has-not-found');
26001
26002        // By default the inset autocomplete should show the clear button when not explicitly overwritten.
26003        if (!angular.isDefined(attrs.mdClearButton) && !scope.floatingLabel) {
26004          scope.clearButton = true;
26005        }
26006      }
26007    },
26008    template:     function (element, attr) {
26009      var noItemsTemplate = getNoItemsTemplate(),
26010          itemTemplate    = getItemTemplate(),
26011          leftover        = element.html(),
26012          tabindex        = attr.tabindex;
26013
26014      // Set our attribute for the link function above which runs later.
26015      // We will set an attribute, because otherwise the stored variables will be trashed when
26016      // removing the element is hidden while retrieving the template. For example when using ngIf.
26017      if (noItemsTemplate) element.attr('md-has-not-found', true);
26018
26019      // Always set our tabindex of the autocomplete directive to -1, because our input
26020      // will hold the actual tabindex.
26021      element.attr('tabindex', '-1');
26022
26023      return '\
26024        <md-autocomplete-wrap\
26025            ng-class="{ \'md-whiteframe-z1\': !floatingLabel, \
26026                        \'md-menu-showing\': !$mdAutocompleteCtrl.hidden, \
26027                        \'md-show-clear-button\': !!clearButton }">\
26028          ' + getInputElement() + '\
26029          ' + getClearButton() + '\
26030          <md-progress-linear\
26031              class="' + (attr.mdFloatingLabel ? 'md-inline' : '') + '"\
26032              ng-if="$mdAutocompleteCtrl.loadingIsVisible()"\
26033              md-mode="indeterminate"></md-progress-linear>\
26034          <md-virtual-repeat-container\
26035              md-auto-shrink\
26036              md-auto-shrink-min="1"\
26037              ng-mouseenter="$mdAutocompleteCtrl.listEnter()"\
26038              ng-mouseleave="$mdAutocompleteCtrl.listLeave()"\
26039              ng-mouseup="$mdAutocompleteCtrl.mouseUp()"\
26040              ng-hide="$mdAutocompleteCtrl.hidden"\
26041              class="md-autocomplete-suggestions-container md-whiteframe-z1"\
26042              ng-class="{ \'md-not-found\': $mdAutocompleteCtrl.notFoundVisible() }"\
26043              role="presentation">\
26044            <ul class="md-autocomplete-suggestions"\
26045                ng-class="::menuClass"\
26046                id="ul-{{$mdAutocompleteCtrl.id}}">\
26047              <li md-virtual-repeat="item in $mdAutocompleteCtrl.matches"\
26048                  ng-class="{ selected: $index === $mdAutocompleteCtrl.index }"\
26049                  ng-click="$mdAutocompleteCtrl.select($index)"\
26050                  md-extra-name="$mdAutocompleteCtrl.itemName">\
26051                  ' + itemTemplate + '\
26052                  </li>' + noItemsTemplate + '\
26053            </ul>\
26054          </md-virtual-repeat-container>\
26055        </md-autocomplete-wrap>';
26056
26057      function getItemTemplate() {
26058        var templateTag = element.find('md-item-template').detach(),
26059            html = templateTag.length ? templateTag.html() : element.html();
26060        if (!templateTag.length) element.empty();
26061        return '<md-autocomplete-parent-scope md-autocomplete-replace>' + html + '</md-autocomplete-parent-scope>';
26062      }
26063
26064      function getNoItemsTemplate() {
26065        var templateTag = element.find('md-not-found').detach(),
26066            template = templateTag.length ? templateTag.html() : '';
26067        return template
26068            ? '<li ng-if="$mdAutocompleteCtrl.notFoundVisible()"\
26069                         md-autocomplete-parent-scope>' + template + '</li>'
26070            : '';
26071
26072      }
26073
26074      function getInputElement () {
26075        if (attr.mdFloatingLabel) {
26076          return '\
26077            <md-input-container ng-if="floatingLabel">\
26078              <label>{{floatingLabel}}</label>\
26079              <input type="search"\
26080                  ' + (tabindex != null ? 'tabindex="' + tabindex + '"' : '') + '\
26081                  id="{{ inputId || \'fl-input-\' + $mdAutocompleteCtrl.id }}"\
26082                  name="{{inputName}}"\
26083                  autocomplete="off"\
26084                  ng-required="$mdAutocompleteCtrl.isRequired"\
26085                  ng-readonly="$mdAutocompleteCtrl.isReadonly"\
26086                  ng-minlength="inputMinlength"\
26087                  ng-maxlength="inputMaxlength"\
26088                  ng-disabled="$mdAutocompleteCtrl.isDisabled"\
26089                  ng-model="$mdAutocompleteCtrl.scope.searchText"\
26090                  ng-model-options="{ allowInvalid: true }"\
26091                  ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
26092                  ng-blur="$mdAutocompleteCtrl.blur($event)"\
26093                  ng-focus="$mdAutocompleteCtrl.focus($event)"\
26094                  aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
26095                  aria-label="{{floatingLabel}}"\
26096                  aria-autocomplete="list"\
26097                  role="combobox"\
26098                  aria-haspopup="true"\
26099                  aria-activedescendant=""\
26100                  aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
26101              <div md-autocomplete-parent-scope md-autocomplete-replace>' + leftover + '</div>\
26102            </md-input-container>';
26103        } else {
26104          return '\
26105            <input type="search"\
26106                ' + (tabindex != null ? 'tabindex="' + tabindex + '"' : '') + '\
26107                id="{{ inputId || \'input-\' + $mdAutocompleteCtrl.id }}"\
26108                name="{{inputName}}"\
26109                ng-if="!floatingLabel"\
26110                autocomplete="off"\
26111                ng-required="$mdAutocompleteCtrl.isRequired"\
26112                ng-disabled="$mdAutocompleteCtrl.isDisabled"\
26113                ng-readonly="$mdAutocompleteCtrl.isReadonly"\
26114                ng-minlength="inputMinlength"\
26115                ng-maxlength="inputMaxlength"\
26116                ng-model="$mdAutocompleteCtrl.scope.searchText"\
26117                ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
26118                ng-blur="$mdAutocompleteCtrl.blur($event)"\
26119                ng-focus="$mdAutocompleteCtrl.focus($event)"\
26120                placeholder="{{placeholder}}"\
26121                aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
26122                aria-label="{{placeholder}}"\
26123                aria-autocomplete="list"\
26124                role="combobox"\
26125                aria-haspopup="true"\
26126                aria-activedescendant=""\
26127                aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>';
26128        }
26129      }
26130
26131      function getClearButton() {
26132        return '' +
26133          '<button ' +
26134              'type="button" ' +
26135              'aria-label="Clear Input" ' +
26136              'tabindex="-1" ' +
26137              'ng-if="clearButton && $mdAutocompleteCtrl.scope.searchText && !$mdAutocompleteCtrl.isDisabled" ' +
26138              'ng-click="$mdAutocompleteCtrl.clear($event)">' +
26139            '<md-icon md-svg-src="' + $$mdSvgRegistry.mdClose + '"></md-icon>' +
26140          '</button>';
26141        }
26142    }
26143  };
26144}
26145
26146})();
26147(function(){
26148"use strict";
26149
26150
26151MdAutocompleteItemScopeDirective.$inject = ["$compile", "$mdUtil"];angular
26152  .module('material.components.autocomplete')
26153  .directive('mdAutocompleteParentScope', MdAutocompleteItemScopeDirective);
26154
26155function MdAutocompleteItemScopeDirective($compile, $mdUtil) {
26156  return {
26157    restrict: 'AE',
26158    compile: compile,
26159    terminal: true,
26160    transclude: 'element'
26161  };
26162
26163  function compile(tElement, tAttr, transclude) {
26164    return function postLink(scope, element, attr) {
26165      var ctrl = scope.$mdAutocompleteCtrl;
26166      var newScope = ctrl.parent.$new();
26167      var itemName = ctrl.itemName;
26168
26169      // Watch for changes to our scope's variables and copy them to the new scope
26170      watchVariable('$index', '$index');
26171      watchVariable('item', itemName);
26172
26173      // Ensure that $digest calls on our scope trigger $digest on newScope.
26174      connectScopes();
26175
26176      // Link the element against newScope.
26177      transclude(newScope, function(clone) {
26178        element.after(clone);
26179      });
26180
26181      /**
26182       * Creates a watcher for variables that are copied from the parent scope
26183       * @param variable
26184       * @param alias
26185       */
26186      function watchVariable(variable, alias) {
26187        newScope[alias] = scope[variable];
26188
26189        scope.$watch(variable, function(value) {
26190          $mdUtil.nextTick(function() {
26191            newScope[alias] = value;
26192          });
26193        });
26194      }
26195
26196      /**
26197       * Creates watchers on scope and newScope that ensure that for any
26198       * $digest of scope, newScope is also $digested.
26199       */
26200      function connectScopes() {
26201        var scopeDigesting = false;
26202        var newScopeDigesting = false;
26203
26204        scope.$watch(function() {
26205          if (newScopeDigesting || scopeDigesting) {
26206            return;
26207          }
26208
26209          scopeDigesting = true;
26210          scope.$$postDigest(function() {
26211            if (!newScopeDigesting) {
26212              newScope.$digest();
26213            }
26214
26215            scopeDigesting = newScopeDigesting = false;
26216          });
26217        });
26218
26219        newScope.$watch(function() {
26220          newScopeDigesting = true;
26221        });
26222      }
26223    };
26224  }
26225}
26226})();
26227(function(){
26228"use strict";
26229
26230
26231MdHighlightCtrl.$inject = ["$scope", "$element", "$attrs"];angular
26232    .module('material.components.autocomplete')
26233    .controller('MdHighlightCtrl', MdHighlightCtrl);
26234
26235function MdHighlightCtrl ($scope, $element, $attrs) {
26236  this.$scope = $scope;
26237  this.$element = $element;
26238  this.$attrs = $attrs;
26239
26240  // Cache the Regex to avoid rebuilding each time.
26241  this.regex = null;
26242}
26243
26244MdHighlightCtrl.prototype.init = function(unsafeTermFn, unsafeContentFn) {
26245
26246  this.flags = this.$attrs.mdHighlightFlags || '';
26247
26248  this.unregisterFn = this.$scope.$watch(function($scope) {
26249    return {
26250      term: unsafeTermFn($scope),
26251      contentText: unsafeContentFn($scope)
26252    };
26253  }.bind(this), this.onRender.bind(this), true);
26254
26255  this.$element.on('$destroy', this.unregisterFn);
26256};
26257
26258/**
26259 * Triggered once a new change has been recognized and the highlighted
26260 * text needs to be updated.
26261 */
26262MdHighlightCtrl.prototype.onRender = function(state, prevState) {
26263
26264  var contentText = state.contentText;
26265
26266  /* Update the regex if it's outdated, because we don't want to rebuilt it constantly. */
26267  if (this.regex === null || state.term !== prevState.term) {
26268    this.regex = this.createRegex(state.term, this.flags);
26269  }
26270
26271  /* If a term is available apply the regex to the content */
26272  if (state.term) {
26273    this.applyRegex(contentText);
26274  } else {
26275    this.$element.text(contentText);
26276  }
26277
26278};
26279
26280/**
26281 * Decomposes the specified text into different tokens (whether match or not).
26282 * Breaking down the string guarantees proper XSS protection due to the native browser
26283 * escaping of unsafe text.
26284 */
26285MdHighlightCtrl.prototype.applyRegex = function(text) {
26286  var tokens = this.resolveTokens(text);
26287
26288  this.$element.empty();
26289
26290  tokens.forEach(function (token) {
26291
26292    if (token.isMatch) {
26293      var tokenEl = angular.element('<span class="highlight">').text(token.text);
26294
26295      this.$element.append(tokenEl);
26296    } else {
26297      this.$element.append(document.createTextNode(token));
26298    }
26299
26300  }.bind(this));
26301
26302};
26303
26304  /**
26305 * Decomposes the specified text into different tokens by running the regex against the text.
26306 */
26307MdHighlightCtrl.prototype.resolveTokens = function(string) {
26308  var tokens = [];
26309  var lastIndex = 0;
26310
26311  // Use replace here, because it supports global and single regular expressions at same time.
26312  string.replace(this.regex, function(match, index) {
26313    appendToken(lastIndex, index);
26314
26315    tokens.push({
26316      text: match,
26317      isMatch: true
26318    });
26319
26320    lastIndex = index + match.length;
26321  });
26322
26323  // Append the missing text as a token.
26324  appendToken(lastIndex);
26325
26326  return tokens;
26327
26328  function appendToken(from, to) {
26329    var targetText = string.slice(from, to);
26330    targetText && tokens.push(targetText);
26331  }
26332};
26333
26334/** Creates a regex for the specified text with the given flags. */
26335MdHighlightCtrl.prototype.createRegex = function(term, flags) {
26336  var startFlag = '', endFlag = '';
26337  var regexTerm = this.sanitizeRegex(term);
26338
26339  if (flags.indexOf('^') >= 0) startFlag = '^';
26340  if (flags.indexOf('$') >= 0) endFlag = '$';
26341
26342  return new RegExp(startFlag + regexTerm + endFlag, flags.replace(/[$\^]/g, ''));
26343};
26344
26345/** Sanitizes a regex by removing all common RegExp identifiers */
26346MdHighlightCtrl.prototype.sanitizeRegex = function(term) {
26347  return term && term.toString().replace(/[\\\^\$\*\+\?\.\(\)\|\{}\[\]]/g, '\\$&');
26348};
26349
26350})();
26351(function(){
26352"use strict";
26353
26354
26355MdHighlight.$inject = ["$interpolate", "$parse"];angular
26356    .module('material.components.autocomplete')
26357    .directive('mdHighlightText', MdHighlight);
26358
26359/**
26360 * @ngdoc directive
26361 * @name mdHighlightText
26362 * @module material.components.autocomplete
26363 *
26364 * @description
26365 * The `md-highlight-text` directive allows you to specify text that should be highlighted within
26366 *     an element.  Highlighted text will be wrapped in `<span class="highlight"></span>` which can
26367 *     be styled through CSS.  Please note that child elements may not be used with this directive.
26368 *
26369 * @param {string} md-highlight-text A model to be searched for
26370 * @param {string=} md-highlight-flags A list of flags (loosely based on JavaScript RexExp flags).
26371 * #### **Supported flags**:
26372 * - `g`: Find all matches within the provided text
26373 * - `i`: Ignore case when searching for matches
26374 * - `$`: Only match if the text ends with the search term
26375 * - `^`: Only match if the text begins with the search term
26376 *
26377 * @usage
26378 * <hljs lang="html">
26379 * <input placeholder="Enter a search term..." ng-model="searchTerm" type="text" />
26380 * <ul>
26381 *   <li ng-repeat="result in results" md-highlight-text="searchTerm">
26382 *     {{result.text}}
26383 *   </li>
26384 * </ul>
26385 * </hljs>
26386 */
26387
26388function MdHighlight ($interpolate, $parse) {
26389  return {
26390    terminal: true,
26391    controller: 'MdHighlightCtrl',
26392    compile: function mdHighlightCompile(tElement, tAttr) {
26393      var termExpr = $parse(tAttr.mdHighlightText);
26394      var unsafeContentExpr = $interpolate(tElement.html());
26395
26396      return function mdHighlightLink(scope, element, attr, ctrl) {
26397        ctrl.init(termExpr, unsafeContentExpr);
26398      };
26399    }
26400  };
26401}
26402
26403})();
26404(function(){
26405"use strict";
26406
26407
26408MdChipCtrl.$inject = ["$scope", "$element", "$mdConstant", "$timeout", "$mdUtil"];angular
26409  .module('material.components.chips')
26410  .controller('MdChipCtrl', MdChipCtrl);
26411
26412/**
26413 * Controller for the MdChip component. Responsible for handling keyboard
26414 * events and editting the chip if needed.
26415 *
26416 * @param $scope
26417 * @param $element
26418 * @param $mdConstant
26419 * @param $timeout
26420 * @param $mdUtil
26421 * @constructor
26422 */
26423function MdChipCtrl ($scope, $element, $mdConstant, $timeout, $mdUtil) {
26424  /**
26425   * @type {$scope}
26426   */
26427  this.$scope = $scope;
26428
26429  /**
26430   * @type {$element}
26431   */
26432  this.$element = $element;
26433
26434  /**
26435   * @type {$mdConstant}
26436   */
26437  this.$mdConstant = $mdConstant;
26438
26439  /**
26440   * @type {$timeout}
26441   */
26442  this.$timeout = $timeout;
26443
26444  /**
26445   * @type {$mdUtil}
26446   */
26447  this.$mdUtil = $mdUtil;
26448
26449  /**
26450   * @type {boolean}
26451   */
26452  this.isEditting = false;
26453
26454  /**
26455   * @type {MdChipsCtrl}
26456   */
26457  this.parentController = undefined;
26458
26459  /**
26460   * @type {boolean}
26461   */
26462  this.enableChipEdit = false;
26463}
26464
26465
26466/**
26467 * @param {MdChipsCtrl} controller
26468 */
26469MdChipCtrl.prototype.init = function(controller) {
26470  this.parentController = controller;
26471  this.enableChipEdit = this.parentController.enableChipEdit;
26472
26473  if (this.enableChipEdit) {
26474    this.$element.on('keydown', this.chipKeyDown.bind(this));
26475    this.$element.on('mousedown', this.chipMouseDown.bind(this));
26476    this.getChipContent().addClass('_md-chip-content-edit-is-enabled');
26477  }
26478};
26479
26480
26481/**
26482 * @return {Object}
26483 */
26484MdChipCtrl.prototype.getChipContent = function() {
26485  var chipContents = this.$element[0].getElementsByClassName('md-chip-content');
26486  return angular.element(chipContents[0]);
26487};
26488
26489
26490/**
26491 * @return {Object}
26492 */
26493MdChipCtrl.prototype.getContentElement = function() {
26494  return angular.element(this.getChipContent().children()[0]);
26495};
26496
26497
26498/**
26499 * @return {number}
26500 */
26501MdChipCtrl.prototype.getChipIndex = function() {
26502  return parseInt(this.$element.attr('index'));
26503};
26504
26505
26506/**
26507 * Presents an input element to edit the contents of the chip.
26508 */
26509MdChipCtrl.prototype.goOutOfEditMode = function() {
26510  if (!this.isEditting) return;
26511
26512  this.isEditting = false;
26513  this.$element.removeClass('_md-chip-editing');
26514  this.getChipContent()[0].contentEditable = 'false';
26515  var chipIndex = this.getChipIndex();
26516
26517  var content = this.getContentElement().text();
26518  if (content) {
26519    this.parentController.updateChipContents(
26520        chipIndex,
26521        this.getContentElement().text()
26522    );
26523
26524    this.$mdUtil.nextTick(function() {
26525      if (this.parentController.selectedChip === chipIndex) {
26526        this.parentController.focusChip(chipIndex);
26527      }
26528    }.bind(this));
26529  } else {
26530    this.parentController.removeChipAndFocusInput(chipIndex);
26531  }
26532};
26533
26534
26535/**
26536 * Given an HTML element. Selects contents of it.
26537 * @param node
26538 */
26539MdChipCtrl.prototype.selectNodeContents = function(node) {
26540  var range, selection;
26541  if (document.body.createTextRange) {
26542    range = document.body.createTextRange();
26543    range.moveToElementText(node);
26544    range.select();
26545  } else if (window.getSelection) {
26546    selection = window.getSelection();
26547    range = document.createRange();
26548    range.selectNodeContents(node);
26549    selection.removeAllRanges();
26550    selection.addRange(range);
26551  }
26552};
26553
26554
26555/**
26556 * Presents an input element to edit the contents of the chip.
26557 */
26558MdChipCtrl.prototype.goInEditMode = function() {
26559  this.isEditting = true;
26560  this.$element.addClass('_md-chip-editing');
26561  this.getChipContent()[0].contentEditable = 'true';
26562  this.getChipContent().on('blur', function() {
26563    this.goOutOfEditMode();
26564  }.bind(this));
26565
26566  this.selectNodeContents(this.getChipContent()[0]);
26567};
26568
26569
26570/**
26571 * Handles the keydown event on the chip element. If enable-chip-edit attribute is
26572 * set to true, space or enter keys can trigger going into edit mode. Enter can also
26573 * trigger submitting if the chip is already being edited.
26574 * @param event
26575 */
26576MdChipCtrl.prototype.chipKeyDown = function(event) {
26577  if (!this.isEditting &&
26578    (event.keyCode === this.$mdConstant.KEY_CODE.ENTER ||
26579    event.keyCode === this.$mdConstant.KEY_CODE.SPACE)) {
26580    event.preventDefault();
26581    this.goInEditMode();
26582  } else if (this.isEditting &&
26583    event.keyCode === this.$mdConstant.KEY_CODE.ENTER) {
26584    event.preventDefault();
26585    this.goOutOfEditMode();
26586  }
26587};
26588
26589
26590/**
26591 * Handles the double click event
26592 */
26593MdChipCtrl.prototype.chipMouseDown = function() {
26594  if(this.getChipIndex() == this.parentController.selectedChip &&
26595    this.enableChipEdit &&
26596    !this.isEditting) {
26597    this.goInEditMode();
26598  }
26599};
26600
26601})();
26602(function(){
26603"use strict";
26604
26605
26606MdChip.$inject = ["$mdTheming", "$mdUtil", "$compile", "$timeout"];angular
26607  .module('material.components.chips')
26608  .directive('mdChip', MdChip);
26609
26610/**
26611 * @ngdoc directive
26612 * @name mdChip
26613 * @module material.components.chips
26614 *
26615 * @description
26616 * `<md-chip>` is a component used within `<md-chips>` and is responsible for rendering individual
26617 * chips.
26618 *
26619 *
26620 * @usage
26621 * <hljs lang="html">
26622 *   <md-chip>{{$chip}}</md-chip>
26623 * </hljs>
26624 *
26625 */
26626
26627// This hint text is hidden within a chip but used by screen readers to
26628// inform the user how they can interact with a chip.
26629var DELETE_HINT_TEMPLATE = '\
26630    <span ng-if="!$mdChipsCtrl.readonly" class="md-visually-hidden">\
26631      {{$mdChipsCtrl.deleteHint}}\
26632    </span>';
26633
26634/**
26635 * MDChip Directive Definition
26636 *
26637 * @param $mdTheming
26638 * @param $mdUtil
26639 * @ngInject
26640 */
26641function MdChip($mdTheming, $mdUtil, $compile, $timeout) {
26642  var deleteHintTemplate = $mdUtil.processTemplate(DELETE_HINT_TEMPLATE);
26643
26644  return {
26645    restrict: 'E',
26646    require: ['^?mdChips', 'mdChip'],
26647    link: postLink,
26648    controller: 'MdChipCtrl'
26649  };
26650
26651  function postLink(scope, element, attr, ctrls) {
26652    var chipsController = ctrls.shift();
26653    var chipController = ctrls.shift();
26654    var chipContentElement = angular.element(element[0].querySelector('.md-chip-content'));
26655
26656    $mdTheming(element);
26657
26658    if (chipsController) {
26659      chipController.init(chipsController);
26660
26661      // Append our delete hint to the div.md-chip-content (which does not exist at compile time)
26662      chipContentElement.append($compile(deleteHintTemplate)(scope));
26663
26664      // When a chip is blurred, make sure to unset (or reset) the selected chip so that tabbing
26665      // through elements works properly
26666      chipContentElement.on('blur', function() {
26667        chipsController.resetSelectedChip();
26668        chipsController.$scope.$applyAsync();
26669      });
26670    }
26671
26672    // Use $timeout to ensure we run AFTER the element has been added to the DOM so we can focus it.
26673    $timeout(function() {
26674      if (!chipsController) {
26675        return;
26676      }
26677
26678      if (chipsController.shouldFocusLastChip) {
26679        chipsController.focusLastChipThenInput();
26680      }
26681    });
26682  }
26683}
26684
26685})();
26686(function(){
26687"use strict";
26688
26689
26690MdChipRemove.$inject = ["$timeout"];angular
26691    .module('material.components.chips')
26692    .directive('mdChipRemove', MdChipRemove);
26693
26694/**
26695 * @ngdoc directive
26696 * @name mdChipRemove
26697 * @restrict A
26698 * @module material.components.chips
26699 *
26700 * @description
26701 * Designates an element to be used as the delete button for a chip. <br/>
26702 * This element is passed as a child of the `md-chips` element.
26703 *
26704 * The designated button will be just appended to the chip and removes the given chip on click.<br/>
26705 * By default the button is not being styled by the `md-chips` component.
26706 *
26707 * @usage
26708 * <hljs lang="html">
26709 *   <md-chips>
26710 *     <button md-chip-remove="">
26711 *       <md-icon md-svg-icon="md-close"></md-icon>
26712 *     </button>
26713 *   </md-chips>
26714 * </hljs>
26715 */
26716
26717
26718/**
26719 * MdChipRemove Directive Definition.
26720 * 
26721 * @param $timeout
26722 * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}
26723 * @constructor
26724 */
26725function MdChipRemove ($timeout) {
26726  return {
26727    restrict: 'A',
26728    require: '^mdChips',
26729    scope: false,
26730    link: postLink
26731  };
26732
26733  function postLink(scope, element, attr, ctrl) {
26734    element.on('click', function(event) {
26735      scope.$apply(function() {
26736        ctrl.removeChip(scope.$$replacedScope.$index);
26737      });
26738    });
26739
26740    // Child elements aren't available until after a $timeout tick as they are hidden by an
26741    // `ng-if`. see http://goo.gl/zIWfuw
26742    $timeout(function() {
26743      element.attr({ tabindex: -1, 'aria-hidden': true });
26744      element.find('button').attr('tabindex', '-1');
26745    });
26746  }
26747}
26748
26749})();
26750(function(){
26751"use strict";
26752
26753
26754MdChipTransclude.$inject = ["$compile"];angular
26755    .module('material.components.chips')
26756    .directive('mdChipTransclude', MdChipTransclude);
26757
26758function MdChipTransclude ($compile) {
26759  return {
26760    restrict: 'EA',
26761    terminal: true,
26762    link: link,
26763    scope: false
26764  };
26765  function link (scope, element, attr) {
26766    var ctrl = scope.$parent.$mdChipsCtrl,
26767        newScope = ctrl.parent.$new(false, ctrl.parent);
26768    newScope.$$replacedScope = scope;
26769    newScope.$chip = scope.$chip;
26770    newScope.$index = scope.$index;
26771    newScope.$mdChipsCtrl = ctrl;
26772
26773    var newHtml = ctrl.$scope.$eval(attr.mdChipTransclude);
26774
26775    element.html(newHtml);
26776    $compile(element.contents())(newScope);
26777  }
26778}
26779
26780})();
26781(function(){
26782"use strict";
26783
26784/**
26785 * The default chip append delay.
26786 *
26787 * @type {number}
26788 */
26789MdChipsCtrl.$inject = ["$scope", "$attrs", "$mdConstant", "$log", "$element", "$timeout", "$mdUtil"];
26790var DEFAULT_CHIP_APPEND_DELAY = 300;
26791
26792angular
26793    .module('material.components.chips')
26794    .controller('MdChipsCtrl', MdChipsCtrl);
26795
26796/**
26797 * Controller for the MdChips component. Responsible for adding to and
26798 * removing from the list of chips, marking chips as selected, and binding to
26799 * the models of various input components.
26800 *
26801 * @param $scope
26802 * @param $attrs
26803 * @param $mdConstant
26804 * @param $log
26805 * @param $element
26806 * @param $timeout
26807 * @param $mdUtil
26808 * @constructor
26809 */
26810function MdChipsCtrl ($scope, $attrs, $mdConstant, $log, $element, $timeout, $mdUtil) {
26811  /** @type {$timeout} **/
26812  this.$timeout = $timeout;
26813
26814  /** @type {Object} */
26815  this.$mdConstant = $mdConstant;
26816
26817  /** @type {angular.$scope} */
26818  this.$scope = $scope;
26819
26820  /** @type {angular.$scope} */
26821  this.parent = $scope.$parent;
26822
26823  /** @type {$mdUtil} */
26824  this.$mdUtil = $mdUtil;
26825
26826  /** @type {$log} */
26827  this.$log = $log;
26828
26829  /** @type {$element} */
26830  this.$element = $element;
26831
26832  /** @type {$attrs} */
26833  this.$attrs = $attrs;
26834
26835  /** @type {angular.NgModelController} */
26836  this.ngModelCtrl = null;
26837
26838  /** @type {angular.NgModelController} */
26839  this.userInputNgModelCtrl = null;
26840
26841  /** @type {MdAutocompleteCtrl} */
26842  this.autocompleteCtrl = null;
26843
26844  /** @type {Element} */
26845  this.userInputElement = null;
26846
26847  /** @type {Array.<Object>} */
26848  this.items = [];
26849
26850  /** @type {number} */
26851  this.selectedChip = -1;
26852
26853  /** @type {string} */
26854  this.enableChipEdit = $mdUtil.parseAttributeBoolean($attrs.mdEnableChipEdit);
26855
26856  /** @type {string} */
26857  this.addOnBlur = $mdUtil.parseAttributeBoolean($attrs.mdAddOnBlur);
26858
26859  /**
26860   * The text to be used as the aria-label for the input.
26861   * @type {string}
26862   */
26863  this.inputAriaLabel = 'Chips input.';
26864
26865  /**
26866   * Hidden hint text to describe the chips container. Used to give context to screen readers when
26867   * the chips are readonly and the input cannot be selected.
26868   *
26869   * @type {string}
26870   */
26871  this.containerHint = 'Chips container. Use arrow keys to select chips.';
26872
26873  /**
26874   * Hidden hint text for how to delete a chip. Used to give context to screen readers.
26875   * @type {string}
26876   */
26877  this.deleteHint = 'Press delete to remove this chip.';
26878
26879  /**
26880   * Hidden label for the delete button. Used to give context to screen readers.
26881   * @type {string}
26882   */
26883  this.deleteButtonLabel = 'Remove';
26884
26885  /**
26886   * Model used by the input element.
26887   * @type {string}
26888   */
26889  this.chipBuffer = '';
26890
26891  /**
26892   * Whether to use the transformChip expression to transform the chip buffer
26893   * before appending it to the list.
26894   * @type {boolean}
26895   */
26896  this.useTransformChip = false;
26897
26898  /**
26899   * Whether to use the onAdd expression to notify of chip additions.
26900   * @type {boolean}
26901   */
26902  this.useOnAdd = false;
26903
26904  /**
26905   * Whether to use the onRemove expression to notify of chip removals.
26906   * @type {boolean}
26907   */
26908  this.useOnRemove = false;
26909
26910  /**
26911   * The ID of the chips wrapper which is used to build unique IDs for the chips and the aria-owns
26912   * attribute.
26913   *
26914   * Defaults to '_md-chips-wrapper-' plus a unique number.
26915   *
26916   * @type {string}
26917   */
26918  this.wrapperId = '';
26919
26920  /**
26921   * Array of unique numbers which will be auto-generated any time the items change, and is used to
26922   * create unique IDs for the aria-owns attribute.
26923   *
26924   * @type {Array}
26925   */
26926  this.contentIds = [];
26927
26928  /**
26929   * The index of the chip that should have it's tabindex property set to 0 so it is selectable
26930   * via the keyboard.
26931   *
26932   * @type {int}
26933   */
26934  this.ariaTabIndex = null;
26935
26936  /**
26937   * After appending a chip, the chip will be focused for this number of milliseconds before the
26938   * input is refocused.
26939   *
26940   * **Note:** This is **required** for compatibility with certain screen readers in order for
26941   * them to properly allow keyboard access.
26942   *
26943   * @type {number}
26944   */
26945  this.chipAppendDelay = DEFAULT_CHIP_APPEND_DELAY;
26946
26947  this.init();
26948}
26949
26950/**
26951 * Initializes variables and sets up watchers
26952 */
26953MdChipsCtrl.prototype.init = function() {
26954  var ctrl = this;
26955
26956  // Set the wrapper ID
26957  ctrl.wrapperId = '_md-chips-wrapper-' + ctrl.$mdUtil.nextUid();
26958
26959  // Setup a watcher which manages the role and aria-owns attributes
26960  ctrl.$scope.$watchCollection('$mdChipsCtrl.items', function() {
26961    // Make sure our input and wrapper have the correct ARIA attributes
26962    ctrl.setupInputAria();
26963    ctrl.setupWrapperAria();
26964  });
26965
26966  ctrl.$attrs.$observe('mdChipAppendDelay', function(newValue) {
26967    ctrl.chipAppendDelay = parseInt(newValue) || DEFAULT_CHIP_APPEND_DELAY;
26968  });
26969};
26970
26971/**
26972 * If we have an input, ensure it has the appropriate ARIA attributes.
26973 */
26974MdChipsCtrl.prototype.setupInputAria = function() {
26975  var input = this.$element.find('input');
26976
26977  // If we have no input, just return
26978  if (!input) {
26979    return;
26980  }
26981
26982  input.attr('role', 'textbox');
26983  input.attr('aria-multiline', true);
26984};
26985
26986/**
26987 * Ensure our wrapper has the appropriate ARIA attributes.
26988 */
26989MdChipsCtrl.prototype.setupWrapperAria = function() {
26990  var ctrl = this,
26991      wrapper = this.$element.find('md-chips-wrap');
26992
26993  if (this.items && this.items.length) {
26994    // Dynamically add the listbox role on every change because it must be removed when there are
26995    // no items.
26996    wrapper.attr('role', 'listbox');
26997
26998    // Generate some random (but unique) IDs for each chip
26999    this.contentIds = this.items.map(function() {
27000      return ctrl.wrapperId + '-chip-' + ctrl.$mdUtil.nextUid();
27001    });
27002
27003    // Use the contentIDs above to generate the aria-owns attribute
27004    wrapper.attr('aria-owns', this.contentIds.join(' '));
27005  } else {
27006    // If we have no items, then the role and aria-owns attributes MUST be removed
27007    wrapper.removeAttr('role');
27008    wrapper.removeAttr('aria-owns');
27009  }
27010};
27011
27012/**
27013 * Handles the keydown event on the input element: by default <enter> appends
27014 * the buffer to the chip list, while backspace removes the last chip in the
27015 * list if the current buffer is empty.
27016 * @param event
27017 */
27018MdChipsCtrl.prototype.inputKeydown = function(event) {
27019  var chipBuffer = this.getChipBuffer();
27020
27021  // If we have an autocomplete, and it handled the event, we have nothing to do
27022  if (this.autocompleteCtrl && event.isDefaultPrevented && event.isDefaultPrevented()) {
27023    return;
27024  }
27025
27026  if (event.keyCode === this.$mdConstant.KEY_CODE.BACKSPACE) {
27027    // Only select and focus the previous chip, if the current caret position of the
27028    // input element is at the beginning.
27029    if (this.getCursorPosition(event.target) !== 0) {
27030      return;
27031    }
27032
27033    event.preventDefault();
27034    event.stopPropagation();
27035
27036    if (this.items.length) {
27037      this.selectAndFocusChipSafe(this.items.length - 1);
27038    }
27039
27040    return;
27041  }
27042
27043  // By default <enter> appends the buffer to the chip list.
27044  if (!this.separatorKeys || this.separatorKeys.length < 1) {
27045    this.separatorKeys = [this.$mdConstant.KEY_CODE.ENTER];
27046  }
27047
27048  // Support additional separator key codes in an array of `md-separator-keys`.
27049  if (this.separatorKeys.indexOf(event.keyCode) !== -1) {
27050    if ((this.autocompleteCtrl && this.requireMatch) || !chipBuffer) return;
27051    event.preventDefault();
27052
27053    // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
27054    if (this.hasMaxChipsReached()) return;
27055
27056    this.appendChip(chipBuffer.trim());
27057    this.resetChipBuffer();
27058
27059    return false;
27060  }
27061};
27062
27063/**
27064 * Returns the cursor position of the specified input element.
27065 * @param element HTMLInputElement
27066 * @returns {Number} Cursor Position of the input.
27067 */
27068MdChipsCtrl.prototype.getCursorPosition = function(element) {
27069  /*
27070   * Figure out whether the current input for the chips buffer is valid for using
27071   * the selectionStart / end property to retrieve the cursor position.
27072   * Some browsers do not allow the use of those attributes, on different input types.
27073   */
27074  try {
27075    if (element.selectionStart === element.selectionEnd) {
27076      return element.selectionStart;
27077    }
27078  } catch (e) {
27079    if (!element.value) {
27080      return 0;
27081    }
27082  }
27083};
27084
27085
27086/**
27087 * Updates the content of the chip at given index
27088 * @param chipIndex
27089 * @param chipContents
27090 */
27091MdChipsCtrl.prototype.updateChipContents = function(chipIndex, chipContents){
27092  if(chipIndex >= 0 && chipIndex < this.items.length) {
27093    this.items[chipIndex] = chipContents;
27094    this.ngModelCtrl.$setDirty();
27095  }
27096};
27097
27098
27099/**
27100 * Returns true if a chip is currently being edited. False otherwise.
27101 * @return {boolean}
27102 */
27103MdChipsCtrl.prototype.isEditingChip = function() {
27104  return !!this.$element[0].querySelector('._md-chip-editing');
27105};
27106
27107
27108MdChipsCtrl.prototype.isRemovable = function() {
27109  // Return false if we have static chips
27110  if (!this.ngModelCtrl) {
27111    return false;
27112  }
27113
27114  return this.readonly ? this.removable :
27115         angular.isDefined(this.removable) ? this.removable : true;
27116};
27117
27118/**
27119 * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
27120 * keys switch which chips is active
27121 * @param event
27122 */
27123MdChipsCtrl.prototype.chipKeydown = function (event) {
27124  if (this.getChipBuffer()) return;
27125  if (this.isEditingChip()) return;
27126
27127  switch (event.keyCode) {
27128    case this.$mdConstant.KEY_CODE.BACKSPACE:
27129    case this.$mdConstant.KEY_CODE.DELETE:
27130      if (this.selectedChip < 0) return;
27131      event.preventDefault();
27132      // Cancel the delete action only after the event cancel. Otherwise the page will go back.
27133      if (!this.isRemovable()) return;
27134      this.removeAndSelectAdjacentChip(this.selectedChip);
27135      break;
27136    case this.$mdConstant.KEY_CODE.LEFT_ARROW:
27137      event.preventDefault();
27138      // By default, allow selection of -1 which will focus the input; if we're readonly, don't go
27139      // below 0
27140      if (this.selectedChip < 0 || (this.readonly && this.selectedChip == 0)) {
27141        this.selectedChip = this.items.length;
27142      }
27143      if (this.items.length) this.selectAndFocusChipSafe(this.selectedChip - 1);
27144      break;
27145    case this.$mdConstant.KEY_CODE.RIGHT_ARROW:
27146      event.preventDefault();
27147      this.selectAndFocusChipSafe(this.selectedChip + 1);
27148      break;
27149    case this.$mdConstant.KEY_CODE.ESCAPE:
27150    case this.$mdConstant.KEY_CODE.TAB:
27151      if (this.selectedChip < 0) return;
27152      event.preventDefault();
27153      this.onFocus();
27154      break;
27155  }
27156};
27157
27158/**
27159 * Get the input's placeholder - uses `placeholder` when list is empty and `secondary-placeholder`
27160 * when the list is non-empty. If `secondary-placeholder` is not provided, `placeholder` is used
27161 * always.
27162 */
27163MdChipsCtrl.prototype.getPlaceholder = function() {
27164  // Allow `secondary-placeholder` to be blank.
27165  var useSecondary = (this.items && this.items.length &&
27166      (this.secondaryPlaceholder == '' || this.secondaryPlaceholder));
27167  return useSecondary ? this.secondaryPlaceholder : this.placeholder;
27168};
27169
27170/**
27171 * Removes chip at {@code index} and selects the adjacent chip.
27172 * @param index
27173 */
27174MdChipsCtrl.prototype.removeAndSelectAdjacentChip = function(index) {
27175  var self = this;
27176  var selIndex = self.getAdjacentChipIndex(index);
27177  var wrap = this.$element[0].querySelector('md-chips-wrap');
27178  var chip = this.$element[0].querySelector('md-chip[index="' + index + '"]');
27179
27180  self.removeChip(index);
27181
27182  // The dobule-timeout is currently necessary to ensure that the DOM has finalized and the select()
27183  // will find the proper chip since the selection is index-based.
27184  //
27185  // TODO: Investigate calling from within chip $scope.$on('$destroy') to reduce/remove timeouts
27186  self.$timeout(function() {
27187    self.$timeout(function() {
27188      self.selectAndFocusChipSafe(selIndex);
27189    });
27190  });
27191};
27192
27193/**
27194 * Sets the selected chip index to -1.
27195 */
27196MdChipsCtrl.prototype.resetSelectedChip = function() {
27197  this.selectedChip = -1;
27198  this.ariaTabIndex = null;
27199};
27200
27201/**
27202 * Gets the index of an adjacent chip to select after deletion. Adjacency is
27203 * determined as the next chip in the list, unless the target chip is the
27204 * last in the list, then it is the chip immediately preceding the target. If
27205 * there is only one item in the list, -1 is returned (select none).
27206 * The number returned is the index to select AFTER the target has been
27207 * removed.
27208 * If the current chip is not selected, then -1 is returned to select none.
27209 */
27210MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {
27211  var len = this.items.length - 1;
27212  return (len == 0) ? -1 :
27213      (index == len) ? index -1 : index;
27214};
27215
27216/**
27217 * Append the contents of the buffer to the chip list. This method will first
27218 * call out to the md-transform-chip method, if provided.
27219 *
27220 * @param newChip
27221 */
27222MdChipsCtrl.prototype.appendChip = function(newChip) {
27223  this.shouldFocusLastChip = true;
27224  if (this.useTransformChip && this.transformChip) {
27225    var transformedChip = this.transformChip({'$chip': newChip});
27226
27227    // Check to make sure the chip is defined before assigning it, otherwise, we'll just assume
27228    // they want the string version.
27229    if (angular.isDefined(transformedChip)) {
27230      newChip = transformedChip;
27231    }
27232  }
27233
27234  // If items contains an identical object to newChip, do not append
27235  if (angular.isObject(newChip)){
27236    var identical = this.items.some(function(item){
27237      return angular.equals(newChip, item);
27238    });
27239    if (identical) return;
27240  }
27241
27242  // Check for a null (but not undefined), or existing chip and cancel appending
27243  if (newChip == null || this.items.indexOf(newChip) + 1) return;
27244
27245  // Append the new chip onto our list
27246  var length = this.items.push(newChip);
27247  var index = length - 1;
27248
27249  // Update model validation
27250  this.ngModelCtrl.$setDirty();
27251  this.validateModel();
27252
27253  // If they provide the md-on-add attribute, notify them of the chip addition
27254  if (this.useOnAdd && this.onAdd) {
27255    this.onAdd({ '$chip': newChip, '$index': index });
27256  }
27257};
27258
27259/**
27260 * Sets whether to use the md-transform-chip expression. This expression is
27261 * bound to scope and controller in {@code MdChipsDirective} as
27262 * {@code transformChip}. Due to the nature of directive scope bindings, the
27263 * controller cannot know on its own/from the scope whether an expression was
27264 * actually provided.
27265 */
27266MdChipsCtrl.prototype.useTransformChipExpression = function() {
27267  this.useTransformChip = true;
27268};
27269
27270/**
27271 * Sets whether to use the md-on-add expression. This expression is
27272 * bound to scope and controller in {@code MdChipsDirective} as
27273 * {@code onAdd}. Due to the nature of directive scope bindings, the
27274 * controller cannot know on its own/from the scope whether an expression was
27275 * actually provided.
27276 */
27277MdChipsCtrl.prototype.useOnAddExpression = function() {
27278  this.useOnAdd = true;
27279};
27280
27281/**
27282 * Sets whether to use the md-on-remove expression. This expression is
27283 * bound to scope and controller in {@code MdChipsDirective} as
27284 * {@code onRemove}. Due to the nature of directive scope bindings, the
27285 * controller cannot know on its own/from the scope whether an expression was
27286 * actually provided.
27287 */
27288MdChipsCtrl.prototype.useOnRemoveExpression = function() {
27289  this.useOnRemove = true;
27290};
27291
27292/*
27293 * Sets whether to use the md-on-select expression. This expression is
27294 * bound to scope and controller in {@code MdChipsDirective} as
27295 * {@code onSelect}. Due to the nature of directive scope bindings, the
27296 * controller cannot know on its own/from the scope whether an expression was
27297 * actually provided.
27298 */
27299MdChipsCtrl.prototype.useOnSelectExpression = function() {
27300  this.useOnSelect = true;
27301};
27302
27303/**
27304 * Gets the input buffer. The input buffer can be the model bound to the
27305 * default input item {@code this.chipBuffer}, the {@code selectedItem}
27306 * model of an {@code md-autocomplete}, or, through some magic, the model
27307 * bound to any inpput or text area element found within a
27308 * {@code md-input-container} element.
27309 * @return {string}
27310 */
27311MdChipsCtrl.prototype.getChipBuffer = function() {
27312  var chipBuffer =  !this.userInputElement ? this.chipBuffer :
27313                     this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :
27314                     this.userInputElement[0].value;
27315
27316  // Ensure that the chip buffer is always a string. For example, the input element buffer might be falsy.
27317  return angular.isString(chipBuffer) ? chipBuffer : '';
27318};
27319
27320/**
27321 * Resets the input buffer for either the internal input or user provided input element.
27322 */
27323MdChipsCtrl.prototype.resetChipBuffer = function() {
27324  if (this.userInputElement) {
27325    if (this.userInputNgModelCtrl) {
27326      this.userInputNgModelCtrl.$setViewValue('');
27327      this.userInputNgModelCtrl.$render();
27328    } else {
27329      this.userInputElement[0].value = '';
27330    }
27331  } else {
27332    this.chipBuffer = '';
27333  }
27334};
27335
27336MdChipsCtrl.prototype.hasMaxChipsReached = function() {
27337  if (angular.isString(this.maxChips)) this.maxChips = parseInt(this.maxChips, 10) || 0;
27338
27339  return this.maxChips > 0 && this.items.length >= this.maxChips;
27340};
27341
27342/**
27343 * Updates the validity properties for the ngModel.
27344 */
27345MdChipsCtrl.prototype.validateModel = function() {
27346  this.ngModelCtrl.$setValidity('md-max-chips', !this.hasMaxChipsReached());
27347};
27348
27349/**
27350 * Removes the chip at the given index.
27351 * @param index
27352 */
27353MdChipsCtrl.prototype.removeChip = function(index) {
27354  var removed = this.items.splice(index, 1);
27355
27356  // Update model validation
27357  this.ngModelCtrl.$setDirty();
27358  this.validateModel();
27359
27360  if (removed && removed.length && this.useOnRemove && this.onRemove) {
27361    this.onRemove({ '$chip': removed[0], '$index': index });
27362  }
27363};
27364
27365MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {
27366  this.removeChip(index);
27367
27368  if (this.autocompleteCtrl) {
27369    // Always hide the autocomplete dropdown before focusing the autocomplete input.
27370    // Wait for the input to move horizontally, because the chip was removed.
27371    // This can lead to an incorrect dropdown position.
27372    this.autocompleteCtrl.hidden = true;
27373    this.$mdUtil.nextTick(this.onFocus.bind(this));
27374  } else {
27375    this.onFocus();
27376  }
27377
27378};
27379/**
27380 * Selects the chip at `index`,
27381 * @param index
27382 */
27383MdChipsCtrl.prototype.selectAndFocusChipSafe = function(index) {
27384  // If we have no chips, or are asked to select a chip before the first, just focus the input
27385  if (!this.items.length || index === -1) {
27386    return this.focusInput();
27387  }
27388
27389  // If we are asked to select a chip greater than the number of chips...
27390  if (index >= this.items.length) {
27391    if (this.readonly) {
27392      // If we are readonly, jump back to the start (because we have no input)
27393      index = 0;
27394    } else {
27395      // If we are not readonly, we should attempt to focus the input
27396      return this.onFocus();
27397    }
27398  }
27399
27400  index = Math.max(index, 0);
27401  index = Math.min(index, this.items.length - 1);
27402
27403  this.selectChip(index);
27404  this.focusChip(index);
27405};
27406
27407MdChipsCtrl.prototype.focusLastChipThenInput = function() {
27408  var ctrl = this;
27409
27410  ctrl.shouldFocusLastChip = false;
27411
27412  ctrl.focusChip(this.items.length - 1);
27413
27414  ctrl.$timeout(function() {
27415    ctrl.focusInput();
27416  }, ctrl.chipAppendDelay);
27417};
27418
27419MdChipsCtrl.prototype.focusInput = function() {
27420  this.selectChip(-1);
27421  this.onFocus();
27422};
27423
27424/**
27425 * Marks the chip at the given index as selected.
27426 * @param index
27427 */
27428MdChipsCtrl.prototype.selectChip = function(index) {
27429  if (index >= -1 && index <= this.items.length) {
27430    this.selectedChip = index;
27431
27432    // Fire the onSelect if provided
27433    if (this.useOnSelect && this.onSelect) {
27434      this.onSelect({'$chip': this.items[index] });
27435    }
27436  } else {
27437    this.$log.warn('Selected Chip index out of bounds; ignoring.');
27438  }
27439};
27440
27441/**
27442 * Selects the chip at `index` and gives it focus.
27443 * @param index
27444 */
27445MdChipsCtrl.prototype.selectAndFocusChip = function(index) {
27446  this.selectChip(index);
27447  if (index != -1) {
27448    this.focusChip(index);
27449  }
27450};
27451
27452/**
27453 * Call `focus()` on the chip at `index`
27454 */
27455MdChipsCtrl.prototype.focusChip = function(index) {
27456  var chipContent = this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content');
27457
27458  this.ariaTabIndex = index;
27459
27460  chipContent.focus();
27461};
27462
27463/**
27464 * Configures the required interactions with the ngModel Controller.
27465 * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.
27466 * @param ngModelCtrl
27467 */
27468MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {
27469  this.ngModelCtrl = ngModelCtrl;
27470
27471  var self = this;
27472  ngModelCtrl.$render = function() {
27473    // model is updated. do something.
27474    self.items = self.ngModelCtrl.$viewValue;
27475  };
27476};
27477
27478MdChipsCtrl.prototype.onFocus = function () {
27479  var input = this.$element[0].querySelector('input');
27480  input && input.focus();
27481  this.resetSelectedChip();
27482};
27483
27484MdChipsCtrl.prototype.onInputFocus = function () {
27485  this.inputHasFocus = true;
27486
27487  // Make sure we have the appropriate ARIA attributes
27488  this.setupInputAria();
27489
27490  // Make sure we don't have any chips selected
27491  this.resetSelectedChip();
27492};
27493
27494MdChipsCtrl.prototype.onInputBlur = function () {
27495  this.inputHasFocus = false;
27496
27497  if (this.shouldAddOnBlur()) {
27498    this.appendChip(this.getChipBuffer().trim());
27499    this.resetChipBuffer();
27500  }
27501};
27502
27503/**
27504 * Configure event bindings on a user-provided input element.
27505 * @param inputElement
27506 */
27507MdChipsCtrl.prototype.configureUserInput = function(inputElement) {
27508  this.userInputElement = inputElement;
27509
27510  // Find the NgModelCtrl for the input element
27511  var ngModelCtrl = inputElement.controller('ngModel');
27512  // `.controller` will look in the parent as well.
27513  if (ngModelCtrl != this.ngModelCtrl) {
27514    this.userInputNgModelCtrl = ngModelCtrl;
27515  }
27516
27517  var scope = this.$scope;
27518  var ctrl = this;
27519
27520  // Run all of the events using evalAsync because a focus may fire a blur in the same digest loop
27521  var scopeApplyFn = function(event, fn) {
27522    scope.$evalAsync(angular.bind(ctrl, fn, event));
27523  };
27524
27525  // Bind to keydown and focus events of input
27526  inputElement
27527      .attr({ tabindex: 0 })
27528      .on('keydown', function(event) { scopeApplyFn(event, ctrl.inputKeydown) })
27529      .on('focus', function(event) { scopeApplyFn(event, ctrl.onInputFocus) })
27530      .on('blur', function(event) { scopeApplyFn(event, ctrl.onInputBlur) })
27531};
27532
27533MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {
27534  if (ctrl) {
27535    this.autocompleteCtrl = ctrl;
27536
27537    ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {
27538      if (item) {
27539        // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
27540        if (this.hasMaxChipsReached()) return;
27541
27542        this.appendChip(item);
27543        this.resetChipBuffer();
27544      }
27545    }));
27546
27547    this.$element.find('input')
27548        .on('focus',angular.bind(this, this.onInputFocus) )
27549        .on('blur', angular.bind(this, this.onInputBlur) );
27550  }
27551};
27552
27553/**
27554 * Whether the current chip buffer should be added on input blur or not.
27555 * @returns {boolean}
27556 */
27557MdChipsCtrl.prototype.shouldAddOnBlur = function() {
27558
27559  // Update the custom ngModel validators from the chips component.
27560  this.validateModel();
27561
27562  var chipBuffer = this.getChipBuffer().trim();
27563  var isModelValid = this.ngModelCtrl.$valid;
27564  var isAutocompleteShowing = this.autocompleteCtrl && !this.autocompleteCtrl.hidden;
27565
27566  if (this.userInputNgModelCtrl) {
27567    isModelValid = isModelValid && this.userInputNgModelCtrl.$valid;
27568  }
27569
27570  return this.addOnBlur && !this.requireMatch && chipBuffer && isModelValid && !isAutocompleteShowing;
27571};
27572
27573MdChipsCtrl.prototype.hasFocus = function () {
27574  return this.inputHasFocus || this.selectedChip >= 0;
27575};
27576
27577MdChipsCtrl.prototype.contentIdFor = function(index) {
27578  return this.contentIds[index];
27579};
27580
27581})();
27582(function(){
27583"use strict";
27584
27585  
27586  MdChips.$inject = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout", "$$mdSvgRegistry"];angular
27587      .module('material.components.chips')
27588      .directive('mdChips', MdChips);
27589
27590  /**
27591   * @ngdoc directive
27592   * @name mdChips
27593   * @module material.components.chips
27594   *
27595   * @description
27596   * `<md-chips>` is an input component for building lists of strings or objects. The list items are
27597   * displayed as 'chips'. This component can make use of an `<input>` element or an 
27598   * `<md-autocomplete>` element.
27599   *
27600   * ### Custom templates
27601   * A custom template may be provided to render the content of each chip. This is achieved by
27602   * specifying an `<md-chip-template>` element containing the custom content as a child of
27603   * `<md-chips>`.
27604   *
27605   * Note: Any attributes on
27606   * `<md-chip-template>` will be dropped as only the innerHTML is used for the chip template. The
27607   * variables `$chip` and `$index` are available in the scope of `<md-chip-template>`, representing
27608   * the chip object and its index in the list of chips, respectively.
27609   * To override the chip delete control, include an element (ideally a button) with the attribute
27610   * `md-chip-remove`. A click listener to remove the chip will be added automatically. The element
27611   * is also placed as a sibling to the chip content (on which there are also click listeners) to
27612   * avoid a nested ng-click situation.
27613   *
27614   * <!-- Note: We no longer want to include this in the site docs; but it should remain here for
27615   * future developers and those looking at the documentation.
27616   *
27617   * <h3> Pending Features </h3>
27618   * <ul style="padding-left:20px;">
27619   *
27620   *   <ul>Style
27621   *     <li>Colours for hover, press states (ripple?).</li>
27622   *   </ul>
27623   *
27624   *   <ul>Validation
27625   *     <li>allow a validation callback</li>
27626   *     <li>hilighting style for invalid chips</li>
27627   *   </ul>
27628   *
27629   *   <ul>Item mutation
27630   *     <li>Support `
27631   *       <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double
27632   *       click?
27633   *     </li>
27634   *   </ul>
27635   *
27636   *   <ul>Truncation and Disambiguation (?)
27637   *     <li>Truncate chip text where possible, but do not truncate entries such that two are
27638   *     indistinguishable.</li>
27639   *   </ul>
27640   *
27641   *   <ul>Drag and Drop
27642   *     <li>Drag and drop chips between related `<md-chips>` elements.
27643   *     </li>
27644   *   </ul>
27645   * </ul>
27646   *
27647   * //-->
27648   *
27649   * Sometimes developers want to limit the amount of possible chips.<br/>
27650   * You can specify the maximum amount of chips by using the following markup.
27651   *
27652   * <hljs lang="html">
27653   *   <md-chips
27654   *       ng-model="myItems"
27655   *       placeholder="Add an item"
27656   *       md-max-chips="5">
27657   *   </md-chips>
27658   * </hljs>
27659   *
27660   * In some cases, you have an autocomplete inside of the `md-chips`.<br/>
27661   * When the maximum amount of chips has been reached, you can also disable the autocomplete selection.<br/>
27662   * Here is an example markup.
27663   *
27664   * <hljs lang="html">
27665   *   <md-chips ng-model="myItems" md-max-chips="5">
27666   *     <md-autocomplete ng-hide="myItems.length > 5" ...></md-autocomplete>
27667   *   </md-chips>
27668   * </hljs>
27669   *
27670   * ### Accessibility
27671   *
27672   * The `md-chips` component supports keyboard and screen reader users since Version 1.1.2. In
27673   * order to achieve this, we modified the chips behavior to select newly appended chips for
27674   * `300ms` before re-focusing the input and allowing the user to type.
27675   *
27676   * For most users, this delay is small enough that it will not be noticeable but allows certain
27677   * screen readers to function properly (JAWS and NVDA in particular).
27678   *
27679   * We introduced a new `md-chip-append-delay` option to allow developers to better control this
27680   * behavior.
27681   *
27682   * Please refer to the documentation of this option (below) for more information.
27683   *
27684   * @param {string=|object=} ng-model A model to which the list of items will be bound.
27685   * @param {string=} placeholder Placeholder text that will be forwarded to the input.
27686   * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
27687   *    displayed when there is at least one item in the list
27688   * @param {boolean=} md-removable Enables or disables the deletion of chips through the
27689   *    removal icon or the Delete/Backspace key. Defaults to true.
27690   * @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
27691   *    the input and delete buttons. If no `ng-model` is provided, the chips will automatically be
27692   *    marked as readonly.<br/><br/>
27693   *    When `md-removable` is not defined, the `md-remove` behavior will be overwritten and disabled.
27694   * @param {string=} md-enable-chip-edit Set this to "true" to enable editing of chip contents. The user can 
27695   *    go into edit mode with pressing "space", "enter", or double clicking on the chip. Chip edit is only
27696   *    supported for chips with basic template.
27697   * @param {number=} md-max-chips The maximum number of chips allowed to add through user input.
27698   *    <br/><br/>The validation property `md-max-chips` can be used when the max chips
27699   *    amount is reached.
27700   * @param {boolean=} md-add-on-blur When set to true, remaining text inside of the input will
27701   *    be converted into a new chip on blur.
27702   * @param {expression} md-transform-chip An expression of form `myFunction($chip)` that when called
27703   *    expects one of the following return values:
27704   *    - an object representing the `$chip` input string
27705   *    - `undefined` to simply add the `$chip` input string, or
27706   *    - `null` to prevent the chip from being appended
27707   * @param {expression=} md-on-add An expression which will be called when a chip has been
27708   *    added.
27709   * @param {expression=} md-on-remove An expression which will be called when a chip has been
27710   *    removed.
27711   * @param {expression=} md-on-select An expression which will be called when a chip is selected.
27712   * @param {boolean} md-require-match If true, and the chips template contains an autocomplete,
27713   *    only allow selection of pre-defined chips (i.e. you cannot add new ones).
27714   * @param {string=} input-aria-label A string read by screen readers to identify the input.
27715   * @param {string=} container-hint A string read by screen readers informing users of how to
27716   *    navigate the chips. Used in readonly mode.
27717   * @param {string=} delete-hint A string read by screen readers instructing users that pressing
27718   *    the delete key will remove the chip.
27719   * @param {string=} delete-button-label A label for the delete button. Also hidden and read by
27720   *    screen readers.
27721   * @param {expression=} md-separator-keys An array of key codes used to separate chips.
27722   * @param {string=} md-chip-append-delay The number of milliseconds that the component will select
27723   *    a newly appended chip before allowing a user to type into the input. This is **necessary**
27724   *    for keyboard accessibility for screen readers. It defaults to 300ms and any number less than
27725   *    300 can cause issues with screen readers (particularly JAWS and sometimes NVDA).
27726   *
27727   *    _Available since Version 1.1.2._
27728   *
27729   *    **Note:** You can safely set this to `0` in one of the following two instances:
27730   *
27731   *    1. You are targeting an iOS or Safari-only application (where users would use VoiceOver) or
27732   *    only ChromeVox users.
27733   *
27734   *    2. If you have utilized the `md-separator-keys` to disable the `enter` keystroke in
27735   *    favor of another one (such as `,` or `;`).
27736   *
27737   * @usage
27738   * <hljs lang="html">
27739   *   <md-chips
27740   *       ng-model="myItems"
27741   *       placeholder="Add an item"
27742   *       readonly="isReadOnly">
27743   *   </md-chips>
27744   * </hljs>
27745   *
27746   * <h3>Validation</h3>
27747   * When using [ngMessages](https://docs.angularjs.org/api/ngMessages), you can show errors based
27748   * on our custom validators.
27749   * <hljs lang="html">
27750   *   <form name="userForm">
27751   *     <md-chips
27752   *       name="fruits"
27753   *       ng-model="myItems"
27754   *       placeholder="Add an item"
27755   *       md-max-chips="5">
27756   *     </md-chips>
27757   *     <div ng-messages="userForm.fruits.$error" ng-if="userForm.$dirty">
27758   *       <div ng-message="md-max-chips">You reached the maximum amount of chips</div>
27759   *    </div>
27760   *   </form>
27761   * </hljs>
27762   *
27763   */
27764
27765  var MD_CHIPS_TEMPLATE = '\
27766      <md-chips-wrap\
27767          id="{{$mdChipsCtrl.wrapperId}}"\
27768          tabindex="{{$mdChipsCtrl.readonly ? 0 : -1}}"\
27769          ng-keydown="$mdChipsCtrl.chipKeydown($event)"\
27770          ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus(), \
27771                      \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly,\
27772                      \'md-removable\': $mdChipsCtrl.isRemovable() }"\
27773          aria-setsize="{{$mdChipsCtrl.items.length}}"\
27774          class="md-chips">\
27775        <span ng-if="$mdChipsCtrl.readonly" class="md-visually-hidden">\
27776          {{$mdChipsCtrl.containerHint}}\
27777        </span>\
27778        <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
27779            index="{{$index}}"\
27780            ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index, \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly}">\
27781          <div class="md-chip-content"\
27782              tabindex="{{$mdChipsCtrl.ariaTabIndex == $index ? 0 : -1}}"\
27783              id="{{$mdChipsCtrl.contentIdFor($index)}}"\
27784              role="option"\
27785              aria-selected="{{$mdChipsCtrl.selectedChip == $index}}" \
27786              aria-posinset="{{$index}}"\
27787              ng-click="!$mdChipsCtrl.readonly && $mdChipsCtrl.focusChip($index)"\
27788              ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)"\
27789              md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div>\
27790          <div ng-if="$mdChipsCtrl.isRemovable()"\
27791               class="md-chip-remove-container"\
27792               tabindex="-1"\
27793               md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\
27794        </md-chip>\
27795        <div class="md-chip-input-container" ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl">\
27796          <div md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\
27797        </div>\
27798      </md-chips-wrap>';
27799
27800  var CHIP_INPUT_TEMPLATE = '\
27801        <input\
27802            class="md-input"\
27803            tabindex="0"\
27804            aria-label="{{$mdChipsCtrl.inputAriaLabel}}" \
27805            placeholder="{{$mdChipsCtrl.getPlaceholder()}}"\
27806            ng-model="$mdChipsCtrl.chipBuffer"\
27807            ng-focus="$mdChipsCtrl.onInputFocus()"\
27808            ng-blur="$mdChipsCtrl.onInputBlur()"\
27809            ng-keydown="$mdChipsCtrl.inputKeydown($event)">';
27810
27811  var CHIP_DEFAULT_TEMPLATE = '\
27812      <span>{{$chip}}</span>';
27813
27814  var CHIP_REMOVE_TEMPLATE = '\
27815      <button\
27816          class="md-chip-remove"\
27817          ng-if="$mdChipsCtrl.isRemovable()"\
27818          ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
27819          type="button"\
27820          tabindex="-1">\
27821        <md-icon md-svg-src="{{ $mdChipsCtrl.mdCloseIcon }}"></md-icon>\
27822        <span class="md-visually-hidden">\
27823          {{$mdChipsCtrl.deleteButtonLabel}}\
27824        </span>\
27825      </button>';
27826
27827  /**
27828   * MDChips Directive Definition
27829   */
27830  function MdChips ($mdTheming, $mdUtil, $compile, $log, $timeout, $$mdSvgRegistry) {
27831    // Run our templates through $mdUtil.processTemplate() to allow custom start/end symbols
27832    var templates = getTemplates();
27833
27834    return {
27835      template: function(element, attrs) {
27836        // Clone the element into an attribute. By prepending the attribute
27837        // name with '$', Angular won't write it into the DOM. The cloned
27838        // element propagates to the link function via the attrs argument,
27839        // where various contained-elements can be consumed.
27840        attrs['$mdUserTemplate'] = element.clone();
27841        return templates.chips;
27842      },
27843      require: ['mdChips'],
27844      restrict: 'E',
27845      controller: 'MdChipsCtrl',
27846      controllerAs: '$mdChipsCtrl',
27847      bindToController: true,
27848      compile: compile,
27849      scope: {
27850        readonly: '=readonly',
27851        removable: '=mdRemovable',
27852        placeholder: '@',
27853        secondaryPlaceholder: '@',
27854        maxChips: '@mdMaxChips',
27855        transformChip: '&mdTransformChip',
27856        onAppend: '&mdOnAppend',
27857        onAdd: '&mdOnAdd',
27858        onRemove: '&mdOnRemove',
27859        onSelect: '&mdOnSelect',
27860        inputAriaLabel: '@',
27861        containerHint: '@',
27862        deleteHint: '@',
27863        deleteButtonLabel: '@',
27864        separatorKeys: '=?mdSeparatorKeys',
27865        requireMatch: '=?mdRequireMatch',
27866        chipAppendDelayString: '@?mdChipAppendDelay'
27867      }
27868    };
27869
27870    /**
27871     * Builds the final template for `md-chips` and returns the postLink function.
27872     *
27873     * Building the template involves 3 key components:
27874     * static chips
27875     * chip template
27876     * input control
27877     *
27878     * If no `ng-model` is provided, only the static chip work needs to be done.
27879     *
27880     * If no user-passed `md-chip-template` exists, the default template is used. This resulting
27881     * template is appended to the chip content element.
27882     *
27883     * The remove button may be overridden by passing an element with an md-chip-remove attribute.
27884     *
27885     * If an `input` or `md-autocomplete` element is provided by the caller, it is set aside for
27886     * transclusion later. The transclusion happens in `postLink` as the parent scope is required.
27887     * If no user input is provided, a default one is appended to the input container node in the
27888     * template.
27889     *
27890     * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for
27891     * transclusion in the `postLink` function.
27892     *
27893     *
27894     * @param element
27895     * @param attr
27896     * @returns {Function}
27897     */
27898    function compile(element, attr) {
27899      // Grab the user template from attr and reset the attribute to null.
27900      var userTemplate = attr['$mdUserTemplate'];
27901      attr['$mdUserTemplate'] = null;
27902
27903      var chipTemplate = getTemplateByQuery('md-chips>md-chip-template');
27904
27905      var chipRemoveSelector = $mdUtil
27906        .prefixer()
27907        .buildList('md-chip-remove')
27908        .map(function(attr) {
27909          return 'md-chips>*[' + attr + ']';
27910        })
27911        .join(',');
27912
27913      // Set the chip remove, chip contents and chip input templates. The link function will put
27914      // them on the scope for transclusion later.
27915      var chipRemoveTemplate   = getTemplateByQuery(chipRemoveSelector) || templates.remove,
27916          chipContentsTemplate = chipTemplate || templates.default,
27917          chipInputTemplate    = getTemplateByQuery('md-chips>md-autocomplete')
27918              || getTemplateByQuery('md-chips>input')
27919              || templates.input,
27920          staticChips = userTemplate.find('md-chip');
27921
27922      // Warn of malformed template. See #2545
27923      if (userTemplate[0].querySelector('md-chip-template>*[md-chip-remove]')) {
27924        $log.warn('invalid placement of md-chip-remove within md-chip-template.');
27925      }
27926
27927      function getTemplateByQuery (query) {
27928        if (!attr.ngModel) return;
27929        var element = userTemplate[0].querySelector(query);
27930        return element && element.outerHTML;
27931      }
27932
27933      /**
27934       * Configures controller and transcludes.
27935       */
27936      return function postLink(scope, element, attrs, controllers) {
27937        $mdUtil.initOptionalProperties(scope, attr);
27938
27939        $mdTheming(element);
27940        var mdChipsCtrl = controllers[0];
27941        if(chipTemplate) {
27942          // Chip editing functionality assumes we are using the default chip template.
27943          mdChipsCtrl.enableChipEdit = false;
27944        }
27945
27946        mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;
27947        mdChipsCtrl.chipRemoveTemplate   = chipRemoveTemplate;
27948        mdChipsCtrl.chipInputTemplate    = chipInputTemplate;
27949
27950        mdChipsCtrl.mdCloseIcon = $$mdSvgRegistry.mdClose;
27951
27952        element
27953            .attr({ tabindex: -1 })
27954            .on('focus', function () { mdChipsCtrl.onFocus(); });
27955
27956        if (attr.ngModel) {
27957          mdChipsCtrl.configureNgModel(element.controller('ngModel'));
27958
27959          // If an `md-transform-chip` attribute was set, tell the controller to use the expression
27960          // before appending chips.
27961          if (attrs.mdTransformChip) mdChipsCtrl.useTransformChipExpression();
27962
27963          // If an `md-on-append` attribute was set, tell the controller to use the expression
27964          // when appending chips.
27965          //
27966          // DEPRECATED: Will remove in official 1.0 release
27967          if (attrs.mdOnAppend) mdChipsCtrl.useOnAppendExpression();
27968
27969          // If an `md-on-add` attribute was set, tell the controller to use the expression
27970          // when adding chips.
27971          if (attrs.mdOnAdd) mdChipsCtrl.useOnAddExpression();
27972
27973          // If an `md-on-remove` attribute was set, tell the controller to use the expression
27974          // when removing chips.
27975          if (attrs.mdOnRemove) mdChipsCtrl.useOnRemoveExpression();
27976
27977          // If an `md-on-select` attribute was set, tell the controller to use the expression
27978          // when selecting chips.
27979          if (attrs.mdOnSelect) mdChipsCtrl.useOnSelectExpression();
27980
27981          // The md-autocomplete and input elements won't be compiled until after this directive
27982          // is complete (due to their nested nature). Wait a tick before looking for them to
27983          // configure the controller.
27984          if (chipInputTemplate != templates.input) {
27985            // The autocomplete will not appear until the readonly attribute is not true (i.e.
27986            // false or undefined), so we have to watch the readonly and then on the next tick
27987            // after the chip transclusion has run, we can configure the autocomplete and user
27988            // input.
27989            scope.$watch('$mdChipsCtrl.readonly', function(readonly) {
27990              if (!readonly) {
27991
27992                $mdUtil.nextTick(function(){
27993
27994                  if (chipInputTemplate.indexOf('<md-autocomplete') === 0) {
27995                    var autocompleteEl = element.find('md-autocomplete');
27996                    mdChipsCtrl.configureAutocomplete(autocompleteEl.controller('mdAutocomplete'));
27997                  }
27998
27999                  mdChipsCtrl.configureUserInput(element.find('input'));
28000                });
28001              }
28002            });
28003          }
28004
28005          // At the next tick, if we find an input, make sure it has the md-input class
28006          $mdUtil.nextTick(function() {
28007            var input = element.find('input');
28008
28009            input && input.toggleClass('md-input', true);
28010          });
28011        }
28012
28013        // Compile with the parent's scope and prepend any static chips to the wrapper.
28014        if (staticChips.length > 0) {
28015          var compiledStaticChips = $compile(staticChips.clone())(scope.$parent);
28016          $timeout(function() { element.find('md-chips-wrap').prepend(compiledStaticChips); });
28017        }
28018      };
28019    }
28020
28021    function getTemplates() {
28022      return {
28023        chips: $mdUtil.processTemplate(MD_CHIPS_TEMPLATE),
28024        input: $mdUtil.processTemplate(CHIP_INPUT_TEMPLATE),
28025        default: $mdUtil.processTemplate(CHIP_DEFAULT_TEMPLATE),
28026        remove: $mdUtil.processTemplate(CHIP_REMOVE_TEMPLATE)
28027      };
28028    }
28029  }
28030
28031})();
28032(function(){
28033"use strict";
28034
28035angular
28036    .module('material.components.chips')
28037    .controller('MdContactChipsCtrl', MdContactChipsCtrl);
28038
28039
28040
28041/**
28042 * Controller for the MdContactChips component
28043 * @constructor
28044 */
28045function MdContactChipsCtrl () {
28046  /** @type {Object} */
28047  this.selectedItem = null;
28048
28049  /** @type {string} */
28050  this.searchText = '';
28051}
28052
28053
28054MdContactChipsCtrl.prototype.queryContact = function(searchText) {
28055  return this.contactQuery({'$query': searchText});
28056};
28057
28058
28059MdContactChipsCtrl.prototype.itemName = function(item) {
28060  return item[this.contactName];
28061};
28062
28063})();
28064(function(){
28065"use strict";
28066
28067
28068MdContactChips.$inject = ["$mdTheming", "$mdUtil"];angular
28069  .module('material.components.chips')
28070  .directive('mdContactChips', MdContactChips);
28071
28072/**
28073 * @ngdoc directive
28074 * @name mdContactChips
28075 * @module material.components.chips
28076 *
28077 * @description
28078 * `<md-contact-chips>` is an input component based on `md-chips` and makes use of an
28079 * `md-autocomplete` element. The component allows the caller to supply a query expression which
28080 * returns  a list of possible contacts. The user can select one of these and add it to the list of
28081 * chips.
28082 *
28083 * You may also use the `md-highlight-text` directive along with its parameters to control the
28084 * appearance of the matched text inside of the contacts' autocomplete popup.
28085 *
28086 * @param {string=|object=} ng-model A model to bind the list of items to
28087 * @param {string=} placeholder Placeholder text that will be forwarded to the input.
28088 * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
28089 *    displayed when there is at least on item in the list
28090 * @param {expression} md-contacts An expression expected to return contacts matching the search
28091 *    test, `$query`. If this expression involves a promise, a loading bar is displayed while
28092 *    waiting for it to resolve.
28093 * @param {string} md-contact-name The field name of the contact object representing the
28094 *    contact's name.
28095 * @param {string} md-contact-email The field name of the contact object representing the
28096 *    contact's email address.
28097 * @param {string} md-contact-image The field name of the contact object representing the
28098 *    contact's image.
28099 * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will
28100 *    make suggestions
28101 *
28102 * @param {expression=} filter-selected Whether to filter selected contacts from the list of
28103 *    suggestions shown in the autocomplete.
28104 *
28105 *    ***Note:** This attribute has been removed but may come back.*
28106 *
28107 *
28108 *
28109 * @usage
28110 * <hljs lang="html">
28111 *   <md-contact-chips
28112 *       ng-model="ctrl.contacts"
28113 *       md-contacts="ctrl.querySearch($query)"
28114 *       md-contact-name="name"
28115 *       md-contact-image="image"
28116 *       md-contact-email="email"
28117 *       placeholder="To">
28118 *   </md-contact-chips>
28119 * </hljs>
28120 *
28121 */
28122
28123
28124var MD_CONTACT_CHIPS_TEMPLATE = '\
28125      <md-chips class="md-contact-chips"\
28126          ng-model="$mdContactChipsCtrl.contacts"\
28127          md-require-match="$mdContactChipsCtrl.requireMatch"\
28128          md-chip-append-delay="{{$mdContactChipsCtrl.chipAppendDelay}}" \
28129          md-autocomplete-snap>\
28130          <md-autocomplete\
28131              md-menu-class="md-contact-chips-suggestions"\
28132              md-selected-item="$mdContactChipsCtrl.selectedItem"\
28133              md-search-text="$mdContactChipsCtrl.searchText"\
28134              md-items="item in $mdContactChipsCtrl.queryContact($mdContactChipsCtrl.searchText)"\
28135              md-item-text="$mdContactChipsCtrl.itemName(item)"\
28136              md-no-cache="true"\
28137              md-min-length="$mdContactChipsCtrl.minLength"\
28138              md-autoselect\
28139              placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\
28140                  $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\
28141            <div class="md-contact-suggestion">\
28142              <img \
28143                  ng-src="{{item[$mdContactChipsCtrl.contactImage]}}"\
28144                  alt="{{item[$mdContactChipsCtrl.contactName]}}"\
28145                  ng-if="item[$mdContactChipsCtrl.contactImage]" />\
28146              <span class="md-contact-name" md-highlight-text="$mdContactChipsCtrl.searchText"\
28147                    md-highlight-flags="{{$mdContactChipsCtrl.highlightFlags}}">\
28148                {{item[$mdContactChipsCtrl.contactName]}}\
28149              </span>\
28150              <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\
28151            </div>\
28152          </md-autocomplete>\
28153          <md-chip-template>\
28154            <div class="md-contact-avatar">\
28155              <img \
28156                  ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\
28157                  alt="{{$chip[$mdContactChipsCtrl.contactName]}}"\
28158                  ng-if="$chip[$mdContactChipsCtrl.contactImage]" />\
28159            </div>\
28160            <div class="md-contact-name">\
28161              {{$chip[$mdContactChipsCtrl.contactName]}}\
28162            </div>\
28163          </md-chip-template>\
28164      </md-chips>';
28165
28166
28167/**
28168 * MDContactChips Directive Definition
28169 *
28170 * @param $mdTheming
28171 * @returns {*}
28172 * @ngInject
28173 */
28174function MdContactChips($mdTheming, $mdUtil) {
28175  return {
28176    template: function(element, attrs) {
28177      return MD_CONTACT_CHIPS_TEMPLATE;
28178    },
28179    restrict: 'E',
28180    controller: 'MdContactChipsCtrl',
28181    controllerAs: '$mdContactChipsCtrl',
28182    bindToController: true,
28183    compile: compile,
28184    scope: {
28185      contactQuery: '&mdContacts',
28186      placeholder: '@',
28187      secondaryPlaceholder: '@',
28188      contactName: '@mdContactName',
28189      contactImage: '@mdContactImage',
28190      contactEmail: '@mdContactEmail',
28191      contacts: '=ngModel',
28192      requireMatch: '=?mdRequireMatch',
28193      minLength: '=?mdMinLength',
28194      highlightFlags: '@?mdHighlightFlags',
28195      chipAppendDelay: '@?mdChipAppendDelay'
28196    }
28197  };
28198
28199  function compile(element, attr) {
28200    return function postLink(scope, element, attrs, controllers) {
28201      var contactChipsController = controllers;
28202
28203      $mdUtil.initOptionalProperties(scope, attr);
28204      $mdTheming(element);
28205
28206      element.attr('tabindex', '-1');
28207
28208      attrs.$observe('mdChipAppendDelay', function(newValue) {
28209        contactChipsController.chipAppendDelay = newValue;
28210      });
28211    };
28212  }
28213}
28214
28215})();
28216(function(){
28217"use strict";
28218
28219(function() {
28220  'use strict';
28221
28222  /**
28223   * @ngdoc directive
28224   * @name mdCalendar
28225   * @module material.components.datepicker
28226   *
28227   * @param {Date} ng-model The component's model. Should be a Date object.
28228   * @param {Date=} md-min-date Expression representing the minimum date.
28229   * @param {Date=} md-max-date Expression representing the maximum date.
28230   * @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not.
28231   *
28232   * @description
28233   * `<md-calendar>` is a component that renders a calendar that can be used to select a date.
28234   * It is a part of the `<md-datepicker` pane, however it can also be used on it's own.
28235   *
28236   * @usage
28237   *
28238   * <hljs lang="html">
28239   *   <md-calendar ng-model="birthday"></md-calendar>
28240   * </hljs>
28241   */
28242  CalendarCtrl.$inject = ["$element", "$scope", "$$mdDateUtil", "$mdUtil", "$mdConstant", "$mdTheming", "$$rAF", "$attrs", "$mdDateLocale"];
28243  angular.module('material.components.datepicker')
28244    .directive('mdCalendar', calendarDirective);
28245
28246  // POST RELEASE
28247  // TODO(jelbourn): Mac Cmd + left / right == Home / End
28248  // TODO(jelbourn): Refactor month element creation to use cloneNode (performance).
28249  // TODO(jelbourn): Define virtual scrolling constants (compactness) users can override.
28250  // TODO(jelbourn): Animated month transition on ng-model change (virtual-repeat)
28251  // TODO(jelbourn): Scroll snapping (virtual repeat)
28252  // TODO(jelbourn): Remove superfluous row from short months (virtual-repeat)
28253  // TODO(jelbourn): Month headers stick to top when scrolling.
28254  // TODO(jelbourn): Previous month opacity is lowered when partially scrolled out of view.
28255  // TODO(jelbourn): Support md-calendar standalone on a page (as a tabstop w/ aria-live
28256  //     announcement and key handling).
28257  // Read-only calendar (not just date-picker).
28258
28259  function calendarDirective() {
28260    return {
28261      template: function(tElement, tAttr) {
28262        // TODO(crisbeto): This is a workaround that allows the calendar to work, without
28263        // a datepicker, until issue #8585 gets resolved. It can safely be removed
28264        // afterwards. This ensures that the virtual repeater scrolls to the proper place on load by
28265        // deferring the execution until the next digest. It's necessary only if the calendar is used
28266        // without a datepicker, otherwise it's already wrapped in an ngIf.
28267        var extraAttrs = tAttr.hasOwnProperty('ngIf') ? '' : 'ng-if="calendarCtrl.isInitialized"';
28268        var template = '' +
28269          '<div ng-switch="calendarCtrl.currentView" ' + extraAttrs + '>' +
28270            '<md-calendar-year ng-switch-when="year"></md-calendar-year>' +
28271            '<md-calendar-month ng-switch-default></md-calendar-month>' +
28272          '</div>';
28273
28274        return template;
28275      },
28276      scope: {
28277        minDate: '=mdMinDate',
28278        maxDate: '=mdMaxDate',
28279        dateFilter: '=mdDateFilter',
28280        _currentView: '@mdCurrentView'
28281      },
28282      require: ['ngModel', 'mdCalendar'],
28283      controller: CalendarCtrl,
28284      controllerAs: 'calendarCtrl',
28285      bindToController: true,
28286      link: function(scope, element, attrs, controllers) {
28287        var ngModelCtrl = controllers[0];
28288        var mdCalendarCtrl = controllers[1];
28289        mdCalendarCtrl.configureNgModel(ngModelCtrl);
28290      }
28291    };
28292  }
28293
28294  /**
28295   * Occasionally the hideVerticalScrollbar method might read an element's
28296   * width as 0, because it hasn't been laid out yet. This value will be used
28297   * as a fallback, in order to prevent scenarios where the element's width
28298   * would otherwise have been set to 0. This value is the "usual" width of a
28299   * calendar within a floating calendar pane.
28300   */
28301  var FALLBACK_WIDTH = 340;
28302
28303  /** Next identifier for calendar instance. */
28304  var nextUniqueId = 0;
28305
28306  /**
28307   * Controller for the mdCalendar component.
28308   * @ngInject @constructor
28309   */
28310  function CalendarCtrl($element, $scope, $$mdDateUtil, $mdUtil,
28311    $mdConstant, $mdTheming, $$rAF, $attrs, $mdDateLocale) {
28312
28313    $mdTheming($element);
28314
28315    /** @final {!angular.JQLite} */
28316    this.$element = $element;
28317
28318    /** @final {!angular.Scope} */
28319    this.$scope = $scope;
28320
28321    /** @final */
28322    this.dateUtil = $$mdDateUtil;
28323
28324    /** @final */
28325    this.$mdUtil = $mdUtil;
28326
28327    /** @final */
28328    this.keyCode = $mdConstant.KEY_CODE;
28329
28330    /** @final */
28331    this.$$rAF = $$rAF;
28332
28333    /** @final */
28334    this.$mdDateLocale = $mdDateLocale;
28335
28336    /** @final {Date} */
28337    this.today = this.dateUtil.createDateAtMidnight();
28338
28339    /** @type {!angular.NgModelController} */
28340    this.ngModelCtrl = null;
28341
28342    /** @type {String} Class applied to the selected date cell. */
28343    this.SELECTED_DATE_CLASS = 'md-calendar-selected-date';
28344
28345    /** @type {String} Class applied to the cell for today. */
28346    this.TODAY_CLASS = 'md-calendar-date-today';
28347
28348    /** @type {String} Class applied to the focused cell. */
28349    this.FOCUSED_DATE_CLASS = 'md-focus';
28350
28351    /** @final {number} Unique ID for this calendar instance. */
28352    this.id = nextUniqueId++;
28353
28354    /**
28355     * The date that is currently focused or showing in the calendar. This will initially be set
28356     * to the ng-model value if set, otherwise to today. It will be updated as the user navigates
28357     * to other months. The cell corresponding to the displayDate does not necesarily always have
28358     * focus in the document (such as for cases when the user is scrolling the calendar).
28359     * @type {Date}
28360     */
28361    this.displayDate = null;
28362
28363    /**
28364     * The selected date. Keep track of this separately from the ng-model value so that we
28365     * can know, when the ng-model value changes, what the previous value was before it's updated
28366     * in the component's UI.
28367     *
28368     * @type {Date}
28369     */
28370    this.selectedDate = null;
28371
28372    /**
28373     * The first date that can be rendered by the calendar. The default is taken
28374     * from the mdDateLocale provider and is limited by the mdMinDate.
28375     * @type {Date}
28376     */
28377    this.firstRenderableDate = null;
28378
28379    /**
28380     * The last date that can be rendered by the calendar. The default comes
28381     * from the mdDateLocale provider and is limited by the maxDate.
28382     * @type {Date}
28383     */
28384    this.lastRenderableDate = null;
28385
28386    /**
28387     * Used to toggle initialize the root element in the next digest.
28388     * @type {Boolean}
28389     */
28390    this.isInitialized = false;
28391
28392    /**
28393     * Cache for the  width of the element without a scrollbar. Used to hide the scrollbar later on
28394     * and to avoid extra reflows when switching between views.
28395     * @type {Number}
28396     */
28397    this.width = 0;
28398
28399    /**
28400     * Caches the width of the scrollbar in order to be used when hiding it and to avoid extra reflows.
28401     * @type {Number}
28402     */
28403    this.scrollbarWidth = 0;
28404
28405    // Unless the user specifies so, the calendar should not be a tab stop.
28406    // This is necessary because ngAria might add a tabindex to anything with an ng-model
28407    // (based on whether or not the user has turned that particular feature on/off).
28408    if (!$attrs.tabindex) {
28409      $element.attr('tabindex', '-1');
28410    }
28411
28412    var boundKeyHandler = angular.bind(this, this.handleKeyEvent);
28413
28414
28415
28416    // If use the md-calendar directly in the body without datepicker,
28417    // handleKeyEvent will disable other inputs on the page.
28418    // So only apply the handleKeyEvent on the body when the md-calendar inside datepicker,
28419    // otherwise apply on the calendar element only.
28420
28421    var handleKeyElement;
28422    if ($element.parent().hasClass('md-datepicker-calendar')) {
28423      handleKeyElement = angular.element(document.body);
28424    } else {
28425      handleKeyElement = $element;
28426    }
28427
28428    // Bind the keydown handler to the body, in order to handle cases where the focused
28429    // element gets removed from the DOM and stops propagating click events.
28430    handleKeyElement.on('keydown', boundKeyHandler);
28431
28432    $scope.$on('$destroy', function() {
28433      handleKeyElement.off('keydown', boundKeyHandler);
28434    });
28435
28436    // For Angular 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
28437    // manually call the $onInit hook.
28438    if (angular.version.major === 1 && angular.version.minor <= 4) {
28439      this.$onInit();
28440    }
28441
28442  }
28443
28444  /**
28445   * Angular Lifecycle hook for newer Angular versions.
28446   * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
28447   */
28448  CalendarCtrl.prototype.$onInit = function() {
28449
28450    /**
28451     * The currently visible calendar view. Note the prefix on the scope value,
28452     * which is necessary, because the datepicker seems to reset the real one value if the
28453     * calendar is open, but the value on the datepicker's scope is empty.
28454     * @type {String}
28455     */
28456    this.currentView = this._currentView || 'month';
28457
28458    var dateLocale = this.$mdDateLocale;
28459
28460    if (this.minDate && this.minDate > dateLocale.firstRenderableDate) {
28461      this.firstRenderableDate = this.minDate;
28462    } else {
28463      this.firstRenderableDate = dateLocale.firstRenderableDate;
28464    }
28465
28466    if (this.maxDate && this.maxDate < dateLocale.lastRenderableDate) {
28467      this.lastRenderableDate = this.maxDate;
28468    } else {
28469      this.lastRenderableDate = dateLocale.lastRenderableDate;
28470    }
28471  };
28472
28473  /**
28474   * Sets up the controller's reference to ngModelController.
28475   * @param {!angular.NgModelController} ngModelCtrl
28476   */
28477  CalendarCtrl.prototype.configureNgModel = function(ngModelCtrl) {
28478    var self = this;
28479
28480    self.ngModelCtrl = ngModelCtrl;
28481
28482    self.$mdUtil.nextTick(function() {
28483      self.isInitialized = true;
28484    });
28485
28486    ngModelCtrl.$render = function() {
28487      var value = this.$viewValue;
28488
28489      // Notify the child scopes of any changes.
28490      self.$scope.$broadcast('md-calendar-parent-changed', value);
28491
28492      // Set up the selectedDate if it hasn't been already.
28493      if (!self.selectedDate) {
28494        self.selectedDate = value;
28495      }
28496
28497      // Also set up the displayDate.
28498      if (!self.displayDate) {
28499        self.displayDate = self.selectedDate || self.today;
28500      }
28501    };
28502  };
28503
28504  /**
28505   * Sets the ng-model value for the calendar and emits a change event.
28506   * @param {Date} date
28507   */
28508  CalendarCtrl.prototype.setNgModelValue = function(date) {
28509    var value = this.dateUtil.createDateAtMidnight(date);
28510    this.focus(value);
28511    this.$scope.$emit('md-calendar-change', value);
28512    this.ngModelCtrl.$setViewValue(value);
28513    this.ngModelCtrl.$render();
28514    return value;
28515  };
28516
28517  /**
28518   * Sets the current view that should be visible in the calendar
28519   * @param {string} newView View name to be set.
28520   * @param {number|Date} time Date object or a timestamp for the new display date.
28521   */
28522  CalendarCtrl.prototype.setCurrentView = function(newView, time) {
28523    var self = this;
28524
28525    self.$mdUtil.nextTick(function() {
28526      self.currentView = newView;
28527
28528      if (time) {
28529        self.displayDate = angular.isDate(time) ? time : new Date(time);
28530      }
28531    });
28532  };
28533
28534  /**
28535   * Focus the cell corresponding to the given date.
28536   * @param {Date} date The date to be focused.
28537   */
28538  CalendarCtrl.prototype.focus = function(date) {
28539    if (this.dateUtil.isValidDate(date)) {
28540      var previousFocus = this.$element[0].querySelector('.md-focus');
28541      if (previousFocus) {
28542        previousFocus.classList.remove(this.FOCUSED_DATE_CLASS);
28543      }
28544
28545      var cellId = this.getDateId(date, this.currentView);
28546      var cell = document.getElementById(cellId);
28547      if (cell) {
28548        cell.classList.add(this.FOCUSED_DATE_CLASS);
28549        cell.focus();
28550        this.displayDate = date;
28551      }
28552    } else {
28553      var rootElement = this.$element[0].querySelector('[ng-switch]');
28554
28555      if (rootElement) {
28556        rootElement.focus();
28557      }
28558    }
28559  };
28560
28561  /**
28562   * Normalizes the key event into an action name. The action will be broadcast
28563   * to the child controllers.
28564   * @param {KeyboardEvent} event
28565   * @returns {String} The action that should be taken, or null if the key
28566   * does not match a calendar shortcut.
28567   */
28568  CalendarCtrl.prototype.getActionFromKeyEvent = function(event) {
28569    var keyCode = this.keyCode;
28570
28571    switch (event.which) {
28572      case keyCode.ENTER: return 'select';
28573
28574      case keyCode.RIGHT_ARROW: return 'move-right';
28575      case keyCode.LEFT_ARROW: return 'move-left';
28576
28577      // TODO(crisbeto): Might want to reconsider using metaKey, because it maps
28578      // to the "Windows" key on PC, which opens the start menu or resizes the browser.
28579      case keyCode.DOWN_ARROW: return event.metaKey ? 'move-page-down' : 'move-row-down';
28580      case keyCode.UP_ARROW: return event.metaKey ? 'move-page-up' : 'move-row-up';
28581
28582      case keyCode.PAGE_DOWN: return 'move-page-down';
28583      case keyCode.PAGE_UP: return 'move-page-up';
28584
28585      case keyCode.HOME: return 'start';
28586      case keyCode.END: return 'end';
28587
28588      default: return null;
28589    }
28590  };
28591
28592  /**
28593   * Handles a key event in the calendar with the appropriate action. The action will either
28594   * be to select the focused date or to navigate to focus a new date.
28595   * @param {KeyboardEvent} event
28596   */
28597  CalendarCtrl.prototype.handleKeyEvent = function(event) {
28598    var self = this;
28599
28600    this.$scope.$apply(function() {
28601      // Capture escape and emit back up so that a wrapping component
28602      // (such as a date-picker) can decide to close.
28603      if (event.which == self.keyCode.ESCAPE || event.which == self.keyCode.TAB) {
28604        self.$scope.$emit('md-calendar-close');
28605
28606        if (event.which == self.keyCode.TAB) {
28607          event.preventDefault();
28608        }
28609
28610        return;
28611      }
28612
28613      // Broadcast the action that any child controllers should take.
28614      var action = self.getActionFromKeyEvent(event);
28615      if (action) {
28616        event.preventDefault();
28617        event.stopPropagation();
28618        self.$scope.$broadcast('md-calendar-parent-action', action);
28619      }
28620    });
28621  };
28622
28623  /**
28624   * Hides the vertical scrollbar on the calendar scroller of a child controller by
28625   * setting the width on the calendar scroller and the `overflow: hidden` wrapper
28626   * around the scroller, and then setting a padding-right on the scroller equal
28627   * to the width of the browser's scrollbar.
28628   *
28629   * This will cause a reflow.
28630   *
28631   * @param {object} childCtrl The child controller whose scrollbar should be hidden.
28632   */
28633  CalendarCtrl.prototype.hideVerticalScrollbar = function(childCtrl) {
28634    var self = this;
28635    var element = childCtrl.$element[0];
28636    var scrollMask = element.querySelector('.md-calendar-scroll-mask');
28637
28638    if (self.width > 0) {
28639      setWidth();
28640    } else {
28641      self.$$rAF(function() {
28642        var scroller = childCtrl.calendarScroller;
28643
28644        self.scrollbarWidth = scroller.offsetWidth - scroller.clientWidth;
28645        self.width = element.querySelector('table').offsetWidth;
28646        setWidth();
28647      });
28648    }
28649
28650    function setWidth() {
28651      var width = self.width || FALLBACK_WIDTH;
28652      var scrollbarWidth = self.scrollbarWidth;
28653      var scroller = childCtrl.calendarScroller;
28654
28655      scrollMask.style.width = width + 'px';
28656      scroller.style.width = (width + scrollbarWidth) + 'px';
28657      scroller.style.paddingRight = scrollbarWidth + 'px';
28658    }
28659  };
28660
28661  /**
28662   * Gets an identifier for a date unique to the calendar instance for internal
28663   * purposes. Not to be displayed.
28664   * @param {Date} date The date for which the id is being generated
28665   * @param {string} namespace Namespace for the id. (month, year etc.)
28666   * @returns {string}
28667   */
28668  CalendarCtrl.prototype.getDateId = function(date, namespace) {
28669    if (!namespace) {
28670      throw new Error('A namespace for the date id has to be specified.');
28671    }
28672
28673    return [
28674      'md',
28675      this.id,
28676      namespace,
28677      date.getFullYear(),
28678      date.getMonth(),
28679      date.getDate()
28680    ].join('-');
28681  };
28682
28683  /**
28684   * Util to trigger an extra digest on a parent scope, in order to to ensure that
28685   * any child virtual repeaters have updated. This is necessary, because the virtual
28686   * repeater doesn't update the $index the first time around since the content isn't
28687   * in place yet. The case, in which this is an issue, is when the repeater has less
28688   * than a page of content (e.g. a month or year view has a min or max date).
28689   */
28690  CalendarCtrl.prototype.updateVirtualRepeat = function() {
28691    var scope = this.$scope;
28692    var virtualRepeatResizeListener = scope.$on('$md-resize-enable', function() {
28693      if (!scope.$$phase) {
28694        scope.$apply();
28695      }
28696
28697      virtualRepeatResizeListener();
28698    });
28699  };
28700})();
28701
28702})();
28703(function(){
28704"use strict";
28705
28706(function() {
28707  'use strict';
28708
28709  CalendarMonthCtrl.$inject = ["$element", "$scope", "$animate", "$q", "$$mdDateUtil", "$mdDateLocale"];
28710  angular.module('material.components.datepicker')
28711    .directive('mdCalendarMonth', calendarDirective);
28712
28713  /**
28714   * Height of one calendar month tbody. This must be made known to the virtual-repeat and is
28715   * subsequently used for scrolling to specific months.
28716   */
28717  var TBODY_HEIGHT = 265;
28718
28719  /**
28720   * Height of a calendar month with a single row. This is needed to calculate the offset for
28721   * rendering an extra month in virtual-repeat that only contains one row.
28722   */
28723  var TBODY_SINGLE_ROW_HEIGHT = 45;
28724
28725  /** Private directive that represents a list of months inside the calendar. */
28726  function calendarDirective() {
28727    return {
28728      template:
28729        '<table aria-hidden="true" class="md-calendar-day-header"><thead></thead></table>' +
28730        '<div class="md-calendar-scroll-mask">' +
28731        '<md-virtual-repeat-container class="md-calendar-scroll-container" ' +
28732              'md-offset-size="' + (TBODY_SINGLE_ROW_HEIGHT - TBODY_HEIGHT) + '">' +
28733            '<table role="grid" tabindex="0" class="md-calendar" aria-readonly="true">' +
28734              '<tbody ' +
28735                  'md-calendar-month-body ' +
28736                  'role="rowgroup" ' +
28737                  'md-virtual-repeat="i in monthCtrl.items" ' +
28738                  'md-month-offset="$index" ' +
28739                  'class="md-calendar-month" ' +
28740                  'md-start-index="monthCtrl.getSelectedMonthIndex()" ' +
28741                  'md-item-size="' + TBODY_HEIGHT + '">' +
28742
28743                // The <tr> ensures that the <tbody> will always have the
28744                // proper height, even if it's empty. If it's content is
28745                // compiled, the <tr> will be overwritten.
28746                '<tr aria-hidden="true" style="height:' + TBODY_HEIGHT + 'px;"></tr>' +
28747              '</tbody>' +
28748            '</table>' +
28749          '</md-virtual-repeat-container>' +
28750        '</div>',
28751      require: ['^^mdCalendar', 'mdCalendarMonth'],
28752      controller: CalendarMonthCtrl,
28753      controllerAs: 'monthCtrl',
28754      bindToController: true,
28755      link: function(scope, element, attrs, controllers) {
28756        var calendarCtrl = controllers[0];
28757        var monthCtrl = controllers[1];
28758        monthCtrl.initialize(calendarCtrl);
28759      }
28760    };
28761  }
28762
28763  /**
28764   * Controller for the calendar month component.
28765   * @ngInject @constructor
28766   */
28767  function CalendarMonthCtrl($element, $scope, $animate, $q,
28768    $$mdDateUtil, $mdDateLocale) {
28769
28770    /** @final {!angular.JQLite} */
28771    this.$element = $element;
28772
28773    /** @final {!angular.Scope} */
28774    this.$scope = $scope;
28775
28776    /** @final {!angular.$animate} */
28777    this.$animate = $animate;
28778
28779    /** @final {!angular.$q} */
28780    this.$q = $q;
28781
28782    /** @final */
28783    this.dateUtil = $$mdDateUtil;
28784
28785    /** @final */
28786    this.dateLocale = $mdDateLocale;
28787
28788    /** @final {HTMLElement} */
28789    this.calendarScroller = $element[0].querySelector('.md-virtual-repeat-scroller');
28790
28791    /** @type {boolean} */
28792    this.isInitialized = false;
28793
28794    /** @type {boolean} */
28795    this.isMonthTransitionInProgress = false;
28796
28797    var self = this;
28798
28799    /**
28800     * Handles a click event on a date cell.
28801     * Created here so that every cell can use the same function instance.
28802     * @this {HTMLTableCellElement} The cell that was clicked.
28803     */
28804    this.cellClickHandler = function() {
28805      var timestamp = $$mdDateUtil.getTimestampFromNode(this);
28806      self.$scope.$apply(function() {
28807        self.calendarCtrl.setNgModelValue(timestamp);
28808      });
28809    };
28810
28811    /**
28812     * Handles click events on the month headers. Switches
28813     * the calendar to the year view.
28814     * @this {HTMLTableCellElement} The cell that was clicked.
28815     */
28816    this.headerClickHandler = function() {
28817      self.calendarCtrl.setCurrentView('year', $$mdDateUtil.getTimestampFromNode(this));
28818    };
28819  }
28820
28821  /*** Initialization ***/
28822
28823  /**
28824   * Initialize the controller by saving a reference to the calendar and
28825   * setting up the object that will be iterated by the virtual repeater.
28826   */
28827  CalendarMonthCtrl.prototype.initialize = function(calendarCtrl) {
28828    /**
28829     * Dummy array-like object for virtual-repeat to iterate over. The length is the total
28830     * number of months that can be viewed. We add 2 months: one to include the current month
28831     * and one for the last dummy month.
28832     *
28833     * This is shorter than ideal because of a (potential) Firefox bug
28834     * https://bugzilla.mozilla.org/show_bug.cgi?id=1181658.
28835     */
28836
28837    this.items = {
28838      length: this.dateUtil.getMonthDistance(
28839        calendarCtrl.firstRenderableDate,
28840        calendarCtrl.lastRenderableDate
28841      ) + 2
28842    };
28843
28844    this.calendarCtrl = calendarCtrl;
28845    this.attachScopeListeners();
28846    calendarCtrl.updateVirtualRepeat();
28847
28848    // Fire the initial render, since we might have missed it the first time it fired.
28849    calendarCtrl.ngModelCtrl && calendarCtrl.ngModelCtrl.$render();
28850  };
28851
28852  /**
28853   * Gets the "index" of the currently selected date as it would be in the virtual-repeat.
28854   * @returns {number}
28855   */
28856  CalendarMonthCtrl.prototype.getSelectedMonthIndex = function() {
28857    var calendarCtrl = this.calendarCtrl;
28858
28859    return this.dateUtil.getMonthDistance(
28860      calendarCtrl.firstRenderableDate,
28861      calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today
28862    );
28863  };
28864
28865  /**
28866   * Change the selected date in the calendar (ngModel value has already been changed).
28867   * @param {Date} date
28868   */
28869  CalendarMonthCtrl.prototype.changeSelectedDate = function(date) {
28870    var self = this;
28871    var calendarCtrl = self.calendarCtrl;
28872    var previousSelectedDate = calendarCtrl.selectedDate;
28873    calendarCtrl.selectedDate = date;
28874
28875    this.changeDisplayDate(date).then(function() {
28876      var selectedDateClass = calendarCtrl.SELECTED_DATE_CLASS;
28877      var namespace = 'month';
28878
28879      // Remove the selected class from the previously selected date, if any.
28880      if (previousSelectedDate) {
28881        var prevDateCell = document.getElementById(calendarCtrl.getDateId(previousSelectedDate, namespace));
28882        if (prevDateCell) {
28883          prevDateCell.classList.remove(selectedDateClass);
28884          prevDateCell.setAttribute('aria-selected', 'false');
28885        }
28886      }
28887
28888      // Apply the select class to the new selected date if it is set.
28889      if (date) {
28890        var dateCell = document.getElementById(calendarCtrl.getDateId(date, namespace));
28891        if (dateCell) {
28892          dateCell.classList.add(selectedDateClass);
28893          dateCell.setAttribute('aria-selected', 'true');
28894        }
28895      }
28896    });
28897  };
28898
28899  /**
28900   * Change the date that is being shown in the calendar. If the given date is in a different
28901   * month, the displayed month will be transitioned.
28902   * @param {Date} date
28903   */
28904  CalendarMonthCtrl.prototype.changeDisplayDate = function(date) {
28905    // Initialization is deferred until this function is called because we want to reflect
28906    // the starting value of ngModel.
28907    if (!this.isInitialized) {
28908      this.buildWeekHeader();
28909      this.calendarCtrl.hideVerticalScrollbar(this);
28910      this.isInitialized = true;
28911      return this.$q.when();
28912    }
28913
28914    // If trying to show an invalid date or a transition is in progress, do nothing.
28915    if (!this.dateUtil.isValidDate(date) || this.isMonthTransitionInProgress) {
28916      return this.$q.when();
28917    }
28918
28919    this.isMonthTransitionInProgress = true;
28920    var animationPromise = this.animateDateChange(date);
28921
28922    this.calendarCtrl.displayDate = date;
28923
28924    var self = this;
28925    animationPromise.then(function() {
28926      self.isMonthTransitionInProgress = false;
28927    });
28928
28929    return animationPromise;
28930  };
28931
28932  /**
28933   * Animates the transition from the calendar's current month to the given month.
28934   * @param {Date} date
28935   * @returns {angular.$q.Promise} The animation promise.
28936   */
28937  CalendarMonthCtrl.prototype.animateDateChange = function(date) {
28938    if (this.dateUtil.isValidDate(date)) {
28939      var monthDistance = this.dateUtil.getMonthDistance(this.calendarCtrl.firstRenderableDate, date);
28940      this.calendarScroller.scrollTop = monthDistance * TBODY_HEIGHT;
28941    }
28942
28943    return this.$q.when();
28944  };
28945
28946  /**
28947   * Builds and appends a day-of-the-week header to the calendar.
28948   * This should only need to be called once during initialization.
28949   */
28950  CalendarMonthCtrl.prototype.buildWeekHeader = function() {
28951    var firstDayOfWeek = this.dateLocale.firstDayOfWeek;
28952    var shortDays = this.dateLocale.shortDays;
28953
28954    var row = document.createElement('tr');
28955    for (var i = 0; i < 7; i++) {
28956      var th = document.createElement('th');
28957      th.textContent = shortDays[(i + firstDayOfWeek) % 7];
28958      row.appendChild(th);
28959    }
28960
28961    this.$element.find('thead').append(row);
28962  };
28963
28964  /**
28965   * Attaches listeners for the scope events that are broadcast by the calendar.
28966   */
28967  CalendarMonthCtrl.prototype.attachScopeListeners = function() {
28968    var self = this;
28969
28970    self.$scope.$on('md-calendar-parent-changed', function(event, value) {
28971      self.changeSelectedDate(value);
28972    });
28973
28974    self.$scope.$on('md-calendar-parent-action', angular.bind(this, this.handleKeyEvent));
28975  };
28976
28977  /**
28978   * Handles the month-specific keyboard interactions.
28979   * @param {Object} event Scope event object passed by the calendar.
28980   * @param {String} action Action, corresponding to the key that was pressed.
28981   */
28982  CalendarMonthCtrl.prototype.handleKeyEvent = function(event, action) {
28983    var calendarCtrl = this.calendarCtrl;
28984    var displayDate = calendarCtrl.displayDate;
28985
28986    if (action === 'select') {
28987      calendarCtrl.setNgModelValue(displayDate);
28988    } else {
28989      var date = null;
28990      var dateUtil = this.dateUtil;
28991
28992      switch (action) {
28993        case 'move-right': date = dateUtil.incrementDays(displayDate, 1); break;
28994        case 'move-left': date = dateUtil.incrementDays(displayDate, -1); break;
28995
28996        case 'move-page-down': date = dateUtil.incrementMonths(displayDate, 1); break;
28997        case 'move-page-up': date = dateUtil.incrementMonths(displayDate, -1); break;
28998
28999        case 'move-row-down': date = dateUtil.incrementDays(displayDate, 7); break;
29000        case 'move-row-up': date = dateUtil.incrementDays(displayDate, -7); break;
29001
29002        case 'start': date = dateUtil.getFirstDateOfMonth(displayDate); break;
29003        case 'end': date = dateUtil.getLastDateOfMonth(displayDate); break;
29004      }
29005
29006      if (date) {
29007        date = this.dateUtil.clampDate(date, calendarCtrl.minDate, calendarCtrl.maxDate);
29008
29009        this.changeDisplayDate(date).then(function() {
29010          calendarCtrl.focus(date);
29011        });
29012      }
29013    }
29014  };
29015})();
29016
29017})();
29018(function(){
29019"use strict";
29020
29021(function() {
29022  'use strict';
29023
29024  mdCalendarMonthBodyDirective.$inject = ["$compile", "$$mdSvgRegistry"];
29025  CalendarMonthBodyCtrl.$inject = ["$element", "$$mdDateUtil", "$mdDateLocale"];
29026  angular.module('material.components.datepicker')
29027      .directive('mdCalendarMonthBody', mdCalendarMonthBodyDirective);
29028
29029  /**
29030   * Private directive consumed by md-calendar-month. Having this directive lets the calender use
29031   * md-virtual-repeat and also cleanly separates the month DOM construction functions from
29032   * the rest of the calendar controller logic.
29033   * @ngInject
29034   */
29035  function mdCalendarMonthBodyDirective($compile, $$mdSvgRegistry) {
29036    var ARROW_ICON = $compile('<md-icon md-svg-src="' +
29037      $$mdSvgRegistry.mdTabsArrow + '"></md-icon>')({})[0];
29038
29039    return {
29040      require: ['^^mdCalendar', '^^mdCalendarMonth', 'mdCalendarMonthBody'],
29041      scope: { offset: '=mdMonthOffset' },
29042      controller: CalendarMonthBodyCtrl,
29043      controllerAs: 'mdMonthBodyCtrl',
29044      bindToController: true,
29045      link: function(scope, element, attrs, controllers) {
29046        var calendarCtrl = controllers[0];
29047        var monthCtrl = controllers[1];
29048        var monthBodyCtrl = controllers[2];
29049
29050        monthBodyCtrl.calendarCtrl = calendarCtrl;
29051        monthBodyCtrl.monthCtrl = monthCtrl;
29052        monthBodyCtrl.arrowIcon = ARROW_ICON.cloneNode(true);
29053
29054        // The virtual-repeat re-uses the same DOM elements, so there are only a limited number
29055        // of repeated items that are linked, and then those elements have their bindings updated.
29056        // Since the months are not generated by bindings, we simply regenerate the entire thing
29057        // when the binding (offset) changes.
29058        scope.$watch(function() { return monthBodyCtrl.offset; }, function(offset) {
29059          if (angular.isNumber(offset)) {
29060            monthBodyCtrl.generateContent();
29061          }
29062        });
29063      }
29064    };
29065  }
29066
29067  /**
29068   * Controller for a single calendar month.
29069   * @ngInject @constructor
29070   */
29071  function CalendarMonthBodyCtrl($element, $$mdDateUtil, $mdDateLocale) {
29072    /** @final {!angular.JQLite} */
29073    this.$element = $element;
29074
29075    /** @final */
29076    this.dateUtil = $$mdDateUtil;
29077
29078    /** @final */
29079    this.dateLocale = $mdDateLocale;
29080
29081    /** @type {Object} Reference to the month view. */
29082    this.monthCtrl = null;
29083
29084    /** @type {Object} Reference to the calendar. */
29085    this.calendarCtrl = null;
29086
29087    /**
29088     * Number of months from the start of the month "items" that the currently rendered month
29089     * occurs. Set via angular data binding.
29090     * @type {number}
29091     */
29092    this.offset = null;
29093
29094    /**
29095     * Date cell to focus after appending the month to the document.
29096     * @type {HTMLElement}
29097     */
29098    this.focusAfterAppend = null;
29099  }
29100
29101  /** Generate and append the content for this month to the directive element. */
29102  CalendarMonthBodyCtrl.prototype.generateContent = function() {
29103    var date = this.dateUtil.incrementMonths(this.calendarCtrl.firstRenderableDate, this.offset);
29104
29105    this.$element
29106      .empty()
29107      .append(this.buildCalendarForMonth(date));
29108
29109    if (this.focusAfterAppend) {
29110      this.focusAfterAppend.classList.add(this.calendarCtrl.FOCUSED_DATE_CLASS);
29111      this.focusAfterAppend.focus();
29112      this.focusAfterAppend = null;
29113    }
29114  };
29115
29116  /**
29117   * Creates a single cell to contain a date in the calendar with all appropriate
29118   * attributes and classes added. If a date is given, the cell content will be set
29119   * based on the date.
29120   * @param {Date=} opt_date
29121   * @returns {HTMLElement}
29122   */
29123  CalendarMonthBodyCtrl.prototype.buildDateCell = function(opt_date) {
29124    var monthCtrl = this.monthCtrl;
29125    var calendarCtrl = this.calendarCtrl;
29126
29127    // TODO(jelbourn): cloneNode is likely a faster way of doing this.
29128    var cell = document.createElement('td');
29129    cell.tabIndex = -1;
29130    cell.classList.add('md-calendar-date');
29131    cell.setAttribute('role', 'gridcell');
29132
29133    if (opt_date) {
29134      cell.setAttribute('tabindex', '-1');
29135      cell.setAttribute('aria-label', this.dateLocale.longDateFormatter(opt_date));
29136      cell.id = calendarCtrl.getDateId(opt_date, 'month');
29137
29138      // Use `data-timestamp` attribute because IE10 does not support the `dataset` property.
29139      cell.setAttribute('data-timestamp', opt_date.getTime());
29140
29141      // TODO(jelourn): Doing these comparisons for class addition during generation might be slow.
29142      // It may be better to finish the construction and then query the node and add the class.
29143      if (this.dateUtil.isSameDay(opt_date, calendarCtrl.today)) {
29144        cell.classList.add(calendarCtrl.TODAY_CLASS);
29145      }
29146
29147      if (this.dateUtil.isValidDate(calendarCtrl.selectedDate) &&
29148          this.dateUtil.isSameDay(opt_date, calendarCtrl.selectedDate)) {
29149        cell.classList.add(calendarCtrl.SELECTED_DATE_CLASS);
29150        cell.setAttribute('aria-selected', 'true');
29151      }
29152
29153      var cellText = this.dateLocale.dates[opt_date.getDate()];
29154
29155      if (this.isDateEnabled(opt_date)) {
29156        // Add a indicator for select, hover, and focus states.
29157        var selectionIndicator = document.createElement('span');
29158        selectionIndicator.classList.add('md-calendar-date-selection-indicator');
29159        selectionIndicator.textContent = cellText;
29160        cell.appendChild(selectionIndicator);
29161        cell.addEventListener('click', monthCtrl.cellClickHandler);
29162
29163        if (calendarCtrl.displayDate && this.dateUtil.isSameDay(opt_date, calendarCtrl.displayDate)) {
29164          this.focusAfterAppend = cell;
29165        }
29166      } else {
29167        cell.classList.add('md-calendar-date-disabled');
29168        cell.textContent = cellText;
29169      }
29170    }
29171
29172    return cell;
29173  };
29174
29175  /**
29176   * Check whether date is in range and enabled
29177   * @param {Date=} opt_date
29178   * @return {boolean} Whether the date is enabled.
29179   */
29180  CalendarMonthBodyCtrl.prototype.isDateEnabled = function(opt_date) {
29181    return this.dateUtil.isDateWithinRange(opt_date,
29182          this.calendarCtrl.minDate, this.calendarCtrl.maxDate) &&
29183          (!angular.isFunction(this.calendarCtrl.dateFilter)
29184           || this.calendarCtrl.dateFilter(opt_date));
29185  };
29186
29187  /**
29188   * Builds a `tr` element for the calendar grid.
29189   * @param rowNumber The week number within the month.
29190   * @returns {HTMLElement}
29191   */
29192  CalendarMonthBodyCtrl.prototype.buildDateRow = function(rowNumber) {
29193    var row = document.createElement('tr');
29194    row.setAttribute('role', 'row');
29195
29196    // Because of an NVDA bug (with Firefox), the row needs an aria-label in order
29197    // to prevent the entire row being read aloud when the user moves between rows.
29198    // See http://community.nvda-project.org/ticket/4643.
29199    row.setAttribute('aria-label', this.dateLocale.weekNumberFormatter(rowNumber));
29200
29201    return row;
29202  };
29203
29204  /**
29205   * Builds the <tbody> content for the given date's month.
29206   * @param {Date=} opt_dateInMonth
29207   * @returns {DocumentFragment} A document fragment containing the <tr> elements.
29208   */
29209  CalendarMonthBodyCtrl.prototype.buildCalendarForMonth = function(opt_dateInMonth) {
29210    var date = this.dateUtil.isValidDate(opt_dateInMonth) ? opt_dateInMonth : new Date();
29211
29212    var firstDayOfMonth = this.dateUtil.getFirstDateOfMonth(date);
29213    var firstDayOfTheWeek = this.getLocaleDay_(firstDayOfMonth);
29214    var numberOfDaysInMonth = this.dateUtil.getNumberOfDaysInMonth(date);
29215
29216    // Store rows for the month in a document fragment so that we can append them all at once.
29217    var monthBody = document.createDocumentFragment();
29218
29219    var rowNumber = 1;
29220    var row = this.buildDateRow(rowNumber);
29221    monthBody.appendChild(row);
29222
29223    // If this is the final month in the list of items, only the first week should render,
29224    // so we should return immediately after the first row is complete and has been
29225    // attached to the body.
29226    var isFinalMonth = this.offset === this.monthCtrl.items.length - 1;
29227
29228    // Add a label for the month. If the month starts on a Sun/Mon/Tues, the month label
29229    // goes on a row above the first of the month. Otherwise, the month label takes up the first
29230    // two cells of the first row.
29231    var blankCellOffset = 0;
29232    var monthLabelCell = document.createElement('td');
29233    var monthLabelCellContent = document.createElement('span');
29234
29235    monthLabelCellContent.textContent = this.dateLocale.monthHeaderFormatter(date);
29236    monthLabelCell.appendChild(monthLabelCellContent);
29237    monthLabelCell.classList.add('md-calendar-month-label');
29238    // If the entire month is after the max date, render the label as a disabled state.
29239    if (this.calendarCtrl.maxDate && firstDayOfMonth > this.calendarCtrl.maxDate) {
29240      monthLabelCell.classList.add('md-calendar-month-label-disabled');
29241    } else {
29242      monthLabelCell.addEventListener('click', this.monthCtrl.headerClickHandler);
29243      monthLabelCell.setAttribute('data-timestamp', firstDayOfMonth.getTime());
29244      monthLabelCell.setAttribute('aria-label', this.dateLocale.monthFormatter(date));
29245      monthLabelCell.appendChild(this.arrowIcon.cloneNode(true));
29246    }
29247
29248    if (firstDayOfTheWeek <= 2) {
29249      monthLabelCell.setAttribute('colspan', '7');
29250
29251      var monthLabelRow = this.buildDateRow();
29252      monthLabelRow.appendChild(monthLabelCell);
29253      monthBody.insertBefore(monthLabelRow, row);
29254
29255      if (isFinalMonth) {
29256        return monthBody;
29257      }
29258    } else {
29259      blankCellOffset = 3;
29260      monthLabelCell.setAttribute('colspan', '3');
29261      row.appendChild(monthLabelCell);
29262    }
29263
29264    // Add a blank cell for each day of the week that occurs before the first of the month.
29265    // For example, if the first day of the month is a Tuesday, add blank cells for Sun and Mon.
29266    // The blankCellOffset is needed in cases where the first N cells are used by the month label.
29267    for (var i = blankCellOffset; i < firstDayOfTheWeek; i++) {
29268      row.appendChild(this.buildDateCell());
29269    }
29270
29271    // Add a cell for each day of the month, keeping track of the day of the week so that
29272    // we know when to start a new row.
29273    var dayOfWeek = firstDayOfTheWeek;
29274    var iterationDate = firstDayOfMonth;
29275    for (var d = 1; d <= numberOfDaysInMonth; d++) {
29276      // If we've reached the end of the week, start a new row.
29277      if (dayOfWeek === 7) {
29278        // We've finished the first row, so we're done if this is the final month.
29279        if (isFinalMonth) {
29280          return monthBody;
29281        }
29282        dayOfWeek = 0;
29283        rowNumber++;
29284        row = this.buildDateRow(rowNumber);
29285        monthBody.appendChild(row);
29286      }
29287
29288      iterationDate.setDate(d);
29289      var cell = this.buildDateCell(iterationDate);
29290      row.appendChild(cell);
29291
29292      dayOfWeek++;
29293    }
29294
29295    // Ensure that the last row of the month has 7 cells.
29296    while (row.childNodes.length < 7) {
29297      row.appendChild(this.buildDateCell());
29298    }
29299
29300    // Ensure that all months have 6 rows. This is necessary for now because the virtual-repeat
29301    // requires that all items have exactly the same height.
29302    while (monthBody.childNodes.length < 6) {
29303      var whitespaceRow = this.buildDateRow();
29304      for (var j = 0; j < 7; j++) {
29305        whitespaceRow.appendChild(this.buildDateCell());
29306      }
29307      monthBody.appendChild(whitespaceRow);
29308    }
29309
29310    return monthBody;
29311  };
29312
29313  /**
29314   * Gets the day-of-the-week index for a date for the current locale.
29315   * @private
29316   * @param {Date} date
29317   * @returns {number} The column index of the date in the calendar.
29318   */
29319  CalendarMonthBodyCtrl.prototype.getLocaleDay_ = function(date) {
29320    return (date.getDay() + (7 - this.dateLocale.firstDayOfWeek)) % 7;
29321  };
29322})();
29323
29324})();
29325(function(){
29326"use strict";
29327
29328(function() {
29329  'use strict';
29330
29331  CalendarYearCtrl.$inject = ["$element", "$scope", "$animate", "$q", "$$mdDateUtil"];
29332  angular.module('material.components.datepicker')
29333    .directive('mdCalendarYear', calendarDirective);
29334
29335  /**
29336   * Height of one calendar year tbody. This must be made known to the virtual-repeat and is
29337   * subsequently used for scrolling to specific years.
29338   */
29339  var TBODY_HEIGHT = 88;
29340
29341  /** Private component, representing a list of years in the calendar. */
29342  function calendarDirective() {
29343    return {
29344      template:
29345        '<div class="md-calendar-scroll-mask">' +
29346          '<md-virtual-repeat-container class="md-calendar-scroll-container">' +
29347            '<table role="grid" tabindex="0" class="md-calendar" aria-readonly="true">' +
29348              '<tbody ' +
29349                  'md-calendar-year-body ' +
29350                  'role="rowgroup" ' +
29351                  'md-virtual-repeat="i in yearCtrl.items" ' +
29352                  'md-year-offset="$index" class="md-calendar-year" ' +
29353                  'md-start-index="yearCtrl.getFocusedYearIndex()" ' +
29354                  'md-item-size="' + TBODY_HEIGHT + '">' +
29355                // The <tr> ensures that the <tbody> will have the proper
29356                // height, even though it may be empty.
29357                '<tr aria-hidden="true" style="height:' + TBODY_HEIGHT + 'px;"></tr>' +
29358              '</tbody>' +
29359            '</table>' +
29360          '</md-virtual-repeat-container>' +
29361        '</div>',
29362      require: ['^^mdCalendar', 'mdCalendarYear'],
29363      controller: CalendarYearCtrl,
29364      controllerAs: 'yearCtrl',
29365      bindToController: true,
29366      link: function(scope, element, attrs, controllers) {
29367        var calendarCtrl = controllers[0];
29368        var yearCtrl = controllers[1];
29369        yearCtrl.initialize(calendarCtrl);
29370      }
29371    };
29372  }
29373
29374  /**
29375   * Controller for the mdCalendar component.
29376   * @ngInject @constructor
29377   */
29378  function CalendarYearCtrl($element, $scope, $animate, $q, $$mdDateUtil) {
29379
29380    /** @final {!angular.JQLite} */
29381    this.$element = $element;
29382
29383    /** @final {!angular.Scope} */
29384    this.$scope = $scope;
29385
29386    /** @final {!angular.$animate} */
29387    this.$animate = $animate;
29388
29389    /** @final {!angular.$q} */
29390    this.$q = $q;
29391
29392    /** @final */
29393    this.dateUtil = $$mdDateUtil;
29394
29395    /** @final {HTMLElement} */
29396    this.calendarScroller = $element[0].querySelector('.md-virtual-repeat-scroller');
29397
29398    /** @type {boolean} */
29399    this.isInitialized = false;
29400
29401    /** @type {boolean} */
29402    this.isMonthTransitionInProgress = false;
29403
29404    var self = this;
29405
29406    /**
29407     * Handles a click event on a date cell.
29408     * Created here so that every cell can use the same function instance.
29409     * @this {HTMLTableCellElement} The cell that was clicked.
29410     */
29411    this.cellClickHandler = function() {
29412      self.calendarCtrl.setCurrentView('month', $$mdDateUtil.getTimestampFromNode(this));
29413    };
29414  }
29415
29416  /**
29417   * Initialize the controller by saving a reference to the calendar and
29418   * setting up the object that will be iterated by the virtual repeater.
29419   */
29420  CalendarYearCtrl.prototype.initialize = function(calendarCtrl) {
29421    /**
29422     * Dummy array-like object for virtual-repeat to iterate over. The length is the total
29423     * number of years that can be viewed. We add 1 extra in order to include the current year.
29424     */
29425    this.items = {
29426      length: this.dateUtil.getYearDistance(
29427        calendarCtrl.firstRenderableDate,
29428        calendarCtrl.lastRenderableDate
29429      ) + 1
29430    };
29431
29432    this.calendarCtrl = calendarCtrl;
29433    this.attachScopeListeners();
29434    calendarCtrl.updateVirtualRepeat();
29435
29436    // Fire the initial render, since we might have missed it the first time it fired.
29437    calendarCtrl.ngModelCtrl && calendarCtrl.ngModelCtrl.$render();
29438  };
29439
29440  /**
29441   * Gets the "index" of the currently selected date as it would be in the virtual-repeat.
29442   * @returns {number}
29443   */
29444  CalendarYearCtrl.prototype.getFocusedYearIndex = function() {
29445    var calendarCtrl = this.calendarCtrl;
29446
29447    return this.dateUtil.getYearDistance(
29448      calendarCtrl.firstRenderableDate,
29449      calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today
29450    );
29451  };
29452
29453  /**
29454   * Change the date that is highlighted in the calendar.
29455   * @param {Date} date
29456   */
29457  CalendarYearCtrl.prototype.changeDate = function(date) {
29458    // Initialization is deferred until this function is called because we want to reflect
29459    // the starting value of ngModel.
29460    if (!this.isInitialized) {
29461      this.calendarCtrl.hideVerticalScrollbar(this);
29462      this.isInitialized = true;
29463      return this.$q.when();
29464    } else if (this.dateUtil.isValidDate(date) && !this.isMonthTransitionInProgress) {
29465      var self = this;
29466      var animationPromise = this.animateDateChange(date);
29467
29468      self.isMonthTransitionInProgress = true;
29469      self.calendarCtrl.displayDate = date;
29470
29471      return animationPromise.then(function() {
29472        self.isMonthTransitionInProgress = false;
29473      });
29474    }
29475  };
29476
29477  /**
29478   * Animates the transition from the calendar's current month to the given month.
29479   * @param {Date} date
29480   * @returns {angular.$q.Promise} The animation promise.
29481   */
29482  CalendarYearCtrl.prototype.animateDateChange = function(date) {
29483    if (this.dateUtil.isValidDate(date)) {
29484      var monthDistance = this.dateUtil.getYearDistance(this.calendarCtrl.firstRenderableDate, date);
29485      this.calendarScroller.scrollTop = monthDistance * TBODY_HEIGHT;
29486    }
29487
29488    return this.$q.when();
29489  };
29490
29491  /**
29492   * Handles the year-view-specific keyboard interactions.
29493   * @param {Object} event Scope event object passed by the calendar.
29494   * @param {String} action Action, corresponding to the key that was pressed.
29495   */
29496  CalendarYearCtrl.prototype.handleKeyEvent = function(event, action) {
29497    var calendarCtrl = this.calendarCtrl;
29498    var displayDate = calendarCtrl.displayDate;
29499
29500    if (action === 'select') {
29501      this.changeDate(displayDate).then(function() {
29502        calendarCtrl.setCurrentView('month', displayDate);
29503        calendarCtrl.focus(displayDate);
29504      });
29505    } else {
29506      var date = null;
29507      var dateUtil = this.dateUtil;
29508
29509      switch (action) {
29510        case 'move-right': date = dateUtil.incrementMonths(displayDate, 1); break;
29511        case 'move-left': date = dateUtil.incrementMonths(displayDate, -1); break;
29512
29513        case 'move-row-down': date = dateUtil.incrementMonths(displayDate, 6); break;
29514        case 'move-row-up': date = dateUtil.incrementMonths(displayDate, -6); break;
29515      }
29516
29517      if (date) {
29518        var min = calendarCtrl.minDate ? dateUtil.getFirstDateOfMonth(calendarCtrl.minDate) : null;
29519        var max = calendarCtrl.maxDate ? dateUtil.getFirstDateOfMonth(calendarCtrl.maxDate) : null;
29520        date = dateUtil.getFirstDateOfMonth(this.dateUtil.clampDate(date, min, max));
29521
29522        this.changeDate(date).then(function() {
29523          calendarCtrl.focus(date);
29524        });
29525      }
29526    }
29527  };
29528
29529  /**
29530   * Attaches listeners for the scope events that are broadcast by the calendar.
29531   */
29532  CalendarYearCtrl.prototype.attachScopeListeners = function() {
29533    var self = this;
29534
29535    self.$scope.$on('md-calendar-parent-changed', function(event, value) {
29536      self.changeDate(value);
29537    });
29538
29539    self.$scope.$on('md-calendar-parent-action', angular.bind(self, self.handleKeyEvent));
29540  };
29541})();
29542
29543})();
29544(function(){
29545"use strict";
29546
29547(function() {
29548  'use strict';
29549
29550  CalendarYearBodyCtrl.$inject = ["$element", "$$mdDateUtil", "$mdDateLocale"];
29551  angular.module('material.components.datepicker')
29552      .directive('mdCalendarYearBody', mdCalendarYearDirective);
29553
29554  /**
29555   * Private component, consumed by the md-calendar-year, which separates the DOM construction logic
29556   * and allows for the year view to use md-virtual-repeat.
29557   */
29558  function mdCalendarYearDirective() {
29559    return {
29560      require: ['^^mdCalendar', '^^mdCalendarYear', 'mdCalendarYearBody'],
29561      scope: { offset: '=mdYearOffset' },
29562      controller: CalendarYearBodyCtrl,
29563      controllerAs: 'mdYearBodyCtrl',
29564      bindToController: true,
29565      link: function(scope, element, attrs, controllers) {
29566        var calendarCtrl = controllers[0];
29567        var yearCtrl = controllers[1];
29568        var yearBodyCtrl = controllers[2];
29569
29570        yearBodyCtrl.calendarCtrl = calendarCtrl;
29571        yearBodyCtrl.yearCtrl = yearCtrl;
29572
29573        scope.$watch(function() { return yearBodyCtrl.offset; }, function(offset) {
29574          if (angular.isNumber(offset)) {
29575            yearBodyCtrl.generateContent();
29576          }
29577        });
29578      }
29579    };
29580  }
29581
29582  /**
29583   * Controller for a single year.
29584   * @ngInject @constructor
29585   */
29586  function CalendarYearBodyCtrl($element, $$mdDateUtil, $mdDateLocale) {
29587    /** @final {!angular.JQLite} */
29588    this.$element = $element;
29589
29590    /** @final */
29591    this.dateUtil = $$mdDateUtil;
29592
29593    /** @final */
29594    this.dateLocale = $mdDateLocale;
29595
29596    /** @type {Object} Reference to the calendar. */
29597    this.calendarCtrl = null;
29598
29599    /** @type {Object} Reference to the year view. */
29600    this.yearCtrl = null;
29601
29602    /**
29603     * Number of months from the start of the month "items" that the currently rendered month
29604     * occurs. Set via angular data binding.
29605     * @type {number}
29606     */
29607    this.offset = null;
29608
29609    /**
29610     * Date cell to focus after appending the month to the document.
29611     * @type {HTMLElement}
29612     */
29613    this.focusAfterAppend = null;
29614  }
29615
29616  /** Generate and append the content for this year to the directive element. */
29617  CalendarYearBodyCtrl.prototype.generateContent = function() {
29618    var date = this.dateUtil.incrementYears(this.calendarCtrl.firstRenderableDate, this.offset);
29619
29620    this.$element
29621      .empty()
29622      .append(this.buildCalendarForYear(date));
29623
29624    if (this.focusAfterAppend) {
29625      this.focusAfterAppend.classList.add(this.calendarCtrl.FOCUSED_DATE_CLASS);
29626      this.focusAfterAppend.focus();
29627      this.focusAfterAppend = null;
29628    }
29629  };
29630
29631  /**
29632   * Creates a single cell to contain a year in the calendar.
29633   * @param {number} opt_year Four-digit year.
29634   * @param {number} opt_month Zero-indexed month.
29635   * @returns {HTMLElement}
29636   */
29637  CalendarYearBodyCtrl.prototype.buildMonthCell = function(year, month) {
29638    var calendarCtrl = this.calendarCtrl;
29639    var yearCtrl = this.yearCtrl;
29640    var cell = this.buildBlankCell();
29641
29642    // Represent this month/year as a date.
29643    var firstOfMonth = new Date(year, month, 1);
29644    cell.setAttribute('aria-label', this.dateLocale.monthFormatter(firstOfMonth));
29645    cell.id = calendarCtrl.getDateId(firstOfMonth, 'year');
29646
29647    // Use `data-timestamp` attribute because IE10 does not support the `dataset` property.
29648    cell.setAttribute('data-timestamp', firstOfMonth.getTime());
29649
29650    if (this.dateUtil.isSameMonthAndYear(firstOfMonth, calendarCtrl.today)) {
29651      cell.classList.add(calendarCtrl.TODAY_CLASS);
29652    }
29653
29654    if (this.dateUtil.isValidDate(calendarCtrl.selectedDate) &&
29655        this.dateUtil.isSameMonthAndYear(firstOfMonth, calendarCtrl.selectedDate)) {
29656      cell.classList.add(calendarCtrl.SELECTED_DATE_CLASS);
29657      cell.setAttribute('aria-selected', 'true');
29658    }
29659
29660    var cellText = this.dateLocale.shortMonths[month];
29661
29662    if (this.dateUtil.isMonthWithinRange(firstOfMonth,
29663        calendarCtrl.minDate, calendarCtrl.maxDate)) {
29664      var selectionIndicator = document.createElement('span');
29665      selectionIndicator.classList.add('md-calendar-date-selection-indicator');
29666      selectionIndicator.textContent = cellText;
29667      cell.appendChild(selectionIndicator);
29668      cell.addEventListener('click', yearCtrl.cellClickHandler);
29669
29670      if (calendarCtrl.displayDate && this.dateUtil.isSameMonthAndYear(firstOfMonth, calendarCtrl.displayDate)) {
29671        this.focusAfterAppend = cell;
29672      }
29673    } else {
29674      cell.classList.add('md-calendar-date-disabled');
29675      cell.textContent = cellText;
29676    }
29677
29678    return cell;
29679  };
29680
29681  /**
29682   * Builds a blank cell.
29683   * @return {HTMLTableCellElement}
29684   */
29685  CalendarYearBodyCtrl.prototype.buildBlankCell = function() {
29686    var cell = document.createElement('td');
29687    cell.tabIndex = -1;
29688    cell.classList.add('md-calendar-date');
29689    cell.setAttribute('role', 'gridcell');
29690
29691    cell.setAttribute('tabindex', '-1');
29692    return cell;
29693  };
29694
29695  /**
29696   * Builds the <tbody> content for the given year.
29697   * @param {Date} date Date for which the content should be built.
29698   * @returns {DocumentFragment} A document fragment containing the months within the year.
29699   */
29700  CalendarYearBodyCtrl.prototype.buildCalendarForYear = function(date) {
29701    // Store rows for the month in a document fragment so that we can append them all at once.
29702    var year = date.getFullYear();
29703    var yearBody = document.createDocumentFragment();
29704
29705    var monthCell, i;
29706    // First row contains label and Jan-Jun.
29707    var firstRow = document.createElement('tr');
29708    var labelCell = document.createElement('td');
29709    labelCell.className = 'md-calendar-month-label';
29710    labelCell.textContent = year;
29711    firstRow.appendChild(labelCell);
29712
29713    for (i = 0; i < 6; i++) {
29714      firstRow.appendChild(this.buildMonthCell(year, i));
29715    }
29716    yearBody.appendChild(firstRow);
29717
29718    // Second row contains a blank cell and Jul-Dec.
29719    var secondRow = document.createElement('tr');
29720    secondRow.appendChild(this.buildBlankCell());
29721    for (i = 6; i < 12; i++) {
29722      secondRow.appendChild(this.buildMonthCell(year, i));
29723    }
29724    yearBody.appendChild(secondRow);
29725
29726    return yearBody;
29727  };
29728})();
29729
29730})();
29731(function(){
29732"use strict";
29733
29734(function() {
29735  'use strict';
29736
29737  /**
29738   * @ngdoc service
29739   * @name $mdDateLocaleProvider
29740   * @module material.components.datepicker
29741   *
29742   * @description
29743   * The `$mdDateLocaleProvider` is the provider that creates the `$mdDateLocale` service.
29744   * This provider that allows the user to specify messages, formatters, and parsers for date
29745   * internationalization. The `$mdDateLocale` service itself is consumed by Angular Material
29746   * components that deal with dates.
29747   *
29748   * @property {(Array<string>)=} months Array of month names (in order).
29749   * @property {(Array<string>)=} shortMonths Array of abbreviated month names.
29750   * @property {(Array<string>)=} days Array of the days of the week (in order).
29751   * @property {(Array<string>)=} shortDays Array of abbreviated dayes of the week.
29752   * @property {(Array<string>)=} dates Array of dates of the month. Only necessary for locales
29753   *     using a numeral system other than [1, 2, 3...].
29754   * @property {(Array<string>)=} firstDayOfWeek The first day of the week. Sunday = 0, Monday = 1,
29755   *    etc.
29756   * @property {(function(string): Date)=} parseDate Function to parse a date object from a string.
29757   * @property {(function(Date, string): string)=} formatDate Function to format a date object to a
29758   *     string. The datepicker directive also provides the time zone, if it was specified.
29759   * @property {(function(Date): string)=} monthHeaderFormatter Function that returns the label for
29760   *     a month given a date.
29761   * @property {(function(Date): string)=} monthFormatter Function that returns the full name of a month
29762   *     for a giben date.
29763   * @property {(function(number): string)=} weekNumberFormatter Function that returns a label for
29764   *     a week given the week number.
29765   * @property {(string)=} msgCalendar Translation of the label "Calendar" for the current locale.
29766   * @property {(string)=} msgOpenCalendar Translation of the button label "Open calendar" for the
29767   *     current locale.
29768   * @property {Date=} firstRenderableDate The date from which the datepicker calendar will begin
29769   * rendering. Note that this will be ignored if a minimum date is set. Defaults to January 1st 1880.
29770   * @property {Date=} lastRenderableDate The last date that will be rendered by the datepicker
29771   * calendar. Note that this will be ignored if a maximum date is set. Defaults to January 1st 2130.
29772   *
29773   * @usage
29774   * <hljs lang="js">
29775   * myAppModule.config(function($mdDateLocaleProvider) {
29776   *
29777   *     // Example of a French localization.
29778   *     $mdDateLocaleProvider.months = ['janvier', 'février', 'mars', ...];
29779   *     $mdDateLocaleProvider.shortMonths = ['janv', 'févr', 'mars', ...];
29780   *     $mdDateLocaleProvider.days = ['dimanche', 'lundi', 'mardi', ...];
29781   *     $mdDateLocaleProvider.shortDays = ['Di', 'Lu', 'Ma', ...];
29782   *
29783   *     // Can change week display to start on Monday.
29784   *     $mdDateLocaleProvider.firstDayOfWeek = 1;
29785   *
29786   *     // Optional.
29787   *     $mdDateLocaleProvider.dates = [1, 2, 3, 4, 5, 6, ...];
29788   *
29789   *     // Example uses moment.js to parse and format dates.
29790   *     $mdDateLocaleProvider.parseDate = function(dateString) {
29791   *       var m = moment(dateString, 'L', true);
29792   *       return m.isValid() ? m.toDate() : new Date(NaN);
29793   *     };
29794   *
29795   *     $mdDateLocaleProvider.formatDate = function(date) {
29796   *       var m = moment(date);
29797   *       return m.isValid() ? m.format('L') : '';
29798   *     };
29799   *
29800   *     $mdDateLocaleProvider.monthHeaderFormatter = function(date) {
29801   *       return myShortMonths[date.getMonth()] + ' ' + date.getFullYear();
29802   *     };
29803   *
29804   *     // In addition to date display, date components also need localized messages
29805   *     // for aria-labels for screen-reader users.
29806   *
29807   *     $mdDateLocaleProvider.weekNumberFormatter = function(weekNumber) {
29808   *       return 'Semaine ' + weekNumber;
29809   *     };
29810   *
29811   *     $mdDateLocaleProvider.msgCalendar = 'Calendrier';
29812   *     $mdDateLocaleProvider.msgOpenCalendar = 'Ouvrir le calendrier';
29813   *
29814   *     // You can also set when your calendar begins and ends.
29815   *     $mdDateLocaleProvider.firstRenderableDate = new Date(1776, 6, 4);
29816   *     $mdDateLocaleProvider.lastRenderableDate = new Date(2012, 11, 21);
29817   * });
29818   * </hljs>
29819   *
29820   */
29821  angular.module('material.components.datepicker').config(["$provide", function($provide) {
29822    // TODO(jelbourn): Assert provided values are correctly formatted. Need assertions.
29823
29824    /** @constructor */
29825    function DateLocaleProvider() {
29826      /** Array of full month names. E.g., ['January', 'Febuary', ...] */
29827      this.months = null;
29828
29829      /** Array of abbreviated month names. E.g., ['Jan', 'Feb', ...] */
29830      this.shortMonths = null;
29831
29832      /** Array of full day of the week names. E.g., ['Monday', 'Tuesday', ...] */
29833      this.days = null;
29834
29835      /** Array of abbreviated dat of the week names. E.g., ['M', 'T', ...] */
29836      this.shortDays = null;
29837
29838      /** Array of dates of a month (1 - 31). Characters might be different in some locales. */
29839      this.dates = null;
29840
29841      /** Index of the first day of the week. 0 = Sunday, 1 = Monday, etc. */
29842      this.firstDayOfWeek = 0;
29843
29844      /**
29845       * Function that converts the date portion of a Date to a string.
29846       * @type {(function(Date): string)}
29847       */
29848      this.formatDate = null;
29849
29850      /**
29851       * Function that converts a date string to a Date object (the date portion)
29852       * @type {function(string): Date}
29853       */
29854      this.parseDate = null;
29855
29856      /**
29857       * Function that formats a Date into a month header string.
29858       * @type {function(Date): string}
29859       */
29860      this.monthHeaderFormatter = null;
29861
29862      /**
29863       * Function that formats a week number into a label for the week.
29864       * @type {function(number): string}
29865       */
29866      this.weekNumberFormatter = null;
29867
29868      /**
29869       * Function that formats a date into a long aria-label that is read
29870       * when the focused date changes.
29871       * @type {function(Date): string}
29872       */
29873      this.longDateFormatter = null;
29874
29875      /**
29876       * ARIA label for the calendar "dialog" used in the datepicker.
29877       * @type {string}
29878       */
29879      this.msgCalendar = '';
29880
29881      /**
29882       * ARIA label for the datepicker's "Open calendar" buttons.
29883       * @type {string}
29884       */
29885      this.msgOpenCalendar = '';
29886    }
29887
29888    /**
29889     * Factory function that returns an instance of the dateLocale service.
29890     * @ngInject
29891     * @param $locale
29892     * @returns {DateLocale}
29893     */
29894    DateLocaleProvider.prototype.$get = function($locale, $filter) {
29895      /**
29896       * Default date-to-string formatting function.
29897       * @param {!Date} date
29898       * @param {string=} timezone
29899       * @returns {string}
29900       */
29901      function defaultFormatDate(date, timezone) {
29902        if (!date) {
29903          return '';
29904        }
29905
29906        // All of the dates created through ng-material *should* be set to midnight.
29907        // If we encounter a date where the localeTime shows at 11pm instead of midnight,
29908        // we have run into an issue with DST where we need to increment the hour by one:
29909        // var d = new Date(1992, 9, 8, 0, 0, 0);
29910        // d.toLocaleString(); // == "10/7/1992, 11:00:00 PM"
29911        var localeTime = date.toLocaleTimeString();
29912        var formatDate = date;
29913        if (date.getHours() === 0 &&
29914            (localeTime.indexOf('11:') !== -1 || localeTime.indexOf('23:') !== -1)) {
29915          formatDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 1, 0, 0);
29916        }
29917
29918        return $filter('date')(formatDate, 'M/d/yyyy', timezone);
29919      }
29920
29921      /**
29922       * Default string-to-date parsing function.
29923       * @param {string} dateString
29924       * @returns {!Date}
29925       */
29926      function defaultParseDate(dateString) {
29927        return new Date(dateString);
29928      }
29929
29930      /**
29931       * Default function to determine whether a string makes sense to be
29932       * parsed to a Date object.
29933       *
29934       * This is very permissive and is just a basic sanity check to ensure that
29935       * things like single integers aren't able to be parsed into dates.
29936       * @param {string} dateString
29937       * @returns {boolean}
29938       */
29939      function defaultIsDateComplete(dateString) {
29940        dateString = dateString.trim();
29941
29942        // Looks for three chunks of content (either numbers or text) separated
29943        // by delimiters.
29944        var re = /^(([a-zA-Z]{3,}|[0-9]{1,4})([ \.,]+|[\/\-])){2}([a-zA-Z]{3,}|[0-9]{1,4})$/;
29945        return re.test(dateString);
29946      }
29947
29948      /**
29949       * Default date-to-string formatter to get a month header.
29950       * @param {!Date} date
29951       * @returns {string}
29952       */
29953      function defaultMonthHeaderFormatter(date) {
29954        return service.shortMonths[date.getMonth()] + ' ' + date.getFullYear();
29955      }
29956
29957      /**
29958       * Default formatter for a month.
29959       * @param {!Date} date
29960       * @returns {string}
29961       */
29962      function defaultMonthFormatter(date) {
29963        return service.months[date.getMonth()] + ' ' + date.getFullYear();
29964      }
29965
29966      /**
29967       * Default week number formatter.
29968       * @param number
29969       * @returns {string}
29970       */
29971      function defaultWeekNumberFormatter(number) {
29972        return 'Week ' + number;
29973      }
29974
29975      /**
29976       * Default formatter for date cell aria-labels.
29977       * @param {!Date} date
29978       * @returns {string}
29979       */
29980      function defaultLongDateFormatter(date) {
29981        // Example: 'Thursday June 18 2015'
29982        return [
29983          service.days[date.getDay()],
29984          service.months[date.getMonth()],
29985          service.dates[date.getDate()],
29986          date.getFullYear()
29987        ].join(' ');
29988      }
29989
29990      // The default "short" day strings are the first character of each day,
29991      // e.g., "Monday" => "M".
29992      var defaultShortDays = $locale.DATETIME_FORMATS.SHORTDAY.map(function(day) {
29993        return day.substring(0, 1);
29994      });
29995
29996      // The default dates are simply the numbers 1 through 31.
29997      var defaultDates = Array(32);
29998      for (var i = 1; i <= 31; i++) {
29999        defaultDates[i] = i;
30000      }
30001
30002      // Default ARIA messages are in English (US).
30003      var defaultMsgCalendar = 'Calendar';
30004      var defaultMsgOpenCalendar = 'Open calendar';
30005
30006      // Default start/end dates that are rendered in the calendar.
30007      var defaultFirstRenderableDate = new Date(1880, 0, 1);
30008      var defaultLastRendereableDate = new Date(defaultFirstRenderableDate.getFullYear() + 250, 0, 1);
30009
30010      var service = {
30011        months: this.months || $locale.DATETIME_FORMATS.MONTH,
30012        shortMonths: this.shortMonths || $locale.DATETIME_FORMATS.SHORTMONTH,
30013        days: this.days || $locale.DATETIME_FORMATS.DAY,
30014        shortDays: this.shortDays || defaultShortDays,
30015        dates: this.dates || defaultDates,
30016        firstDayOfWeek: this.firstDayOfWeek || 0,
30017        formatDate: this.formatDate || defaultFormatDate,
30018        parseDate: this.parseDate || defaultParseDate,
30019        isDateComplete: this.isDateComplete || defaultIsDateComplete,
30020        monthHeaderFormatter: this.monthHeaderFormatter || defaultMonthHeaderFormatter,
30021        monthFormatter: this.monthFormatter || defaultMonthFormatter,
30022        weekNumberFormatter: this.weekNumberFormatter || defaultWeekNumberFormatter,
30023        longDateFormatter: this.longDateFormatter || defaultLongDateFormatter,
30024        msgCalendar: this.msgCalendar || defaultMsgCalendar,
30025        msgOpenCalendar: this.msgOpenCalendar || defaultMsgOpenCalendar,
30026        firstRenderableDate: this.firstRenderableDate || defaultFirstRenderableDate,
30027        lastRenderableDate: this.lastRenderableDate || defaultLastRendereableDate
30028      };
30029
30030      return service;
30031    };
30032    DateLocaleProvider.prototype.$get.$inject = ["$locale", "$filter"];
30033
30034    $provide.provider('$mdDateLocale', new DateLocaleProvider());
30035  }]);
30036})();
30037
30038})();
30039(function(){
30040"use strict";
30041
30042(function() {
30043  'use strict';
30044
30045  /**
30046   * Utility for performing date calculations to facilitate operation of the calendar and
30047   * datepicker.
30048   */
30049  angular.module('material.components.datepicker').factory('$$mdDateUtil', function() {
30050    return {
30051      getFirstDateOfMonth: getFirstDateOfMonth,
30052      getNumberOfDaysInMonth: getNumberOfDaysInMonth,
30053      getDateInNextMonth: getDateInNextMonth,
30054      getDateInPreviousMonth: getDateInPreviousMonth,
30055      isInNextMonth: isInNextMonth,
30056      isInPreviousMonth: isInPreviousMonth,
30057      getDateMidpoint: getDateMidpoint,
30058      isSameMonthAndYear: isSameMonthAndYear,
30059      getWeekOfMonth: getWeekOfMonth,
30060      incrementDays: incrementDays,
30061      incrementMonths: incrementMonths,
30062      getLastDateOfMonth: getLastDateOfMonth,
30063      isSameDay: isSameDay,
30064      getMonthDistance: getMonthDistance,
30065      isValidDate: isValidDate,
30066      setDateTimeToMidnight: setDateTimeToMidnight,
30067      createDateAtMidnight: createDateAtMidnight,
30068      isDateWithinRange: isDateWithinRange,
30069      incrementYears: incrementYears,
30070      getYearDistance: getYearDistance,
30071      clampDate: clampDate,
30072      getTimestampFromNode: getTimestampFromNode,
30073      isMonthWithinRange: isMonthWithinRange
30074    };
30075
30076    /**
30077     * Gets the first day of the month for the given date's month.
30078     * @param {Date} date
30079     * @returns {Date}
30080     */
30081    function getFirstDateOfMonth(date) {
30082      return new Date(date.getFullYear(), date.getMonth(), 1);
30083    }
30084
30085    /**
30086     * Gets the number of days in the month for the given date's month.
30087     * @param date
30088     * @returns {number}
30089     */
30090    function getNumberOfDaysInMonth(date) {
30091      return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
30092    }
30093
30094    /**
30095     * Get an arbitrary date in the month after the given date's month.
30096     * @param date
30097     * @returns {Date}
30098     */
30099    function getDateInNextMonth(date) {
30100      return new Date(date.getFullYear(), date.getMonth() + 1, 1);
30101    }
30102
30103    /**
30104     * Get an arbitrary date in the month before the given date's month.
30105     * @param date
30106     * @returns {Date}
30107     */
30108    function getDateInPreviousMonth(date) {
30109      return new Date(date.getFullYear(), date.getMonth() - 1, 1);
30110    }
30111
30112    /**
30113     * Gets whether two dates have the same month and year.
30114     * @param {Date} d1
30115     * @param {Date} d2
30116     * @returns {boolean}
30117     */
30118    function isSameMonthAndYear(d1, d2) {
30119      return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth();
30120    }
30121
30122    /**
30123     * Gets whether two dates are the same day (not not necesarily the same time).
30124     * @param {Date} d1
30125     * @param {Date} d2
30126     * @returns {boolean}
30127     */
30128    function isSameDay(d1, d2) {
30129      return d1.getDate() == d2.getDate() && isSameMonthAndYear(d1, d2);
30130    }
30131
30132    /**
30133     * Gets whether a date is in the month immediately after some date.
30134     * @param {Date} startDate The date from which to compare.
30135     * @param {Date} endDate The date to check.
30136     * @returns {boolean}
30137     */
30138    function isInNextMonth(startDate, endDate) {
30139      var nextMonth = getDateInNextMonth(startDate);
30140      return isSameMonthAndYear(nextMonth, endDate);
30141    }
30142
30143    /**
30144     * Gets whether a date is in the month immediately before some date.
30145     * @param {Date} startDate The date from which to compare.
30146     * @param {Date} endDate The date to check.
30147     * @returns {boolean}
30148     */
30149    function isInPreviousMonth(startDate, endDate) {
30150      var previousMonth = getDateInPreviousMonth(startDate);
30151      return isSameMonthAndYear(endDate, previousMonth);
30152    }
30153
30154    /**
30155     * Gets the midpoint between two dates.
30156     * @param {Date} d1
30157     * @param {Date} d2
30158     * @returns {Date}
30159     */
30160    function getDateMidpoint(d1, d2) {
30161      return createDateAtMidnight((d1.getTime() + d2.getTime()) / 2);
30162    }
30163
30164    /**
30165     * Gets the week of the month that a given date occurs in.
30166     * @param {Date} date
30167     * @returns {number} Index of the week of the month (zero-based).
30168     */
30169    function getWeekOfMonth(date) {
30170      var firstDayOfMonth = getFirstDateOfMonth(date);
30171      return Math.floor((firstDayOfMonth.getDay() + date.getDate() - 1) / 7);
30172    }
30173
30174    /**
30175     * Gets a new date incremented by the given number of days. Number of days can be negative.
30176     * @param {Date} date
30177     * @param {number} numberOfDays
30178     * @returns {Date}
30179     */
30180    function incrementDays(date, numberOfDays) {
30181      return new Date(date.getFullYear(), date.getMonth(), date.getDate() + numberOfDays);
30182    }
30183
30184    /**
30185     * Gets a new date incremented by the given number of months. Number of months can be negative.
30186     * If the date of the given month does not match the target month, the date will be set to the
30187     * last day of the month.
30188     * @param {Date} date
30189     * @param {number} numberOfMonths
30190     * @returns {Date}
30191     */
30192    function incrementMonths(date, numberOfMonths) {
30193      // If the same date in the target month does not actually exist, the Date object will
30194      // automatically advance *another* month by the number of missing days.
30195      // For example, if you try to go from Jan. 30 to Feb. 30, you'll end up on March 2.
30196      // So, we check if the month overflowed and go to the last day of the target month instead.
30197      var dateInTargetMonth = new Date(date.getFullYear(), date.getMonth() + numberOfMonths, 1);
30198      var numberOfDaysInMonth = getNumberOfDaysInMonth(dateInTargetMonth);
30199      if (numberOfDaysInMonth < date.getDate()) {
30200        dateInTargetMonth.setDate(numberOfDaysInMonth);
30201      } else {
30202        dateInTargetMonth.setDate(date.getDate());
30203      }
30204
30205      return dateInTargetMonth;
30206    }
30207
30208    /**
30209     * Get the integer distance between two months. This *only* considers the month and year
30210     * portion of the Date instances.
30211     *
30212     * @param {Date} start
30213     * @param {Date} end
30214     * @returns {number} Number of months between `start` and `end`. If `end` is before `start`
30215     *     chronologically, this number will be negative.
30216     */
30217    function getMonthDistance(start, end) {
30218      return (12 * (end.getFullYear() - start.getFullYear())) + (end.getMonth() - start.getMonth());
30219    }
30220
30221    /**
30222     * Gets the last day of the month for the given date.
30223     * @param {Date} date
30224     * @returns {Date}
30225     */
30226    function getLastDateOfMonth(date) {
30227      return new Date(date.getFullYear(), date.getMonth(), getNumberOfDaysInMonth(date));
30228    }
30229
30230    /**
30231     * Checks whether a date is valid.
30232     * @param {Date} date
30233     * @return {boolean} Whether the date is a valid Date.
30234     */
30235    function isValidDate(date) {
30236      return date && date.getTime && !isNaN(date.getTime());
30237    }
30238
30239    /**
30240     * Sets a date's time to midnight.
30241     * @param {Date} date
30242     */
30243    function setDateTimeToMidnight(date) {
30244      if (isValidDate(date)) {
30245        date.setHours(0, 0, 0, 0);
30246      }
30247    }
30248
30249    /**
30250     * Creates a date with the time set to midnight.
30251     * Drop-in replacement for two forms of the Date constructor:
30252     * 1. No argument for Date representing now.
30253     * 2. Single-argument value representing number of seconds since Unix Epoch
30254     * or a Date object.
30255     * @param {number|Date=} opt_value
30256     * @return {Date} New date with time set to midnight.
30257     */
30258    function createDateAtMidnight(opt_value) {
30259      var date;
30260      if (angular.isUndefined(opt_value)) {
30261        date = new Date();
30262      } else {
30263        date = new Date(opt_value);
30264      }
30265      setDateTimeToMidnight(date);
30266      return date;
30267    }
30268
30269     /**
30270      * Checks if a date is within a min and max range, ignoring the time component.
30271      * If minDate or maxDate are not dates, they are ignored.
30272      * @param {Date} date
30273      * @param {Date} minDate
30274      * @param {Date} maxDate
30275      */
30276     function isDateWithinRange(date, minDate, maxDate) {
30277       var dateAtMidnight = createDateAtMidnight(date);
30278       var minDateAtMidnight = isValidDate(minDate) ? createDateAtMidnight(minDate) : null;
30279       var maxDateAtMidnight = isValidDate(maxDate) ? createDateAtMidnight(maxDate) : null;
30280       return (!minDateAtMidnight || minDateAtMidnight <= dateAtMidnight) &&
30281           (!maxDateAtMidnight || maxDateAtMidnight >= dateAtMidnight);
30282     }
30283
30284    /**
30285     * Gets a new date incremented by the given number of years. Number of years can be negative.
30286     * See `incrementMonths` for notes on overflow for specific dates.
30287     * @param {Date} date
30288     * @param {number} numberOfYears
30289     * @returns {Date}
30290     */
30291     function incrementYears(date, numberOfYears) {
30292       return incrementMonths(date, numberOfYears * 12);
30293     }
30294
30295     /**
30296      * Get the integer distance between two years. This *only* considers the year portion of the
30297      * Date instances.
30298      *
30299      * @param {Date} start
30300      * @param {Date} end
30301      * @returns {number} Number of months between `start` and `end`. If `end` is before `start`
30302      *     chronologically, this number will be negative.
30303      */
30304     function getYearDistance(start, end) {
30305       return end.getFullYear() - start.getFullYear();
30306     }
30307
30308     /**
30309      * Clamps a date between a minimum and a maximum date.
30310      * @param {Date} date Date to be clamped
30311      * @param {Date=} minDate Minimum date
30312      * @param {Date=} maxDate Maximum date
30313      * @return {Date}
30314      */
30315     function clampDate(date, minDate, maxDate) {
30316       var boundDate = date;
30317       if (minDate && date < minDate) {
30318         boundDate = new Date(minDate.getTime());
30319       }
30320       if (maxDate && date > maxDate) {
30321         boundDate = new Date(maxDate.getTime());
30322       }
30323       return boundDate;
30324     }
30325
30326     /**
30327      * Extracts and parses the timestamp from a DOM node.
30328      * @param  {HTMLElement} node Node from which the timestamp will be extracted.
30329      * @return {number} Time since epoch.
30330      */
30331     function getTimestampFromNode(node) {
30332       if (node && node.hasAttribute('data-timestamp')) {
30333         return Number(node.getAttribute('data-timestamp'));
30334       }
30335     }
30336
30337     /**
30338      * Checks if a month is within a min and max range, ignoring the date and time components.
30339      * If minDate or maxDate are not dates, they are ignored.
30340      * @param {Date} date
30341      * @param {Date} minDate
30342      * @param {Date} maxDate
30343      */
30344     function isMonthWithinRange(date, minDate, maxDate) {
30345       var month = date.getMonth();
30346       var year = date.getFullYear();
30347
30348       return (!minDate || minDate.getFullYear() < year || minDate.getMonth() <= month) &&
30349        (!maxDate || maxDate.getFullYear() > year || maxDate.getMonth() >= month);
30350     }
30351  });
30352})();
30353
30354})();
30355(function(){
30356"use strict";
30357
30358(function() {
30359  'use strict';
30360
30361  // POST RELEASE
30362  // TODO(jelbourn): Demo that uses moment.js
30363  // TODO(jelbourn): make sure this plays well with validation and ngMessages.
30364  // TODO(jelbourn): calendar pane doesn't open up outside of visible viewport.
30365  // TODO(jelbourn): forward more attributes to the internal input (required, autofocus, etc.)
30366  // TODO(jelbourn): something better for mobile (calendar panel takes up entire screen?)
30367  // TODO(jelbourn): input behavior (masking? auto-complete?)
30368
30369
30370  DatePickerCtrl.$inject = ["$scope", "$element", "$attrs", "$window", "$mdConstant", "$mdTheming", "$mdUtil", "$mdDateLocale", "$$mdDateUtil", "$$rAF", "$filter"];
30371  datePickerDirective.$inject = ["$$mdSvgRegistry", "$mdUtil", "$mdAria", "inputDirective"];
30372  angular.module('material.components.datepicker')
30373      .directive('mdDatepicker', datePickerDirective);
30374
30375  /**
30376   * @ngdoc directive
30377   * @name mdDatepicker
30378   * @module material.components.datepicker
30379   *
30380   * @param {Date} ng-model The component's model. Expects a JavaScript Date object.
30381   * @param {Object=} ng-model-options Allows tuning of the way in which `ng-model` is being updated. Also allows
30382   * for a timezone to be specified. <a href="https://docs.angularjs.org/api/ng/directive/ngModelOptions#usage">Read more at the ngModelOptions docs.</a>
30383   * @param {expression=} ng-change Expression evaluated when the model value changes.
30384   * @param {expression=} ng-focus Expression evaluated when the input is focused or the calendar is opened.
30385   * @param {expression=} ng-blur Expression evaluated when focus is removed from the input or the calendar is closed.
30386   * @param {boolean=} ng-disabled Whether the datepicker is disabled.
30387   * @param {boolean=} ng-required Whether a value is required for the datepicker.
30388   * @param {Date=} md-min-date Expression representing a min date (inclusive).
30389   * @param {Date=} md-max-date Expression representing a max date (inclusive).
30390   * @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not.
30391   * @param {String=} md-placeholder The date input placeholder value.
30392   * @param {String=} md-open-on-focus When present, the calendar will be opened when the input is focused.
30393   * @param {Boolean=} md-is-open Expression that can be used to open the datepicker's calendar on-demand.
30394   * @param {String=} md-current-view Default open view of the calendar pane. Can be either "month" or "year".
30395   * @param {String=} md-hide-icons Determines which datepicker icons should be hidden. Note that this may cause the
30396   * datepicker to not align properly with other components. **Use at your own risk.** Possible values are:
30397   * * `"all"` - Hides all icons.
30398   * * `"calendar"` - Only hides the calendar icon.
30399   * * `"triangle"` - Only hides the triangle icon.
30400   * @param {Object=} md-date-locale Allows for the values from the `$mdDateLocaleProvider` to be
30401   * ovewritten on a per-element basis (e.g. `msgOpenCalendar` can be overwritten with
30402   * `md-date-locale="{ msgOpenCalendar: 'Open a special calendar' }"`).
30403   *
30404   * @description
30405   * `<md-datepicker>` is a component used to select a single date.
30406   * For information on how to configure internationalization for the date picker,
30407   * see `$mdDateLocaleProvider`.
30408   *
30409   * This component supports [ngMessages](https://docs.angularjs.org/api/ngMessages/directive/ngMessages).
30410   * Supported attributes are:
30411   * * `required`: whether a required date is not set.
30412   * * `mindate`: whether the selected date is before the minimum allowed date.
30413   * * `maxdate`: whether the selected date is after the maximum allowed date.
30414   * * `debounceInterval`: ms to delay input processing (since last debounce reset); default value 500ms
30415   *
30416   * @usage
30417   * <hljs lang="html">
30418   *   <md-datepicker ng-model="birthday"></md-datepicker>
30419   * </hljs>
30420   *
30421   */
30422
30423  function datePickerDirective($$mdSvgRegistry, $mdUtil, $mdAria, inputDirective) {
30424    return {
30425      template: function(tElement, tAttrs) {
30426        // Buttons are not in the tab order because users can open the calendar via keyboard
30427        // interaction on the text input, and multiple tab stops for one component (picker)
30428        // may be confusing.
30429        var hiddenIcons = tAttrs.mdHideIcons;
30430        var ariaLabelValue = tAttrs.ariaLabel || tAttrs.mdPlaceholder;
30431
30432        var calendarButton = (hiddenIcons === 'all' || hiddenIcons === 'calendar') ? '' :
30433          '<md-button class="md-datepicker-button md-icon-button" type="button" ' +
30434              'tabindex="-1" aria-hidden="true" ' +
30435              'ng-click="ctrl.openCalendarPane($event)">' +
30436            '<md-icon class="md-datepicker-calendar-icon" aria-label="md-calendar" ' +
30437                     'md-svg-src="' + $$mdSvgRegistry.mdCalendar + '"></md-icon>' +
30438          '</md-button>';
30439
30440        var triangleButton = '';
30441
30442        if (hiddenIcons !== 'all' && hiddenIcons !== 'triangle') {
30443          triangleButton = '' +
30444            '<md-button type="button" md-no-ink ' +
30445              'class="md-datepicker-triangle-button md-icon-button" ' +
30446              'ng-click="ctrl.openCalendarPane($event)" ' +
30447              'aria-label="{{::ctrl.locale.msgOpenCalendar}}">' +
30448            '<div class="md-datepicker-expand-triangle"></div>' +
30449          '</md-button>';
30450
30451          tElement.addClass(HAS_TRIANGLE_ICON_CLASS);
30452        }
30453
30454        return calendarButton +
30455        '<div class="md-datepicker-input-container" ng-class="{\'md-datepicker-focused\': ctrl.isFocused}">' +
30456          '<input ' +
30457            (ariaLabelValue ? 'aria-label="' + ariaLabelValue + '" ' : '') +
30458            'class="md-datepicker-input" ' +
30459            'aria-haspopup="true" ' +
30460            'aria-expanded="{{ctrl.isCalendarOpen}}" ' +
30461            'ng-focus="ctrl.setFocused(true)" ' +
30462            'ng-blur="ctrl.setFocused(false)"> ' +
30463            triangleButton +
30464        '</div>' +
30465
30466        // This pane will be detached from here and re-attached to the document body.
30467        '<div class="md-datepicker-calendar-pane md-whiteframe-z1" id="{{::ctrl.calendarPaneId}}">' +
30468          '<div class="md-datepicker-input-mask">' +
30469            '<div class="md-datepicker-input-mask-opaque"></div>' +
30470          '</div>' +
30471          '<div class="md-datepicker-calendar">' +
30472            '<md-calendar role="dialog" aria-label="{{::ctrl.locale.msgCalendar}}" ' +
30473                'md-current-view="{{::ctrl.currentView}}"' +
30474                'md-min-date="ctrl.minDate"' +
30475                'md-max-date="ctrl.maxDate"' +
30476                'md-date-filter="ctrl.dateFilter"' +
30477                'ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen">' +
30478            '</md-calendar>' +
30479          '</div>' +
30480        '</div>';
30481      },
30482      require: ['ngModel', 'mdDatepicker', '?^mdInputContainer', '?^form'],
30483      scope: {
30484        minDate: '=mdMinDate',
30485        maxDate: '=mdMaxDate',
30486        placeholder: '@mdPlaceholder',
30487        currentView: '@mdCurrentView',
30488        dateFilter: '=mdDateFilter',
30489        isOpen: '=?mdIsOpen',
30490        debounceInterval: '=mdDebounceInterval',
30491        dateLocale: '=mdDateLocale'
30492      },
30493      controller: DatePickerCtrl,
30494      controllerAs: 'ctrl',
30495      bindToController: true,
30496      link: function(scope, element, attr, controllers) {
30497        var ngModelCtrl = controllers[0];
30498        var mdDatePickerCtrl = controllers[1];
30499        var mdInputContainer = controllers[2];
30500        var parentForm = controllers[3];
30501        var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
30502
30503        mdDatePickerCtrl.configureNgModel(ngModelCtrl, mdInputContainer, inputDirective);
30504
30505        if (mdInputContainer) {
30506          // We need to move the spacer after the datepicker itself,
30507          // because md-input-container adds it after the
30508          // md-datepicker-input by default. The spacer gets wrapped in a
30509          // div, because it floats and gets aligned next to the datepicker.
30510          // There are easier ways of working around this with CSS (making the
30511          // datepicker 100% wide, change the `display` etc.), however they
30512          // break the alignment with any other form controls.
30513          var spacer = element[0].querySelector('.md-errors-spacer');
30514
30515          if (spacer) {
30516            element.after(angular.element('<div>').append(spacer));
30517          }
30518
30519          mdInputContainer.setHasPlaceholder(attr.mdPlaceholder);
30520          mdInputContainer.input = element;
30521          mdInputContainer.element
30522            .addClass(INPUT_CONTAINER_CLASS)
30523            .toggleClass(HAS_CALENDAR_ICON_CLASS, attr.mdHideIcons !== 'calendar' && attr.mdHideIcons !== 'all');
30524
30525          if (!mdInputContainer.label) {
30526            $mdAria.expect(element, 'aria-label', attr.mdPlaceholder);
30527          } else if(!mdNoAsterisk) {
30528            attr.$observe('required', function(value) {
30529              mdInputContainer.label.toggleClass('md-required', !!value);
30530            });
30531          }
30532
30533          scope.$watch(mdInputContainer.isErrorGetter || function() {
30534            return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
30535          }, mdInputContainer.setInvalid);
30536        } else if (parentForm) {
30537          // If invalid, highlights the input when the parent form is submitted.
30538          var parentSubmittedWatcher = scope.$watch(function() {
30539            return parentForm.$submitted;
30540          }, function(isSubmitted) {
30541            if (isSubmitted) {
30542              mdDatePickerCtrl.updateErrorState();
30543              parentSubmittedWatcher();
30544            }
30545          });
30546        }
30547      }
30548    };
30549  }
30550
30551  /** Additional offset for the input's `size` attribute, which is updated based on its content. */
30552  var EXTRA_INPUT_SIZE = 3;
30553
30554  /** Class applied to the container if the date is invalid. */
30555  var INVALID_CLASS = 'md-datepicker-invalid';
30556
30557  /** Class applied to the datepicker when it's open. */
30558  var OPEN_CLASS = 'md-datepicker-open';
30559
30560  /** Class applied to the md-input-container, if a datepicker is placed inside it */
30561  var INPUT_CONTAINER_CLASS = '_md-datepicker-floating-label';
30562
30563  /** Class to be applied when the calendar icon is enabled. */
30564  var HAS_CALENDAR_ICON_CLASS = '_md-datepicker-has-calendar-icon';
30565
30566  /** Class to be applied when the triangle icon is enabled. */
30567  var HAS_TRIANGLE_ICON_CLASS = '_md-datepicker-has-triangle-icon';
30568
30569  /** Default time in ms to debounce input event by. */
30570  var DEFAULT_DEBOUNCE_INTERVAL = 500;
30571
30572  /**
30573   * Height of the calendar pane used to check if the pane is going outside the boundary of
30574   * the viewport. See calendar.scss for how $md-calendar-height is computed; an extra 20px is
30575   * also added to space the pane away from the exact edge of the screen.
30576   *
30577   *  This is computed statically now, but can be changed to be measured if the circumstances
30578   *  of calendar sizing are changed.
30579   */
30580  var CALENDAR_PANE_HEIGHT = 368;
30581
30582  /**
30583   * Width of the calendar pane used to check if the pane is going outside the boundary of
30584   * the viewport. See calendar.scss for how $md-calendar-width is computed; an extra 20px is
30585   * also added to space the pane away from the exact edge of the screen.
30586   *
30587   *  This is computed statically now, but can be changed to be measured if the circumstances
30588   *  of calendar sizing are changed.
30589   */
30590  var CALENDAR_PANE_WIDTH = 360;
30591
30592  /** Used for checking whether the current user agent is on iOS or Android. */
30593  var IS_MOBILE_REGEX = /ipad|iphone|ipod|android/i;
30594
30595  /**
30596   * Controller for md-datepicker.
30597   *
30598   * @ngInject @constructor
30599   */
30600  function DatePickerCtrl($scope, $element, $attrs, $window, $mdConstant,
30601    $mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF, $filter) {
30602
30603    /** @final */
30604    this.$window = $window;
30605
30606    /** @final */
30607    this.dateUtil = $$mdDateUtil;
30608
30609    /** @final */
30610    this.$mdConstant = $mdConstant;
30611
30612    /* @final */
30613    this.$mdUtil = $mdUtil;
30614
30615    /** @final */
30616    this.$$rAF = $$rAF;
30617
30618    /** @final */
30619    this.$mdDateLocale = $mdDateLocale;
30620
30621    /**
30622     * The root document element. This is used for attaching a top-level click handler to
30623     * close the calendar panel when a click outside said panel occurs. We use `documentElement`
30624     * instead of body because, when scrolling is disabled, some browsers consider the body element
30625     * to be completely off the screen and propagate events directly to the html element.
30626     * @type {!angular.JQLite}
30627     */
30628    this.documentElement = angular.element(document.documentElement);
30629
30630    /** @type {!angular.NgModelController} */
30631    this.ngModelCtrl = null;
30632
30633    /** @type {HTMLInputElement} */
30634    this.inputElement = $element[0].querySelector('input');
30635
30636    /** @final {!angular.JQLite} */
30637    this.ngInputElement = angular.element(this.inputElement);
30638
30639    /** @type {HTMLElement} */
30640    this.inputContainer = $element[0].querySelector('.md-datepicker-input-container');
30641
30642    /** @type {HTMLElement} Floating calendar pane. */
30643    this.calendarPane = $element[0].querySelector('.md-datepicker-calendar-pane');
30644
30645    /** @type {HTMLElement} Calendar icon button. */
30646    this.calendarButton = $element[0].querySelector('.md-datepicker-button');
30647
30648    /**
30649     * Element covering everything but the input in the top of the floating calendar pane.
30650     * @type {!angular.JQLite}
30651     */
30652    this.inputMask = angular.element($element[0].querySelector('.md-datepicker-input-mask-opaque'));
30653
30654    /** @final {!angular.JQLite} */
30655    this.$element = $element;
30656
30657    /** @final {!angular.Attributes} */
30658    this.$attrs = $attrs;
30659
30660    /** @final {!angular.Scope} */
30661    this.$scope = $scope;
30662
30663    /** @type {Date} */
30664    this.date = null;
30665
30666    /** @type {boolean} */
30667    this.isFocused = false;
30668
30669    /** @type {boolean} */
30670    this.isDisabled;
30671    this.setDisabled($element[0].disabled || angular.isString($attrs.disabled));
30672
30673    /** @type {boolean} Whether the date-picker's calendar pane is open. */
30674    this.isCalendarOpen = false;
30675
30676    /** @type {boolean} Whether the calendar should open when the input is focused. */
30677    this.openOnFocus = $attrs.hasOwnProperty('mdOpenOnFocus');
30678
30679    /** @final */
30680    this.mdInputContainer = null;
30681
30682    /**
30683     * Element from which the calendar pane was opened. Keep track of this so that we can return
30684     * focus to it when the pane is closed.
30685     * @type {HTMLElement}
30686     */
30687    this.calendarPaneOpenedFrom = null;
30688
30689    /** @type {String} Unique id for the calendar pane. */
30690    this.calendarPaneId = 'md-date-pane-' + $mdUtil.nextUid();
30691
30692    /** Pre-bound click handler is saved so that the event listener can be removed. */
30693    this.bodyClickHandler = angular.bind(this, this.handleBodyClick);
30694
30695    /**
30696     * Name of the event that will trigger a close. Necessary to sniff the browser, because
30697     * the resize event doesn't make sense on mobile and can have a negative impact since it
30698     * triggers whenever the browser zooms in on a focused input.
30699     */
30700    this.windowEventName = IS_MOBILE_REGEX.test(
30701      navigator.userAgent || navigator.vendor || window.opera
30702    ) ? 'orientationchange' : 'resize';
30703
30704    /** Pre-bound close handler so that the event listener can be removed. */
30705    this.windowEventHandler = $mdUtil.debounce(angular.bind(this, this.closeCalendarPane), 100);
30706
30707    /** Pre-bound handler for the window blur event. Allows for it to be removed later. */
30708    this.windowBlurHandler = angular.bind(this, this.handleWindowBlur);
30709
30710    /** The built-in Angular date filter. */
30711    this.ngDateFilter = $filter('date');
30712
30713    /** @type {Number} Extra margin for the left side of the floating calendar pane. */
30714    this.leftMargin = 20;
30715
30716    /** @type {Number} Extra margin for the top of the floating calendar. Gets determined on the first open. */
30717    this.topMargin = null;
30718
30719    // Unless the user specifies so, the datepicker should not be a tab stop.
30720    // This is necessary because ngAria might add a tabindex to anything with an ng-model
30721    // (based on whether or not the user has turned that particular feature on/off).
30722    if ($attrs.tabindex) {
30723      this.ngInputElement.attr('tabindex', $attrs.tabindex);
30724      $attrs.$set('tabindex', null);
30725    } else {
30726      $attrs.$set('tabindex', '-1');
30727    }
30728
30729    $attrs.$set('aria-owns', this.calendarPaneId);
30730
30731    $mdTheming($element);
30732    $mdTheming(angular.element(this.calendarPane));
30733
30734    var self = this;
30735
30736    $scope.$on('$destroy', function() {
30737      self.detachCalendarPane();
30738    });
30739
30740    if ($attrs.mdIsOpen) {
30741      $scope.$watch('ctrl.isOpen', function(shouldBeOpen) {
30742        if (shouldBeOpen) {
30743          self.openCalendarPane({
30744            target: self.inputElement
30745          });
30746        } else {
30747          self.closeCalendarPane();
30748        }
30749      });
30750    }
30751
30752    // For Angular 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
30753    // manually call the $onInit hook.
30754    if (angular.version.major === 1 && angular.version.minor <= 4) {
30755      this.$onInit();
30756    }
30757
30758  }
30759
30760  /**
30761   * Angular Lifecycle hook for newer Angular versions.
30762   * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
30763   */
30764  DatePickerCtrl.prototype.$onInit = function() {
30765
30766    /**
30767     * Holds locale-specific formatters, parsers, labels etc. Allows
30768     * the user to override specific ones from the $mdDateLocale provider.
30769     * @type {!Object}
30770     */
30771    this.locale = this.dateLocale ? angular.extend({}, this.$mdDateLocale, this.dateLocale) : this.$mdDateLocale;
30772
30773    this.installPropertyInterceptors();
30774    this.attachChangeListeners();
30775    this.attachInteractionListeners();
30776  };
30777
30778  /**
30779   * Sets up the controller's reference to ngModelController and
30780   * applies Angular's `input[type="date"]` directive.
30781   * @param {!angular.NgModelController} ngModelCtrl Instance of the ngModel controller.
30782   * @param {Object} mdInputContainer Instance of the mdInputContainer controller.
30783   * @param {Object} inputDirective Config for Angular's `input` directive.
30784   */
30785  DatePickerCtrl.prototype.configureNgModel = function(ngModelCtrl, mdInputContainer, inputDirective) {
30786    this.ngModelCtrl = ngModelCtrl;
30787    this.mdInputContainer = mdInputContainer;
30788
30789    // The input needs to be [type="date"] in order to be picked up by Angular.
30790    this.$attrs.$set('type', 'date');
30791
30792    // Invoke the `input` directive link function, adding a stub for the element.
30793    // This allows us to re-use Angular's logic for setting the timezone via ng-model-options.
30794    // It works by calling the link function directly which then adds the proper `$parsers` and
30795    // `$formatters` to the ngModel controller.
30796    inputDirective[0].link.pre(this.$scope, {
30797      on: angular.noop,
30798      val: angular.noop,
30799      0: {}
30800    }, this.$attrs, [ngModelCtrl]);
30801
30802    var self = this;
30803
30804    // Responds to external changes to the model value.
30805    self.ngModelCtrl.$formatters.push(function(value) {
30806      if (value && !(value instanceof Date)) {
30807        throw Error('The ng-model for md-datepicker must be a Date instance. ' +
30808            'Currently the model is a: ' + (typeof value));
30809      }
30810
30811      self.onExternalChange(value);
30812
30813      return value;
30814    });
30815
30816    // Responds to external error state changes (e.g. ng-required based on another input).
30817    ngModelCtrl.$viewChangeListeners.unshift(angular.bind(this, this.updateErrorState));
30818
30819    // Forwards any events from the input to the root element. This is necessary to get `updateOn`
30820    // working for events that don't bubble (e.g. 'blur') since Angular binds the handlers to
30821    // the `<md-datepicker>`.
30822    var updateOn = self.$mdUtil.getModelOption(ngModelCtrl, 'updateOn');
30823
30824    if (updateOn) {
30825      this.ngInputElement.on(
30826        updateOn,
30827        angular.bind(this.$element, this.$element.triggerHandler, updateOn)
30828      );
30829    }
30830  };
30831
30832  /**
30833   * Attach event listeners for both the text input and the md-calendar.
30834   * Events are used instead of ng-model so that updates don't infinitely update the other
30835   * on a change. This should also be more performant than using a $watch.
30836   */
30837  DatePickerCtrl.prototype.attachChangeListeners = function() {
30838    var self = this;
30839
30840    self.$scope.$on('md-calendar-change', function(event, date) {
30841      self.setModelValue(date);
30842      self.onExternalChange(date);
30843      self.closeCalendarPane();
30844    });
30845
30846    self.ngInputElement.on('input', angular.bind(self, self.resizeInputElement));
30847
30848    var debounceInterval = angular.isDefined(this.debounceInterval) ?
30849        this.debounceInterval : DEFAULT_DEBOUNCE_INTERVAL;
30850    self.ngInputElement.on('input', self.$mdUtil.debounce(self.handleInputEvent,
30851        debounceInterval, self));
30852  };
30853
30854  /** Attach event listeners for user interaction. */
30855  DatePickerCtrl.prototype.attachInteractionListeners = function() {
30856    var self = this;
30857    var $scope = this.$scope;
30858    var keyCodes = this.$mdConstant.KEY_CODE;
30859
30860    // Add event listener through angular so that we can triggerHandler in unit tests.
30861    self.ngInputElement.on('keydown', function(event) {
30862      if (event.altKey && event.keyCode == keyCodes.DOWN_ARROW) {
30863        self.openCalendarPane(event);
30864        $scope.$digest();
30865      }
30866    });
30867
30868    if (self.openOnFocus) {
30869      self.ngInputElement.on('focus', angular.bind(self, self.openCalendarPane));
30870      angular.element(self.$window).on('blur', self.windowBlurHandler);
30871
30872      $scope.$on('$destroy', function() {
30873        angular.element(self.$window).off('blur', self.windowBlurHandler);
30874      });
30875    }
30876
30877    $scope.$on('md-calendar-close', function() {
30878      self.closeCalendarPane();
30879    });
30880  };
30881
30882  /**
30883   * Capture properties set to the date-picker and imperitively handle internal changes.
30884   * This is done to avoid setting up additional $watches.
30885   */
30886  DatePickerCtrl.prototype.installPropertyInterceptors = function() {
30887    var self = this;
30888
30889    if (this.$attrs.ngDisabled) {
30890      // The expression is to be evaluated against the directive element's scope and not
30891      // the directive's isolate scope.
30892      var scope = this.$scope.$parent;
30893
30894      if (scope) {
30895        scope.$watch(this.$attrs.ngDisabled, function(isDisabled) {
30896          self.setDisabled(isDisabled);
30897        });
30898      }
30899    }
30900
30901    Object.defineProperty(this, 'placeholder', {
30902      get: function() { return self.inputElement.placeholder; },
30903      set: function(value) { self.inputElement.placeholder = value || ''; }
30904    });
30905  };
30906
30907  /**
30908   * Sets whether the date-picker is disabled.
30909   * @param {boolean} isDisabled
30910   */
30911  DatePickerCtrl.prototype.setDisabled = function(isDisabled) {
30912    this.isDisabled = isDisabled;
30913    this.inputElement.disabled = isDisabled;
30914
30915    if (this.calendarButton) {
30916      this.calendarButton.disabled = isDisabled;
30917    }
30918  };
30919
30920  /**
30921   * Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are:
30922   *   - mindate: whether the selected date is before the minimum date.
30923   *   - maxdate: whether the selected flag is after the maximum date.
30924   *   - filtered: whether the selected date is allowed by the custom filtering function.
30925   *   - valid: whether the entered text input is a valid date
30926   *
30927   * The 'required' flag is handled automatically by ngModel.
30928   *
30929   * @param {Date=} opt_date Date to check. If not given, defaults to the datepicker's model value.
30930   */
30931  DatePickerCtrl.prototype.updateErrorState = function(opt_date) {
30932    var date = opt_date || this.date;
30933
30934    // Clear any existing errors to get rid of anything that's no longer relevant.
30935    this.clearErrorState();
30936
30937    if (this.dateUtil.isValidDate(date)) {
30938      // Force all dates to midnight in order to ignore the time portion.
30939      date = this.dateUtil.createDateAtMidnight(date);
30940
30941      if (this.dateUtil.isValidDate(this.minDate)) {
30942        var minDate = this.dateUtil.createDateAtMidnight(this.minDate);
30943        this.ngModelCtrl.$setValidity('mindate', date >= minDate);
30944      }
30945
30946      if (this.dateUtil.isValidDate(this.maxDate)) {
30947        var maxDate = this.dateUtil.createDateAtMidnight(this.maxDate);
30948        this.ngModelCtrl.$setValidity('maxdate', date <= maxDate);
30949      }
30950
30951      if (angular.isFunction(this.dateFilter)) {
30952        this.ngModelCtrl.$setValidity('filtered', this.dateFilter(date));
30953      }
30954    } else {
30955      // The date is seen as "not a valid date" if there is *something* set
30956      // (i.e.., not null or undefined), but that something isn't a valid date.
30957      this.ngModelCtrl.$setValidity('valid', date == null);
30958    }
30959
30960    angular.element(this.inputContainer).toggleClass(INVALID_CLASS, !this.ngModelCtrl.$valid);
30961  };
30962
30963  /** Clears any error flags set by `updateErrorState`. */
30964  DatePickerCtrl.prototype.clearErrorState = function() {
30965    this.inputContainer.classList.remove(INVALID_CLASS);
30966    ['mindate', 'maxdate', 'filtered', 'valid'].forEach(function(field) {
30967      this.ngModelCtrl.$setValidity(field, true);
30968    }, this);
30969  };
30970
30971  /** Resizes the input element based on the size of its content. */
30972  DatePickerCtrl.prototype.resizeInputElement = function() {
30973    this.inputElement.size = this.inputElement.value.length + EXTRA_INPUT_SIZE;
30974  };
30975
30976  /**
30977   * Sets the model value if the user input is a valid date.
30978   * Adds an invalid class to the input element if not.
30979   */
30980  DatePickerCtrl.prototype.handleInputEvent = function() {
30981    var inputString = this.inputElement.value;
30982    var parsedDate = inputString ? this.locale.parseDate(inputString) : null;
30983    this.dateUtil.setDateTimeToMidnight(parsedDate);
30984
30985    // An input string is valid if it is either empty (representing no date)
30986    // or if it parses to a valid date that the user is allowed to select.
30987    var isValidInput = inputString == '' || (
30988      this.dateUtil.isValidDate(parsedDate) &&
30989      this.locale.isDateComplete(inputString) &&
30990      this.isDateEnabled(parsedDate)
30991    );
30992
30993    // The datepicker's model is only updated when there is a valid input.
30994    if (isValidInput) {
30995      this.setModelValue(parsedDate);
30996      this.date = parsedDate;
30997    }
30998
30999    this.updateErrorState(parsedDate);
31000  };
31001
31002  /**
31003   * Check whether date is in range and enabled
31004   * @param {Date=} opt_date
31005   * @return {boolean} Whether the date is enabled.
31006   */
31007  DatePickerCtrl.prototype.isDateEnabled = function(opt_date) {
31008    return this.dateUtil.isDateWithinRange(opt_date, this.minDate, this.maxDate) &&
31009          (!angular.isFunction(this.dateFilter) || this.dateFilter(opt_date));
31010  };
31011
31012  /** Position and attach the floating calendar to the document. */
31013  DatePickerCtrl.prototype.attachCalendarPane = function() {
31014    var calendarPane = this.calendarPane;
31015    var body = document.body;
31016
31017    calendarPane.style.transform = '';
31018    this.$element.addClass(OPEN_CLASS);
31019    this.mdInputContainer && this.mdInputContainer.element.addClass(OPEN_CLASS);
31020    angular.element(body).addClass('md-datepicker-is-showing');
31021
31022    var elementRect = this.inputContainer.getBoundingClientRect();
31023    var bodyRect = body.getBoundingClientRect();
31024
31025    if (!this.topMargin || this.topMargin < 0) {
31026      this.topMargin = (this.inputMask.parent().prop('clientHeight') - this.ngInputElement.prop('clientHeight')) / 2;
31027    }
31028
31029    // Check to see if the calendar pane would go off the screen. If so, adjust position
31030    // accordingly to keep it within the viewport.
31031    var paneTop = elementRect.top - bodyRect.top - this.topMargin;
31032    var paneLeft = elementRect.left - bodyRect.left - this.leftMargin;
31033
31034    // If ng-material has disabled body scrolling (for example, if a dialog is open),
31035    // then it's possible that the already-scrolled body has a negative top/left. In this case,
31036    // we want to treat the "real" top as (0 - bodyRect.top). In a normal scrolling situation,
31037    // though, the top of the viewport should just be the body's scroll position.
31038    var viewportTop = (bodyRect.top < 0 && document.body.scrollTop == 0) ?
31039        -bodyRect.top :
31040        document.body.scrollTop;
31041
31042    var viewportLeft = (bodyRect.left < 0 && document.body.scrollLeft == 0) ?
31043        -bodyRect.left :
31044        document.body.scrollLeft;
31045
31046    var viewportBottom = viewportTop + this.$window.innerHeight;
31047    var viewportRight = viewportLeft + this.$window.innerWidth;
31048
31049    // Creates an overlay with a hole the same size as element. We remove a pixel or two
31050    // on each end to make it overlap slightly. The overlay's background is added in
31051    // the theme in the form of a box-shadow with a huge spread.
31052    this.inputMask.css({
31053      position: 'absolute',
31054      left: this.leftMargin + 'px',
31055      top: this.topMargin + 'px',
31056      width: (elementRect.width - 1) + 'px',
31057      height: (elementRect.height - 2) + 'px'
31058    });
31059
31060    // If the right edge of the pane would be off the screen and shifting it left by the
31061    // difference would not go past the left edge of the screen. If the calendar pane is too
31062    // big to fit on the screen at all, move it to the left of the screen and scale the entire
31063    // element down to fit.
31064    if (paneLeft + CALENDAR_PANE_WIDTH > viewportRight) {
31065      if (viewportRight - CALENDAR_PANE_WIDTH > 0) {
31066        paneLeft = viewportRight - CALENDAR_PANE_WIDTH;
31067      } else {
31068        paneLeft = viewportLeft;
31069        var scale = this.$window.innerWidth / CALENDAR_PANE_WIDTH;
31070        calendarPane.style.transform = 'scale(' + scale + ')';
31071      }
31072
31073      calendarPane.classList.add('md-datepicker-pos-adjusted');
31074    }
31075
31076    // If the bottom edge of the pane would be off the screen and shifting it up by the
31077    // difference would not go past the top edge of the screen.
31078    if (paneTop + CALENDAR_PANE_HEIGHT > viewportBottom &&
31079        viewportBottom - CALENDAR_PANE_HEIGHT > viewportTop) {
31080      paneTop = viewportBottom - CALENDAR_PANE_HEIGHT;
31081      calendarPane.classList.add('md-datepicker-pos-adjusted');
31082    }
31083
31084    calendarPane.style.left = paneLeft + 'px';
31085    calendarPane.style.top = paneTop + 'px';
31086    document.body.appendChild(calendarPane);
31087
31088    // Add CSS class after one frame to trigger open animation.
31089    this.$$rAF(function() {
31090      calendarPane.classList.add('md-pane-open');
31091    });
31092  };
31093
31094  /** Detach the floating calendar pane from the document. */
31095  DatePickerCtrl.prototype.detachCalendarPane = function() {
31096    this.$element.removeClass(OPEN_CLASS);
31097    this.mdInputContainer && this.mdInputContainer.element.removeClass(OPEN_CLASS);
31098    angular.element(document.body).removeClass('md-datepicker-is-showing');
31099    this.calendarPane.classList.remove('md-pane-open');
31100    this.calendarPane.classList.remove('md-datepicker-pos-adjusted');
31101
31102    if (this.isCalendarOpen) {
31103      this.$mdUtil.enableScrolling();
31104    }
31105
31106    if (this.calendarPane.parentNode) {
31107      // Use native DOM removal because we do not want any of the
31108      // angular state of this element to be disposed.
31109      this.calendarPane.parentNode.removeChild(this.calendarPane);
31110    }
31111  };
31112
31113  /**
31114   * Open the floating calendar pane.
31115   * @param {Event} event
31116   */
31117  DatePickerCtrl.prototype.openCalendarPane = function(event) {
31118    if (!this.isCalendarOpen && !this.isDisabled && !this.inputFocusedOnWindowBlur) {
31119      this.isCalendarOpen = this.isOpen = true;
31120      this.calendarPaneOpenedFrom = event.target;
31121
31122      // Because the calendar pane is attached directly to the body, it is possible that the
31123      // rest of the component (input, etc) is in a different scrolling container, such as
31124      // an md-content. This means that, if the container is scrolled, the pane would remain
31125      // stationary. To remedy this, we disable scrolling while the calendar pane is open, which
31126      // also matches the native behavior for things like `<select>` on Mac and Windows.
31127      this.$mdUtil.disableScrollAround(this.calendarPane);
31128
31129      this.attachCalendarPane();
31130      this.focusCalendar();
31131      this.evalAttr('ngFocus');
31132
31133      // Attach click listener inside of a timeout because, if this open call was triggered by a
31134      // click, we don't want it to be immediately propogated up to the body and handled.
31135      var self = this;
31136      this.$mdUtil.nextTick(function() {
31137        // Use 'touchstart` in addition to click in order to work on iOS Safari, where click
31138        // events aren't propogated under most circumstances.
31139        // See http://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
31140        self.documentElement.on('click touchstart', self.bodyClickHandler);
31141      }, false);
31142
31143      window.addEventListener(this.windowEventName, this.windowEventHandler);
31144    }
31145  };
31146
31147  /** Close the floating calendar pane. */
31148  DatePickerCtrl.prototype.closeCalendarPane = function() {
31149    if (this.isCalendarOpen) {
31150      var self = this;
31151
31152      self.detachCalendarPane();
31153      self.ngModelCtrl.$setTouched();
31154      self.evalAttr('ngBlur');
31155
31156      self.documentElement.off('click touchstart', self.bodyClickHandler);
31157      window.removeEventListener(self.windowEventName, self.windowEventHandler);
31158
31159      self.calendarPaneOpenedFrom.focus();
31160      self.calendarPaneOpenedFrom = null;
31161
31162      if (self.openOnFocus) {
31163        // Ensures that all focus events have fired before resetting
31164        // the calendar. Prevents the calendar from reopening immediately
31165        // in IE when md-open-on-focus is set. Also it needs to trigger
31166        // a digest, in order to prevent issues where the calendar wasn't
31167        // showing up on the next open.
31168        self.$mdUtil.nextTick(reset);
31169      } else {
31170        reset();
31171      }
31172    }
31173
31174    function reset(){
31175      self.isCalendarOpen = self.isOpen = false;
31176    }
31177  };
31178
31179  /** Gets the controller instance for the calendar in the floating pane. */
31180  DatePickerCtrl.prototype.getCalendarCtrl = function() {
31181    return angular.element(this.calendarPane.querySelector('md-calendar')).controller('mdCalendar');
31182  };
31183
31184  /** Focus the calendar in the floating pane. */
31185  DatePickerCtrl.prototype.focusCalendar = function() {
31186    // Use a timeout in order to allow the calendar to be rendered, as it is gated behind an ng-if.
31187    var self = this;
31188    this.$mdUtil.nextTick(function() {
31189      self.getCalendarCtrl().focus();
31190    }, false);
31191  };
31192
31193  /**
31194   * Sets whether the input is currently focused.
31195   * @param {boolean} isFocused
31196   */
31197  DatePickerCtrl.prototype.setFocused = function(isFocused) {
31198    if (!isFocused) {
31199      this.ngModelCtrl.$setTouched();
31200    }
31201
31202    // The ng* expressions shouldn't be evaluated when mdOpenOnFocus is on,
31203    // because they also get called when the calendar is opened/closed.
31204    if (!this.openOnFocus) {
31205      this.evalAttr(isFocused ? 'ngFocus' : 'ngBlur');
31206    }
31207
31208    this.isFocused = isFocused;
31209  };
31210
31211  /**
31212   * Handles a click on the document body when the floating calendar pane is open.
31213   * Closes the floating calendar pane if the click is not inside of it.
31214   * @param {MouseEvent} event
31215   */
31216  DatePickerCtrl.prototype.handleBodyClick = function(event) {
31217    if (this.isCalendarOpen) {
31218      var isInCalendar = this.$mdUtil.getClosest(event.target, 'md-calendar');
31219
31220      if (!isInCalendar) {
31221        this.closeCalendarPane();
31222      }
31223
31224      this.$scope.$digest();
31225    }
31226  };
31227
31228  /**
31229   * Handles the event when the user navigates away from the current tab. Keeps track of
31230   * whether the input was focused when the event happened, in order to prevent the calendar
31231   * from re-opening.
31232   */
31233  DatePickerCtrl.prototype.handleWindowBlur = function() {
31234    this.inputFocusedOnWindowBlur = document.activeElement === this.inputElement;
31235  };
31236
31237  /**
31238   * Evaluates an attribute expression against the parent scope.
31239   * @param {String} attr Name of the attribute to be evaluated.
31240   */
31241  DatePickerCtrl.prototype.evalAttr = function(attr) {
31242    if (this.$attrs[attr]) {
31243      this.$scope.$parent.$eval(this.$attrs[attr]);
31244    }
31245  };
31246
31247  /**
31248   * Sets the ng-model value by first converting the date object into a strng. Converting it
31249   * is necessary, in order to pass Angular's `input[type="date"]` validations. Angular turns
31250   * the value into a Date object afterwards, before setting it on the model.
31251   * @param {Date=} value Date to be set as the model value.
31252   */
31253  DatePickerCtrl.prototype.setModelValue = function(value) {
31254    var timezone = this.$mdUtil.getModelOption(this.ngModelCtrl, 'timezone');
31255    this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd', timezone));
31256  };
31257
31258  /**
31259   * Updates the datepicker when a model change occurred externally.
31260   * @param {Date=} value Value that was set to the model.
31261   */
31262  DatePickerCtrl.prototype.onExternalChange = function(value) {
31263    var timezone = this.$mdUtil.getModelOption(this.ngModelCtrl, 'timezone');
31264
31265    this.date = value;
31266    this.inputElement.value = this.locale.formatDate(value, timezone);
31267    this.mdInputContainer && this.mdInputContainer.setHasValue(!!value);
31268    this.resizeInputElement();
31269    this.updateErrorState();
31270  };
31271})();
31272
31273})();
31274(function(){
31275"use strict";
31276
31277angular
31278  .module('material.components.icon')
31279  .directive('mdIcon', ['$mdIcon', '$mdTheming', '$mdAria', '$sce', mdIconDirective]);
31280
31281/**
31282 * @ngdoc directive
31283 * @name mdIcon
31284 * @module material.components.icon
31285 *
31286 * @restrict E
31287 *
31288 * @description
31289 * The `md-icon` directive makes it easier to use vector-based icons in your app (as opposed to
31290 * raster-based icons types like PNG). The directive supports both icon fonts and SVG icons.
31291 *
31292 * Icons should be considered view-only elements that should not be used directly as buttons; instead nest a `<md-icon>`
31293 * inside a `md-button` to add hover and click features.
31294 *
31295 * ### Icon fonts
31296 * Icon fonts are a technique in which you use a font where the glyphs in the font are
31297 * your icons instead of text. Benefits include a straightforward way to bundle everything into a
31298 * single HTTP request, simple scaling, easy color changing, and more.
31299 *
31300 * `md-icon` lets you consume an icon font by letting you reference specific icons in that font
31301 * by name rather than character code.
31302 *
31303 * ### SVG
31304 * For SVGs, the problem with using `<img>` or a CSS `background-image` is that you can't take
31305 * advantage of some SVG features, such as styling specific parts of the icon with CSS or SVG
31306 * animation.
31307 *
31308 * `md-icon` makes it easier to use SVG icons by *inlining* the SVG into an `<svg>` element in the
31309 * document. The most straightforward way of referencing an SVG icon is via URL, just like a
31310 * traditional `<img>`. `$mdIconProvider`, as a convenience, lets you _name_ an icon so you can
31311 * reference it by name instead of URL throughout your templates.
31312 *
31313 * Additionally, you may not want to make separate HTTP requests for every icon, so you can bundle
31314 * your SVG icons together and pre-load them with $mdIconProvider as an icon set. An icon set can
31315 * also be given a name, which acts as a namespace for individual icons, so you can reference them
31316 * like `"social:cake"`.
31317 *
31318 * When using SVGs, both external SVGs (via URLs) or sets of SVGs [from icon sets] can be
31319 * easily loaded and used. When using font-icons, developers must follow three (3) simple steps:
31320 *
31321 * <ol>
31322 * <li>Load the font library. e.g.<br/>
31323 *    `<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
31324 *    rel="stylesheet">`
31325 * </li>
31326 * <li>
31327 *   Use either (a) font-icon class names or (b) a fontset and a font ligature to render the font glyph by
31328 *   using its textual name _or_ numerical character reference. Note that `material-icons` is the default fontset when
31329 *   none is specified.
31330 * </li>
31331 * <li> Use any of the following templates: <br/>
31332 *   <ul>
31333 *     <li>`<md-icon md-font-icon="classname"></md-icon>`</li>
31334 *     <li>`<md-icon md-font-set="font library classname or alias">textual_name</md-icon>`</li>
31335 *     <li>`<md-icon> numerical_character_reference </md-icon>`</li>
31336 *     <li>`<md-icon ng_bind="'textual_name'"></md-icon>`</li>
31337 *     <li>`<md-icon ng-bind="scopeVariable"></md-icon>`</li>
31338 *   </ul>
31339 * </li>
31340 * </ol>
31341 *
31342 * Full details for these steps can be found:
31343 *
31344 * <ul>
31345 * <li>http://google.github.io/material-design-icons/</li>
31346 * <li>http://google.github.io/material-design-icons/#icon-font-for-the-web</li>
31347 * </ul>
31348 *
31349 * The Material Design icon style <code>.material-icons</code> and the icon font references are published in
31350 * Material Design Icons:
31351 *
31352 * <ul>
31353 * <li>https://design.google.com/icons/</li>
31354 * <li>https://design.google.com/icons/#ic_accessibility</li>
31355 * </ul>
31356 *
31357 * ### Localization
31358 *
31359 * Because an `md-icon` element's text content is not intended to translated, it is recommended to declare the text
31360 * content for an `md-icon` element in its start tag. Instead of using the HTML text content, consider using `ng-bind`
31361 * with a scope variable or literal string.
31362 *
31363 * Examples:
31364 *
31365 * <ul>
31366 *   <li>`<md-icon ng-bind="myIconVariable"></md-icon>`</li>
31367 *   <li>`<md-icon ng-bind="'menu'"></md-icon>`
31368 * </ul>
31369 *
31370 * <h2 id="material_design_icons">Material Design Icons</h2>
31371 * Using the Material Design Icon-Selector, developers can easily and quickly search for a Material Design font-icon and
31372 * determine its textual name and character reference code. Click on any icon to see the slide-up information
31373 * panel with details regarding a SVG download or information on the font-icon usage.
31374 *
31375 * <a href="https://www.google.com/design/icons/#ic_accessibility" target="_blank" style="border-bottom:none;">
31376 * <img src="https://cloud.githubusercontent.com/assets/210413/7902490/fe8dd14c-0780-11e5-98fb-c821cc6475e6.png"
31377 *      aria-label="Material Design Icon-Selector" style="max-width:75%;padding-left:10%">
31378 * </a>
31379 *
31380 * <span class="image_caption">
31381 *  Click on the image above to link to the
31382 *  <a href="https://design.google.com/icons/#ic_accessibility" target="_blank">Material Design Icon-Selector</a>.
31383 * </span>
31384 *
31385 * @param {string} md-font-icon String name of CSS icon associated with the font-face will be used
31386 * to render the icon. Requires the fonts and the named CSS styles to be preloaded.
31387 * @param {string} md-font-set CSS style name associated with the font library; which will be assigned as
31388 * the class for the font-icon ligature. This value may also be an alias that is used to lookup the classname;
31389 * internally use `$mdIconProvider.fontSet(<alias>)` to determine the style name.
31390 * @param {string} md-svg-src String URL (or expression) used to load, cache, and display an
31391 *     external SVG.
31392 * @param {string} md-svg-icon md-svg-icon String name used for lookup of the icon from the internal cache;
31393 *     interpolated strings or expressions may also be used. Specific set names can be used with
31394 *     the syntax `<set name>:<icon name>`.<br/><br/>
31395 * To use icon sets, developers are required to pre-register the sets using the `$mdIconProvider` service.
31396 * @param {string=} aria-label Labels icon for accessibility. If an empty string is provided, icon
31397 * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no aria-label on the icon
31398 * nor a label on the parent element, a warning will be logged to the console.
31399 * @param {string=} alt Labels icon for accessibility. If an empty string is provided, icon
31400 * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no alt on the icon
31401 * nor a label on the parent element, a warning will be logged to the console.
31402 *
31403 * @usage
31404 * When using SVGs:
31405 * <hljs lang="html">
31406 *
31407 *  <!-- Icon ID; may contain optional icon set prefix; icons must registered using $mdIconProvider -->
31408 *  <md-icon md-svg-icon="social:android"    aria-label="android " ></md-icon>
31409 *
31410 *  <!-- Icon urls; may be preloaded in templateCache -->
31411 *  <md-icon md-svg-src="/android.svg"       aria-label="android " ></md-icon>
31412 *  <md-icon md-svg-src="{{ getAndroid() }}" aria-label="android " ></md-icon>
31413 *
31414 * </hljs>
31415 *
31416 * Use the <code>$mdIconProvider</code> to configure your application with
31417 * svg iconsets.
31418 *
31419 * <hljs lang="js">
31420 *  angular.module('appSvgIconSets', ['ngMaterial'])
31421 *    .controller('DemoCtrl', function($scope) {})
31422 *    .config(function($mdIconProvider) {
31423 *      $mdIconProvider
31424 *         .iconSet('social', 'img/icons/sets/social-icons.svg', 24)
31425 *         .defaultIconSet('img/icons/sets/core-icons.svg', 24);
31426 *     });
31427 * </hljs>
31428 *
31429 *
31430 * When using Font Icons with classnames:
31431 * <hljs lang="html">
31432 *
31433 *  <md-icon md-font-icon="android" aria-label="android" ></md-icon>
31434 *  <md-icon class="icon_home"      aria-label="Home"    ></md-icon>
31435 *
31436 * </hljs>
31437 *
31438 * When using Material Font Icons with ligatures:
31439 * <hljs lang="html">
31440 *  <!--
31441 *  For Material Design Icons
31442 *  The class '.material-icons' is auto-added if a style has NOT been specified
31443 *  since `material-icons` is the default fontset. So your markup:
31444 *  -->
31445 *  <md-icon> face </md-icon>
31446 *  <!-- becomes this at runtime: -->
31447 *  <md-icon md-font-set="material-icons"> face </md-icon>
31448 *  <!-- If the fontset does not support ligature names, then we need to use the ligature unicode.-->
31449 *  <md-icon> &#xE87C; </md-icon>
31450 *  <!-- The class '.material-icons' must be manually added if other styles are also specified-->
31451 *  <md-icon class="material-icons md-light md-48"> face </md-icon>
31452 * </hljs>
31453 *
31454 * When using other Font-Icon libraries:
31455 *
31456 * <hljs lang="js">
31457 *  // Specify a font-icon style alias
31458 *  angular.config(function($mdIconProvider) {
31459 *    $mdIconProvider.fontSet('md', 'material-icons');
31460 *  });
31461 * </hljs>
31462 *
31463 * <hljs lang="html">
31464 *  <md-icon md-font-set="md">favorite</md-icon>
31465 * </hljs>
31466 *
31467 */
31468function mdIconDirective($mdIcon, $mdTheming, $mdAria, $sce) {
31469
31470  return {
31471    restrict: 'E',
31472    link : postLink
31473  };
31474
31475
31476  /**
31477   * Directive postLink
31478   * Supports embedded SVGs, font-icons, & external SVGs
31479   */
31480  function postLink(scope, element, attr) {
31481    $mdTheming(element);
31482    var lastFontIcon = attr.mdFontIcon;
31483    var lastFontSet = $mdIcon.fontSet(attr.mdFontSet);
31484
31485    prepareForFontIcon();
31486
31487    attr.$observe('mdFontIcon', fontIconChanged);
31488    attr.$observe('mdFontSet', fontIconChanged);
31489
31490    // Keep track of the content of the svg src so we can compare against it later to see if the
31491    // attribute is static (and thus safe).
31492    var originalSvgSrc = element[0].getAttribute(attr.$attr.mdSvgSrc);
31493
31494    // If using a font-icon, then the textual name of the icon itself
31495    // provides the aria-label.
31496
31497    var label = attr.alt || attr.mdFontIcon || attr.mdSvgIcon || element.text();
31498    var attrName = attr.$normalize(attr.$attr.mdSvgIcon || attr.$attr.mdSvgSrc || '');
31499
31500    if ( !attr['aria-label'] ) {
31501
31502      if (label !== '' && !parentsHaveText() ) {
31503
31504        $mdAria.expect(element, 'aria-label', label);
31505        $mdAria.expect(element, 'role', 'img');
31506
31507      } else if ( !element.text() ) {
31508        // If not a font-icon with ligature, then
31509        // hide from the accessibility layer.
31510
31511        $mdAria.expect(element, 'aria-hidden', 'true');
31512      }
31513    }
31514
31515    if (attrName) {
31516      // Use either pre-configured SVG or URL source, respectively.
31517      attr.$observe(attrName, function(attrVal) {
31518        element.empty();
31519        if (attrVal) {
31520          $mdIcon(attrVal)
31521            .then(function(svg) {
31522            element.empty();
31523            element.append(svg);
31524          });
31525        }
31526
31527      });
31528    }
31529
31530    function parentsHaveText() {
31531      var parent = element.parent();
31532      if (parent.attr('aria-label') || parent.text()) {
31533        return true;
31534      }
31535      else if(parent.parent().attr('aria-label') || parent.parent().text()) {
31536        return true;
31537      }
31538      return false;
31539    }
31540
31541    function prepareForFontIcon() {
31542      if (!attr.mdSvgIcon && !attr.mdSvgSrc) {
31543        if (attr.mdFontIcon) {
31544          element.addClass('md-font ' + attr.mdFontIcon);
31545        }
31546
31547        element.addClass(lastFontSet);
31548      }
31549    }
31550
31551    function fontIconChanged() {
31552      if (!attr.mdSvgIcon && !attr.mdSvgSrc) {
31553        if (attr.mdFontIcon) {
31554          element.removeClass(lastFontIcon);
31555          element.addClass(attr.mdFontIcon);
31556
31557          lastFontIcon = attr.mdFontIcon;
31558        }
31559
31560        var fontSet = $mdIcon.fontSet(attr.mdFontSet);
31561
31562        if (lastFontSet !== fontSet) {
31563          element.removeClass(lastFontSet);
31564          element.addClass(fontSet);
31565
31566          lastFontSet = fontSet;
31567        }
31568      }
31569    }
31570  }
31571}
31572
31573})();
31574(function(){
31575"use strict";
31576
31577  
31578MdIconService.$inject = ["config", "$templateRequest", "$q", "$log", "$mdUtil", "$sce"];angular
31579    .module('material.components.icon')
31580    .constant('$$mdSvgRegistry', {
31581        'mdTabsArrow':   'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwb2x5Z29uIHBvaW50cz0iMTUuNCw3LjQgMTQsNiA4LDEyIDE0LDE4IDE1LjQsMTYuNiAxMC44LDEyICIvPjwvZz48L3N2Zz4=',
31582        'mdClose':       'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xOSA2LjQxbC0xLjQxLTEuNDEtNS41OSA1LjU5LTUuNTktNS41OS0xLjQxIDEuNDEgNS41OSA1LjU5LTUuNTkgNS41OSAxLjQxIDEuNDEgNS41OS01LjU5IDUuNTkgNS41OSAxLjQxLTEuNDEtNS41OS01LjU5eiIvPjwvZz48L3N2Zz4=',
31583        'mdCancel':      'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xMiAyYy01LjUzIDAtMTAgNC40Ny0xMCAxMHM0LjQ3IDEwIDEwIDEwIDEwLTQuNDcgMTAtMTAtNC40Ny0xMC0xMC0xMHptNSAxMy41OWwtMS40MSAxLjQxLTMuNTktMy41OS0zLjU5IDMuNTktMS40MS0xLjQxIDMuNTktMy41OS0zLjU5LTMuNTkgMS40MS0xLjQxIDMuNTkgMy41OSAzLjU5LTMuNTkgMS40MSAxLjQxLTMuNTkgMy41OSAzLjU5IDMuNTl6Ii8+PC9nPjwvc3ZnPg==',
31584        'mdMenu':        'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zLDZIMjFWOEgzVjZNMywxMUgyMVYxM0gzVjExTTMsMTZIMjFWMThIM1YxNloiIC8+PC9zdmc+',
31585        'mdToggleArrow': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiPjxwYXRoIGQ9Ik0yNCAxNmwtMTIgMTIgMi44MyAyLjgzIDkuMTctOS4xNyA5LjE3IDkuMTcgMi44My0yLjgzeiIvPjxwYXRoIGQ9Ik0wIDBoNDh2NDhoLTQ4eiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==',
31586        'mdCalendar':    'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgM2gtMVYxaC0ydjJIOFYxSDZ2Mkg1Yy0xLjExIDAtMS45OS45LTEuOTkgMkwzIDE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY4aDE0djExek03IDEwaDV2NUg3eiIvPjwvc3ZnPg==',
31587        'mdChecked':     'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiLz48L2c+PC9zdmc+'
31588    })
31589    .provider('$mdIcon', MdIconProvider);
31590
31591/**
31592 * @ngdoc service
31593 * @name $mdIconProvider
31594 * @module material.components.icon
31595 *
31596 * @description
31597 * `$mdIconProvider` is used only to register icon IDs with URLs. These configuration features allow
31598 * icons and icon sets to be pre-registered and associated with source URLs **before** the `<md-icon />`
31599 * directives are compiled.
31600 *
31601 * If using font-icons, the developer is responsible for loading the fonts.
31602 *
31603 * If using SVGs, loading of the actual svg files are deferred to on-demand requests and are loaded
31604 * internally by the `$mdIcon` service using the `$templateRequest` service. When an SVG is
31605 * requested by name/ID, the `$mdIcon` service searches its registry for the associated source URL;
31606 * that URL is used to on-demand load and parse the SVG dynamically.
31607 *
31608 * The `$templateRequest` service expects the icons source to be loaded over trusted URLs.<br/>
31609 * This means, when loading icons from an external URL, you have to trust the URL in the `$sceDelegateProvider`.
31610 *
31611 * <hljs lang="js">
31612 *   app.config(function($sceDelegateProvider) {
31613 *     $sceDelegateProvider.resourceUrlWhitelist([
31614 *       // Adding 'self' to the whitelist, will allow requests from the current origin.
31615 *       'self',
31616 *       // Using double asterisks here, will allow all URLs to load.
31617 *       // We recommend to only specify the given domain you want to allow.
31618 *       '**'
31619 *     ]);
31620 *   });
31621 * </hljs>
31622 *
31623 * Read more about the [$sceDelegateProvider](https://docs.angularjs.org/api/ng/provider/$sceDelegateProvider).
31624 *
31625 * **Notice:** Most font-icons libraries do not support ligatures (for example `fontawesome`).<br/>
31626 *  In such cases you are not able to use the icon's ligature name - Like so:
31627 *
31628 *  <hljs lang="html">
31629 *    <md-icon md-font-set="fa">fa-bell</md-icon>
31630 *  </hljs>
31631 *
31632 * You should instead use the given unicode, instead of the ligature name.
31633 *
31634 * <p ng-hide="true"> ##// Notice we can't use a hljs element here, because the characters will be escaped.</p>
31635 *  ```html
31636 *    <md-icon md-font-set="fa">&#xf0f3</md-icon>
31637 *  ```
31638 *
31639 * All unicode ligatures are prefixed with the `&#x` string.
31640 *
31641 * @usage
31642 * <hljs lang="js">
31643 *   app.config(function($mdIconProvider) {
31644    *
31645    *     // Configure URLs for icons specified by [set:]id.
31646    *
31647    *     $mdIconProvider
31648    *          .defaultFontSet( 'fa' )                   // This sets our default fontset className.
31649    *          .defaultIconSet('my/app/icons.svg')       // Register a default set of SVG icons
31650    *          .iconSet('social', 'my/app/social.svg')   // Register a named icon set of SVGs
31651    *          .icon('android', 'my/app/android.svg')    // Register a specific icon (by name)
31652    *          .icon('work:chair', 'my/app/chair.svg');  // Register icon in a specific set
31653    *   });
31654 * </hljs>
31655 *
31656 * SVG icons and icon sets can be easily pre-loaded and cached using either (a) a build process or (b) a runtime
31657 * **startup** process (shown below):
31658 *
31659 * <hljs lang="js">
31660 *   app.config(function($mdIconProvider) {
31661    *
31662    *     // Register a default set of SVG icon definitions
31663    *     $mdIconProvider.defaultIconSet('my/app/icons.svg')
31664    *
31665    *   })
31666 *   .run(function($templateRequest){
31667    *
31668    *     // Pre-fetch icons sources by URL and cache in the $templateCache...
31669    *     // subsequent $templateRequest calls will look there first.
31670    *
31671    *     var urls = [ 'imy/app/icons.svg', 'img/icons/android.svg'];
31672    *
31673    *     angular.forEach(urls, function(url) {
31674    *       $templateRequest(url);
31675    *     });
31676    *
31677    *   });
31678 *
31679 * </hljs>
31680 *
31681 * > <b>Note:</b> The loaded SVG data is subsequently cached internally for future requests.
31682 *
31683 */
31684
31685/**
31686 * @ngdoc method
31687 * @name $mdIconProvider#icon
31688 *
31689 * @description
31690 * Register a source URL for a specific icon name; the name may include optional 'icon set' name prefix.
31691 * These icons  will later be retrieved from the cache using `$mdIcon( <icon name> )`
31692 *
31693 * @param {string} id Icon name/id used to register the icon
31694 * @param {string} url specifies the external location for the data file. Used internally by
31695 * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
31696 * was configured.
31697 * @param {number=} viewBoxSize Sets the width and height the icon's viewBox.
31698 * It is ignored for icons with an existing viewBox. Default size is 24.
31699 *
31700 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
31701 *
31702 * @usage
31703 * <hljs lang="js">
31704 *   app.config(function($mdIconProvider) {
31705    *
31706    *     // Configure URLs for icons specified by [set:]id.
31707    *
31708    *     $mdIconProvider
31709    *          .icon('android', 'my/app/android.svg')    // Register a specific icon (by name)
31710    *          .icon('work:chair', 'my/app/chair.svg');  // Register icon in a specific set
31711    *   });
31712 * </hljs>
31713 *
31714 */
31715/**
31716 * @ngdoc method
31717 * @name $mdIconProvider#iconSet
31718 *
31719 * @description
31720 * Register a source URL for a 'named' set of icons; group of SVG definitions where each definition
31721 * has an icon id. Individual icons can be subsequently retrieved from this cached set using
31722 * `$mdIcon(<icon set name>:<icon name>)`
31723 *
31724 * @param {string} id Icon name/id used to register the iconset
31725 * @param {string} url specifies the external location for the data file. Used internally by
31726 * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
31727 * was configured.
31728 * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set.
31729 * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size.
31730 * Default value is 24.
31731 *
31732 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
31733 *
31734 *
31735 * @usage
31736 * <hljs lang="js">
31737 *   app.config(function($mdIconProvider) {
31738    *
31739    *     // Configure URLs for icons specified by [set:]id.
31740    *
31741    *     $mdIconProvider
31742    *          .iconSet('social', 'my/app/social.svg')   // Register a named icon set
31743    *   });
31744 * </hljs>
31745 *
31746 */
31747/**
31748 * @ngdoc method
31749 * @name $mdIconProvider#defaultIconSet
31750 *
31751 * @description
31752 * Register a source URL for the default 'named' set of icons. Unless explicitly registered,
31753 * subsequent lookups of icons will failover to search this 'default' icon set.
31754 * Icon can be retrieved from this cached, default set using `$mdIcon(<name>)`
31755 *
31756 * @param {string} url specifies the external location for the data file. Used internally by
31757 * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
31758 * was configured.
31759 * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set.
31760 * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size.
31761 * Default value is 24.
31762 *
31763 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
31764 *
31765 * @usage
31766 * <hljs lang="js">
31767 *   app.config(function($mdIconProvider) {
31768    *
31769    *     // Configure URLs for icons specified by [set:]id.
31770    *
31771    *     $mdIconProvider
31772    *          .defaultIconSet( 'my/app/social.svg' )   // Register a default icon set
31773    *   });
31774 * </hljs>
31775 *
31776 */
31777/**
31778 * @ngdoc method
31779 * @name $mdIconProvider#defaultFontSet
31780 *
31781 * @description
31782 * When using Font-Icons, Angular Material assumes the the Material Design icons will be used and automatically
31783 * configures the default font-set == 'material-icons'. Note that the font-set references the font-icon library
31784 * class style that should be applied to the `<md-icon>`.
31785 *
31786 * Configuring the default means that the attributes
31787 * `md-font-set="material-icons"` or `class="material-icons"` do not need to be explicitly declared on the
31788 * `<md-icon>` markup. For example:
31789 *
31790 *  `<md-icon> face </md-icon>`
31791 *  will render as
31792 *  `<span class="material-icons"> face </span>`, and
31793 *
31794 *  `<md-icon md-font-set="fa"> face </md-icon>`
31795 *  will render as
31796 *  `<span class="fa"> face </span>`
31797 *
31798 * @param {string} name of the font-library style that should be applied to the md-icon DOM element
31799 *
31800 * @usage
31801 * <hljs lang="js">
31802 *   app.config(function($mdIconProvider) {
31803   *     $mdIconProvider.defaultFontSet( 'fa' );
31804   *   });
31805 * </hljs>
31806 *
31807 */
31808
31809/**
31810 * @ngdoc method
31811 * @name $mdIconProvider#fontSet
31812 *
31813 * @description
31814 * When using a font set for `<md-icon>` you must specify the correct font classname in the `md-font-set`
31815 * attribute. If the fonset className is really long, your markup may become cluttered... an easy
31816 * solution is to define an `alias` for your fontset:
31817 *
31818 * @param {string} alias of the specified fontset.
31819 * @param {string} className of the fontset.
31820 *
31821 * @usage
31822 * <hljs lang="js">
31823 *   app.config(function($mdIconProvider) {
31824   *     // In this case, we set an alias for the `material-icons` fontset.
31825   *     $mdIconProvider.fontSet('md', 'material-icons');
31826   *   });
31827 * </hljs>
31828 *
31829 */
31830
31831/**
31832 * @ngdoc method
31833 * @name $mdIconProvider#defaultViewBoxSize
31834 *
31835 * @description
31836 * While `<md-icon />` markup can also be style with sizing CSS, this method configures
31837 * the default width **and** height used for all icons; unless overridden by specific CSS.
31838 * The default sizing is (24px, 24px).
31839 * @param {number=} viewBoxSize Sets the width and height of the viewBox for an icon or an icon set.
31840 * All icons in a set should be the same size. The default value is 24.
31841 *
31842 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
31843 *
31844 * @usage
31845 * <hljs lang="js">
31846 *   app.config(function($mdIconProvider) {
31847    *
31848    *     // Configure URLs for icons specified by [set:]id.
31849    *
31850    *     $mdIconProvider
31851    *          .defaultViewBoxSize(36)   // Register a default icon size (width == height)
31852    *   });
31853 * </hljs>
31854 *
31855 */
31856
31857var config = {
31858  defaultViewBoxSize: 24,
31859  defaultFontSet: 'material-icons',
31860  fontSets: []
31861};
31862
31863function MdIconProvider() {
31864}
31865
31866MdIconProvider.prototype = {
31867  icon: function(id, url, viewBoxSize) {
31868    if (id.indexOf(':') == -1) id = '$default:' + id;
31869
31870    config[id] = new ConfigurationItem(url, viewBoxSize);
31871    return this;
31872  },
31873
31874  iconSet: function(id, url, viewBoxSize) {
31875    config[id] = new ConfigurationItem(url, viewBoxSize);
31876    return this;
31877  },
31878
31879  defaultIconSet: function(url, viewBoxSize) {
31880    var setName = '$default';
31881
31882    if (!config[setName]) {
31883      config[setName] = new ConfigurationItem(url, viewBoxSize);
31884    }
31885
31886    config[setName].viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
31887
31888    return this;
31889  },
31890
31891  defaultViewBoxSize: function(viewBoxSize) {
31892    config.defaultViewBoxSize = viewBoxSize;
31893    return this;
31894  },
31895
31896  /**
31897   * Register an alias name associated with a font-icon library style ;
31898   */
31899  fontSet: function fontSet(alias, className) {
31900    config.fontSets.push({
31901      alias: alias,
31902      fontSet: className || alias
31903    });
31904    return this;
31905  },
31906
31907  /**
31908   * Specify a default style name associated with a font-icon library
31909   * fallback to Material Icons.
31910   *
31911   */
31912  defaultFontSet: function defaultFontSet(className) {
31913    config.defaultFontSet = !className ? '' : className;
31914    return this;
31915  },
31916
31917  defaultIconSize: function defaultIconSize(iconSize) {
31918    config.defaultIconSize = iconSize;
31919    return this;
31920  },
31921
31922  $get: ['$templateRequest', '$q', '$log', '$mdUtil', '$sce', function($templateRequest, $q, $log, $mdUtil, $sce) {
31923    return MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce);
31924  }]
31925};
31926
31927/**
31928 *  Configuration item stored in the Icon registry; used for lookups
31929 *  to load if not already cached in the `loaded` cache
31930 */
31931function ConfigurationItem(url, viewBoxSize) {
31932  this.url = url;
31933  this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
31934}
31935
31936/**
31937 * @ngdoc service
31938 * @name $mdIcon
31939 * @module material.components.icon
31940 *
31941 * @description
31942 * The `$mdIcon` service is a function used to lookup SVG icons.
31943 *
31944 * @param {string} id Query value for a unique Id or URL. If the argument is a URL, then the service will retrieve the icon element
31945 * from its internal cache or load the icon and cache it first. If the value is not a URL-type string, then an ID lookup is
31946 * performed. The Id may be a unique icon ID or may include an iconSet ID prefix.
31947 *
31948 * For the **id** query to work properly, this means that all id-to-URL mappings must have been previously configured
31949 * using the `$mdIconProvider`.
31950 *
31951 * @returns {angular.$q.Promise} A promise that gets resolved to a clone of the initial SVG DOM element; which was
31952 * created from the SVG markup in the SVG data file. If an error occurs (e.g. the icon cannot be found) the promise
31953 * will get rejected.
31954 *
31955 * @usage
31956 * <hljs lang="js">
31957 * function SomeDirective($mdIcon) {
31958  *
31959  *   // See if the icon has already been loaded, if not
31960  *   // then lookup the icon from the registry cache, load and cache
31961  *   // it for future requests.
31962  *   // NOTE: ID queries require configuration with $mdIconProvider
31963  *
31964  *   $mdIcon('android').then(function(iconEl)    { element.append(iconEl); });
31965  *   $mdIcon('work:chair').then(function(iconEl) { element.append(iconEl); });
31966  *
31967  *   // Load and cache the external SVG using a URL
31968  *
31969  *   $mdIcon('img/icons/android.svg').then(function(iconEl) {
31970  *     element.append(iconEl);
31971  *   });
31972  * };
31973 * </hljs>
31974 *
31975 * > <b>Note:</b> The `<md-icon>` directive internally uses the `$mdIcon` service to query, loaded,
31976 *   and instantiate SVG DOM elements.
31977 */
31978
31979/* @ngInject */
31980function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
31981  var iconCache = {};
31982  var svgCache = {};
31983  var urlRegex = /[-\w@:%\+.~#?&//=]{2,}\.[a-z]{2,4}\b(\/[-\w@:%\+.~#?&//=]*)?/i;
31984  var dataUrlRegex = /^data:image\/svg\+xml[\s*;\w\-\=]*?(base64)?,(.*)$/i;
31985
31986  Icon.prototype = {clone: cloneSVG, prepare: prepareAndStyle};
31987  getIcon.fontSet = findRegisteredFontSet;
31988
31989  // Publish service...
31990  return getIcon;
31991
31992  /**
31993   * Actual $mdIcon service is essentially a lookup function
31994   */
31995  function getIcon(id) {
31996    id = id || '';
31997
31998    // If the "id" provided is not a string, the only other valid value is a $sce trust wrapper
31999    // over a URL string. If the value is not trusted, this will intentionally throw an error
32000    // because the user is attempted to use an unsafe URL, potentially opening themselves up
32001    // to an XSS attack.
32002    if (!angular.isString(id)) {
32003      id = $sce.getTrustedUrl(id);
32004    }
32005
32006    // If already loaded and cached, use a clone of the cached icon.
32007    // Otherwise either load by URL, or lookup in the registry and then load by URL, and cache.
32008
32009    if (iconCache[id]) {
32010      return $q.when(transformClone(iconCache[id]));
32011    }
32012
32013    if (urlRegex.test(id) || dataUrlRegex.test(id)) {
32014      return loadByURL(id).then(cacheIcon(id));
32015    }
32016
32017    if (id.indexOf(':') == -1) {
32018      id = '$default:' + id;
32019    }
32020
32021    var load = config[id] ? loadByID : loadFromIconSet;
32022    return load(id)
32023      .then(cacheIcon(id));
32024  }
32025
32026  /**
32027   * Lookup registered fontSet style using its alias...
32028   * If not found,
32029   */
32030  function findRegisteredFontSet(alias) {
32031    var useDefault = angular.isUndefined(alias) || !(alias && alias.length);
32032    if (useDefault) return config.defaultFontSet;
32033
32034    var result = alias;
32035    angular.forEach(config.fontSets, function(it) {
32036      if (it.alias == alias) result = it.fontSet || result;
32037    });
32038
32039    return result;
32040  }
32041
32042  function transformClone(cacheElement) {
32043    var clone = cacheElement.clone();
32044    var cacheSuffix = '_cache' + $mdUtil.nextUid();
32045
32046    // We need to modify for each cached icon the id attributes.
32047    // This is needed because SVG id's are treated as normal DOM ids
32048    // and should not have a duplicated id.
32049    if (clone.id) clone.id += cacheSuffix;
32050    angular.forEach(clone.querySelectorAll('[id]'), function(item) {
32051      item.id += cacheSuffix;
32052    });
32053
32054    return clone;
32055  }
32056
32057  /**
32058   * Prepare and cache the loaded icon for the specified `id`
32059   */
32060  function cacheIcon(id) {
32061
32062    return function updateCache(icon) {
32063      iconCache[id] = isIcon(icon) ? icon : new Icon(icon, config[id]);
32064
32065      return iconCache[id].clone();
32066    };
32067  }
32068
32069  /**
32070   * Lookup the configuration in the registry, if !registered throw an error
32071   * otherwise load the icon [on-demand] using the registered URL.
32072   *
32073   */
32074  function loadByID(id) {
32075    var iconConfig = config[id];
32076    return loadByURL(iconConfig.url).then(function(icon) {
32077      return new Icon(icon, iconConfig);
32078    });
32079  }
32080
32081  /**
32082   *    Loads the file as XML and uses querySelector( <id> ) to find
32083   *    the desired node...
32084   */
32085  function loadFromIconSet(id) {
32086    var setName = id.substring(0, id.lastIndexOf(':')) || '$default';
32087    var iconSetConfig = config[setName];
32088
32089    return !iconSetConfig ? announceIdNotFound(id) : loadByURL(iconSetConfig.url).then(extractFromSet);
32090
32091    function extractFromSet(set) {
32092      var iconName = id.slice(id.lastIndexOf(':') + 1);
32093      var icon = set.querySelector('#' + iconName);
32094      return icon ? new Icon(icon, iconSetConfig) : announceIdNotFound(id);
32095    }
32096
32097    function announceIdNotFound(id) {
32098      var msg = 'icon ' + id + ' not found';
32099      $log.warn(msg);
32100
32101      return $q.reject(msg || id);
32102    }
32103  }
32104
32105  /**
32106   * Load the icon by URL (may use the $templateCache).
32107   * Extract the data for later conversion to Icon
32108   */
32109  function loadByURL(url) {
32110    /* Load the icon from embedded data URL. */
32111    function loadByDataUrl(url) {
32112      var results = dataUrlRegex.exec(url);
32113      var isBase64 = /base64/i.test(url);
32114      var data = isBase64 ? window.atob(results[2]) : results[2];
32115
32116      return $q.when(angular.element(data)[0]);
32117    }
32118
32119    /* Load the icon by URL using HTTP. */
32120    function loadByHttpUrl(url) {
32121      return $q(function(resolve, reject) {
32122        // Catch HTTP or generic errors not related to incorrect icon IDs.
32123        var announceAndReject = function(err) {
32124            var msg = angular.isString(err) ? err : (err.message || err.data || err.statusText);
32125            $log.warn(msg);
32126            reject(err);
32127          },
32128          extractSvg = function(response) {
32129            if (!svgCache[url]) {
32130              svgCache[url] = angular.element('<div>').append(response)[0].querySelector('svg');
32131            }
32132            resolve(svgCache[url]);
32133          };
32134
32135        $templateRequest(url, true).then(extractSvg, announceAndReject);
32136      });
32137    }
32138
32139    return dataUrlRegex.test(url)
32140      ? loadByDataUrl(url)
32141      : loadByHttpUrl(url);
32142  }
32143
32144  /**
32145   * Check target signature to see if it is an Icon instance.
32146   */
32147  function isIcon(target) {
32148    return angular.isDefined(target.element) && angular.isDefined(target.config);
32149  }
32150
32151  /**
32152   *  Define the Icon class
32153   */
32154  function Icon(el, config) {
32155    if (el && el.tagName != 'svg') {
32156      el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">').append(el.cloneNode(true))[0];
32157    }
32158
32159    // Inject the namespace if not available...
32160    if (!el.getAttribute('xmlns')) {
32161      el.setAttribute('xmlns', "http://www.w3.org/2000/svg");
32162    }
32163
32164    this.element = el;
32165    this.config = config;
32166    this.prepare();
32167  }
32168
32169  /**
32170   *  Prepare the DOM element that will be cached in the
32171   *  loaded iconCache store.
32172   */
32173  function prepareAndStyle() {
32174    var viewBoxSize = this.config ? this.config.viewBoxSize : config.defaultViewBoxSize;
32175    angular.forEach({
32176      'fit': '',
32177      'height': '100%',
32178      'width': '100%',
32179      'preserveAspectRatio': 'xMidYMid meet',
32180      'viewBox': this.element.getAttribute('viewBox') || ('0 0 ' + viewBoxSize + ' ' + viewBoxSize),
32181      'focusable': false // Disable IE11s default behavior to make SVGs focusable
32182    }, function(val, attr) {
32183      this.element.setAttribute(attr, val);
32184    }, this);
32185  }
32186
32187  /**
32188   * Clone the Icon DOM element.
32189   */
32190  function cloneSVG() {
32191    // If the element or any of its children have a style attribute, then a CSP policy without
32192    // 'unsafe-inline' in the style-src directive, will result in a violation.
32193    return this.element.cloneNode(true);
32194  }
32195
32196}
32197
32198})();
32199(function(){
32200"use strict";
32201
32202
32203
32204MenuController.$inject = ["$mdMenu", "$attrs", "$element", "$scope", "$mdUtil", "$timeout", "$rootScope", "$q", "$log"];
32205angular
32206    .module('material.components.menu')
32207    .controller('mdMenuCtrl', MenuController);
32208
32209/**
32210 * @ngInject
32211 */
32212function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout, $rootScope, $q, $log) {
32213
32214  var prefixer = $mdUtil.prefixer();
32215  var menuContainer;
32216  var self = this;
32217  var triggerElement;
32218
32219  this.nestLevel = parseInt($attrs.mdNestLevel, 10) || 0;
32220
32221  /**
32222   * Called by our linking fn to provide access to the menu-content
32223   * element removed during link
32224   */
32225  this.init = function init(setMenuContainer, opts) {
32226    opts = opts || {};
32227    menuContainer = setMenuContainer;
32228
32229    // Default element for ARIA attributes has the ngClick or ngMouseenter expression
32230    triggerElement = $element[0].querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter']));
32231    triggerElement.setAttribute('aria-expanded', 'false');
32232
32233    this.isInMenuBar = opts.isInMenuBar;
32234    this.nestedMenus = $mdUtil.nodesToArray(menuContainer[0].querySelectorAll('.md-nested-menu'));
32235
32236    menuContainer.on('$mdInterimElementRemove', function() {
32237      self.isOpen = false;
32238      $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
32239    });
32240    $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
32241
32242    var menuContainerId = 'menu_container_' + $mdUtil.nextUid();
32243    menuContainer.attr('id', menuContainerId);
32244    angular.element(triggerElement).attr({
32245      'aria-owns': menuContainerId,
32246      'aria-haspopup': 'true'
32247    });
32248
32249    $scope.$on('$destroy', angular.bind(this, function() {
32250      this.disableHoverListener();
32251      $mdMenu.destroy();
32252    }));
32253
32254    menuContainer.on('$destroy', function() {
32255      $mdMenu.destroy();
32256    });
32257  };
32258
32259  var openMenuTimeout, menuItems, deregisterScopeListeners = [];
32260  this.enableHoverListener = function() {
32261    deregisterScopeListeners.push($rootScope.$on('$mdMenuOpen', function(event, el) {
32262      if (menuContainer[0].contains(el[0])) {
32263        self.currentlyOpenMenu = el.controller('mdMenu');
32264        self.isAlreadyOpening = false;
32265        self.currentlyOpenMenu.registerContainerProxy(self.triggerContainerProxy.bind(self));
32266      }
32267    }));
32268    deregisterScopeListeners.push($rootScope.$on('$mdMenuClose', function(event, el) {
32269      if (menuContainer[0].contains(el[0])) {
32270        self.currentlyOpenMenu = undefined;
32271      }
32272    }));
32273    menuItems = angular.element($mdUtil.nodesToArray(menuContainer[0].children[0].children));
32274    menuItems.on('mouseenter', self.handleMenuItemHover);
32275    menuItems.on('mouseleave', self.handleMenuItemMouseLeave);
32276  };
32277
32278  this.disableHoverListener = function() {
32279    while (deregisterScopeListeners.length) {
32280      deregisterScopeListeners.shift()();
32281    }
32282    menuItems && menuItems.off('mouseenter', self.handleMenuItemHover);
32283    menuItems && menuItems.off('mouseleave', self.handleMenuItemMouseLeave);
32284  };
32285
32286  this.handleMenuItemHover = function(event) {
32287    if (self.isAlreadyOpening) return;
32288    var nestedMenu = (
32289      event.target.querySelector('md-menu')
32290        || $mdUtil.getClosest(event.target, 'MD-MENU')
32291    );
32292    openMenuTimeout = $timeout(function() {
32293      if (nestedMenu) {
32294        nestedMenu = angular.element(nestedMenu).controller('mdMenu');
32295      }
32296
32297      if (self.currentlyOpenMenu && self.currentlyOpenMenu != nestedMenu) {
32298        var closeTo = self.nestLevel + 1;
32299        self.currentlyOpenMenu.close(true, { closeTo: closeTo });
32300        self.isAlreadyOpening = !!nestedMenu;
32301        nestedMenu && nestedMenu.open();
32302      } else if (nestedMenu && !nestedMenu.isOpen && nestedMenu.open) {
32303        self.isAlreadyOpening = !!nestedMenu;
32304        nestedMenu && nestedMenu.open();
32305      }
32306    }, nestedMenu ? 100 : 250);
32307    var focusableTarget = event.currentTarget.querySelector('.md-button:not([disabled])');
32308    focusableTarget && focusableTarget.focus();
32309  };
32310
32311  this.handleMenuItemMouseLeave = function() {
32312    if (openMenuTimeout) {
32313      $timeout.cancel(openMenuTimeout);
32314      openMenuTimeout = undefined;
32315    }
32316  };
32317
32318
32319  /**
32320   * Uses the $mdMenu interim element service to open the menu contents
32321   */
32322  this.open = function openMenu(ev) {
32323    ev && ev.stopPropagation();
32324    ev && ev.preventDefault();
32325    if (self.isOpen) return;
32326    self.enableHoverListener();
32327    self.isOpen = true;
32328    $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
32329    triggerElement = triggerElement || (ev ? ev.target : $element[0]);
32330    triggerElement.setAttribute('aria-expanded', 'true');
32331    $scope.$emit('$mdMenuOpen', $element);
32332    $mdMenu.show({
32333      scope: $scope,
32334      mdMenuCtrl: self,
32335      nestLevel: self.nestLevel,
32336      element: menuContainer,
32337      target: triggerElement,
32338      preserveElement: true,
32339      parent: 'body'
32340    }).finally(function() {
32341      triggerElement.setAttribute('aria-expanded', 'false');
32342      self.disableHoverListener();
32343    });
32344  };
32345
32346  this.onIsOpenChanged = function(isOpen) {
32347    if (isOpen) {
32348      menuContainer.attr('aria-hidden', 'false');
32349      $element[0].classList.add('md-open');
32350      angular.forEach(self.nestedMenus, function(el) {
32351        el.classList.remove('md-open');
32352      });
32353    } else {
32354      menuContainer.attr('aria-hidden', 'true');
32355      $element[0].classList.remove('md-open');
32356    }
32357    $scope.$mdMenuIsOpen = self.isOpen;
32358  };
32359
32360  this.focusMenuContainer = function focusMenuContainer() {
32361    var focusTarget = menuContainer[0]
32362      .querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
32363
32364    if (!focusTarget) focusTarget = menuContainer[0].querySelector('.md-button:not([disabled])');
32365    focusTarget.focus();
32366  };
32367
32368  this.registerContainerProxy = function registerContainerProxy(handler) {
32369    this.containerProxy = handler;
32370  };
32371
32372  this.triggerContainerProxy = function triggerContainerProxy(ev) {
32373    this.containerProxy && this.containerProxy(ev);
32374  };
32375
32376  this.destroy = function() {
32377    return self.isOpen ? $mdMenu.destroy() : $q.when(false);
32378  };
32379
32380  // Use the $mdMenu interim element service to close the menu contents
32381  this.close = function closeMenu(skipFocus, closeOpts) {
32382    if ( !self.isOpen ) return;
32383    self.isOpen = false;
32384    $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
32385
32386    var eventDetails = angular.extend({}, closeOpts, { skipFocus: skipFocus });
32387    $scope.$emit('$mdMenuClose', $element, eventDetails);
32388    $mdMenu.hide(null, closeOpts);
32389
32390    if (!skipFocus) {
32391      var el = self.restoreFocusTo || $element.find('button')[0];
32392      if (el instanceof angular.element) el = el[0];
32393      if (el) el.focus();
32394    }
32395  };
32396
32397  /**
32398   * Build a nice object out of our string attribute which specifies the
32399   * target mode for left and top positioning
32400   */
32401  this.positionMode = function positionMode() {
32402    var attachment = ($attrs.mdPositionMode || 'target').split(' ');
32403
32404    // If attachment is a single item, duplicate it for our second value.
32405    // ie. 'target' -> 'target target'
32406    if (attachment.length == 1) {
32407      attachment.push(attachment[0]);
32408    }
32409
32410    return {
32411      left: attachment[0],
32412      top: attachment[1]
32413    };
32414  };
32415
32416  /**
32417   * Build a nice object out of our string attribute which specifies
32418   * the offset of top and left in pixels.
32419   */
32420  this.offsets = function offsets() {
32421    var position = ($attrs.mdOffset || '0 0').split(' ').map(parseFloat);
32422    if (position.length == 2) {
32423      return {
32424        left: position[0],
32425        top: position[1]
32426      };
32427    } else if (position.length == 1) {
32428      return {
32429        top: position[0],
32430        left: position[0]
32431      };
32432    } else {
32433      throw Error('Invalid offsets specified. Please follow format <x, y> or <n>');
32434    }
32435  };
32436
32437  // Functionality that is exposed in the view.
32438  $scope.$mdMenu = {
32439    open: this.open,
32440    close: this.close
32441  };
32442
32443  // Deprecated APIs
32444  $scope.$mdOpenMenu = angular.bind(this, function() {
32445    $log.warn('mdMenu: The $mdOpenMenu method is deprecated. Please use `$mdMenu.open`.');
32446    return this.open.apply(this, arguments);
32447  });
32448}
32449
32450})();
32451(function(){
32452"use strict";
32453
32454/**
32455 * @ngdoc directive
32456 * @name mdMenu
32457 * @module material.components.menu
32458 * @restrict E
32459 * @description
32460 *
32461 * Menus are elements that open when clicked. They are useful for displaying
32462 * additional options within the context of an action.
32463 *
32464 * Every `md-menu` must specify exactly two child elements. The first element is what is
32465 * left in the DOM and is used to open the menu. This element is called the trigger element.
32466 * The trigger element's scope has access to `$mdMenu.open($event)`
32467 * which it may call to open the menu. By passing $event as argument, the
32468 * corresponding event is stopped from propagating up the DOM-tree. Similarly, `$mdMenu.close()`
32469 * can be used to close the menu.
32470 *
32471 * The second element is the `md-menu-content` element which represents the
32472 * contents of the menu when it is open. Typically this will contain `md-menu-item`s,
32473 * but you can do custom content as well.
32474 *
32475 * <hljs lang="html">
32476 * <md-menu>
32477 *  <!-- Trigger element is a md-button with an icon -->
32478 *  <md-button ng-click="$mdMenu.open($event)" class="md-icon-button" aria-label="Open sample menu">
32479 *    <md-icon md-svg-icon="call:phone"></md-icon>
32480 *  </md-button>
32481 *  <md-menu-content>
32482 *    <md-menu-item><md-button ng-click="doSomething()">Do Something</md-button></md-menu-item>
32483 *  </md-menu-content>
32484 * </md-menu>
32485 * </hljs>
32486
32487 * ## Sizing Menus
32488 *
32489 * The width of the menu when it is open may be specified by specifying a `width`
32490 * attribute on the `md-menu-content` element.
32491 * See the [Material Design Spec](https://material.google.com/components/menus.html#menus-simple-menus)
32492 * for more information.
32493 *
32494 *
32495 * ## Aligning Menus
32496 *
32497 * When a menu opens, it is important that the content aligns with the trigger element.
32498 * Failure to align menus can result in jarring experiences for users as content
32499 * suddenly shifts. To help with this, `md-menu` provides serveral APIs to help
32500 * with alignment.
32501 *
32502 * ### Target Mode
32503 *
32504 * By default, `md-menu` will attempt to align the `md-menu-content` by aligning
32505 * designated child elements in both the trigger and the menu content.
32506 *
32507 * To specify the alignment element in the `trigger` you can use the `md-menu-origin`
32508 * attribute on a child element. If no `md-menu-origin` is specified, the `md-menu`
32509 * will be used as the origin element.
32510 *
32511 * Similarly, the `md-menu-content` may specify a `md-menu-align-target` for a
32512 * `md-menu-item` to specify the node that it should try and align with.
32513 *
32514 * In this example code, we specify an icon to be our origin element, and an
32515 * icon in our menu content to be our alignment target. This ensures that both
32516 * icons are aligned when the menu opens.
32517 *
32518 * <hljs lang="html">
32519 * <md-menu>
32520 *  <md-button ng-click="$mdMenu.open($event)" class="md-icon-button" aria-label="Open some menu">
32521 *    <md-icon md-menu-origin md-svg-icon="call:phone"></md-icon>
32522 *  </md-button>
32523 *  <md-menu-content>
32524 *    <md-menu-item>
32525 *      <md-button ng-click="doSomething()" aria-label="Do something">
32526 *        <md-icon md-menu-align-target md-svg-icon="call:phone"></md-icon>
32527 *        Do Something
32528 *      </md-button>
32529 *    </md-menu-item>
32530 *  </md-menu-content>
32531 * </md-menu>
32532 * </hljs>
32533 *
32534 * Sometimes we want to specify alignment on the right side of an element, for example
32535 * if we have a menu on the right side a toolbar, we want to right align our menu content.
32536 *
32537 * We can specify the origin by using the `md-position-mode` attribute on both
32538 * the `x` and `y` axis. Right now only the `x-axis` has more than one option.
32539 * You may specify the default mode of `target target` or
32540 * `target-right target` to specify a right-oriented alignment target. See the
32541 * position section of the demos for more examples.
32542 *
32543 * ### Menu Offsets
32544 *
32545 * It is sometimes unavoidable to need to have a deeper level of control for
32546 * the positioning of a menu to ensure perfect alignment. `md-menu` provides
32547 * the `md-offset` attribute to allow pixel level specificty of adjusting the
32548 * exact positioning.
32549 *
32550 * This offset is provided in the format of `x y` or `n` where `n` will be used
32551 * in both the `x` and `y` axis.
32552 *
32553 * For example, to move a menu by `2px` down from the top, we can use:
32554 * <hljs lang="html">
32555 * <md-menu md-offset="0 2">
32556 *   <!-- menu-content -->
32557 * </md-menu>
32558 * </hljs>
32559 *
32560 * ### Auto Focus
32561 * By default, when a menu opens, `md-menu` focuses the first button in the menu content.
32562 *
32563 * But sometimes you would like to focus another specific menu item instead of the first.<br/>
32564 * This can be done by applying the `md-autofocus` directive on the given element.
32565 *
32566 * <hljs lang="html">
32567 * <md-menu-item>
32568 *   <md-button md-autofocus ng-click="doSomething()">
32569 *     Auto Focus
32570 *   </md-button>
32571 * </md-menu-item>
32572 * </hljs>
32573 *
32574 *
32575 * ### Preventing close
32576 *
32577 * Sometimes you would like to be able to click on a menu item without having the menu
32578 * close. To do this, ngMaterial exposes the `md-prevent-menu-close` attribute which
32579 * can be added to a button inside a menu to stop the menu from automatically closing.
32580 * You can then close the menu either by using `$mdMenu.close()` in the template,
32581 * or programatically by injecting `$mdMenu` and calling `$mdMenu.hide()`.
32582 *
32583 * <hljs lang="html">
32584 * <md-menu-content ng-mouseleave="$mdMenu.close()">
32585 *   <md-menu-item>
32586 *     <md-button ng-click="doSomething()" aria-label="Do something" md-prevent-menu-close="md-prevent-menu-close">
32587 *       <md-icon md-menu-align-target md-svg-icon="call:phone"></md-icon>
32588 *       Do Something
32589 *     </md-button>
32590 *   </md-menu-item>
32591 * </md-menu-content>
32592 * </hljs>
32593 *
32594 * @usage
32595 * <hljs lang="html">
32596 * <md-menu>
32597 *  <md-button ng-click="$mdMenu.open($event)" class="md-icon-button">
32598 *    <md-icon md-svg-icon="call:phone"></md-icon>
32599 *  </md-button>
32600 *  <md-menu-content>
32601 *    <md-menu-item><md-button ng-click="doSomething()">Do Something</md-button></md-menu-item>
32602 *  </md-menu-content>
32603 * </md-menu>
32604 * </hljs>
32605 *
32606 * @param {string} md-position-mode The position mode in the form of
32607 *           `x`, `y`. Default value is `target`,`target`. Right now the `x` axis
32608 *           also supports `target-right`.
32609 * @param {string} md-offset An offset to apply to the dropdown after positioning
32610 *           `x`, `y`. Default value is `0`,`0`.
32611 *
32612 */
32613
32614MenuDirective.$inject = ["$mdUtil"];
32615angular
32616    .module('material.components.menu')
32617    .directive('mdMenu', MenuDirective);
32618
32619/**
32620 * @ngInject
32621 */
32622function MenuDirective($mdUtil) {
32623  var INVALID_PREFIX = 'Invalid HTML for md-menu: ';
32624  return {
32625    restrict: 'E',
32626    require: ['mdMenu', '?^mdMenuBar'],
32627    controller: 'mdMenuCtrl', // empty function to be built by link
32628    scope: true,
32629    compile: compile
32630  };
32631
32632  function compile(templateElement) {
32633    templateElement.addClass('md-menu');
32634
32635    var triggerEl = templateElement.children()[0];
32636    var contentEl = templateElement.children()[1];
32637
32638    var prefixer = $mdUtil.prefixer();
32639
32640    if (!prefixer.hasAttribute(triggerEl, 'ng-click')) {
32641      triggerEl = triggerEl
32642          .querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter'])) || triggerEl;
32643    }
32644
32645    var isButtonTrigger = triggerEl.nodeName === 'MD-BUTTON' || triggerEl.nodeName === 'BUTTON';
32646
32647    if (triggerEl && isButtonTrigger && !triggerEl.hasAttribute('type')) {
32648      triggerEl.setAttribute('type', 'button');
32649    }
32650
32651    if (!triggerEl) {
32652      throw Error(INVALID_PREFIX + 'Expected the menu to have a trigger element.');
32653    }
32654
32655    if (!contentEl || contentEl.nodeName !== 'MD-MENU-CONTENT') {
32656      throw Error(INVALID_PREFIX + 'Expected the menu to contain a `md-menu-content` element.');
32657    }
32658
32659    // Default element for ARIA attributes has the ngClick or ngMouseenter expression
32660    triggerEl && triggerEl.setAttribute('aria-haspopup', 'true');
32661
32662    var nestedMenus = templateElement[0].querySelectorAll('md-menu');
32663    var nestingDepth = parseInt(templateElement[0].getAttribute('md-nest-level'), 10) || 0;
32664    if (nestedMenus) {
32665      angular.forEach($mdUtil.nodesToArray(nestedMenus), function(menuEl) {
32666        if (!menuEl.hasAttribute('md-position-mode')) {
32667          menuEl.setAttribute('md-position-mode', 'cascade');
32668        }
32669        menuEl.classList.add('_md-nested-menu');
32670        menuEl.setAttribute('md-nest-level', nestingDepth + 1);
32671      });
32672    }
32673    return link;
32674  }
32675
32676  function link(scope, element, attr, ctrls) {
32677    var mdMenuCtrl = ctrls[0];
32678    var isInMenuBar = !!ctrls[1];
32679    // Move everything into a md-menu-container and pass it to the controller
32680    var menuContainer = angular.element( '<div class="_md md-open-menu-container md-whiteframe-z2"></div>');
32681    var menuContents = element.children()[1];
32682
32683    element.addClass('_md');     // private md component indicator for styling
32684
32685    if (!menuContents.hasAttribute('role')) {
32686      menuContents.setAttribute('role', 'menu');
32687    }
32688    menuContainer.append(menuContents);
32689
32690    element.on('$destroy', function() {
32691      menuContainer.remove();
32692    });
32693
32694    element.append(menuContainer);
32695    menuContainer[0].style.display = 'none';
32696    mdMenuCtrl.init(menuContainer, { isInMenuBar: isInMenuBar });
32697
32698  }
32699}
32700
32701})();
32702(function(){
32703"use strict";
32704
32705
32706MenuProvider.$inject = ["$$interimElementProvider"];angular
32707  .module('material.components.menu')
32708  .provider('$mdMenu', MenuProvider);
32709
32710/*
32711 * Interim element provider for the menu.
32712 * Handles behavior for a menu while it is open, including:
32713 *    - handling animating the menu opening/closing
32714 *    - handling key/mouse events on the menu element
32715 *    - handling enabling/disabling scroll while the menu is open
32716 *    - handling redrawing during resizes and orientation changes
32717 *
32718 */
32719
32720function MenuProvider($$interimElementProvider) {
32721  menuDefaultOptions.$inject = ["$mdUtil", "$mdTheming", "$mdConstant", "$document", "$window", "$q", "$$rAF", "$animateCss", "$animate", "$log"];
32722  var MENU_EDGE_MARGIN = 8;
32723
32724  return $$interimElementProvider('$mdMenu')
32725    .setDefaults({
32726      methods: ['target'],
32727      options: menuDefaultOptions
32728    });
32729
32730  /* @ngInject */
32731  function menuDefaultOptions($mdUtil, $mdTheming, $mdConstant, $document, $window, $q, $$rAF,
32732                              $animateCss, $animate, $log) {
32733
32734    var prefixer = $mdUtil.prefixer();
32735    var animator = $mdUtil.dom.animator;
32736
32737    return {
32738      parent: 'body',
32739      onShow: onShow,
32740      onRemove: onRemove,
32741      hasBackdrop: true,
32742      disableParentScroll: true,
32743      skipCompile: true,
32744      preserveScope: true,
32745      multiple: true,
32746      themable: true
32747    };
32748
32749    /**
32750     * Show modal backdrop element...
32751     * @returns {function(): void} A function that removes this backdrop
32752     */
32753    function showBackdrop(scope, element, options) {
32754      if (options.nestLevel) return angular.noop;
32755
32756      // If we are not within a dialog...
32757      if (options.disableParentScroll && !$mdUtil.getClosest(options.target, 'MD-DIALOG')) {
32758        // !! DO this before creating the backdrop; since disableScrollAround()
32759        //    configures the scroll offset; which is used by mdBackDrop postLink()
32760        options.restoreScroll = $mdUtil.disableScrollAround(options.element, options.parent);
32761      } else {
32762        options.disableParentScroll = false;
32763      }
32764
32765      if (options.hasBackdrop) {
32766        options.backdrop = $mdUtil.createBackdrop(scope, "md-menu-backdrop md-click-catcher");
32767
32768        $animate.enter(options.backdrop, $document[0].body);
32769      }
32770
32771      /**
32772       * Hide and destroys the backdrop created by showBackdrop()
32773       */
32774      return function hideBackdrop() {
32775        if (options.backdrop) options.backdrop.remove();
32776        if (options.disableParentScroll) options.restoreScroll();
32777      };
32778    }
32779
32780    /**
32781     * Removing the menu element from the DOM and remove all associated event listeners
32782     * and backdrop
32783     */
32784    function onRemove(scope, element, opts) {
32785      opts.cleanupInteraction();
32786      opts.cleanupBackdrop();
32787      opts.cleanupResizing();
32788      opts.hideBackdrop();
32789
32790      // Before the menu is closing remove the clickable class.
32791      element.removeClass('md-clickable');
32792
32793      // For navigation $destroy events, do a quick, non-animated removal,
32794      // but for normal closes (from clicks, etc) animate the removal
32795
32796      return (opts.$destroy === true) ? detachAndClean() : animateRemoval().then( detachAndClean );
32797
32798      /**
32799       * For normal closes, animate the removal.
32800       * For forced closes (like $destroy events), skip the animations
32801       */
32802      function animateRemoval() {
32803        return $animateCss(element, {addClass: 'md-leave'}).start();
32804      }
32805
32806      /**
32807       * Detach the element
32808       */
32809      function detachAndClean() {
32810        element.removeClass('md-active');
32811        detachElement(element, opts);
32812        opts.alreadyOpen = false;
32813      }
32814
32815    }
32816
32817    /**
32818     * Inserts and configures the staged Menu element into the DOM, positioning it,
32819     * and wiring up various interaction events
32820     */
32821    function onShow(scope, element, opts) {
32822      sanitizeAndConfigure(opts);
32823
32824      if (opts.menuContentEl[0]) {
32825        // Inherit the theme from the target element.
32826        $mdTheming.inherit(opts.menuContentEl, opts.target);
32827      } else {
32828        $log.warn(
32829          '$mdMenu: Menu elements should always contain a `md-menu-content` element,' +
32830          'otherwise interactivity features will not work properly.',
32831          element
32832        );
32833      }
32834      
32835      // Register various listeners to move menu on resize/orientation change
32836      opts.cleanupResizing = startRepositioningOnResize();
32837      opts.hideBackdrop = showBackdrop(scope, element, opts);
32838
32839      // Return the promise for when our menu is done animating in
32840      return showMenu()
32841        .then(function(response) {
32842          opts.alreadyOpen = true;
32843          opts.cleanupInteraction = activateInteraction();
32844          opts.cleanupBackdrop = setupBackdrop();
32845
32846          // Since the menu finished its animation, mark the menu as clickable.
32847          element.addClass('md-clickable');
32848
32849          return response;
32850        });
32851
32852      /**
32853       * Place the menu into the DOM and call positioning related functions
32854       */
32855      function showMenu() {
32856        opts.parent.append(element);
32857        element[0].style.display = '';
32858
32859        return $q(function(resolve) {
32860          var position = calculateMenuPosition(element, opts);
32861
32862          element.removeClass('md-leave');
32863
32864          // Animate the menu scaling, and opacity [from its position origin (default == top-left)]
32865          // to normal scale.
32866          $animateCss(element, {
32867            addClass: 'md-active',
32868            from: animator.toCss(position),
32869            to: animator.toCss({transform: ''})
32870          })
32871          .start()
32872          .then(resolve);
32873
32874        });
32875      }
32876
32877      /**
32878       * Check for valid opts and set some sane defaults
32879       */
32880      function sanitizeAndConfigure() {
32881        if (!opts.target) {
32882          throw Error(
32883            '$mdMenu.show() expected a target to animate from in options.target'
32884          );
32885        }
32886        angular.extend(opts, {
32887          alreadyOpen: false,
32888          isRemoved: false,
32889          target: angular.element(opts.target), //make sure it's not a naked dom node
32890          parent: angular.element(opts.parent),
32891          menuContentEl: angular.element(element[0].querySelector('md-menu-content'))
32892        });
32893      }
32894
32895      /**
32896       * Configure various resize listeners for screen changes
32897       */
32898      function startRepositioningOnResize() {
32899
32900        var repositionMenu = (function(target, options) {
32901          return $$rAF.throttle(function() {
32902            if (opts.isRemoved) return;
32903            var position = calculateMenuPosition(target, options);
32904
32905            target.css(animator.toCss(position));
32906          });
32907        })(element, opts);
32908
32909        $window.addEventListener('resize', repositionMenu);
32910        $window.addEventListener('orientationchange', repositionMenu);
32911
32912        return function stopRepositioningOnResize() {
32913
32914          // Disable resizing handlers
32915          $window.removeEventListener('resize', repositionMenu);
32916          $window.removeEventListener('orientationchange', repositionMenu);
32917
32918        };
32919      }
32920
32921      /**
32922       * Sets up the backdrop and listens for click elements.
32923       * Once the backdrop will be clicked, the menu will automatically close.
32924       * @returns {!Function} Function to remove the backdrop.
32925       */
32926      function setupBackdrop() {
32927        if (!opts.backdrop) return angular.noop;
32928
32929        opts.backdrop.on('click', onBackdropClick);
32930
32931        return function() {
32932          opts.backdrop.off('click', onBackdropClick);
32933        }
32934      }
32935
32936      /**
32937       * Function to be called whenever the backdrop is clicked.
32938       * @param {!MouseEvent} event
32939       */
32940      function onBackdropClick(event) {
32941        event.preventDefault();
32942        event.stopPropagation();
32943
32944        scope.$apply(function() {
32945          opts.mdMenuCtrl.close(true, { closeAll: true });
32946        });
32947      }
32948
32949      /**
32950       * Activate interaction on the menu. Resolves the focus target and closes the menu on
32951       * escape or option click.
32952       * @returns {!Function} Function to deactivate the interaction listeners.
32953       */
32954      function activateInteraction() {
32955        if (!opts.menuContentEl[0]) return angular.noop;
32956
32957        // Wire up keyboard listeners.
32958        // - Close on escape,
32959        // - focus next item on down arrow,
32960        // - focus prev item on up
32961        opts.menuContentEl.on('keydown', onMenuKeyDown);
32962        opts.menuContentEl[0].addEventListener('click', captureClickListener, true);
32963
32964        // kick off initial focus in the menu on the first enabled element
32965        var focusTarget = opts.menuContentEl[0]
32966          .querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
32967
32968        if ( !focusTarget ) {
32969          var childrenLen = opts.menuContentEl[0].children.length;
32970          for(var childIndex = 0; childIndex < childrenLen; childIndex++) {
32971            var child = opts.menuContentEl[0].children[childIndex];
32972            focusTarget = child.querySelector('.md-button:not([disabled])');
32973            if (focusTarget) {
32974              break;
32975            }
32976            if (child.firstElementChild && !child.firstElementChild.disabled) {
32977              focusTarget = child.firstElementChild;
32978              break;
32979            }
32980          }
32981        }
32982
32983        focusTarget && focusTarget.focus();
32984
32985        return function cleanupInteraction() {
32986          opts.menuContentEl.off('keydown', onMenuKeyDown);
32987          opts.menuContentEl[0].removeEventListener('click', captureClickListener, true);
32988        };
32989
32990        // ************************************
32991        // internal functions
32992        // ************************************
32993
32994        function onMenuKeyDown(ev) {
32995          var handled;
32996          switch (ev.keyCode) {
32997            case $mdConstant.KEY_CODE.ESCAPE:
32998              opts.mdMenuCtrl.close(false, { closeAll: true });
32999              handled = true;
33000              break;
33001            case $mdConstant.KEY_CODE.UP_ARROW:
33002              if (!focusMenuItem(ev, opts.menuContentEl, opts, -1) && !opts.nestLevel) {
33003                opts.mdMenuCtrl.triggerContainerProxy(ev);
33004              }
33005              handled = true;
33006              break;
33007            case $mdConstant.KEY_CODE.DOWN_ARROW:
33008              if (!focusMenuItem(ev, opts.menuContentEl, opts, 1) && !opts.nestLevel) {
33009                opts.mdMenuCtrl.triggerContainerProxy(ev);
33010              }
33011              handled = true;
33012              break;
33013            case $mdConstant.KEY_CODE.LEFT_ARROW:
33014              if (opts.nestLevel) {
33015                opts.mdMenuCtrl.close();
33016              } else {
33017                opts.mdMenuCtrl.triggerContainerProxy(ev);
33018              }
33019              handled = true;
33020              break;
33021            case $mdConstant.KEY_CODE.RIGHT_ARROW:
33022              var parentMenu = $mdUtil.getClosest(ev.target, 'MD-MENU');
33023              if (parentMenu && parentMenu != opts.parent[0]) {
33024                ev.target.click();
33025              } else {
33026                opts.mdMenuCtrl.triggerContainerProxy(ev);
33027              }
33028              handled = true;
33029              break;
33030          }
33031          if (handled) {
33032            ev.preventDefault();
33033            ev.stopImmediatePropagation();
33034          }
33035        }
33036
33037        function onBackdropClick(e) {
33038          e.preventDefault();
33039          e.stopPropagation();
33040          scope.$apply(function() {
33041            opts.mdMenuCtrl.close(true, { closeAll: true });
33042          });
33043        }
33044
33045        // Close menu on menu item click, if said menu-item is not disabled
33046        function captureClickListener(e) {
33047          var target = e.target;
33048          // Traverse up the event until we get to the menuContentEl to see if
33049          // there is an ng-click and that the ng-click is not disabled
33050          do {
33051            if (target == opts.menuContentEl[0]) return;
33052            if ((hasAnyAttribute(target, ['ng-click', 'ng-href', 'ui-sref']) ||
33053                target.nodeName == 'BUTTON' || target.nodeName == 'MD-BUTTON') && !hasAnyAttribute(target, ['md-prevent-menu-close'])) {
33054              var closestMenu = $mdUtil.getClosest(target, 'MD-MENU');
33055              if (!target.hasAttribute('disabled') && (!closestMenu || closestMenu == opts.parent[0])) {
33056                close();
33057              }
33058              break;
33059            }
33060          } while (target = target.parentNode);
33061
33062          function close() {
33063            scope.$apply(function() {
33064              opts.mdMenuCtrl.close(true, { closeAll: true });
33065            });
33066          }
33067
33068          function hasAnyAttribute(target, attrs) {
33069            if (!target) return false;
33070
33071            for (var i = 0, attr; attr = attrs[i]; ++i) {
33072              if (prefixer.hasAttribute(target, attr)) {
33073                return true;
33074              }
33075            }
33076
33077            return false;
33078          }
33079        }
33080
33081      }
33082    }
33083
33084    /**
33085     * Takes a keypress event and focuses the next/previous menu
33086     * item from the emitting element
33087     * @param {event} e - The origin keypress event
33088     * @param {angular.element} menuEl - The menu element
33089     * @param {object} opts - The interim element options for the mdMenu
33090     * @param {number} direction - The direction to move in (+1 = next, -1 = prev)
33091     */
33092    function focusMenuItem(e, menuEl, opts, direction) {
33093      var currentItem = $mdUtil.getClosest(e.target, 'MD-MENU-ITEM');
33094
33095      var items = $mdUtil.nodesToArray(menuEl[0].children);
33096      var currentIndex = items.indexOf(currentItem);
33097
33098      // Traverse through our elements in the specified direction (+/-1) and try to
33099      // focus them until we find one that accepts focus
33100      var didFocus;
33101      for (var i = currentIndex + direction; i >= 0 && i < items.length; i = i + direction) {
33102        var focusTarget = items[i].querySelector('.md-button');
33103        didFocus = attemptFocus(focusTarget);
33104        if (didFocus) {
33105          break;
33106        }
33107      }
33108      return didFocus;
33109    }
33110
33111    /**
33112     * Attempts to focus an element. Checks whether that element is the currently
33113     * focused element after attempting.
33114     * @param {HTMLElement} el - the element to attempt focus on
33115     * @returns {bool} - whether the element was successfully focused
33116     */
33117    function attemptFocus(el) {
33118      if (el && el.getAttribute('tabindex') != -1) {
33119        el.focus();
33120        return ($document[0].activeElement == el);
33121      }
33122    }
33123
33124    /**
33125     * Use browser to remove this element without triggering a $destroy event
33126     */
33127    function detachElement(element, opts) {
33128      if (!opts.preserveElement) {
33129        if (toNode(element).parentNode === toNode(opts.parent)) {
33130          toNode(opts.parent).removeChild(toNode(element));
33131        }
33132      } else {
33133        toNode(element).style.display = 'none';
33134      }
33135    }
33136
33137    /**
33138     * Computes menu position and sets the style on the menu container
33139     * @param {HTMLElement} el - the menu container element
33140     * @param {object} opts - the interim element options object
33141     */
33142    function calculateMenuPosition(el, opts) {
33143
33144      var containerNode = el[0],
33145        openMenuNode = el[0].firstElementChild,
33146        openMenuNodeRect = openMenuNode.getBoundingClientRect(),
33147        boundryNode = $document[0].body,
33148        boundryNodeRect = boundryNode.getBoundingClientRect();
33149
33150      var menuStyle = $window.getComputedStyle(openMenuNode);
33151
33152      var originNode = opts.target[0].querySelector(prefixer.buildSelector('md-menu-origin')) || opts.target[0],
33153        originNodeRect = originNode.getBoundingClientRect();
33154
33155      var bounds = {
33156        left: boundryNodeRect.left + MENU_EDGE_MARGIN,
33157        top: Math.max(boundryNodeRect.top, 0) + MENU_EDGE_MARGIN,
33158        bottom: Math.max(boundryNodeRect.bottom, Math.max(boundryNodeRect.top, 0) + boundryNodeRect.height) - MENU_EDGE_MARGIN,
33159        right: boundryNodeRect.right - MENU_EDGE_MARGIN
33160      };
33161
33162      var alignTarget, alignTargetRect = { top:0, left : 0, right:0, bottom:0 }, existingOffsets  = { top:0, left : 0, right:0, bottom:0  };
33163      var positionMode = opts.mdMenuCtrl.positionMode();
33164
33165      if (positionMode.top == 'target' || positionMode.left == 'target' || positionMode.left == 'target-right') {
33166        alignTarget = firstVisibleChild();
33167        if ( alignTarget ) {
33168          // TODO: Allow centering on an arbitrary node, for now center on first menu-item's child
33169          alignTarget = alignTarget.firstElementChild || alignTarget;
33170          alignTarget = alignTarget.querySelector(prefixer.buildSelector('md-menu-align-target')) || alignTarget;
33171          alignTargetRect = alignTarget.getBoundingClientRect();
33172
33173          existingOffsets = {
33174            top: parseFloat(containerNode.style.top || 0),
33175            left: parseFloat(containerNode.style.left || 0)
33176          };
33177        }
33178      }
33179
33180      var position = {};
33181      var transformOrigin = 'top ';
33182
33183      switch (positionMode.top) {
33184        case 'target':
33185          position.top = existingOffsets.top + originNodeRect.top - alignTargetRect.top;
33186          break;
33187        case 'cascade':
33188          position.top = originNodeRect.top - parseFloat(menuStyle.paddingTop) - originNode.style.top;
33189          break;
33190        case 'bottom':
33191          position.top = originNodeRect.top + originNodeRect.height;
33192          break;
33193        default:
33194          throw new Error('Invalid target mode "' + positionMode.top + '" specified for md-menu on Y axis.');
33195      }
33196
33197      var rtl = ($mdUtil.bidi() == 'rtl');
33198
33199      switch (positionMode.left) {
33200        case 'target':
33201          position.left = existingOffsets.left + originNodeRect.left - alignTargetRect.left;
33202          transformOrigin += rtl ? 'right'  : 'left';
33203          break;
33204        case 'target-left':
33205          position.left = originNodeRect.left;
33206          transformOrigin += 'left';
33207          break;
33208        case 'target-right':
33209          position.left = originNodeRect.right - openMenuNodeRect.width + (openMenuNodeRect.right - alignTargetRect.right);
33210          transformOrigin += 'right';
33211          break;
33212        case 'cascade':
33213          var willFitRight = rtl ? (originNodeRect.left - openMenuNodeRect.width) < bounds.left : (originNodeRect.right + openMenuNodeRect.width) < bounds.right;
33214          position.left = willFitRight ? originNodeRect.right - originNode.style.left : originNodeRect.left - originNode.style.left - openMenuNodeRect.width;
33215          transformOrigin += willFitRight ? 'left' : 'right';
33216          break;
33217        case 'right':
33218          if (rtl) {
33219            position.left = originNodeRect.right - originNodeRect.width;
33220            transformOrigin += 'left';
33221          } else {
33222            position.left = originNodeRect.right - openMenuNodeRect.width;
33223            transformOrigin += 'right';
33224          }
33225          break;
33226        case 'left':
33227          if (rtl) {
33228            position.left = originNodeRect.right - openMenuNodeRect.width;
33229            transformOrigin += 'right';
33230          } else {
33231            position.left = originNodeRect.left;
33232            transformOrigin += 'left';
33233          }
33234          break;
33235        default:
33236          throw new Error('Invalid target mode "' + positionMode.left + '" specified for md-menu on X axis.');
33237      }
33238
33239      var offsets = opts.mdMenuCtrl.offsets();
33240      position.top += offsets.top;
33241      position.left += offsets.left;
33242
33243      clamp(position);
33244
33245      var scaleX = Math.round(100 * Math.min(originNodeRect.width / containerNode.offsetWidth, 1.0)) / 100;
33246      var scaleY = Math.round(100 * Math.min(originNodeRect.height / containerNode.offsetHeight, 1.0)) / 100;
33247
33248      return {
33249        top: Math.round(position.top),
33250        left: Math.round(position.left),
33251        // Animate a scale out if we aren't just repositioning
33252        transform: !opts.alreadyOpen ? $mdUtil.supplant('scale({0},{1})', [scaleX, scaleY]) : undefined,
33253        transformOrigin: transformOrigin
33254      };
33255
33256      /**
33257       * Clamps the repositioning of the menu within the confines of
33258       * bounding element (often the screen/body)
33259       */
33260      function clamp(pos) {
33261        pos.top = Math.max(Math.min(pos.top, bounds.bottom - containerNode.offsetHeight), bounds.top);
33262        pos.left = Math.max(Math.min(pos.left, bounds.right - containerNode.offsetWidth), bounds.left);
33263      }
33264
33265      /**
33266       * Gets the first visible child in the openMenuNode
33267       * Necessary incase menu nodes are being dynamically hidden
33268       */
33269      function firstVisibleChild() {
33270        for (var i = 0; i < openMenuNode.children.length; ++i) {
33271          if ($window.getComputedStyle(openMenuNode.children[i]).display != 'none') {
33272            return openMenuNode.children[i];
33273          }
33274        }
33275      }
33276    }
33277  }
33278  function toNode(el) {
33279    if (el instanceof angular.element) {
33280      el = el[0];
33281    }
33282    return el;
33283  }
33284}
33285
33286})();
33287(function(){
33288"use strict";
33289
33290
33291MenuBarController.$inject = ["$scope", "$rootScope", "$element", "$attrs", "$mdConstant", "$document", "$mdUtil", "$timeout"];
33292angular
33293  .module('material.components.menuBar')
33294  .controller('MenuBarController', MenuBarController);
33295
33296var BOUND_MENU_METHODS = ['handleKeyDown', 'handleMenuHover', 'scheduleOpenHoveredMenu', 'cancelScheduledOpen'];
33297
33298/**
33299 * @ngInject
33300 */
33301function MenuBarController($scope, $rootScope, $element, $attrs, $mdConstant, $document, $mdUtil, $timeout) {
33302  this.$element = $element;
33303  this.$attrs = $attrs;
33304  this.$mdConstant = $mdConstant;
33305  this.$mdUtil = $mdUtil;
33306  this.$document = $document;
33307  this.$scope = $scope;
33308  this.$rootScope = $rootScope;
33309  this.$timeout = $timeout;
33310
33311  var self = this;
33312  angular.forEach(BOUND_MENU_METHODS, function(methodName) {
33313    self[methodName] = angular.bind(self, self[methodName]);
33314  });
33315}
33316
33317MenuBarController.prototype.init = function() {
33318  var $element = this.$element;
33319  var $mdUtil = this.$mdUtil;
33320  var $scope = this.$scope;
33321
33322  var self = this;
33323  var deregisterFns = [];
33324  $element.on('keydown', this.handleKeyDown);
33325  this.parentToolbar = $mdUtil.getClosest($element, 'MD-TOOLBAR');
33326
33327  deregisterFns.push(this.$rootScope.$on('$mdMenuOpen', function(event, el) {
33328    if (self.getMenus().indexOf(el[0]) != -1) {
33329      $element[0].classList.add('md-open');
33330      el[0].classList.add('md-open');
33331      self.currentlyOpenMenu = el.controller('mdMenu');
33332      self.currentlyOpenMenu.registerContainerProxy(self.handleKeyDown);
33333      self.enableOpenOnHover();
33334    }
33335  }));
33336
33337  deregisterFns.push(this.$rootScope.$on('$mdMenuClose', function(event, el, opts) {
33338    var rootMenus = self.getMenus();
33339    if (rootMenus.indexOf(el[0]) != -1) {
33340      $element[0].classList.remove('md-open');
33341      el[0].classList.remove('md-open');
33342    }
33343
33344    if ($element[0].contains(el[0])) {
33345      var parentMenu = el[0];
33346      while (parentMenu && rootMenus.indexOf(parentMenu) == -1) {
33347        parentMenu = $mdUtil.getClosest(parentMenu, 'MD-MENU', true);
33348      }
33349      if (parentMenu) {
33350        if (!opts.skipFocus) parentMenu.querySelector('button:not([disabled])').focus();
33351        self.currentlyOpenMenu = undefined;
33352        self.disableOpenOnHover();
33353        self.setKeyboardMode(true);
33354      }
33355    }
33356  }));
33357
33358  $scope.$on('$destroy', function() {
33359    self.disableOpenOnHover();
33360    while (deregisterFns.length) {
33361      deregisterFns.shift()();
33362    }
33363  });
33364
33365
33366  this.setKeyboardMode(true);
33367};
33368
33369MenuBarController.prototype.setKeyboardMode = function(enabled) {
33370  if (enabled) this.$element[0].classList.add('md-keyboard-mode');
33371  else this.$element[0].classList.remove('md-keyboard-mode');
33372};
33373
33374MenuBarController.prototype.enableOpenOnHover = function() {
33375  if (this.openOnHoverEnabled) return;
33376
33377  var self = this;
33378
33379  self.openOnHoverEnabled = true;
33380
33381  if (self.parentToolbar) {
33382    self.parentToolbar.classList.add('md-has-open-menu');
33383
33384    // Needs to be on the next tick so it doesn't close immediately.
33385    self.$mdUtil.nextTick(function() {
33386      angular.element(self.parentToolbar).on('click', self.handleParentClick);
33387    }, false);
33388  }
33389
33390  angular
33391    .element(self.getMenus())
33392    .on('mouseenter', self.handleMenuHover);
33393};
33394
33395MenuBarController.prototype.handleMenuHover = function(e) {
33396  this.setKeyboardMode(false);
33397  if (this.openOnHoverEnabled) {
33398    this.scheduleOpenHoveredMenu(e);
33399  }
33400};
33401
33402MenuBarController.prototype.disableOpenOnHover = function() {
33403  if (!this.openOnHoverEnabled) return;
33404
33405  this.openOnHoverEnabled = false;
33406
33407  if (this.parentToolbar) {
33408    this.parentToolbar.classList.remove('md-has-open-menu');
33409    angular.element(this.parentToolbar).off('click', this.handleParentClick);
33410  }
33411
33412  angular
33413    .element(this.getMenus())
33414    .off('mouseenter', this.handleMenuHover);
33415};
33416
33417MenuBarController.prototype.scheduleOpenHoveredMenu = function(e) {
33418  var menuEl = angular.element(e.currentTarget);
33419  var menuCtrl = menuEl.controller('mdMenu');
33420  this.setKeyboardMode(false);
33421  this.scheduleOpenMenu(menuCtrl);
33422};
33423
33424MenuBarController.prototype.scheduleOpenMenu = function(menuCtrl) {
33425  var self = this;
33426  var $timeout = this.$timeout;
33427  if (menuCtrl != self.currentlyOpenMenu) {
33428    $timeout.cancel(self.pendingMenuOpen);
33429    self.pendingMenuOpen = $timeout(function() {
33430      self.pendingMenuOpen = undefined;
33431      if (self.currentlyOpenMenu) {
33432        self.currentlyOpenMenu.close(true, { closeAll: true });
33433      }
33434      menuCtrl.open();
33435    }, 200, false);
33436  }
33437};
33438
33439MenuBarController.prototype.handleKeyDown = function(e) {
33440  var keyCodes = this.$mdConstant.KEY_CODE;
33441  var currentMenu = this.currentlyOpenMenu;
33442  var wasOpen = currentMenu && currentMenu.isOpen;
33443  this.setKeyboardMode(true);
33444  var handled, newMenu, newMenuCtrl;
33445  switch (e.keyCode) {
33446    case keyCodes.DOWN_ARROW:
33447      if (currentMenu) {
33448        currentMenu.focusMenuContainer();
33449      } else {
33450        this.openFocusedMenu();
33451      }
33452      handled = true;
33453      break;
33454    case keyCodes.UP_ARROW:
33455      currentMenu && currentMenu.close();
33456      handled = true;
33457      break;
33458    case keyCodes.LEFT_ARROW:
33459      newMenu = this.focusMenu(-1);
33460      if (wasOpen) {
33461        newMenuCtrl = angular.element(newMenu).controller('mdMenu');
33462        this.scheduleOpenMenu(newMenuCtrl);
33463      }
33464      handled = true;
33465      break;
33466    case keyCodes.RIGHT_ARROW:
33467      newMenu = this.focusMenu(+1);
33468      if (wasOpen) {
33469        newMenuCtrl = angular.element(newMenu).controller('mdMenu');
33470        this.scheduleOpenMenu(newMenuCtrl);
33471      }
33472      handled = true;
33473      break;
33474  }
33475  if (handled) {
33476    e && e.preventDefault && e.preventDefault();
33477    e && e.stopImmediatePropagation && e.stopImmediatePropagation();
33478  }
33479};
33480
33481MenuBarController.prototype.focusMenu = function(direction) {
33482  var menus = this.getMenus();
33483  var focusedIndex = this.getFocusedMenuIndex();
33484
33485  if (focusedIndex == -1) { focusedIndex = this.getOpenMenuIndex(); }
33486
33487  var changed = false;
33488
33489  if (focusedIndex == -1) { focusedIndex = 0; changed = true; }
33490  else if (
33491    direction < 0 && focusedIndex > 0 ||
33492    direction > 0 && focusedIndex < menus.length - direction
33493  ) {
33494    focusedIndex += direction;
33495    changed = true;
33496  }
33497  if (changed) {
33498    menus[focusedIndex].querySelector('button').focus();
33499    return menus[focusedIndex];
33500  }
33501};
33502
33503MenuBarController.prototype.openFocusedMenu = function() {
33504  var menu = this.getFocusedMenu();
33505  menu && angular.element(menu).controller('mdMenu').open();
33506};
33507
33508MenuBarController.prototype.getMenus = function() {
33509  var $element = this.$element;
33510  return this.$mdUtil.nodesToArray($element[0].children)
33511    .filter(function(el) { return el.nodeName == 'MD-MENU'; });
33512};
33513
33514MenuBarController.prototype.getFocusedMenu = function() {
33515  return this.getMenus()[this.getFocusedMenuIndex()];
33516};
33517
33518MenuBarController.prototype.getFocusedMenuIndex = function() {
33519  var $mdUtil = this.$mdUtil;
33520  var focusedEl = $mdUtil.getClosest(
33521    this.$document[0].activeElement,
33522    'MD-MENU'
33523  );
33524  if (!focusedEl) return -1;
33525
33526  var focusedIndex = this.getMenus().indexOf(focusedEl);
33527  return focusedIndex;
33528};
33529
33530MenuBarController.prototype.getOpenMenuIndex = function() {
33531  var menus = this.getMenus();
33532  for (var i = 0; i < menus.length; ++i) {
33533    if (menus[i].classList.contains('md-open')) return i;
33534  }
33535  return -1;
33536};
33537
33538MenuBarController.prototype.handleParentClick = function(event) {
33539  var openMenu = this.querySelector('md-menu.md-open');
33540
33541  if (openMenu && !openMenu.contains(event.target)) {
33542    angular.element(openMenu).controller('mdMenu').close(true, {
33543      closeAll: true
33544    });
33545  }
33546};
33547
33548})();
33549(function(){
33550"use strict";
33551
33552/**
33553 * @ngdoc directive
33554 * @name mdMenuBar
33555 * @module material.components.menuBar
33556 * @restrict E
33557 * @description
33558 *
33559 * Menu bars are containers that hold multiple menus. They change the behavior and appearence
33560 * of the `md-menu` directive to behave similar to an operating system provided menu.
33561 *
33562 * @usage
33563 * <hljs lang="html">
33564 * <md-menu-bar>
33565 *   <md-menu>
33566 *     <button ng-click="$mdMenu.open()">
33567 *       File
33568 *     </button>
33569 *     <md-menu-content>
33570 *       <md-menu-item>
33571 *         <md-button ng-click="ctrl.sampleAction('share', $event)">
33572 *           Share...
33573 *         </md-button>
33574 *       </md-menu-item>
33575 *       <md-menu-divider></md-menu-divider>
33576 *       <md-menu-item>
33577 *       <md-menu-item>
33578 *         <md-menu>
33579 *           <md-button ng-click="$mdMenu.open()">New</md-button>
33580 *           <md-menu-content>
33581 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Document', $event)">Document</md-button></md-menu-item>
33582 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Spreadsheet', $event)">Spreadsheet</md-button></md-menu-item>
33583 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Presentation', $event)">Presentation</md-button></md-menu-item>
33584 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Form', $event)">Form</md-button></md-menu-item>
33585 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Drawing', $event)">Drawing</md-button></md-menu-item>
33586 *           </md-menu-content>
33587 *         </md-menu>
33588 *       </md-menu-item>
33589 *     </md-menu-content>
33590 *   </md-menu>
33591 * </md-menu-bar>
33592 * </hljs>
33593 *
33594 * ## Menu Bar Controls
33595 *
33596 * You may place `md-menu-items` that function as controls within menu bars.
33597 * There are two modes that are exposed via the `type` attribute of the `md-menu-item`.
33598 * `type="checkbox"` will function as a boolean control for the `ng-model` attribute of the
33599 * `md-menu-item`. `type="radio"` will function like a radio button, setting the `ngModel`
33600 * to the `string` value of the `value` attribute. If you need non-string values, you can use
33601 * `ng-value` to provide an expression (this is similar to how angular's native `input[type=radio]` works.
33602 *
33603 * <hljs lang="html">
33604 * <md-menu-bar>
33605 *  <md-menu>
33606 *    <button ng-click="$mdMenu.open()">
33607 *      Sample Menu
33608 *    </button>
33609 *    <md-menu-content>
33610 *      <md-menu-item type="checkbox" ng-model="settings.allowChanges">Allow changes</md-menu-item>
33611 *      <md-menu-divider></md-menu-divider>
33612 *      <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 1</md-menu-item>
33613 *      <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 2</md-menu-item>
33614 *      <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 3</md-menu-item>
33615 *    </md-menu-content>
33616 *  </md-menu>
33617 * </md-menu-bar>
33618 * </hljs>
33619 *
33620 *
33621 * ### Nesting Menus
33622 *
33623 * Menus may be nested within menu bars. This is commonly called cascading menus.
33624 * To nest a menu place the nested menu inside the content of the `md-menu-item`.
33625 * <hljs lang="html">
33626 * <md-menu-item>
33627 *   <md-menu>
33628 *     <button ng-click="$mdMenu.open()">New</md-button>
33629 *     <md-menu-content>
33630 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Document', $event)">Document</md-button></md-menu-item>
33631 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Spreadsheet', $event)">Spreadsheet</md-button></md-menu-item>
33632 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Presentation', $event)">Presentation</md-button></md-menu-item>
33633 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Form', $event)">Form</md-button></md-menu-item>
33634 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Drawing', $event)">Drawing</md-button></md-menu-item>
33635 *     </md-menu-content>
33636 *   </md-menu>
33637 * </md-menu-item>
33638 * </hljs>
33639 *
33640 */
33641
33642MenuBarDirective.$inject = ["$mdUtil", "$mdTheming"];
33643angular
33644  .module('material.components.menuBar')
33645  .directive('mdMenuBar', MenuBarDirective);
33646
33647/* @ngInject */
33648function MenuBarDirective($mdUtil, $mdTheming) {
33649  return {
33650    restrict: 'E',
33651    require: 'mdMenuBar',
33652    controller: 'MenuBarController',
33653
33654    compile: function compile(templateEl, templateAttrs) {
33655      if (!templateAttrs.ariaRole) {
33656        templateEl[0].setAttribute('role', 'menubar');
33657      }
33658      angular.forEach(templateEl[0].children, function(menuEl) {
33659        if (menuEl.nodeName == 'MD-MENU') {
33660          if (!menuEl.hasAttribute('md-position-mode')) {
33661            menuEl.setAttribute('md-position-mode', 'left bottom');
33662
33663            // Since we're in the compile function and actual `md-buttons` are not compiled yet,
33664            // we need to query for possible `md-buttons` as well.
33665            menuEl.querySelector('button, a, md-button').setAttribute('role', 'menuitem');
33666          }
33667          var contentEls = $mdUtil.nodesToArray(menuEl.querySelectorAll('md-menu-content'));
33668          angular.forEach(contentEls, function(contentEl) {
33669            contentEl.classList.add('md-menu-bar-menu');
33670            contentEl.classList.add('md-dense');
33671            if (!contentEl.hasAttribute('width')) {
33672              contentEl.setAttribute('width', 5);
33673            }
33674          });
33675        }
33676      });
33677
33678      // Mark the child menu items that they're inside a menu bar. This is necessary,
33679      // because mnMenuItem has special behaviour during compilation, depending on
33680      // whether it is inside a mdMenuBar. We can usually figure this out via the DOM,
33681      // however if a directive that uses documentFragment is applied to the child (e.g. ngRepeat),
33682      // the element won't have a parent and won't compile properly.
33683      templateEl.find('md-menu-item').addClass('md-in-menu-bar');
33684
33685      return function postLink(scope, el, attr, ctrl) {
33686        el.addClass('_md');     // private md component indicator for styling
33687        $mdTheming(scope, el);
33688        ctrl.init();
33689      };
33690    }
33691  };
33692
33693}
33694
33695})();
33696(function(){
33697"use strict";
33698
33699
33700angular
33701  .module('material.components.menuBar')
33702  .directive('mdMenuDivider', MenuDividerDirective);
33703
33704
33705function MenuDividerDirective() {
33706  return {
33707    restrict: 'E',
33708    compile: function(templateEl, templateAttrs) {
33709      if (!templateAttrs.role) {
33710        templateEl[0].setAttribute('role', 'separator');
33711      }
33712    }
33713  };
33714}
33715
33716})();
33717(function(){
33718"use strict";
33719
33720
33721MenuItemController.$inject = ["$scope", "$element", "$attrs"];
33722angular
33723  .module('material.components.menuBar')
33724  .controller('MenuItemController', MenuItemController);
33725
33726
33727/**
33728 * @ngInject
33729 */
33730function MenuItemController($scope, $element, $attrs) {
33731  this.$element = $element;
33732  this.$attrs = $attrs;
33733  this.$scope = $scope;
33734}
33735
33736MenuItemController.prototype.init = function(ngModel) {
33737  var $element = this.$element;
33738  var $attrs = this.$attrs;
33739
33740  this.ngModel = ngModel;
33741  if ($attrs.type == 'checkbox' || $attrs.type == 'radio') {
33742    this.mode  = $attrs.type;
33743    this.iconEl = $element[0].children[0];
33744    this.buttonEl = $element[0].children[1];
33745    if (ngModel) {
33746      // Clear ngAria set attributes
33747      this.initClickListeners();
33748    }
33749  }
33750};
33751
33752// ngAria auto sets attributes on a menu-item with a ngModel.
33753// We don't want this because our content (buttons) get the focus
33754// and set their own aria attributes appropritately. Having both
33755// breaks NVDA / JAWS. This undeoes ngAria's attrs.
33756MenuItemController.prototype.clearNgAria = function() {
33757  var el = this.$element[0];
33758  var clearAttrs = ['role', 'tabindex', 'aria-invalid', 'aria-checked'];
33759  angular.forEach(clearAttrs, function(attr) {
33760    el.removeAttribute(attr);
33761  });
33762};
33763
33764MenuItemController.prototype.initClickListeners = function() {
33765  var self = this;
33766  var ngModel = this.ngModel;
33767  var $scope = this.$scope;
33768  var $attrs = this.$attrs;
33769  var $element = this.$element;
33770  var mode = this.mode;
33771
33772  this.handleClick = angular.bind(this, this.handleClick);
33773
33774  var icon = this.iconEl;
33775  var button = angular.element(this.buttonEl);
33776  var handleClick = this.handleClick;
33777
33778  $attrs.$observe('disabled', setDisabled);
33779  setDisabled($attrs.disabled);
33780
33781  ngModel.$render = function render() {
33782    self.clearNgAria();
33783    if (isSelected()) {
33784      icon.style.display = '';
33785      button.attr('aria-checked', 'true');
33786    } else {
33787      icon.style.display = 'none';
33788      button.attr('aria-checked', 'false');
33789    }
33790  };
33791
33792  $scope.$$postDigest(ngModel.$render);
33793
33794  function isSelected() {
33795    if (mode == 'radio') {
33796      var val = $attrs.ngValue ? $scope.$eval($attrs.ngValue) : $attrs.value;
33797      return ngModel.$modelValue == val;
33798    } else {
33799      return ngModel.$modelValue;
33800    }
33801  }
33802
33803  function setDisabled(disabled) {
33804    if (disabled) {
33805      button.off('click', handleClick);
33806    } else {
33807      button.on('click', handleClick);
33808    }
33809  }
33810};
33811
33812MenuItemController.prototype.handleClick = function(e) {
33813  var mode = this.mode;
33814  var ngModel = this.ngModel;
33815  var $attrs = this.$attrs;
33816  var newVal;
33817  if (mode == 'checkbox') {
33818    newVal = !ngModel.$modelValue;
33819  } else if (mode == 'radio') {
33820    newVal = $attrs.ngValue ? this.$scope.$eval($attrs.ngValue) : $attrs.value;
33821  }
33822  ngModel.$setViewValue(newVal);
33823  ngModel.$render();
33824};
33825
33826})();
33827(function(){
33828"use strict";
33829
33830
33831MenuItemDirective.$inject = ["$mdUtil", "$mdConstant", "$$mdSvgRegistry"];
33832angular
33833  .module('material.components.menuBar')
33834  .directive('mdMenuItem', MenuItemDirective);
33835
33836 /* @ngInject */
33837function MenuItemDirective($mdUtil, $mdConstant, $$mdSvgRegistry) {
33838  return {
33839    controller: 'MenuItemController',
33840    require: ['mdMenuItem', '?ngModel'],
33841    priority: $mdConstant.BEFORE_NG_ARIA,
33842    compile: function(templateEl, templateAttrs) {
33843      var type = templateAttrs.type;
33844      var inMenuBarClass = 'md-in-menu-bar';
33845
33846      // Note: This allows us to show the `check` icon for the md-menu-bar items.
33847      // The `md-in-menu-bar` class is set by the mdMenuBar directive.
33848      if ((type == 'checkbox' || type == 'radio') && templateEl.hasClass(inMenuBarClass)) {
33849        var text = templateEl[0].textContent;
33850        var buttonEl = angular.element('<md-button type="button"></md-button>');
33851        var iconTemplate = '<md-icon md-svg-src="' + $$mdSvgRegistry.mdChecked + '"></md-icon>';
33852
33853        buttonEl.html(text);
33854        buttonEl.attr('tabindex', '0');
33855
33856        templateEl.html('');
33857        templateEl.append(angular.element(iconTemplate));
33858        templateEl.append(buttonEl);
33859        templateEl.addClass('md-indent').removeClass(inMenuBarClass);
33860
33861        setDefault('role', type == 'checkbox' ? 'menuitemcheckbox' : 'menuitemradio', buttonEl);
33862        moveAttrToButton('ng-disabled');
33863
33864      } else {
33865        setDefault('role', 'menuitem', templateEl[0].querySelector('md-button, button, a'));
33866      }
33867
33868
33869      return function(scope, el, attrs, ctrls) {
33870        var ctrl = ctrls[0];
33871        var ngModel = ctrls[1];
33872        ctrl.init(ngModel);
33873      };
33874
33875      function setDefault(attr, val, el) {
33876        el = el || templateEl;
33877        if (el instanceof angular.element) {
33878          el = el[0];
33879        }
33880        if (!el.hasAttribute(attr)) {
33881          el.setAttribute(attr, val);
33882        }
33883      }
33884
33885      function moveAttrToButton(attribute) {
33886        var attributes = $mdUtil.prefixer(attribute);
33887
33888        angular.forEach(attributes, function(attr) {
33889          if (templateEl[0].hasAttribute(attr)) {
33890            var val = templateEl[0].getAttribute(attr);
33891            buttonEl[0].setAttribute(attr, val);
33892            templateEl[0].removeAttribute(attr);
33893          }
33894        });
33895      }
33896    }
33897  };
33898}
33899
33900})();
33901(function(){
33902"use strict";
33903
33904/**
33905 * @ngdoc directive
33906 * @name mdProgressCircular
33907 * @module material.components.progressCircular
33908 * @restrict E
33909 *
33910 * @description
33911 * The circular progress directive is used to make loading content in your app as delightful and
33912 * painless as possible by minimizing the amount of visual change a user sees before they can view
33913 * and interact with content.
33914 *
33915 * For operations where the percentage of the operation completed can be determined, use a
33916 * determinate indicator. They give users a quick sense of how long an operation will take.
33917 *
33918 * For operations where the user is asked to wait a moment while something finishes up, and it’s
33919 * not necessary to expose what's happening behind the scenes and how long it will take, use an
33920 * indeterminate indicator.
33921 *
33922 * @param {string} md-mode Select from one of two modes: **'determinate'** and **'indeterminate'**.
33923 *
33924 * Note: if the `md-mode` value is set as undefined or specified as not 1 of the two (2) valid modes, then **'indeterminate'**
33925 * will be auto-applied as the mode.
33926 *
33927 * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute.
33928 * If `value=""` is also specified, however, then `md-mode="determinate"` would be auto-injected instead.
33929 * @param {number=} value In determinate mode, this number represents the percentage of the
33930 *     circular progress. Default: 0
33931 * @param {number=} md-diameter This specifies the diameter of the circular progress. The value
33932 * should be a pixel-size value (eg '100'). If this attribute is
33933 * not present then a default value of '50px' is assumed.
33934 *
33935 * @param {boolean=} ng-disabled Determines whether to disable the progress element.
33936 *
33937 * @usage
33938 * <hljs lang="html">
33939 * <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
33940 *
33941 * <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
33942 *
33943 * <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
33944 *
33945 * <md-progress-circular md-mode="indeterminate"></md-progress-circular>
33946 * </hljs>
33947 */
33948
33949MdProgressCircularDirective.$inject = ["$window", "$mdProgressCircular", "$mdTheming", "$mdUtil", "$interval", "$log"];
33950angular
33951  .module('material.components.progressCircular')
33952  .directive('mdProgressCircular', MdProgressCircularDirective);
33953
33954/* @ngInject */
33955function MdProgressCircularDirective($window, $mdProgressCircular, $mdTheming,
33956                                     $mdUtil, $interval, $log) {
33957
33958  // Note that this shouldn't use use $$rAF, because it can cause an infinite loop
33959  // in any tests that call $animate.flush.
33960  var rAF = $window.requestAnimationFrame ||
33961            $window.webkitRequestAnimationFrame ||
33962            angular.noop;
33963
33964  var cAF = $window.cancelAnimationFrame ||
33965            $window.webkitCancelAnimationFrame ||
33966            $window.webkitCancelRequestAnimationFrame ||
33967            angular.noop;
33968
33969  var MODE_DETERMINATE = 'determinate';
33970  var MODE_INDETERMINATE = 'indeterminate';
33971  var DISABLED_CLASS = '_md-progress-circular-disabled';
33972  var INDETERMINATE_CLASS = 'md-mode-indeterminate';
33973
33974  return {
33975    restrict: 'E',
33976    scope: {
33977      value: '@',
33978      mdDiameter: '@',
33979      mdMode: '@'
33980    },
33981    template:
33982      '<svg xmlns="http://www.w3.org/2000/svg">' +
33983        '<path fill="none"/>' +
33984      '</svg>',
33985    compile: function(element, attrs) {
33986      element.attr({
33987        'aria-valuemin': 0,
33988        'aria-valuemax': 100,
33989        'role': 'progressbar'
33990      });
33991
33992      if (angular.isUndefined(attrs.mdMode)) {
33993        var mode = attrs.hasOwnProperty('value') ? MODE_DETERMINATE : MODE_INDETERMINATE;
33994        attrs.$set('mdMode', mode);
33995      } else {
33996        attrs.$set('mdMode', attrs.mdMode.trim());
33997      }
33998
33999      return MdProgressCircularLink;
34000    }
34001  };
34002
34003  function MdProgressCircularLink(scope, element, attrs) {
34004    var node = element[0];
34005    var svg = angular.element(node.querySelector('svg'));
34006    var path = angular.element(node.querySelector('path'));
34007    var startIndeterminate = $mdProgressCircular.startIndeterminate;
34008    var endIndeterminate = $mdProgressCircular.endIndeterminate;
34009    var iterationCount = 0;
34010    var lastAnimationId = 0;
34011    var lastDrawFrame;
34012    var interval;
34013
34014    $mdTheming(element);
34015    element.toggleClass(DISABLED_CLASS, attrs.hasOwnProperty('disabled'));
34016
34017    // If the mode is indeterminate, it doesn't need to
34018    // wait for the next digest. It can start right away.
34019    if(scope.mdMode === MODE_INDETERMINATE){
34020      startIndeterminateAnimation();
34021    }
34022
34023    scope.$on('$destroy', function(){
34024      cleanupIndeterminateAnimation();
34025
34026      if (lastDrawFrame) {
34027        cAF(lastDrawFrame);
34028      }
34029    });
34030
34031    scope.$watchGroup(['value', 'mdMode', function() {
34032      var isDisabled = node.disabled;
34033
34034      // Sometimes the browser doesn't return a boolean, in
34035      // which case we should check whether the attribute is
34036      // present.
34037      if (isDisabled === true || isDisabled === false){
34038        return isDisabled;
34039      }
34040
34041      return angular.isDefined(element.attr('disabled'));
34042    }], function(newValues, oldValues) {
34043      var mode = newValues[1];
34044      var isDisabled = newValues[2];
34045      var wasDisabled = oldValues[2];
34046
34047      if (isDisabled !== wasDisabled) {
34048        element.toggleClass(DISABLED_CLASS, !!isDisabled);
34049      }
34050
34051      if (isDisabled) {
34052        cleanupIndeterminateAnimation();
34053      } else {
34054        if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
34055          mode = MODE_INDETERMINATE;
34056          attrs.$set('mdMode', mode);
34057        }
34058
34059        if (mode === MODE_INDETERMINATE) {
34060          startIndeterminateAnimation();
34061        } else {
34062          var newValue = clamp(newValues[0]);
34063
34064          cleanupIndeterminateAnimation();
34065
34066          element.attr('aria-valuenow', newValue);
34067          renderCircle(clamp(oldValues[0]), newValue);
34068        }
34069      }
34070
34071    });
34072
34073    // This is in a separate watch in order to avoid layout, unless
34074    // the value has actually changed.
34075    scope.$watch('mdDiameter', function(newValue) {
34076      var diameter = getSize(newValue);
34077      var strokeWidth = getStroke(diameter);
34078      var value = clamp(scope.value);
34079      var transformOrigin = (diameter / 2) + 'px';
34080      var dimensions = {
34081        width: diameter + 'px',
34082        height: diameter + 'px'
34083      };
34084
34085      // The viewBox has to be applied via setAttribute, because it is
34086      // case-sensitive. If jQuery is included in the page, `.attr` lowercases
34087      // all attribute names.
34088      svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);
34089
34090      // Usually viewBox sets the dimensions for the SVG, however that doesn't
34091      // seem to be the case on IE10.
34092      // Important! The transform origin has to be set from here and it has to
34093      // be in the format of "Ypx Ypx Ypx", otherwise the rotation wobbles in
34094      // IE and Edge, because they don't account for the stroke width when
34095      // rotating. Also "center" doesn't help in this case, it has to be a
34096      // precise value.
34097      svg
34098        .css(dimensions)
34099        .css('transform-origin', transformOrigin + ' ' + transformOrigin + ' ' + transformOrigin);
34100
34101      element.css(dimensions);
34102
34103      path.attr('stroke-width', strokeWidth);
34104      path.attr('stroke-linecap', 'square');
34105      if (scope.mdMode == MODE_INDETERMINATE) {
34106        path.attr('d', getSvgArc(diameter, strokeWidth, true));
34107        path.attr('stroke-dasharray', (diameter - strokeWidth) * $window.Math.PI * 0.75);
34108        path.attr('stroke-dashoffset', getDashLength(diameter, strokeWidth, 1, 75));
34109      } else {
34110        path.attr('d', getSvgArc(diameter, strokeWidth, false));
34111        path.attr('stroke-dasharray', (diameter - strokeWidth) * $window.Math.PI);
34112        path.attr('stroke-dashoffset', getDashLength(diameter, strokeWidth, 0, 100));
34113        renderCircle(value, value);
34114      }
34115
34116    });
34117
34118    function renderCircle(animateFrom, animateTo, easing, duration, iterationCount, maxValue) {
34119      var id = ++lastAnimationId;
34120      var startTime = $mdUtil.now();
34121      var changeInValue = animateTo - animateFrom;
34122      var diameter = getSize(scope.mdDiameter);
34123      var strokeWidth = getStroke(diameter);
34124      var ease = easing || $mdProgressCircular.easeFn;
34125      var animationDuration = duration || $mdProgressCircular.duration;
34126      var rotation = -90 * (iterationCount || 0);
34127      var dashLimit = maxValue || 100;
34128
34129      // No need to animate it if the values are the same
34130      if (animateTo === animateFrom) {
34131        renderFrame(animateTo);
34132      } else {
34133        lastDrawFrame = rAF(function animation() {
34134          var currentTime = $window.Math.max(0, $window.Math.min($mdUtil.now() - startTime, animationDuration));
34135
34136          renderFrame(ease(currentTime, animateFrom, changeInValue, animationDuration));
34137
34138          // Do not allow overlapping animations
34139          if (id === lastAnimationId && currentTime < animationDuration) {
34140            lastDrawFrame = rAF(animation);
34141          }
34142        });
34143      }
34144
34145      function renderFrame(value) {
34146        path.attr('stroke-dashoffset', getDashLength(diameter, strokeWidth, value, dashLimit));
34147        path.attr('transform','rotate(' + (rotation) + ' ' + diameter/2 + ' ' + diameter/2 + ')');
34148      }
34149    }
34150
34151    function animateIndeterminate() {
34152      renderCircle(
34153        startIndeterminate,
34154        endIndeterminate,
34155        $mdProgressCircular.easeFnIndeterminate,
34156        $mdProgressCircular.durationIndeterminate,
34157        iterationCount,
34158        75
34159      );
34160
34161      // The %4 technically isn't necessary, but it keeps the rotation
34162      // under 360, instead of becoming a crazy large number.
34163      iterationCount = ++iterationCount % 4;
34164
34165    }
34166
34167    function startIndeterminateAnimation() {
34168      if (!interval) {
34169        // Note that this interval isn't supposed to trigger a digest.
34170        interval = $interval(
34171          animateIndeterminate,
34172          $mdProgressCircular.durationIndeterminate,
34173          0,
34174          false
34175        );
34176
34177        animateIndeterminate();
34178
34179        element
34180          .addClass(INDETERMINATE_CLASS)
34181          .removeAttr('aria-valuenow');
34182      }
34183    }
34184
34185    function cleanupIndeterminateAnimation() {
34186      if (interval) {
34187        $interval.cancel(interval);
34188        interval = null;
34189        element.removeClass(INDETERMINATE_CLASS);
34190      }
34191    }
34192  }
34193
34194  /**
34195   * Returns SVG path data for progress circle
34196   * Syntax spec: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
34197   *
34198   * @param {number} diameter Diameter of the container.
34199   * @param {number} strokeWidth Stroke width to be used when drawing circle
34200   * @param {boolean} indeterminate Use if progress circle will be used for indeterminate
34201   *
34202   * @returns {string} String representation of an SVG arc.
34203   */
34204  function getSvgArc(diameter, strokeWidth, indeterminate) {
34205    var radius = diameter / 2;
34206    var offset = strokeWidth / 2;
34207    var start = radius + ',' + offset; // ie: (25, 2.5) or 12 o'clock
34208    var end = offset + ',' + radius;   // ie: (2.5, 25) or  9 o'clock
34209    var arcRadius = radius - offset;
34210    return 'M' + start
34211         + 'A' + arcRadius + ',' + arcRadius + ' 0 1 1 ' + end // 75% circle
34212         + (indeterminate ? '' : 'A' + arcRadius + ',' + arcRadius + ' 0 0 1 ' + start); // loop to start
34213  }
34214
34215  /**
34216   * Return stroke length for progress circle
34217   *
34218   * @param {number} diameter Diameter of the container.
34219   * @param {number} strokeWidth Stroke width to be used when drawing circle
34220   * @param {number} value Percentage of circle (between 0 and 100)
34221   * @param {number} limit Max percentage for circle
34222   *
34223   * @returns {number} Stroke length for progres circle
34224   */
34225  function getDashLength(diameter, strokeWidth, value, limit) {
34226    return (diameter - strokeWidth) * $window.Math.PI * ( (3 * (limit || 100) / 100) - (value/100) );
34227  }
34228
34229  /**
34230   * Limits a value between 0 and 100.
34231   */
34232  function clamp(value) {
34233    return $window.Math.max(0, $window.Math.min(value || 0, 100));
34234  }
34235
34236  /**
34237   * Determines the size of a progress circle, based on the provided
34238   * value in the following formats: `X`, `Ypx`, `Z%`.
34239   */
34240  function getSize(value) {
34241    var defaultValue = $mdProgressCircular.progressSize;
34242
34243    if (value) {
34244      var parsed = parseFloat(value);
34245
34246      if (value.lastIndexOf('%') === value.length - 1) {
34247        parsed = (parsed / 100) * defaultValue;
34248      }
34249
34250      return parsed;
34251    }
34252
34253    return defaultValue;
34254  }
34255
34256  /**
34257   * Determines the circle's stroke width, based on
34258   * the provided diameter.
34259   */
34260  function getStroke(diameter) {
34261    return $mdProgressCircular.strokeWidth / 100 * diameter;
34262  }
34263
34264}
34265
34266})();
34267(function(){
34268"use strict";
34269
34270/**
34271 * @ngdoc service
34272 * @name $mdProgressCircular
34273 * @module material.components.progressCircular
34274 *
34275 * @description
34276 * Allows the user to specify the default options for the `progressCircular` directive.
34277 *
34278 * @property {number} progressSize Diameter of the progress circle in pixels.
34279 * @property {number} strokeWidth Width of the circle's stroke as a percentage of the circle's size.
34280 * @property {number} duration Length of the circle animation in milliseconds.
34281 * @property {function} easeFn Default easing animation function.
34282 * @property {object} easingPresets Collection of pre-defined easing functions.
34283 *
34284 * @property {number} durationIndeterminate Duration of the indeterminate animation.
34285 * @property {number} startIndeterminate Indeterminate animation start point.
34286 * @property {number} endIndeterminate Indeterminate animation end point.
34287 * @property {function} easeFnIndeterminate Easing function to be used when animating
34288 * between the indeterminate values.
34289 *
34290 * @property {(function(object): object)} configure Used to modify the default options.
34291 *
34292 * @usage
34293 * <hljs lang="js">
34294 *   myAppModule.config(function($mdProgressCircularProvider) {
34295 *
34296 *     // Example of changing the default progress options.
34297 *     $mdProgressCircularProvider.configure({
34298 *       progressSize: 100,
34299 *       strokeWidth: 20,
34300 *       duration: 800
34301 *     });
34302 * });
34303 * </hljs>
34304 *
34305 */
34306
34307angular
34308  .module('material.components.progressCircular')
34309  .provider("$mdProgressCircular", MdProgressCircularProvider);
34310
34311function MdProgressCircularProvider() {
34312  var progressConfig = {
34313    progressSize: 50,
34314    strokeWidth: 10,
34315    duration: 100,
34316    easeFn: linearEase,
34317
34318    durationIndeterminate: 1333,
34319    startIndeterminate: 1,
34320    endIndeterminate: 149,
34321    easeFnIndeterminate: materialEase,
34322
34323    easingPresets: {
34324      linearEase: linearEase,
34325      materialEase: materialEase
34326    }
34327  };
34328
34329  return {
34330    configure: function(options) {
34331      progressConfig = angular.extend(progressConfig, options || {});
34332      return progressConfig;
34333    },
34334    $get: function() { return progressConfig; }
34335  };
34336
34337  function linearEase(t, b, c, d) {
34338    return c * t / d + b;
34339  }
34340
34341  function materialEase(t, b, c, d) {
34342    // via http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
34343    // with settings of [0, 0, 1, 1]
34344    var ts = (t /= d) * t;
34345    var tc = ts * t;
34346    return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
34347  }
34348}
34349
34350})();
34351(function(){
34352"use strict";
34353
34354/**
34355 * @ngdoc directive
34356 * @name mdTab
34357 * @module material.components.tabs
34358 *
34359 * @restrict E
34360 *
34361 * @description
34362 * Use the `<md-tab>` a nested directive used within `<md-tabs>` to specify a tab with a **label** and optional *view content*.
34363 *
34364 * If the `label` attribute is not specified, then an optional `<md-tab-label>` tag can be used to specify more
34365 * complex tab header markup. If neither the **label** nor the **md-tab-label** are specified, then the nested
34366 * markup of the `<md-tab>` is used as the tab header markup.
34367 *
34368 * Please note that if you use `<md-tab-label>`, your content **MUST** be wrapped in the `<md-tab-body>` tag.  This
34369 * is to define a clear separation between the tab content and the tab label.
34370 *
34371 * This container is used by the TabsController to show/hide the active tab's content view. This synchronization is
34372 * automatically managed by the internal TabsController whenever the tab selection changes. Selection changes can
34373 * be initiated via data binding changes, programmatic invocation, or user gestures.
34374 *
34375 * @param {string=} label Optional attribute to specify a simple string as the tab label
34376 * @param {boolean=} ng-disabled If present and expression evaluates to truthy, disabled tab selection.
34377 * @param {expression=} md-on-deselect Expression to be evaluated after the tab has been de-selected.
34378 * @param {expression=} md-on-select Expression to be evaluated after the tab has been selected.
34379 * @param {boolean=} md-active When true, sets the active tab.  Note: There can only be one active tab at a time.
34380 *
34381 *
34382 * @usage
34383 *
34384 * <hljs lang="html">
34385 * <md-tab label="" ng-disabled md-on-select="" md-on-deselect="" >
34386 *   <h3>My Tab content</h3>
34387 * </md-tab>
34388 *
34389 * <md-tab >
34390 *   <md-tab-label>
34391 *     <h3>My Tab content</h3>
34392 *   </md-tab-label>
34393 *   <md-tab-body>
34394 *     <p>
34395 *       Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
34396 *       totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
34397 *       dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
34398 *       sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
34399 *     </p>
34400 *   </md-tab-body>
34401 * </md-tab>
34402 * </hljs>
34403 *
34404 */
34405angular
34406    .module('material.components.tabs')
34407    .directive('mdTab', MdTab);
34408
34409function MdTab () {
34410  return {
34411    require:  '^?mdTabs',
34412    terminal: true,
34413    compile:  function (element, attr) {
34414      var label = firstChild(element, 'md-tab-label'),
34415          body  = firstChild(element, 'md-tab-body');
34416
34417      if (label.length === 0) {
34418        label = angular.element('<md-tab-label></md-tab-label>');
34419        if (attr.label) label.text(attr.label);
34420        else label.append(element.contents());
34421
34422        if (body.length === 0) {
34423          var contents = element.contents().detach();
34424          body         = angular.element('<md-tab-body></md-tab-body>');
34425          body.append(contents);
34426        }
34427      }
34428
34429      element.append(label);
34430      if (body.html()) element.append(body);
34431
34432      return postLink;
34433    },
34434    scope:    {
34435      active:   '=?mdActive',
34436      disabled: '=?ngDisabled',
34437      select:   '&?mdOnSelect',
34438      deselect: '&?mdOnDeselect'
34439    }
34440  };
34441
34442  function postLink (scope, element, attr, ctrl) {
34443    if (!ctrl) return;
34444    var index = ctrl.getTabElementIndex(element),
34445        body  = firstChild(element, 'md-tab-body').remove(),
34446        label = firstChild(element, 'md-tab-label').remove(),
34447        data  = ctrl.insertTab({
34448          scope:    scope,
34449          parent:   scope.$parent,
34450          index:    index,
34451          element:  element,
34452          template: body.html(),
34453          label:    label.html()
34454        }, index);
34455
34456    scope.select   = scope.select || angular.noop;
34457    scope.deselect = scope.deselect || angular.noop;
34458
34459    scope.$watch('active', function (active) { if (active) ctrl.select(data.getIndex(), true); });
34460    scope.$watch('disabled', function () { ctrl.refreshIndex(); });
34461    scope.$watch(
34462        function () {
34463          return ctrl.getTabElementIndex(element);
34464        },
34465        function (newIndex) {
34466          data.index = newIndex;
34467          ctrl.updateTabOrder();
34468        }
34469    );
34470    scope.$on('$destroy', function () { ctrl.removeTab(data); });
34471  }
34472
34473  function firstChild (element, tagName) {
34474    var children = element[0].children;
34475    for (var i = 0, len = children.length; i < len; i++) {
34476      var child = children[i];
34477      if (child.tagName === tagName.toUpperCase()) return angular.element(child);
34478    }
34479    return angular.element();
34480  }
34481}
34482
34483})();
34484(function(){
34485"use strict";
34486
34487angular
34488    .module('material.components.tabs')
34489    .directive('mdTabItem', MdTabItem);
34490
34491function MdTabItem () {
34492  return {
34493    require: '^?mdTabs',
34494    link:    function link (scope, element, attr, ctrl) {
34495      if (!ctrl) return;
34496      ctrl.attachRipple(scope, element);
34497    }
34498  };
34499}
34500
34501})();
34502(function(){
34503"use strict";
34504
34505angular
34506    .module('material.components.tabs')
34507    .directive('mdTabLabel', MdTabLabel);
34508
34509function MdTabLabel () {
34510  return { terminal: true };
34511}
34512
34513
34514})();
34515(function(){
34516"use strict";
34517
34518
34519MdTabScroll.$inject = ["$parse"];angular.module('material.components.tabs')
34520    .directive('mdTabScroll', MdTabScroll);
34521
34522function MdTabScroll ($parse) {
34523  return {
34524    restrict: 'A',
34525    compile: function ($element, attr) {
34526      var fn = $parse(attr.mdTabScroll, null, true);
34527      return function ngEventHandler (scope, element) {
34528        element.on('mousewheel', function (event) {
34529          scope.$apply(function () { fn(scope, { $event: event }); });
34530        });
34531      };
34532    }
34533  };
34534}
34535
34536})();
34537(function(){
34538"use strict";
34539
34540
34541MdTabsController.$inject = ["$scope", "$element", "$window", "$mdConstant", "$mdTabInkRipple", "$mdUtil", "$animateCss", "$attrs", "$compile", "$mdTheming", "$mdInteraction"];angular
34542    .module('material.components.tabs')
34543    .controller('MdTabsController', MdTabsController);
34544
34545/**
34546 * @ngInject
34547 */
34548function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipple, $mdUtil,
34549                           $animateCss, $attrs, $compile, $mdTheming, $mdInteraction) {
34550  // define private properties
34551  var ctrl      = this,
34552      locked    = false,
34553      elements  = getElements(),
34554      queue     = [],
34555      destroyed = false,
34556      loaded    = false;
34557
34558
34559  // Define public methods
34560  ctrl.$onInit            = $onInit;
34561  ctrl.updatePagination   = $mdUtil.debounce(updatePagination, 100);
34562  ctrl.redirectFocus      = redirectFocus;
34563  ctrl.attachRipple       = attachRipple;
34564  ctrl.insertTab          = insertTab;
34565  ctrl.removeTab          = removeTab;
34566  ctrl.select             = select;
34567  ctrl.scroll             = scroll;
34568  ctrl.nextPage           = nextPage;
34569  ctrl.previousPage       = previousPage;
34570  ctrl.keydown            = keydown;
34571  ctrl.canPageForward     = canPageForward;
34572  ctrl.canPageBack        = canPageBack;
34573  ctrl.refreshIndex       = refreshIndex;
34574  ctrl.incrementIndex     = incrementIndex;
34575  ctrl.getTabElementIndex = getTabElementIndex;
34576  ctrl.updateInkBarStyles = $mdUtil.debounce(updateInkBarStyles, 100);
34577  ctrl.updateTabOrder     = $mdUtil.debounce(updateTabOrder, 100);
34578  ctrl.getFocusedTabId    = getFocusedTabId;
34579
34580  // For Angular 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
34581  // manually call the $onInit hook.
34582  if (angular.version.major === 1 && angular.version.minor <= 4) {
34583    this.$onInit();
34584  }
34585
34586  /**
34587   * Angular Lifecycle hook for newer Angular versions.
34588   * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
34589   */
34590  function $onInit() {
34591    // Define one-way bindings
34592    defineOneWayBinding('stretchTabs', handleStretchTabs);
34593
34594    // Define public properties with change handlers
34595    defineProperty('focusIndex', handleFocusIndexChange, ctrl.selectedIndex || 0);
34596    defineProperty('offsetLeft', handleOffsetChange, 0);
34597    defineProperty('hasContent', handleHasContent, false);
34598    defineProperty('maxTabWidth', handleMaxTabWidth, getMaxTabWidth());
34599    defineProperty('shouldPaginate', handleShouldPaginate, false);
34600
34601    // Define boolean attributes
34602    defineBooleanAttribute('noInkBar', handleInkBar);
34603    defineBooleanAttribute('dynamicHeight', handleDynamicHeight);
34604    defineBooleanAttribute('noPagination');
34605    defineBooleanAttribute('swipeContent');
34606    defineBooleanAttribute('noDisconnect');
34607    defineBooleanAttribute('autoselect');
34608    defineBooleanAttribute('noSelectClick');
34609    defineBooleanAttribute('centerTabs', handleCenterTabs, false);
34610    defineBooleanAttribute('enableDisconnect');
34611
34612    // Define public properties
34613    ctrl.scope             = $scope;
34614    ctrl.parent            = $scope.$parent;
34615    ctrl.tabs              = [];
34616    ctrl.lastSelectedIndex = null;
34617    ctrl.hasFocus          = false;
34618    ctrl.styleTabItemFocus = false;
34619    ctrl.shouldCenterTabs  = shouldCenterTabs();
34620    ctrl.tabContentPrefix  = 'tab-content-';
34621
34622    // Setup the tabs controller after all bindings are available.
34623    setupTabsController();
34624  }
34625
34626  /**
34627   * Perform setup for the controller, setup events and watcher(s)
34628   */
34629  function setupTabsController () {
34630    ctrl.selectedIndex = ctrl.selectedIndex || 0;
34631    compileTemplate();
34632    configureWatchers();
34633    bindEvents();
34634    $mdTheming($element);
34635    $mdUtil.nextTick(function () {
34636      // Note that the element references need to be updated, because certain "browsers"
34637      // (IE/Edge) lose them and start throwing "Invalid calling object" errors, when we
34638      // compile the element contents down in `compileElement`.
34639      elements = getElements();
34640      updateHeightFromContent();
34641      adjustOffset();
34642      updateInkBarStyles();
34643      ctrl.tabs[ ctrl.selectedIndex ] && ctrl.tabs[ ctrl.selectedIndex ].scope.select();
34644      loaded = true;
34645      updatePagination();
34646    });
34647  }
34648
34649  /**
34650   * Compiles the template provided by the user.  This is passed as an attribute from the tabs
34651   * directive's template function.
34652   */
34653  function compileTemplate () {
34654    var template = $attrs.$mdTabsTemplate,
34655        element  = angular.element($element[0].querySelector('md-tab-data'));
34656
34657    element.html(template);
34658    $compile(element.contents())(ctrl.parent);
34659    delete $attrs.$mdTabsTemplate;
34660  }
34661
34662  /**
34663   * Binds events used by the tabs component.
34664   */
34665  function bindEvents () {
34666    angular.element($window).on('resize', handleWindowResize);
34667    $scope.$on('$destroy', cleanup);
34668  }
34669
34670  /**
34671   * Configure watcher(s) used by Tabs
34672   */
34673  function configureWatchers () {
34674    $scope.$watch('$mdTabsCtrl.selectedIndex', handleSelectedIndexChange);
34675  }
34676
34677  /**
34678   * Creates a one-way binding manually rather than relying on Angular's isolated scope
34679   * @param key
34680   * @param handler
34681   */
34682  function defineOneWayBinding (key, handler) {
34683    var attr = $attrs.$normalize('md-' + key);
34684    if (handler) defineProperty(key, handler);
34685    $attrs.$observe(attr, function (newValue) { ctrl[ key ] = newValue; });
34686  }
34687
34688  /**
34689   * Defines boolean attributes with default value set to true.  (ie. md-stretch-tabs with no value
34690   * will be treated as being truthy)
34691   * @param key
34692   * @param handler
34693   */
34694  function defineBooleanAttribute (key, handler) {
34695    var attr = $attrs.$normalize('md-' + key);
34696    if (handler) defineProperty(key, handler);
34697    if ($attrs.hasOwnProperty(attr)) updateValue($attrs[attr]);
34698    $attrs.$observe(attr, updateValue);
34699    function updateValue (newValue) {
34700      ctrl[ key ] = newValue !== 'false';
34701    }
34702  }
34703
34704  /**
34705   * Remove any events defined by this controller
34706   */
34707  function cleanup () {
34708    destroyed = true;
34709    angular.element($window).off('resize', handleWindowResize);
34710  }
34711
34712  // Change handlers
34713
34714  /**
34715   * Toggles stretch tabs class and updates inkbar when tab stretching changes
34716   * @param stretchTabs
34717   */
34718  function handleStretchTabs (stretchTabs) {
34719    var elements = getElements();
34720    angular.element(elements.wrapper).toggleClass('md-stretch-tabs', shouldStretchTabs());
34721    updateInkBarStyles();
34722  }
34723
34724  function handleCenterTabs (newValue) {
34725    ctrl.shouldCenterTabs = shouldCenterTabs();
34726  }
34727
34728  function handleMaxTabWidth (newWidth, oldWidth) {
34729    if (newWidth !== oldWidth) {
34730      var elements = getElements();
34731
34732      angular.forEach(elements.tabs, function(tab) {
34733        tab.style.maxWidth = newWidth + 'px';
34734      });
34735      $mdUtil.nextTick(ctrl.updateInkBarStyles);
34736    }
34737  }
34738
34739  function handleShouldPaginate (newValue, oldValue) {
34740    if (newValue !== oldValue) {
34741      ctrl.maxTabWidth      = getMaxTabWidth();
34742      ctrl.shouldCenterTabs = shouldCenterTabs();
34743      $mdUtil.nextTick(function () {
34744        ctrl.maxTabWidth = getMaxTabWidth();
34745        adjustOffset(ctrl.selectedIndex);
34746      });
34747    }
34748  }
34749
34750  /**
34751   * Add/remove the `md-no-tab-content` class depending on `ctrl.hasContent`
34752   * @param hasContent
34753   */
34754  function handleHasContent (hasContent) {
34755    $element[ hasContent ? 'removeClass' : 'addClass' ]('md-no-tab-content');
34756  }
34757
34758  /**
34759   * Apply ctrl.offsetLeft to the paging element when it changes
34760   * @param left
34761   */
34762  function handleOffsetChange (left) {
34763    var elements = getElements();
34764    var newValue = ctrl.shouldCenterTabs ? '' : '-' + left + 'px';
34765
34766    angular.element(elements.paging).css($mdConstant.CSS.TRANSFORM, 'translate3d(' + newValue + ', 0, 0)');
34767    $scope.$broadcast('$mdTabsPaginationChanged');
34768  }
34769
34770  /**
34771   * Update the UI whenever `ctrl.focusIndex` is updated
34772   * @param newIndex
34773   * @param oldIndex
34774   */
34775  function handleFocusIndexChange (newIndex, oldIndex) {
34776    if (newIndex === oldIndex) return;
34777    if (!getElements().tabs[ newIndex ]) return;
34778    adjustOffset();
34779    redirectFocus();
34780  }
34781
34782  /**
34783   * Update the UI whenever the selected index changes. Calls user-defined select/deselect methods.
34784   * @param newValue
34785   * @param oldValue
34786   */
34787  function handleSelectedIndexChange (newValue, oldValue) {
34788    if (newValue === oldValue) return;
34789
34790    ctrl.selectedIndex     = getNearestSafeIndex(newValue);
34791    ctrl.lastSelectedIndex = oldValue;
34792    ctrl.updateInkBarStyles();
34793    updateHeightFromContent();
34794    adjustOffset(newValue);
34795    $scope.$broadcast('$mdTabsChanged');
34796    ctrl.tabs[ oldValue ] && ctrl.tabs[ oldValue ].scope.deselect();
34797    ctrl.tabs[ newValue ] && ctrl.tabs[ newValue ].scope.select();
34798  }
34799
34800  function getTabElementIndex(tabEl){
34801    var tabs = $element[0].getElementsByTagName('md-tab');
34802    return Array.prototype.indexOf.call(tabs, tabEl[0]);
34803  }
34804
34805  /**
34806   * Queues up a call to `handleWindowResize` when a resize occurs while the tabs component is
34807   * hidden.
34808   */
34809  function handleResizeWhenVisible () {
34810    // if there is already a watcher waiting for resize, do nothing
34811    if (handleResizeWhenVisible.watcher) return;
34812    // otherwise, we will abuse the $watch function to check for visible
34813    handleResizeWhenVisible.watcher = $scope.$watch(function () {
34814      // since we are checking for DOM size, we use $mdUtil.nextTick() to wait for after the DOM updates
34815      $mdUtil.nextTick(function () {
34816        // if the watcher has already run (ie. multiple digests in one cycle), do nothing
34817        if (!handleResizeWhenVisible.watcher) return;
34818
34819        if ($element.prop('offsetParent')) {
34820          handleResizeWhenVisible.watcher();
34821          handleResizeWhenVisible.watcher = null;
34822
34823          handleWindowResize();
34824        }
34825      }, false);
34826    });
34827  }
34828
34829  // Event handlers / actions
34830
34831  /**
34832   * Handle user keyboard interactions
34833   * @param event
34834   */
34835  function keydown (event) {
34836    switch (event.keyCode) {
34837      case $mdConstant.KEY_CODE.LEFT_ARROW:
34838        event.preventDefault();
34839        incrementIndex(-1, true);
34840        break;
34841      case $mdConstant.KEY_CODE.RIGHT_ARROW:
34842        event.preventDefault();
34843        incrementIndex(1, true);
34844        break;
34845      case $mdConstant.KEY_CODE.SPACE:
34846      case $mdConstant.KEY_CODE.ENTER:
34847        event.preventDefault();
34848        if (!locked) select(ctrl.focusIndex);
34849        break;
34850    }
34851  }
34852
34853  /**
34854   * Update the selected index. Triggers a click event on the original `md-tab` element in order
34855   * to fire user-added click events if canSkipClick or `md-no-select-click` are false.
34856   * @param index
34857   * @param canSkipClick Optionally allow not firing the click event if `md-no-select-click` is also true.
34858   */
34859  function select (index, canSkipClick) {
34860    if (!locked) ctrl.focusIndex = ctrl.selectedIndex = index;
34861    // skip the click event if noSelectClick is enabled
34862    if (canSkipClick && ctrl.noSelectClick) return;
34863    // nextTick is required to prevent errors in user-defined click events
34864    $mdUtil.nextTick(function () {
34865      ctrl.tabs[ index ].element.triggerHandler('click');
34866    }, false);
34867  }
34868
34869  /**
34870   * When pagination is on, this makes sure the selected index is in view.
34871   * @param event
34872   */
34873  function scroll (event) {
34874    if (!ctrl.shouldPaginate) return;
34875    event.preventDefault();
34876    ctrl.offsetLeft = fixOffset(ctrl.offsetLeft - event.wheelDelta);
34877  }
34878
34879  /**
34880   * Slides the tabs over approximately one page forward.
34881   */
34882  function nextPage () {
34883    var elements = getElements();
34884    var viewportWidth = elements.canvas.clientWidth,
34885        totalWidth    = viewportWidth + ctrl.offsetLeft,
34886        i, tab;
34887    for (i = 0; i < elements.tabs.length; i++) {
34888      tab = elements.tabs[ i ];
34889      if (tab.offsetLeft + tab.offsetWidth > totalWidth) break;
34890    }
34891
34892    if (viewportWidth > tab.offsetWidth) {
34893      //Canvas width *greater* than tab width: usual positioning
34894      ctrl.offsetLeft = fixOffset(tab.offsetLeft);
34895    } else {
34896      /**
34897       * Canvas width *smaller* than tab width: positioning at the *end* of current tab to let
34898       * pagination "for loop" to proceed correctly on next tab when nextPage() is called again
34899       */
34900      ctrl.offsetLeft = fixOffset(tab.offsetLeft + (tab.offsetWidth - viewportWidth + 1));
34901    }
34902  }
34903
34904  /**
34905   * Slides the tabs over approximately one page backward.
34906   */
34907  function previousPage () {
34908    var i, tab, elements = getElements();
34909
34910    for (i = 0; i < elements.tabs.length; i++) {
34911      tab = elements.tabs[ i ];
34912      if (tab.offsetLeft + tab.offsetWidth >= ctrl.offsetLeft) break;
34913    }
34914
34915    if (elements.canvas.clientWidth > tab.offsetWidth) {
34916      //Canvas width *greater* than tab width: usual positioning
34917      ctrl.offsetLeft = fixOffset(tab.offsetLeft + tab.offsetWidth - elements.canvas.clientWidth);
34918    } else {
34919      /**
34920       * Canvas width *smaller* than tab width: positioning at the *beginning* of current tab to let
34921       * pagination "for loop" to break correctly on previous tab when previousPage() is called again
34922       */
34923      ctrl.offsetLeft = fixOffset(tab.offsetLeft);
34924    }
34925  }
34926
34927  /**
34928   * Update size calculations when the window is resized.
34929   */
34930  function handleWindowResize () {
34931    ctrl.lastSelectedIndex = ctrl.selectedIndex;
34932    ctrl.offsetLeft        = fixOffset(ctrl.offsetLeft);
34933    $mdUtil.nextTick(function () {
34934      ctrl.updateInkBarStyles();
34935      updatePagination();
34936    });
34937  }
34938
34939  function handleInkBar (hide) {
34940    angular.element(getElements().inkBar).toggleClass('ng-hide', hide);
34941  }
34942
34943  /**
34944   * Toggle dynamic height class when value changes
34945   * @param value
34946   */
34947  function handleDynamicHeight (value) {
34948    $element.toggleClass('md-dynamic-height', value);
34949  }
34950
34951  /**
34952   * Remove a tab from the data and select the nearest valid tab.
34953   * @param tabData
34954   */
34955  function removeTab (tabData) {
34956    if (destroyed) return;
34957    var selectedIndex = ctrl.selectedIndex,
34958        tab           = ctrl.tabs.splice(tabData.getIndex(), 1)[ 0 ];
34959    refreshIndex();
34960    // when removing a tab, if the selected index did not change, we have to manually trigger the
34961    //   tab select/deselect events
34962    if (ctrl.selectedIndex === selectedIndex) {
34963      tab.scope.deselect();
34964      ctrl.tabs[ ctrl.selectedIndex ] && ctrl.tabs[ ctrl.selectedIndex ].scope.select();
34965    }
34966    $mdUtil.nextTick(function () {
34967      updatePagination();
34968      ctrl.offsetLeft = fixOffset(ctrl.offsetLeft);
34969    });
34970  }
34971
34972  /**
34973   * Create an entry in the tabs array for a new tab at the specified index.
34974   * @param tabData
34975   * @param index
34976   * @returns {*}
34977   */
34978  function insertTab (tabData, index) {
34979    var hasLoaded = loaded;
34980    var proto     = {
34981          getIndex:     function () { return ctrl.tabs.indexOf(tab); },
34982          isActive:     function () { return this.getIndex() === ctrl.selectedIndex; },
34983          isLeft:       function () { return this.getIndex() < ctrl.selectedIndex; },
34984          isRight:      function () { return this.getIndex() > ctrl.selectedIndex; },
34985          shouldRender: function () { return !ctrl.noDisconnect || this.isActive(); },
34986          hasFocus:     function () {
34987            return ctrl.styleTabItemFocus
34988                && ctrl.hasFocus && this.getIndex() === ctrl.focusIndex;
34989          },
34990          id:           $mdUtil.nextUid(),
34991          hasContent: !!(tabData.template && tabData.template.trim())
34992        },
34993        tab       = angular.extend(proto, tabData);
34994    if (angular.isDefined(index)) {
34995      ctrl.tabs.splice(index, 0, tab);
34996    } else {
34997      ctrl.tabs.push(tab);
34998    }
34999
35000    processQueue();
35001    updateHasContent();
35002    $mdUtil.nextTick(function () {
35003      updatePagination();
35004      setAriaControls(tab);
35005
35006      // if autoselect is enabled, select the newly added tab
35007      if (hasLoaded && ctrl.autoselect) $mdUtil.nextTick(function () {
35008        $mdUtil.nextTick(function () { select(ctrl.tabs.indexOf(tab)); });
35009      });
35010    });
35011    return tab;
35012  }
35013
35014  // Getter methods
35015
35016  /**
35017   * Gathers references to all of the DOM elements used by this controller.
35018   * @returns {{}}
35019   */
35020  function getElements () {
35021    var elements = {};
35022    var node = $element[0];
35023
35024    // gather tab bar elements
35025    elements.wrapper = node.querySelector('md-tabs-wrapper');
35026    elements.canvas  = elements.wrapper.querySelector('md-tabs-canvas');
35027    elements.paging  = elements.canvas.querySelector('md-pagination-wrapper');
35028    elements.inkBar  = elements.paging.querySelector('md-ink-bar');
35029
35030    elements.contents = node.querySelectorAll('md-tabs-content-wrapper > md-tab-content');
35031    elements.tabs    = elements.paging.querySelectorAll('md-tab-item');
35032    elements.dummies = elements.canvas.querySelectorAll('md-dummy-tab');
35033
35034    return elements;
35035  }
35036
35037  /**
35038   * Determines whether or not the left pagination arrow should be enabled.
35039   * @returns {boolean}
35040   */
35041  function canPageBack () {
35042    return ctrl.offsetLeft > 0;
35043  }
35044
35045  /**
35046   * Determines whether or not the right pagination arrow should be enabled.
35047   * @returns {*|boolean}
35048   */
35049  function canPageForward () {
35050    var elements = getElements();
35051    var lastTab = elements.tabs[ elements.tabs.length - 1 ];
35052    return lastTab && lastTab.offsetLeft + lastTab.offsetWidth > elements.canvas.clientWidth +
35053        ctrl.offsetLeft;
35054  }
35055
35056  /**
35057   * Returns currently focused tab item's element ID
35058   */
35059  function getFocusedTabId() {
35060    var focusedTab = ctrl.tabs[ctrl.focusIndex];
35061    if (!focusedTab || !focusedTab.id) {
35062      return null;
35063    }
35064    return 'tab-item-' + focusedTab.id;
35065  }
35066
35067  /**
35068   * Determines if the UI should stretch the tabs to fill the available space.
35069   * @returns {*}
35070   */
35071  function shouldStretchTabs () {
35072    switch (ctrl.stretchTabs) {
35073      case 'always':
35074        return true;
35075      case 'never':
35076        return false;
35077      default:
35078        return !ctrl.shouldPaginate
35079            && $window.matchMedia('(max-width: 600px)').matches;
35080    }
35081  }
35082
35083  /**
35084   * Determines if the tabs should appear centered.
35085   * @returns {string|boolean}
35086   */
35087  function shouldCenterTabs () {
35088    return ctrl.centerTabs && !ctrl.shouldPaginate;
35089  }
35090
35091  /**
35092   * Determines if pagination is necessary to display the tabs within the available space.
35093   * @returns {boolean}
35094   */
35095  function shouldPaginate () {
35096    if (ctrl.noPagination || !loaded) return false;
35097    var canvasWidth = $element.prop('clientWidth');
35098
35099    angular.forEach(getElements().dummies, function (tab) {
35100      canvasWidth -= tab.offsetWidth;
35101    });
35102
35103    return canvasWidth < 0;
35104  }
35105
35106  /**
35107   * Finds the nearest tab index that is available.  This is primarily used for when the active
35108   * tab is removed.
35109   * @param newIndex
35110   * @returns {*}
35111   */
35112  function getNearestSafeIndex (newIndex) {
35113    if (newIndex === -1) return -1;
35114    var maxOffset = Math.max(ctrl.tabs.length - newIndex, newIndex),
35115        i, tab;
35116    for (i = 0; i <= maxOffset; i++) {
35117      tab = ctrl.tabs[ newIndex + i ];
35118      if (tab && (tab.scope.disabled !== true)) return tab.getIndex();
35119      tab = ctrl.tabs[ newIndex - i ];
35120      if (tab && (tab.scope.disabled !== true)) return tab.getIndex();
35121    }
35122    return newIndex;
35123  }
35124
35125  // Utility methods
35126
35127  /**
35128   * Defines a property using a getter and setter in order to trigger a change handler without
35129   * using `$watch` to observe changes.
35130   * @param key
35131   * @param handler
35132   * @param value
35133   */
35134  function defineProperty (key, handler, value) {
35135    Object.defineProperty(ctrl, key, {
35136      get: function () { return value; },
35137      set: function (newValue) {
35138        var oldValue = value;
35139        value        = newValue;
35140        handler && handler(newValue, oldValue);
35141      }
35142    });
35143  }
35144
35145  /**
35146   * Updates whether or not pagination should be displayed.
35147   */
35148  function updatePagination () {
35149    ctrl.maxTabWidth = getMaxTabWidth();
35150    ctrl.shouldPaginate = shouldPaginate();
35151  }
35152
35153  /**
35154   * Calculates the width of the pagination wrapper by summing the widths of the dummy tabs.
35155   * @returns {number}
35156   */
35157  function calcPagingWidth () {
35158    return calcTabsWidth(getElements().dummies);
35159  }
35160
35161  function calcTabsWidth(tabs) {
35162    var width = 0;
35163
35164    angular.forEach(tabs, function (tab) {
35165      //-- Uses the larger value between `getBoundingClientRect().width` and `offsetWidth`.  This
35166      //   prevents `offsetWidth` value from being rounded down and causing wrapping issues, but
35167      //   also handles scenarios where `getBoundingClientRect()` is inaccurate (ie. tabs inside
35168      //   of a dialog)
35169      width += Math.max(tab.offsetWidth, tab.getBoundingClientRect().width);
35170    });
35171
35172    return Math.ceil(width);
35173  }
35174
35175  function getMaxTabWidth () {
35176    return $element.prop('clientWidth');
35177  }
35178
35179  /**
35180   * Re-orders the tabs and updates the selected and focus indexes to their new positions.
35181   * This is triggered by `tabDirective.js` when the user's tabs have been re-ordered.
35182   */
35183  function updateTabOrder () {
35184    var selectedItem   = ctrl.tabs[ ctrl.selectedIndex ],
35185        focusItem      = ctrl.tabs[ ctrl.focusIndex ];
35186    ctrl.tabs          = ctrl.tabs.sort(function (a, b) {
35187      return a.index - b.index;
35188    });
35189    ctrl.selectedIndex = ctrl.tabs.indexOf(selectedItem);
35190    ctrl.focusIndex    = ctrl.tabs.indexOf(focusItem);
35191  }
35192
35193  /**
35194   * This moves the selected or focus index left or right.  This is used by the keydown handler.
35195   * @param inc
35196   */
35197  function incrementIndex (inc, focus) {
35198    var newIndex,
35199        key   = focus ? 'focusIndex' : 'selectedIndex',
35200        index = ctrl[ key ];
35201    for (newIndex = index + inc;
35202         ctrl.tabs[ newIndex ] && ctrl.tabs[ newIndex ].scope.disabled;
35203         newIndex += inc) {}
35204    if (ctrl.tabs[ newIndex ]) {
35205      ctrl[ key ] = newIndex;
35206    }
35207  }
35208
35209  /**
35210   * This is used to forward focus to dummy elements.  This method is necessary to avoid animation
35211   * issues when attempting to focus an item that is out of view.
35212   */
35213  function redirectFocus () {
35214    ctrl.styleTabItemFocus = ($mdInteraction.getLastInteractionType() === 'keyboard');
35215    getElements().dummies[ ctrl.focusIndex ].focus();
35216  }
35217
35218  /**
35219   * Forces the pagination to move the focused tab into view.
35220   */
35221  function adjustOffset (index) {
35222    var elements = getElements();
35223
35224    if (!angular.isNumber(index)) index = ctrl.focusIndex;
35225    if (!elements.tabs[ index ]) return;
35226    if (ctrl.shouldCenterTabs) return;
35227    var tab         = elements.tabs[ index ],
35228        left        = tab.offsetLeft,
35229        right       = tab.offsetWidth + left;
35230    ctrl.offsetLeft = Math.max(ctrl.offsetLeft, fixOffset(right - elements.canvas.clientWidth + 32 * 2));
35231    ctrl.offsetLeft = Math.min(ctrl.offsetLeft, fixOffset(left));
35232  }
35233
35234  /**
35235   * Iterates through all queued functions and clears the queue.  This is used for functions that
35236   * are called before the UI is ready, such as size calculations.
35237   */
35238  function processQueue () {
35239    queue.forEach(function (func) { $mdUtil.nextTick(func); });
35240    queue = [];
35241  }
35242
35243  /**
35244   * Determines if the tab content area is needed.
35245   */
35246  function updateHasContent () {
35247    var hasContent  = false;
35248
35249    for (var i = 0; i < ctrl.tabs.length; i++) {
35250      if (ctrl.tabs[i].hasContent) {
35251        hasContent = true;
35252        break;
35253      }
35254    }
35255
35256    ctrl.hasContent = hasContent;
35257  }
35258
35259  /**
35260   * Moves the indexes to their nearest valid values.
35261   */
35262  function refreshIndex () {
35263    ctrl.selectedIndex = getNearestSafeIndex(ctrl.selectedIndex);
35264    ctrl.focusIndex    = getNearestSafeIndex(ctrl.focusIndex);
35265  }
35266
35267  /**
35268   * Calculates the content height of the current tab.
35269   * @returns {*}
35270   */
35271  function updateHeightFromContent () {
35272    if (!ctrl.dynamicHeight) return $element.css('height', '');
35273    if (!ctrl.tabs.length) return queue.push(updateHeightFromContent);
35274
35275    var elements = getElements();
35276
35277    var tabContent    = elements.contents[ ctrl.selectedIndex ],
35278        contentHeight = tabContent ? tabContent.offsetHeight : 0,
35279        tabsHeight    = elements.wrapper.offsetHeight,
35280        newHeight     = contentHeight + tabsHeight,
35281        currentHeight = $element.prop('clientHeight');
35282
35283    if (currentHeight === newHeight) return;
35284
35285    // Adjusts calculations for when the buttons are bottom-aligned since this relies on absolute
35286    // positioning.  This should probably be cleaned up if a cleaner solution is possible.
35287    if ($element.attr('md-align-tabs') === 'bottom') {
35288      currentHeight -= tabsHeight;
35289      newHeight -= tabsHeight;
35290      // Need to include bottom border in these calculations
35291      if ($element.attr('md-border-bottom') !== undefined) ++currentHeight;
35292    }
35293
35294    // Lock during animation so the user can't change tabs
35295    locked = true;
35296
35297    var fromHeight = { height: currentHeight + 'px' },
35298        toHeight = { height: newHeight + 'px' };
35299
35300    // Set the height to the current, specific pixel height to fix a bug on iOS where the height
35301    // first animates to 0, then back to the proper height causing a visual glitch
35302    $element.css(fromHeight);
35303
35304    // Animate the height from the old to the new
35305    $animateCss($element, {
35306      from: fromHeight,
35307      to: toHeight,
35308      easing: 'cubic-bezier(0.35, 0, 0.25, 1)',
35309      duration: 0.5
35310    }).start().done(function () {
35311      // Then (to fix the same iOS issue as above), disable transitions and remove the specific
35312      // pixel height so the height can size with browser width/content changes, etc.
35313      $element.css({
35314        transition: 'none',
35315        height: ''
35316      });
35317
35318      // In the next tick, re-allow transitions (if we do it all at once, $element.css is "smart"
35319      // enough to batch it for us instead of doing it immediately, which undoes the original
35320      // transition: none)
35321      $mdUtil.nextTick(function() {
35322        $element.css('transition', '');
35323      });
35324
35325      // And unlock so tab changes can occur
35326      locked = false;
35327    });
35328  }
35329
35330  /**
35331   * Repositions the ink bar to the selected tab.
35332   * @returns {*}
35333   */
35334  function updateInkBarStyles () {
35335    var elements = getElements();
35336
35337    if (!elements.tabs[ ctrl.selectedIndex ]) {
35338      angular.element(elements.inkBar).css({ left: 'auto', right: 'auto' });
35339      return;
35340    }
35341
35342    if (!ctrl.tabs.length) return queue.push(ctrl.updateInkBarStyles);
35343    // if the element is not visible, we will not be able to calculate sizes until it is
35344    // we should treat that as a resize event rather than just updating the ink bar
35345    if (!$element.prop('offsetParent')) return handleResizeWhenVisible();
35346
35347    var index      = ctrl.selectedIndex,
35348        totalWidth = elements.paging.offsetWidth,
35349        tab        = elements.tabs[ index ],
35350        left       = tab.offsetLeft,
35351        right      = totalWidth - left - tab.offsetWidth;
35352
35353    if (ctrl.shouldCenterTabs) {
35354      // We need to use the same calculate process as in the pagination wrapper, to avoid rounding deviations.
35355      var tabWidth = calcTabsWidth(elements.tabs);
35356
35357      if (totalWidth > tabWidth) {
35358        $mdUtil.nextTick(updateInkBarStyles, false);
35359      }
35360    }
35361    updateInkBarClassName();
35362    angular.element(elements.inkBar).css({ left: left + 'px', right: right + 'px' });
35363  }
35364
35365  /**
35366   * Adds left/right classes so that the ink bar will animate properly.
35367   */
35368  function updateInkBarClassName () {
35369    var elements = getElements();
35370    var newIndex = ctrl.selectedIndex,
35371        oldIndex = ctrl.lastSelectedIndex,
35372        ink      = angular.element(elements.inkBar);
35373    if (!angular.isNumber(oldIndex)) return;
35374    ink
35375        .toggleClass('md-left', newIndex < oldIndex)
35376        .toggleClass('md-right', newIndex > oldIndex);
35377  }
35378
35379  /**
35380   * Takes an offset value and makes sure that it is within the min/max allowed values.
35381   * @param value
35382   * @returns {*}
35383   */
35384  function fixOffset (value) {
35385    var elements = getElements();
35386
35387    if (!elements.tabs.length || !ctrl.shouldPaginate) return 0;
35388    var lastTab    = elements.tabs[ elements.tabs.length - 1 ],
35389        totalWidth = lastTab.offsetLeft + lastTab.offsetWidth;
35390    value          = Math.max(0, value);
35391    value          = Math.min(totalWidth - elements.canvas.clientWidth, value);
35392    return value;
35393  }
35394
35395  /**
35396   * Attaches a ripple to the tab item element.
35397   * @param scope
35398   * @param element
35399   */
35400  function attachRipple (scope, element) {
35401    var elements = getElements();
35402    var options = { colorElement: angular.element(elements.inkBar) };
35403    $mdTabInkRipple.attach(scope, element, options);
35404  }
35405
35406  /**
35407   * Sets the `aria-controls` attribute to the elements that
35408   * correspond to the passed-in tab.
35409   * @param tab
35410   */
35411  function setAriaControls (tab) {
35412    if (tab.hasContent) {
35413      var nodes = $element[0].querySelectorAll('[md-tab-id="' + tab.id + '"]');
35414      angular.element(nodes).attr('aria-controls', ctrl.tabContentPrefix + tab.id);
35415    }
35416  }
35417}
35418
35419})();
35420(function(){
35421"use strict";
35422
35423/**
35424 * @ngdoc directive
35425 * @name mdTabs
35426 * @module material.components.tabs
35427 *
35428 * @restrict E
35429 *
35430 * @description
35431 * The `<md-tabs>` directive serves as the container for 1..n `<md-tab>` child directives to
35432 * produces a Tabs components. In turn, the nested `<md-tab>` directive is used to specify a tab
35433 * label for the **header button** and a [optional] tab view content that will be associated with
35434 * each tab button.
35435 *
35436 * Below is the markup for its simplest usage:
35437 *
35438 *  <hljs lang="html">
35439 *  <md-tabs>
35440 *    <md-tab label="Tab #1"></md-tab>
35441 *    <md-tab label="Tab #2"></md-tab>
35442 *    <md-tab label="Tab #3"></md-tab>
35443 *  </md-tabs>
35444 *  </hljs>
35445 *
35446 * Tabs supports three (3) usage scenarios:
35447 *
35448 *  1. Tabs (buttons only)
35449 *  2. Tabs with internal view content
35450 *  3. Tabs with external view content
35451 *
35452 * **Tab-only** support is useful when tab buttons are used for custom navigation regardless of any
35453 * other components, content, or views.
35454 *
35455 * <i><b>Note:</b> If you are using the Tabs component for page-level navigation, please take a look
35456 * at the <a ng-href="./api/directive/mdNavBar">NavBar component</a> instead as it can handle this
35457 * case a bit more natively.</i>
35458 *
35459 * **Tabs with internal views** are the traditional usages where each tab has associated view
35460 * content and the view switching is managed internally by the Tabs component.
35461 *
35462 * **Tabs with external view content** is often useful when content associated with each tab is
35463 * independently managed and data-binding notifications announce tab selection changes.
35464 *
35465 * Additional features also include:
35466 *
35467 * *  Content can include any markup.
35468 * *  If a tab is disabled while active/selected, then the next tab will be auto-selected.
35469 *
35470 * ### Explanation of tab stretching
35471 *
35472 * Initially, tabs will have an inherent size.  This size will either be defined by how much space is needed to accommodate their text or set by the user through CSS.  Calculations will be based on this size.
35473 *
35474 * On mobile devices, tabs will be expanded to fill the available horizontal space.  When this happens, all tabs will become the same size.
35475 *
35476 * On desktops, by default, stretching will never occur.
35477 *
35478 * This default behavior can be overridden through the `md-stretch-tabs` attribute.  Here is a table showing when stretching will occur:
35479 *
35480 * `md-stretch-tabs` | mobile    | desktop
35481 * ------------------|-----------|--------
35482 * `auto`            | stretched | ---
35483 * `always`          | stretched | stretched
35484 * `never`           | ---       | ---
35485 *
35486 * @param {integer=} md-selected Index of the active/selected tab
35487 * @param {boolean=} md-no-ink If present, disables ink ripple effects.
35488 * @param {boolean=} md-no-ink-bar If present, disables the selection ink bar.
35489 * @param {string=}  md-align-tabs Attribute to indicate position of tab buttons: `bottom` or `top`; default is `top`
35490 * @param {string=} md-stretch-tabs Attribute to indicate whether or not to stretch tabs: `auto`, `always`, or `never`; default is `auto`
35491 * @param {boolean=} md-dynamic-height When enabled, the tab wrapper will resize based on the contents of the selected tab
35492 * @param {boolean=} md-border-bottom If present, shows a solid `1px` border between the tabs and their content
35493 * @param {boolean=} md-center-tabs When enabled, tabs will be centered provided there is no need for pagination
35494 * @param {boolean=} md-no-pagination When enabled, pagination will remain off
35495 * @param {boolean=} md-swipe-content When enabled, swipe gestures will be enabled for the content area to jump between tabs
35496 * @param {boolean=} md-enable-disconnect When enabled, scopes will be disconnected for tabs that are not being displayed.  This provides a performance boost, but may also cause unexpected issues and is not recommended for most users.
35497 * @param {boolean=} md-autoselect When present, any tabs added after the initial load will be automatically selected
35498 * @param {boolean=} md-no-select-click When enabled, click events will not be fired when selecting tabs
35499 *
35500 * @usage
35501 * <hljs lang="html">
35502 * <md-tabs md-selected="selectedIndex" >
35503 *   <img ng-src="img/angular.png" class="centered">
35504 *   <md-tab
35505 *       ng-repeat="tab in tabs | orderBy:predicate:reversed"
35506 *       md-on-select="onTabSelected(tab)"
35507 *       md-on-deselect="announceDeselected(tab)"
35508 *       ng-disabled="tab.disabled">
35509 *     <md-tab-label>
35510 *       {{tab.title}}
35511 *       <img src="img/removeTab.png" ng-click="removeTab(tab)" class="delete">
35512 *     </md-tab-label>
35513 *     <md-tab-body>
35514 *       {{tab.content}}
35515 *     </md-tab-body>
35516 *   </md-tab>
35517 * </md-tabs>
35518 * </hljs>
35519 *
35520 */
35521MdTabs.$inject = ["$$mdSvgRegistry"];
35522angular
35523    .module('material.components.tabs')
35524    .directive('mdTabs', MdTabs);
35525
35526function MdTabs ($$mdSvgRegistry) {
35527  return {
35528    scope:            {
35529      selectedIndex: '=?mdSelected'
35530    },
35531    template:         function (element, attr) {
35532      attr.$mdTabsTemplate = element.html();
35533      return '' +
35534        '<md-tabs-wrapper> ' +
35535          '<md-tab-data></md-tab-data> ' +
35536          '<md-prev-button ' +
35537              'tabindex="-1" ' +
35538              'role="button" ' +
35539              'aria-label="Previous Page" ' +
35540              'aria-disabled="{{!$mdTabsCtrl.canPageBack()}}" ' +
35541              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageBack() }" ' +
35542              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
35543              'ng-click="$mdTabsCtrl.previousPage()"> ' +
35544            '<md-icon md-svg-src="'+ $$mdSvgRegistry.mdTabsArrow +'"></md-icon> ' +
35545          '</md-prev-button> ' +
35546          '<md-next-button ' +
35547              'tabindex="-1" ' +
35548              'role="button" ' +
35549              'aria-label="Next Page" ' +
35550              'aria-disabled="{{!$mdTabsCtrl.canPageForward()}}" ' +
35551              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageForward() }" ' +
35552              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
35553              'ng-click="$mdTabsCtrl.nextPage()"> ' +
35554            '<md-icon md-svg-src="'+ $$mdSvgRegistry.mdTabsArrow +'"></md-icon> ' +
35555          '</md-next-button> ' +
35556          '<md-tabs-canvas ' +
35557              'tabindex="{{ $mdTabsCtrl.hasFocus ? -1 : 0 }}" ' +
35558              'aria-activedescendant="{{$mdTabsCtrl.getFocusedTabId()}}" ' +
35559              'ng-focus="$mdTabsCtrl.redirectFocus()" ' +
35560              'ng-class="{ ' +
35561                  '\'md-paginated\': $mdTabsCtrl.shouldPaginate, ' +
35562                  '\'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs ' +
35563              '}" ' +
35564              'ng-keydown="$mdTabsCtrl.keydown($event)" ' +
35565              'role="tablist"> ' +
35566            '<md-pagination-wrapper ' +
35567                'ng-class="{ \'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs }" ' +
35568                'md-tab-scroll="$mdTabsCtrl.scroll($event)"> ' +
35569              '<md-tab-item ' +
35570                  'tabindex="-1" ' +
35571                  'class="md-tab" ' +
35572                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
35573                  'role="tab" ' +
35574                  'md-tab-id="{{::tab.id}}"' +
35575                  'aria-selected="{{tab.isActive()}}" ' +
35576                  'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
35577                  'ng-click="$mdTabsCtrl.select(tab.getIndex())" ' +
35578                  'ng-class="{ ' +
35579                      '\'md-active\':    tab.isActive(), ' +
35580                      '\'md-focused\':   tab.hasFocus(), ' +
35581                      '\'md-disabled\':  tab.scope.disabled ' +
35582                  '}" ' +
35583                  'ng-disabled="tab.scope.disabled" ' +
35584                  'md-swipe-left="$mdTabsCtrl.nextPage()" ' +
35585                  'md-swipe-right="$mdTabsCtrl.previousPage()" ' +
35586                  'md-tabs-template="::tab.label" ' +
35587                  'md-scope="::tab.parent"></md-tab-item> ' +
35588              '<md-ink-bar></md-ink-bar> ' +
35589            '</md-pagination-wrapper> ' +
35590            '<md-tabs-dummy-wrapper class="md-visually-hidden md-dummy-wrapper"> ' +
35591              '<md-dummy-tab ' +
35592                  'class="md-tab" ' +
35593                  'tabindex="-1" ' +
35594                  'id="tab-item-{{::tab.id}}" ' +
35595                  'md-tab-id="{{::tab.id}}"' +
35596                  'aria-selected="{{tab.isActive()}}" ' +
35597                  'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
35598                  'ng-focus="$mdTabsCtrl.hasFocus = true" ' +
35599                  'ng-blur="$mdTabsCtrl.hasFocus = false" ' +
35600                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
35601                  'md-tabs-template="::tab.label" ' +
35602                  'md-scope="::tab.parent"></md-dummy-tab> ' +
35603            '</md-tabs-dummy-wrapper> ' +
35604          '</md-tabs-canvas> ' +
35605        '</md-tabs-wrapper> ' +
35606        '<md-tabs-content-wrapper ng-show="$mdTabsCtrl.hasContent && $mdTabsCtrl.selectedIndex >= 0" class="_md"> ' +
35607          '<md-tab-content ' +
35608              'id="{{:: $mdTabsCtrl.tabContentPrefix + tab.id}}" ' +
35609              'class="_md" ' +
35610              'role="tabpanel" ' +
35611              'aria-labelledby="tab-item-{{::tab.id}}" ' +
35612              'md-swipe-left="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(1)" ' +
35613              'md-swipe-right="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(-1)" ' +
35614              'ng-if="tab.hasContent" ' +
35615              'ng-repeat="(index, tab) in $mdTabsCtrl.tabs" ' +
35616              'ng-class="{ ' +
35617                '\'md-no-transition\': $mdTabsCtrl.lastSelectedIndex == null, ' +
35618                '\'md-active\':        tab.isActive(), ' +
35619                '\'md-left\':          tab.isLeft(), ' +
35620                '\'md-right\':         tab.isRight(), ' +
35621                '\'md-no-scroll\':     $mdTabsCtrl.dynamicHeight ' +
35622              '}"> ' +
35623            '<div ' +
35624                'md-tabs-template="::tab.template" ' +
35625                'md-connected-if="tab.isActive()" ' +
35626                'md-scope="::tab.parent" ' +
35627                'ng-if="$mdTabsCtrl.enableDisconnect || tab.shouldRender()"></div> ' +
35628          '</md-tab-content> ' +
35629        '</md-tabs-content-wrapper>';
35630    },
35631    controller:       'MdTabsController',
35632    controllerAs:     '$mdTabsCtrl',
35633    bindToController: true
35634  };
35635}
35636
35637})();
35638(function(){
35639"use strict";
35640
35641
35642MdTabsDummyWrapper.$inject = ["$mdUtil", "$window"];angular
35643  .module('material.components.tabs')
35644  .directive('mdTabsDummyWrapper', MdTabsDummyWrapper);
35645
35646/**
35647 * @private
35648 *
35649 * @param $mdUtil
35650 * @param $window
35651 * @returns {{require: string, link: link}}
35652 * @constructor
35653 *
35654 * @ngInject
35655 */
35656function MdTabsDummyWrapper ($mdUtil, $window) {
35657  return {
35658    require: '^?mdTabs',
35659    link:    function link (scope, element, attr, ctrl) {
35660      if (!ctrl) return;
35661
35662      var observer;
35663      var disconnect;
35664
35665      var mutationCallback = function() {
35666        ctrl.updatePagination();
35667        ctrl.updateInkBarStyles();
35668      };
35669
35670      if('MutationObserver' in $window) {
35671        var config = {
35672          childList: true,
35673          subtree: true,
35674          // Per https://bugzilla.mozilla.org/show_bug.cgi?id=1138368, browsers will not fire
35675          // the childList mutation, once a <span> element's innerText changes.
35676          // The characterData of the <span> element will change.
35677          characterData: true
35678        };
35679
35680        observer = new MutationObserver(mutationCallback);
35681        observer.observe(element[0], config);
35682        disconnect = observer.disconnect.bind(observer);
35683      } else {
35684        var debounced = $mdUtil.debounce(mutationCallback, 15, null, false);
35685        
35686        element.on('DOMSubtreeModified', debounced);
35687        disconnect = element.off.bind(element, 'DOMSubtreeModified', debounced);
35688      }
35689
35690      // Disconnect the observer
35691      scope.$on('$destroy', function() {
35692        disconnect();
35693      });
35694    }
35695  };
35696}
35697
35698})();
35699(function(){
35700"use strict";
35701
35702
35703MdTabsTemplate.$inject = ["$compile", "$mdUtil"];angular
35704    .module('material.components.tabs')
35705    .directive('mdTabsTemplate', MdTabsTemplate);
35706
35707function MdTabsTemplate ($compile, $mdUtil) {
35708  return {
35709    restrict: 'A',
35710    link:     link,
35711    scope:    {
35712      template:     '=mdTabsTemplate',
35713      connected:    '=?mdConnectedIf',
35714      compileScope: '=mdScope'
35715    },
35716    require:  '^?mdTabs'
35717  };
35718  function link (scope, element, attr, ctrl) {
35719    if (!ctrl) return;
35720
35721    var compileScope = ctrl.enableDisconnect ? scope.compileScope.$new() : scope.compileScope;
35722
35723    element.html(scope.template);
35724    $compile(element.contents())(compileScope);
35725
35726    return $mdUtil.nextTick(handleScope);
35727
35728    function handleScope () {
35729      scope.$watch('connected', function (value) { value === false ? disconnect() : reconnect(); });
35730      scope.$on('$destroy', reconnect);
35731    }
35732
35733    function disconnect () {
35734      if (ctrl.enableDisconnect) $mdUtil.disconnectScope(compileScope);
35735    }
35736
35737    function reconnect () {
35738      if (ctrl.enableDisconnect) $mdUtil.reconnectScope(compileScope);
35739    }
35740  }
35741}
35742
35743})();
35744(function(){ 
35745angular.module("material.core").constant("$MD_THEME_CSS", "md-autocomplete.md-THEME_NAME-theme{background:\"{{background-A100}}\"}md-autocomplete.md-THEME_NAME-theme[disabled]:not([md-floating-label]){background:\"{{background-100}}\"}md-autocomplete.md-THEME_NAME-theme button md-icon path{fill:\"{{background-600}}\"}md-autocomplete.md-THEME_NAME-theme button:after{background:\"{{background-600-0.3}}\"}.md-autocomplete-suggestions-container.md-THEME_NAME-theme{background:\"{{background-A100}}\"}.md-autocomplete-suggestions-container.md-THEME_NAME-theme li{color:\"{{background-900}}\"}.md-autocomplete-suggestions-container.md-THEME_NAME-theme li .highlight{color:\"{{background-600}}\"}.md-autocomplete-suggestions-container.md-THEME_NAME-theme li.selected,.md-autocomplete-suggestions-container.md-THEME_NAME-theme li:hover{background:\"{{background-200}}\"}md-backdrop{background-color:\"{{background-900-0.0}}\"}md-backdrop.md-opaque.md-THEME_NAME-theme{background-color:\"{{background-900-1.0}}\"}md-bottom-sheet.md-THEME_NAME-theme{background-color:\"{{background-50}}\";border-top-color:\"{{background-300}}\"}md-bottom-sheet.md-THEME_NAME-theme.md-list md-list-item{color:\"{{foreground-1}}\"}md-bottom-sheet.md-THEME_NAME-theme .md-subheader{background-color:\"{{background-50}}\";color:\"{{foreground-1}}\"}.md-button.md-THEME_NAME-theme:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme:not([disabled]):hover{background-color:\"{{background-500-0.2}}\"}.md-button.md-THEME_NAME-theme:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-THEME_NAME-theme.md-fab md-icon{color:\"{{accent-contrast}}\"}.md-button.md-THEME_NAME-theme.md-primary{color:\"{{primary-color}}\"}.md-button.md-THEME_NAME-theme.md-primary.md-fab,.md-button.md-THEME_NAME-theme.md-primary.md-raised{color:\"{{primary-contrast}}\";background-color:\"{{primary-color}}\"}.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]) md-icon{color:\"{{primary-contrast}}\"}.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]):hover,.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]):hover{background-color:\"{{primary-600}}\"}.md-button.md-THEME_NAME-theme.md-primary:not([disabled]) md-icon{color:\"{{primary-color}}\"}.md-button.md-THEME_NAME-theme.md-fab{background-color:\"{{accent-color}}\";color:\"{{accent-contrast}}\"}.md-button.md-THEME_NAME-theme.md-fab:not([disabled]) .md-icon{color:\"{{accent-contrast}}\"}.md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover{background-color:\"{{accent-A700}}\"}.md-button.md-THEME_NAME-theme.md-raised{color:\"{{background-900}}\";background-color:\"{{background-50}}\"}.md-button.md-THEME_NAME-theme.md-raised:not([disabled]) md-icon{color:\"{{background-900}}\"}.md-button.md-THEME_NAME-theme.md-raised:not([disabled]):hover{background-color:\"{{background-50}}\"}.md-button.md-THEME_NAME-theme.md-raised:not([disabled]).md-focused{background-color:\"{{background-200}}\"}.md-button.md-THEME_NAME-theme.md-warn{color:\"{{warn-color}}\"}.md-button.md-THEME_NAME-theme.md-warn.md-fab,.md-button.md-THEME_NAME-theme.md-warn.md-raised{color:\"{{warn-contrast}}\";background-color:\"{{warn-color}}\"}.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]) md-icon{color:\"{{warn-contrast}}\"}.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]):hover,.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]):hover{background-color:\"{{warn-600}}\"}.md-button.md-THEME_NAME-theme.md-warn:not([disabled]) md-icon{color:\"{{warn-color}}\"}.md-button.md-THEME_NAME-theme.md-accent{color:\"{{accent-color}}\"}.md-button.md-THEME_NAME-theme.md-accent.md-fab,.md-button.md-THEME_NAME-theme.md-accent.md-raised{color:\"{{accent-contrast}}\";background-color:\"{{accent-color}}\"}.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]) md-icon{color:\"{{accent-contrast}}\"}.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]):hover,.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]):hover{background-color:\"{{accent-A700}}\"}.md-button.md-THEME_NAME-theme.md-accent:not([disabled]) md-icon{color:\"{{accent-color}}\"}.md-button.md-THEME_NAME-theme.md-accent[disabled],.md-button.md-THEME_NAME-theme.md-fab[disabled],.md-button.md-THEME_NAME-theme.md-raised[disabled],.md-button.md-THEME_NAME-theme.md-warn[disabled],.md-button.md-THEME_NAME-theme[disabled]{color:\"{{foreground-3}}\";cursor:default}.md-button.md-THEME_NAME-theme.md-accent[disabled] md-icon,.md-button.md-THEME_NAME-theme.md-fab[disabled] md-icon,.md-button.md-THEME_NAME-theme.md-raised[disabled] md-icon,.md-button.md-THEME_NAME-theme.md-warn[disabled] md-icon,.md-button.md-THEME_NAME-theme[disabled] md-icon{color:\"{{foreground-3}}\"}.md-button.md-THEME_NAME-theme.md-fab[disabled],.md-button.md-THEME_NAME-theme.md-raised[disabled]{background-color:\"{{foreground-4}}\"}.md-button.md-THEME_NAME-theme[disabled]{background-color:transparent}._md a.md-THEME_NAME-theme:not(.md-button).md-primary{color:\"{{primary-color}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-primary:hover{color:\"{{primary-700}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-accent:hover{color:\"{{accent-700}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-accent{color:\"{{accent-color}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-accent:hover{color:\"{{accent-A700}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-warn{color:\"{{warn-color}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-warn:hover{color:\"{{warn-700}}\"}md-card.md-THEME_NAME-theme{color:\"{{foreground-1}}\";background-color:\"{{background-hue-1}}\";border-radius:2px}md-card.md-THEME_NAME-theme .md-card-image{border-radius:2px 2px 0 0}md-card.md-THEME_NAME-theme md-card-header md-card-avatar md-icon{color:\"{{background-color}}\";background-color:\"{{foreground-3}}\"}md-card.md-THEME_NAME-theme md-card-header md-card-header-text .md-subhead,md-card.md-THEME_NAME-theme md-card-title md-card-title-text:not(:only-child) .md-subhead{color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme .md-ripple{color:\"{{accent-A700}}\"}md-checkbox.md-THEME_NAME-theme.md-checked .md-ripple{color:\"{{background-600}}\"}md-checkbox.md-THEME_NAME-theme.md-checked.md-focused .md-container:before{background-color:\"{{accent-color-0.26}}\"}md-checkbox.md-THEME_NAME-theme .md-ink-ripple{color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme.md-checked .md-ink-ripple{color:\"{{accent-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not(.md-checked) .md-icon{border-color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme.md-checked .md-icon{background-color:\"{{accent-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme.md-checked .md-icon:after{border-color:\"{{accent-contrast-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-ripple{color:\"{{primary-600}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ripple{color:\"{{background-600}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-ink-ripple{color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple{color:\"{{primary-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-icon{background-color:\"{{primary-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:\"{{primary-color-0.26}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-icon:after{border-color:\"{{primary-contrast-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{color:\"{{foreground-3}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-ripple{color:\"{{warn-600}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-ink-ripple{color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple{color:\"{{warn-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-icon{background-color:\"{{warn-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:\"{{warn-color-0.26}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-icon:after{border-color:\"{{background-200}}\"}md-checkbox.md-THEME_NAME-theme[disabled]:not(.md-checked) .md-icon{border-color:\"{{foreground-3}}\"}md-checkbox.md-THEME_NAME-theme[disabled].md-checked .md-icon{background-color:\"{{foreground-3}}\"}md-checkbox.md-THEME_NAME-theme[disabled].md-checked .md-icon:after{border-color:\"{{background-200}}\"}md-checkbox.md-THEME_NAME-theme[disabled] .md-icon:after{border-color:\"{{foreground-3}}\"}md-checkbox.md-THEME_NAME-theme[disabled] .md-label{color:\"{{foreground-3}}\"}md-chips.md-THEME_NAME-theme .md-chips{box-shadow:0 1px \"{{foreground-4}}\"}md-chips.md-THEME_NAME-theme .md-chips.md-focused{box-shadow:0 2px \"{{primary-color}}\"}md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input{color:\"{{foreground-1}}\"}md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input::-moz-placeholder{color:\"{{foreground-3}}\"}md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input:-ms-input-placeholder{color:\"{{foreground-3}}\"}md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:\"{{foreground-3}}\"}md-chips.md-THEME_NAME-theme md-chip{background:\"{{background-300}}\";color:\"{{background-800}}\"}md-chips.md-THEME_NAME-theme md-chip md-icon{color:\"{{background-700}}\"}md-chips.md-THEME_NAME-theme md-chip.md-focused{background:\"{{primary-color}}\";color:\"{{primary-contrast}}\"}md-chips.md-THEME_NAME-theme md-chip.md-focused md-icon{color:\"{{primary-contrast}}\"}md-chips.md-THEME_NAME-theme md-chip._md-chip-editing{background:transparent;color:\"{{background-800}}\"}md-chips.md-THEME_NAME-theme md-chip-remove .md-button md-icon path{fill:\"{{background-500}}\"}.md-contact-suggestion span.md-contact-email{color:\"{{background-400}}\"}md-content.md-THEME_NAME-theme{color:\"{{foreground-1}}\";background-color:\"{{background-default}}\"}.md-calendar.md-THEME_NAME-theme{background:\"{{background-A100}}\";color:\"{{background-A200-0.87}}\"}.md-calendar.md-THEME_NAME-theme tr:last-child td{border-bottom-color:\"{{background-200}}\"}.md-THEME_NAME-theme .md-calendar-day-header{background:\"{{background-300}}\";color:\"{{background-A200-0.87}}\"}.md-THEME_NAME-theme .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid \"{{primary-500}}\"}.md-THEME_NAME-theme .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:\"{{primary-500-0.6}}\"}.md-calendar-date.md-focus .md-THEME_NAME-theme .md-calendar-date-selection-indicator,.md-THEME_NAME-theme .md-calendar-date-selection-indicator:hover{background:\"{{background-300}}\"}.md-THEME_NAME-theme .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-THEME_NAME-theme .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:\"{{primary-500}}\";color:\"{{primary-500-contrast}}\";border-color:transparent}.md-THEME_NAME-theme .md-calendar-date-disabled,.md-THEME_NAME-theme .md-calendar-month-label-disabled{color:\"{{background-A200-0.435}}\"}.md-THEME_NAME-theme .md-datepicker-input{color:\"{{foreground-1}}\"}.md-THEME_NAME-theme .md-datepicker-input:-moz-placeholder,.md-THEME_NAME-theme .md-datepicker-input::-moz-placeholder{color:\"{{foreground-3}}\"}.md-THEME_NAME-theme .md-datepicker-input:-ms-input-placeholder{color:\"{{foreground-3}}\"}.md-THEME_NAME-theme .md-datepicker-input::-webkit-input-placeholder{color:\"{{foreground-3}}\"}.md-THEME_NAME-theme .md-datepicker-input-container{border-bottom-color:\"{{foreground-4}}\"}.md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:\"{{primary-color}}\"}.md-accent .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:\"{{accent-color}}\"}.md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:\"{{warn-A700}}\"}.md-THEME_NAME-theme .md-datepicker-calendar-pane{border-color:\"{{background-hue-1}}\"}.md-THEME_NAME-theme .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:\"{{foreground-2}}\"}.md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon{color:\"{{primary-color}}\"}.md-accent .md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon,.md-THEME_NAME-theme .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:\"{{accent-color}}\"}.md-THEME_NAME-theme .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon{color:\"{{warn-A700}}\"}.md-THEME_NAME-theme .md-datepicker-calendar{background:\"{{background-A100}}\"}.md-THEME_NAME-theme .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px \"{{background-hue-1}}\"}.md-THEME_NAME-theme .md-datepicker-open .md-datepicker-input-container{background:\"{{background-hue-1}}\"}md-dialog.md-THEME_NAME-theme{border-radius:4px;background-color:\"{{background-hue-1}}\";color:\"{{foreground-1}}\"}md-dialog.md-THEME_NAME-theme.md-content-overflow .md-actions,md-dialog.md-THEME_NAME-theme.md-content-overflow md-dialog-actions,md-divider.md-THEME_NAME-theme{border-top-color:\"{{foreground-4}}\"}.layout-gt-lg-row>md-divider.md-THEME_NAME-theme,.layout-gt-md-row>md-divider.md-THEME_NAME-theme,.layout-gt-sm-row>md-divider.md-THEME_NAME-theme,.layout-gt-xs-row>md-divider.md-THEME_NAME-theme,.layout-lg-row>md-divider.md-THEME_NAME-theme,.layout-md-row>md-divider.md-THEME_NAME-theme,.layout-row>md-divider.md-THEME_NAME-theme,.layout-sm-row>md-divider.md-THEME_NAME-theme,.layout-xl-row>md-divider.md-THEME_NAME-theme,.layout-xs-row>md-divider.md-THEME_NAME-theme{border-right-color:\"{{foreground-4}}\"}md-icon.md-THEME_NAME-theme{color:\"{{foreground-2}}\"}md-icon.md-THEME_NAME-theme.md-primary{color:\"{{primary-color}}\"}md-icon.md-THEME_NAME-theme.md-accent{color:\"{{accent-color}}\"}md-icon.md-THEME_NAME-theme.md-warn{color:\"{{warn-color}}\"}md-input-container.md-THEME_NAME-theme .md-input{color:\"{{foreground-1}}\";border-color:\"{{foreground-4}}\"}md-input-container.md-THEME_NAME-theme .md-input:-moz-placeholder,md-input-container.md-THEME_NAME-theme .md-input::-moz-placeholder{color:\"{{foreground-3}}\"}md-input-container.md-THEME_NAME-theme .md-input:-ms-input-placeholder{color:\"{{foreground-3}}\"}md-input-container.md-THEME_NAME-theme .md-input::-webkit-input-placeholder{color:\"{{foreground-3}}\"}md-input-container.md-THEME_NAME-theme>md-icon{color:\"{{foreground-1}}\"}md-input-container.md-THEME_NAME-theme .md-placeholder,md-input-container.md-THEME_NAME-theme label{color:\"{{foreground-3}}\"}md-input-container.md-THEME_NAME-theme label.md-required:after{color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme .md-input-message-animation,md-input-container.md-THEME_NAME-theme .md-input-messages-animation{color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme .md-input-message-animation .md-char-counter,md-input-container.md-THEME_NAME-theme .md-input-messages-animation .md-char-counter{color:\"{{foreground-1}}\"}md-input-container.md-THEME_NAME-theme.md-input-focused .md-input:-moz-placeholder,md-input-container.md-THEME_NAME-theme.md-input-focused .md-input::-moz-placeholder{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme.md-input-focused .md-input:-ms-input-placeholder{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme.md-input-focused .md-input::-webkit-input-placeholder{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-has-value label{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-resized .md-input{border-color:\"{{primary-color}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused label,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused md-icon{color:\"{{primary-color}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:\"{{accent-color}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:\"{{accent-color}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input{border-color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme.md-input-invalid .md-char-counter,md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input-message-animation,md-input-container.md-THEME_NAME-theme.md-input-invalid label{color:\"{{warn-A700}}\"}[disabled] md-input-container.md-THEME_NAME-theme .md-input,md-input-container.md-THEME_NAME-theme .md-input[disabled]{border-bottom-color:transparent;color:\"{{foreground-3}}\";background-image:linear-gradient(90deg,\"{{foreground-3}}\" 0,\"{{foreground-3}}\" 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,\"{{foreground-3}}\" 100%)}md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h3,md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h4,md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h3,md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h4{color:\"{{foreground-1}}\"}md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text p,md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text p{color:\"{{foreground-2}}\"}md-list.md-THEME_NAME-theme .md-proxy-focus.md-focused div.md-no-style{background-color:\"{{background-100}}\"}md-list.md-THEME_NAME-theme md-list-item .md-avatar-icon{background-color:\"{{foreground-3}}\";color:\"{{background-color}}\"}md-list.md-THEME_NAME-theme md-list-item>md-icon{color:\"{{foreground-2}}\"}md-list.md-THEME_NAME-theme md-list-item>md-icon.md-highlight{color:\"{{primary-color}}\"}md-list.md-THEME_NAME-theme md-list-item>md-icon.md-highlight.md-accent{color:\"{{accent-color}}\"}md-menu-content.md-THEME_NAME-theme{background-color:\"{{background-A100}}\"}md-menu-content.md-THEME_NAME-theme md-menu-item{color:\"{{background-A200-0.87}}\"}md-menu-content.md-THEME_NAME-theme md-menu-item md-icon{color:\"{{background-A200-0.54}}\"}md-menu-content.md-THEME_NAME-theme md-menu-item .md-button[disabled],md-menu-content.md-THEME_NAME-theme md-menu-item .md-button[disabled] md-icon{color:\"{{background-A200-0.25}}\"}md-menu-content.md-THEME_NAME-theme md-menu-divider{background-color:\"{{background-A200-0.11}}\"}md-menu-bar.md-THEME_NAME-theme>button.md-button{color:\"{{foreground-2}}\";border-radius:2px}md-menu-bar.md-THEME_NAME-theme md-menu.md-open>button,md-menu-bar.md-THEME_NAME-theme md-menu>button:focus{outline:none;background:\"{{background-200}}\"}md-menu-bar.md-THEME_NAME-theme.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:\"{{ background-500-0.2}}\"}md-menu-bar.md-THEME_NAME-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-THEME_NAME-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-THEME_NAME-theme .md-menu>.md-button:after{color:\"{{background-A200-0.54}}\"}md-menu-content.md-THEME_NAME-theme .md-menu.md-open>.md-button{background-color:\"{{ background-500-0.2}}\"}md-toolbar.md-THEME_NAME-theme.md-menu-toolbar{background-color:\"{{background-A100}}\";color:\"{{background-A200}}\"}md-toolbar.md-THEME_NAME-theme.md-menu-toolbar md-toolbar-filler{background-color:\"{{primary-color}}\";color:\"{{background-A100-0.87}}\"}md-toolbar.md-THEME_NAME-theme.md-menu-toolbar md-toolbar-filler md-icon{color:\"{{background-A100-0.87}}\"}._md-panel-backdrop.md-THEME_NAME-theme{background-color:\"{{background-900-1.0}}\"}md-nav-bar.md-THEME_NAME-theme .md-nav-bar{background-color:transparent;border-color:\"{{foreground-4}}\"}md-nav-bar.md-THEME_NAME-theme .md-button._md-nav-button.md-unselected{color:\"{{foreground-2}}\"}md-nav-bar.md-THEME_NAME-theme md-nav-ink-bar{color:\"{{accent-color}}\";background:\"{{accent-color}}\"}md-progress-circular.md-THEME_NAME-theme path{stroke:\"{{primary-color}}\"}md-progress-circular.md-THEME_NAME-theme.md-warn path{stroke:\"{{warn-color}}\"}md-progress-circular.md-THEME_NAME-theme.md-accent path{stroke:\"{{accent-color}}\"}md-progress-linear.md-THEME_NAME-theme .md-container{background-color:\"{{primary-100}}\"}md-progress-linear.md-THEME_NAME-theme .md-bar{background-color:\"{{primary-color}}\"}md-progress-linear.md-THEME_NAME-theme.md-warn .md-container{background-color:\"{{warn-100}}\"}md-progress-linear.md-THEME_NAME-theme.md-warn .md-bar{background-color:\"{{warn-color}}\"}md-progress-linear.md-THEME_NAME-theme.md-accent .md-container{background-color:\"{{accent-100}}\"}md-progress-linear.md-THEME_NAME-theme.md-accent .md-bar{background-color:\"{{accent-color}}\"}md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-bar1{background-color:\"{{warn-100}}\"}md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(\"{{warn-100}}\" 0,\"{{warn-100}}\" 16%,transparent 42%)}md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-bar1{background-color:\"{{accent-100}}\"}md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(\"{{accent-100}}\" 0,\"{{accent-100}}\" 16%,transparent 42%)}md-radio-button.md-THEME_NAME-theme .md-off{border-color:\"{{foreground-2}}\"}md-radio-button.md-THEME_NAME-theme .md-on{background-color:\"{{accent-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme.md-checked .md-off{border-color:\"{{accent-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme.md-checked .md-ink-ripple{color:\"{{accent-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme .md-container .md-ripple{color:\"{{accent-A700}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-on,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-on,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on{background-color:\"{{primary-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off{border-color:\"{{primary-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:\"{{primary-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple{color:\"{{primary-600}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-on,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-on,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on{background-color:\"{{warn-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off{border-color:\"{{warn-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:\"{{warn-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple{color:\"{{warn-600}}\"}md-radio-button.md-THEME_NAME-theme[disabled],md-radio-group.md-THEME_NAME-theme[disabled]{color:\"{{foreground-3}}\"}md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-off,md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-on,md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-off,md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-on{border-color:\"{{foreground-3}}\"}md-radio-group.md-THEME_NAME-theme .md-checked .md-ink-ripple{color:\"{{accent-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-THEME_NAME-theme.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:\"{{primary-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme .md-checked.md-primary .md-ink-ripple{color:\"{{warn-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked .md-container:before{background-color:\"{{accent-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:\"{{primary-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:\"{{warn-color-0.26}}\"}md-input-container md-select.md-THEME_NAME-theme .md-select-value span:first-child:after{color:\"{{warn-A700}}\"}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-THEME_NAME-theme .md-select-value span:first-child:after{color:\"{{foreground-3}}\"}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-THEME_NAME-theme .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-THEME_NAME-theme .md-select-value.md-select-placeholder{color:\"{{primary-color}}\"}md-input-container.md-input-invalid md-select.md-THEME_NAME-theme .md-select-value{color:\"{{warn-A700}}\"!important;border-bottom-color:\"{{warn-A700}}\"!important}md-input-container.md-input-invalid md-select.md-THEME_NAME-theme.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-THEME_NAME-theme[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,\"{{foreground-3}}\" 0,\"{{foreground-3}}\" 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,\"{{foreground-3}}\" 100%)}md-select.md-THEME_NAME-theme .md-select-value{border-bottom-color:\"{{foreground-4}}\"}md-select.md-THEME_NAME-theme .md-select-value.md-select-placeholder{color:\"{{foreground-3}}\"}md-select.md-THEME_NAME-theme .md-select-value span:first-child:after{color:\"{{warn-A700}}\"}md-select.md-THEME_NAME-theme.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-THEME_NAME-theme.ng-invalid.ng-touched .md-select-value{color:\"{{warn-A700}}\"!important;border-bottom-color:\"{{warn-A700}}\"!important}md-select.md-THEME_NAME-theme.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-value{border-bottom-color:\"{{primary-color}}\";color:\"{{ foreground-1 }}\"}md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-value.md-select-placeholder{color:\"{{ foreground-1 }}\"}md-select.md-THEME_NAME-theme:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-THEME_NAME-theme:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:\"{{accent-color}}\"}md-select.md-THEME_NAME-theme:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:\"{{warn-color}}\"}md-select.md-THEME_NAME-theme[disabled] .md-select-icon,md-select.md-THEME_NAME-theme[disabled] .md-select-value,md-select.md-THEME_NAME-theme[disabled] .md-select-value.md-select-placeholder{color:\"{{foreground-3}}\"}md-select.md-THEME_NAME-theme .md-select-icon{color:\"{{foreground-2}}\"}md-select-menu.md-THEME_NAME-theme md-content{background:\"{{background-A100}}\"}md-select-menu.md-THEME_NAME-theme md-content md-optgroup{color:\"{{background-600-0.87}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option{color:\"{{background-900-0.87}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[disabled] .md-text{color:\"{{background-400-0.87}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option:not([disabled]):focus,md-select-menu.md-THEME_NAME-theme md-content md-option:not([disabled]):hover{background:\"{{background-200}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[selected]{color:\"{{primary-500}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[selected]:focus{color:\"{{primary-600}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[selected].md-accent{color:\"{{accent-color}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[selected].md-accent:focus{color:\"{{accent-A700}}\"}.md-checkbox-enabled.md-THEME_NAME-theme .md-ripple{color:\"{{primary-600}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-ripple{color:\"{{background-600}}\"}.md-checkbox-enabled.md-THEME_NAME-theme .md-ink-ripple{color:\"{{foreground-2}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-ink-ripple{color:\"{{primary-color-0.87}}\"}.md-checkbox-enabled.md-THEME_NAME-theme:not(.md-checked) .md-icon{border-color:\"{{foreground-2}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-icon{background-color:\"{{primary-color-0.87}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected].md-focused .md-container:before{background-color:\"{{primary-color-0.26}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-icon:after{border-color:\"{{primary-contrast-0.87}}\"}.md-checkbox-enabled.md-THEME_NAME-theme .md-indeterminate[disabled] .md-container{color:\"{{foreground-3}}\"}.md-checkbox-enabled.md-THEME_NAME-theme md-option .md-text{color:\"{{background-900-0.87}}\"}md-sidenav.md-THEME_NAME-theme,md-sidenav.md-THEME_NAME-theme md-content{background-color:\"{{background-hue-1}}\"}md-slider.md-THEME_NAME-theme .md-track{background-color:\"{{foreground-3}}\"}md-slider.md-THEME_NAME-theme .md-track-ticks{color:\"{{background-contrast}}\"}md-slider.md-THEME_NAME-theme .md-focus-ring{background-color:\"{{accent-A200-0.2}}\"}md-slider.md-THEME_NAME-theme .md-disabled-thumb{border-color:\"{{background-color}}\";background-color:\"{{background-color}}\"}md-slider.md-THEME_NAME-theme.md-min .md-thumb:after{background-color:\"{{background-color}}\";border-color:\"{{foreground-3}}\"}md-slider.md-THEME_NAME-theme.md-min .md-focus-ring{background-color:\"{{foreground-3-0.38}}\"}md-slider.md-THEME_NAME-theme.md-min[md-discrete] .md-thumb:after{background-color:\"{{background-contrast}}\";border-color:transparent}md-slider.md-THEME_NAME-theme.md-min[md-discrete] .md-sign{background-color:\"{{background-400}}\"}md-slider.md-THEME_NAME-theme.md-min[md-discrete] .md-sign:after{border-top-color:\"{{background-400}}\"}md-slider.md-THEME_NAME-theme.md-min[md-discrete][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:\"{{background-400}}\"}md-slider.md-THEME_NAME-theme .md-track.md-track-fill{background-color:\"{{accent-color}}\"}md-slider.md-THEME_NAME-theme .md-thumb:after{border-color:\"{{accent-color}}\";background-color:\"{{accent-color}}\"}md-slider.md-THEME_NAME-theme .md-sign{background-color:\"{{accent-color}}\"}md-slider.md-THEME_NAME-theme .md-sign:after{border-top-color:\"{{accent-color}}\"}md-slider.md-THEME_NAME-theme[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:\"{{accent-color}}\"}md-slider.md-THEME_NAME-theme .md-thumb-text{color:\"{{accent-contrast}}\"}md-slider.md-THEME_NAME-theme.md-warn .md-focus-ring{background-color:\"{{warn-200-0.38}}\"}md-slider.md-THEME_NAME-theme.md-warn .md-track.md-track-fill{background-color:\"{{warn-color}}\"}md-slider.md-THEME_NAME-theme.md-warn .md-thumb:after{border-color:\"{{warn-color}}\";background-color:\"{{warn-color}}\"}md-slider.md-THEME_NAME-theme.md-warn .md-sign{background-color:\"{{warn-color}}\"}md-slider.md-THEME_NAME-theme.md-warn .md-sign:after{border-top-color:\"{{warn-color}}\"}md-slider.md-THEME_NAME-theme.md-warn[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:\"{{warn-color}}\"}md-slider.md-THEME_NAME-theme.md-warn .md-thumb-text{color:\"{{warn-contrast}}\"}md-slider.md-THEME_NAME-theme.md-primary .md-focus-ring{background-color:\"{{primary-200-0.38}}\"}md-slider.md-THEME_NAME-theme.md-primary .md-track.md-track-fill{background-color:\"{{primary-color}}\"}md-slider.md-THEME_NAME-theme.md-primary .md-thumb:after{border-color:\"{{primary-color}}\";background-color:\"{{primary-color}}\"}md-slider.md-THEME_NAME-theme.md-primary .md-sign{background-color:\"{{primary-color}}\"}md-slider.md-THEME_NAME-theme.md-primary .md-sign:after{border-top-color:\"{{primary-color}}\"}md-slider.md-THEME_NAME-theme.md-primary[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:\"{{primary-color}}\"}md-slider.md-THEME_NAME-theme.md-primary .md-thumb-text{color:\"{{primary-contrast}}\"}md-slider.md-THEME_NAME-theme[disabled] .md-thumb:after{border-color:transparent}md-slider.md-THEME_NAME-theme[disabled]:not(.md-min) .md-thumb:after,md-slider.md-THEME_NAME-theme[disabled][md-discrete] .md-thumb:after{background-color:\"{{foreground-3}}\";border-color:transparent}md-slider.md-THEME_NAME-theme[disabled][readonly] .md-sign{background-color:\"{{background-400}}\"}md-slider.md-THEME_NAME-theme[disabled][readonly] .md-sign:after{border-top-color:\"{{background-400}}\"}md-slider.md-THEME_NAME-theme[disabled][readonly][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:\"{{background-400}}\"}md-slider.md-THEME_NAME-theme[disabled][readonly] .md-disabled-thumb{border-color:transparent;background-color:transparent}md-slider-container[disabled]>:first-child:not(md-slider),md-slider-container[disabled]>:last-child:not(md-slider){color:\"{{foreground-3}}\"}.md-subheader.md-THEME_NAME-theme{color:\"{{ foreground-2-0.23 }}\";background-color:\"{{background-default}}\"}.md-subheader.md-THEME_NAME-theme.md-primary{color:\"{{primary-color}}\"}.md-subheader.md-THEME_NAME-theme.md-accent{color:\"{{accent-color}}\"}.md-subheader.md-THEME_NAME-theme.md-warn{color:\"{{warn-color}}\"}md-switch.md-THEME_NAME-theme .md-ink-ripple{color:\"{{background-500}}\"}md-switch.md-THEME_NAME-theme .md-thumb{background-color:\"{{background-50}}\"}md-switch.md-THEME_NAME-theme .md-bar{background-color:\"{{background-500}}\"}md-switch.md-THEME_NAME-theme.md-checked .md-ink-ripple{color:\"{{accent-color}}\"}md-switch.md-THEME_NAME-theme.md-checked .md-thumb{background-color:\"{{accent-color}}\"}md-switch.md-THEME_NAME-theme.md-checked .md-bar{background-color:\"{{accent-color-0.5}}\"}md-switch.md-THEME_NAME-theme.md-checked.md-focused .md-thumb:before{background-color:\"{{accent-color-0.26}}\"}md-switch.md-THEME_NAME-theme.md-checked.md-primary .md-ink-ripple{color:\"{{primary-color}}\"}md-switch.md-THEME_NAME-theme.md-checked.md-primary .md-thumb{background-color:\"{{primary-color}}\"}md-switch.md-THEME_NAME-theme.md-checked.md-primary .md-bar{background-color:\"{{primary-color-0.5}}\"}md-switch.md-THEME_NAME-theme.md-checked.md-primary.md-focused .md-thumb:before{background-color:\"{{primary-color-0.26}}\"}md-switch.md-THEME_NAME-theme.md-checked.md-warn .md-ink-ripple{color:\"{{warn-color}}\"}md-switch.md-THEME_NAME-theme.md-checked.md-warn .md-thumb{background-color:\"{{warn-color}}\"}md-switch.md-THEME_NAME-theme.md-checked.md-warn .md-bar{background-color:\"{{warn-color-0.5}}\"}md-switch.md-THEME_NAME-theme.md-checked.md-warn.md-focused .md-thumb:before{background-color:\"{{warn-color-0.26}}\"}md-switch.md-THEME_NAME-theme[disabled] .md-thumb{background-color:\"{{background-400}}\"}md-switch.md-THEME_NAME-theme[disabled] .md-bar{background-color:\"{{foreground-4}}\"}md-toast.md-THEME_NAME-theme .md-toast-content{background-color:#323232;color:\"{{background-50}}\"}md-toast.md-THEME_NAME-theme .md-toast-content .md-button{color:\"{{background-50}}\"}md-toast.md-THEME_NAME-theme .md-toast-content .md-button.md-highlight{color:\"{{accent-color}}\"}md-toast.md-THEME_NAME-theme .md-toast-content .md-button.md-highlight.md-primary{color:\"{{primary-color}}\"}md-toast.md-THEME_NAME-theme .md-toast-content .md-button.md-highlight.md-warn{color:\"{{warn-color}}\"}md-tabs.md-THEME_NAME-theme md-tabs-wrapper{background-color:transparent;border-color:\"{{foreground-4}}\"}md-tabs.md-THEME_NAME-theme .md-paginator md-icon{color:\"{{primary-color}}\"}md-tabs.md-THEME_NAME-theme md-ink-bar{color:\"{{accent-color}}\";background:\"{{accent-color}}\"}md-tabs.md-THEME_NAME-theme .md-tab{color:\"{{foreground-2}}\"}md-tabs.md-THEME_NAME-theme .md-tab[disabled],md-tabs.md-THEME_NAME-theme .md-tab[disabled] md-icon{color:\"{{foreground-3}}\"}md-tabs.md-THEME_NAME-theme .md-tab.md-active,md-tabs.md-THEME_NAME-theme .md-tab.md-active md-icon,md-tabs.md-THEME_NAME-theme .md-tab.md-focused,md-tabs.md-THEME_NAME-theme .md-tab.md-focused md-icon{color:\"{{primary-color}}\"}md-tabs.md-THEME_NAME-theme .md-tab.md-focused{background:\"{{primary-color-0.1}}\"}md-tabs.md-THEME_NAME-theme .md-tab .md-ripple-container{color:\"{{accent-A100}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper{background-color:\"{{accent-color}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{accent-A100}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{accent-contrast}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{accent-contrast-0.1}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:\"{{primary-600-1}}\";background:\"{{primary-600-1}}\"}md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper{background-color:\"{{primary-color}}\"}md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{primary-100}}\"}md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{primary-contrast}}\"}md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{primary-contrast-0.1}}\"}md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper{background-color:\"{{warn-color}}\"}md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{warn-100}}\"}md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{warn-contrast}}\"}md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{warn-contrast-0.1}}\"}md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper{background-color:\"{{primary-color}}\"}md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{primary-100}}\"}md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{primary-contrast}}\"}md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{primary-contrast-0.1}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper{background-color:\"{{accent-color}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{accent-A100}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{accent-contrast}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{accent-contrast-0.1}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:\"{{primary-600-1}}\";background:\"{{primary-600-1}}\"}md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper{background-color:\"{{warn-color}}\"}md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{warn-100}}\"}md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{warn-contrast}}\"}md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{warn-contrast-0.1}}\"}md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar){background-color:\"{{primary-color}}\";color:\"{{primary-contrast}}\"}md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar) md-icon{color:\"{{primary-contrast}}\";fill:\"{{primary-contrast}}\"}md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar) .md-button[disabled] md-icon{color:\"{{primary-contrast-0.26}}\";fill:\"{{primary-contrast-0.26}}\"}md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent{background-color:\"{{accent-color}}\";color:\"{{accent-contrast}}\"}md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent .md-ink-ripple{color:\"{{accent-contrast}}\"}md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent md-icon{color:\"{{accent-contrast}}\";fill:\"{{accent-contrast}}\"}md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon{color:\"{{accent-contrast-0.26}}\";fill:\"{{accent-contrast-0.26}}\"}md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-warn{background-color:\"{{warn-color}}\";color:\"{{warn-contrast}}\"}.md-panel.md-tooltip.md-THEME_NAME-theme{color:\"{{background-700-contrast}}\";background-color:\"{{background-700}}\"}body.md-THEME_NAME-theme,html.md-THEME_NAME-theme{color:\"{{foreground-1}}\";background-color:\"{{background-color}}\"}"); 
35746})();
35747
35748
35749})(window, window.angular);;window.ngMaterial={version:{full: "1.1.3"}};