arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
commonroutesApp/www/lib/angular-material/angular-material.js
www/lib/angular-material/angular-material.jsRaw
    1/*!
    2 * Angular Material Design
    3 * https://github.com/angular/material
    4 * @license MIT
    5 * v1.1.1
    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.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.chips","material.components.checkbox","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.navBar","material.components.panel","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.tabs","material.components.toast","material.components.toolbar","material.components.tooltip","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"];
   25angular
   26  .module('material.core', [
   27    'ngAnimate',
   28    'material.core.animate',
   29    'material.core.layout',
   30    'material.core.gestures',
   31    'material.core.theming'
   32  ])
   33  .config(MdCoreConfigure)
   34  .run(DetectNgTouch);
   35
   36
   37/**
   38 * Detect if the ng-Touch module is also being used.
   39 * Warn if detected.
   40 * @ngInject
   41 */
   42function DetectNgTouch($log, $injector) {
   43  if ( $injector.has('$swipe') ) {
   44    var msg = "" +
   45      "You are using the ngTouch module. \n" +
   46      "Angular Material already has mobile click, tap, and swipe support... \n" +
   47      "ngTouch is not supported with Angular Material!";
   48    $log.warn(msg);
   49  }
   50}
   51
   52/**
   53 * @ngInject
   54 */
   55function MdCoreConfigure($provide, $mdThemingProvider) {
   56
   57  $provide.decorator('$$rAF', ["$delegate", rAFDecorator]);
   58
   59  $mdThemingProvider.theme('default')
   60    .primaryPalette('indigo')
   61    .accentPalette('pink')
   62    .warnPalette('deep-orange')
   63    .backgroundPalette('grey');
   64}
   65
   66/**
   67 * @ngInject
   68 */
   69function rAFDecorator($delegate) {
   70  /**
   71   * Use this to throttle events that come in often.
   72   * The throttled function will always use the *last* invocation before the
   73   * coming frame.
   74   *
   75   * For example, window resize events that fire many times a second:
   76   * If we set to use an raf-throttled callback on window resize, then
   77   * our callback will only be fired once per frame, with the last resize
   78   * event that happened before that frame.
   79   *
   80   * @param {function} callback function to debounce
   81   */
   82  $delegate.throttle = function(cb) {
   83    var queuedArgs, alreadyQueued, queueCb, context;
   84    return function debounced() {
   85      queuedArgs = arguments;
   86      context = this;
   87      queueCb = cb;
   88      if (!alreadyQueued) {
   89        alreadyQueued = true;
   90        $delegate(function() {
   91          queueCb.apply(context, Array.prototype.slice.call(queuedArgs));
   92          alreadyQueued = false;
   93        });
   94      }
   95    };
   96  };
   97  return $delegate;
   98}
   99
  100})();
  101(function(){
  102"use strict";
  103
  104angular.module('material.core')
  105  .directive('mdAutofocus', MdAutofocusDirective)
  106
  107  // Support the deprecated md-auto-focus and md-sidenav-focus as well
  108  .directive('mdAutoFocus', MdAutofocusDirective)
  109  .directive('mdSidenavFocus', MdAutofocusDirective);
  110
  111/**
  112 * @ngdoc directive
  113 * @name mdAutofocus
  114 * @module material.core.util
  115 *
  116 * @description
  117 *
  118 * `[md-autofocus]` provides an optional way to identify the focused element when a `$mdDialog`,
  119 * `$mdBottomSheet`, `$mdMenu` or `$mdSidenav` opens or upon page load for input-like elements.
  120 *
  121 * When one of these opens, it will find the first nested element with the `[md-autofocus]`
  122 * attribute directive and optional expression. An expression may be specified as the directive
  123 * value to enable conditional activation of the autofocus.
  124 *
  125 * @usage
  126 *
  127 * ### Dialog
  128 * <hljs lang="html">
  129 * <md-dialog>
  130 *   <form>
  131 *     <md-input-container>
  132 *       <label for="testInput">Label</label>
  133 *       <input id="testInput" type="text" md-autofocus>
  134 *     </md-input-container>
  135 *   </form>
  136 * </md-dialog>
  137 * </hljs>
  138 *
  139 * ### Bottomsheet
  140 * <hljs lang="html">
  141 * <md-bottom-sheet class="md-list md-has-header">
  142 *  <md-subheader>Comment Actions</md-subheader>
  143 *  <md-list>
  144 *    <md-list-item ng-repeat="item in items">
  145 *
  146 *      <md-button md-autofocus="$index == 2">
  147 *        <md-icon md-svg-src="{{item.icon}}"></md-icon>
  148 *        <span class="md-inline-list-icon-label">{{ item.name }}</span>
  149 *      </md-button>
  150 *
  151 *    </md-list-item>
  152 *  </md-list>
  153 * </md-bottom-sheet>
  154 * </hljs>
  155 *
  156 * ### Autocomplete
  157 * <hljs lang="html">
  158 *   <md-autocomplete
  159 *       md-autofocus
  160 *       md-selected-item="selectedItem"
  161 *       md-search-text="searchText"
  162 *       md-items="item in getMatches(searchText)"
  163 *       md-item-text="item.display">
  164 *     <span md-highlight-text="searchText">{{item.display}}</span>
  165 *   </md-autocomplete>
  166 * </hljs>
  167 *
  168 * ### Sidenav
  169 * <hljs lang="html">
  170 * <div layout="row" ng-controller="MyController">
  171 *   <md-sidenav md-component-id="left" class="md-sidenav-left">
  172 *     Left Nav!
  173 *   </md-sidenav>
  174 *
  175 *   <md-content>
  176 *     Center Content
  177 *     <md-button ng-click="openLeftMenu()">
  178 *       Open Left Menu
  179 *     </md-button>
  180 *   </md-content>
  181 *
  182 *   <md-sidenav md-component-id="right"
  183 *     md-is-locked-open="$mdMedia('min-width: 333px')"
  184 *     class="md-sidenav-right">
  185 *     <form>
  186 *       <md-input-container>
  187 *         <label for="testInput">Test input</label>
  188 *         <input id="testInput" type="text"
  189 *                ng-model="data" md-autofocus>
  190 *       </md-input-container>
  191 *     </form>
  192 *   </md-sidenav>
  193 * </div>
  194 * </hljs>
  195 **/
  196function MdAutofocusDirective() {
  197  return {
  198    restrict: 'A',
  199
  200    link: postLink
  201  }
  202}
  203
  204function postLink(scope, element, attrs) {
  205  var attr = attrs.mdAutoFocus || attrs.mdAutofocus || attrs.mdSidenavFocus;
  206
  207  // Setup a watcher on the proper attribute to update a class we can check for in $mdUtil
  208  scope.$watch(attr, function(canAutofocus) {
  209    element.toggleClass('md-autofocus', canAutofocus);
  210  });
  211}
  212
  213})();
  214(function(){
  215"use strict";
  216
  217/**
  218 * @ngdoc module
  219 * @name material.core.colorUtil
  220 * @description
  221 * Color Util
  222 */
  223angular
  224  .module('material.core')
  225  .factory('$mdColorUtil', ColorUtilFactory);
  226
  227function ColorUtilFactory() {
  228  /**
  229   * Converts hex value to RGBA string
  230   * @param color {string}
  231   * @returns {string}
  232   */
  233  function hexToRgba (color) {
  234    var hex   = color[ 0 ] === '#' ? color.substr(1) : color,
  235      dig   = hex.length / 3,
  236      red   = hex.substr(0, dig),
  237      green = hex.substr(dig, dig),
  238      blue  = hex.substr(dig * 2);
  239    if (dig === 1) {
  240      red += red;
  241      green += green;
  242      blue += blue;
  243    }
  244    return 'rgba(' + parseInt(red, 16) + ',' + parseInt(green, 16) + ',' + parseInt(blue, 16) + ',0.1)';
  245  }
  246
  247  /**
  248   * Converts rgba value to hex string
  249   * @param color {string}
  250   * @returns {string}
  251   */
  252  function rgbaToHex(color) {
  253    color = color.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
  254
  255    var hex = (color && color.length === 4) ? "#" +
  256    ("0" + parseInt(color[1],10).toString(16)).slice(-2) +
  257    ("0" + parseInt(color[2],10).toString(16)).slice(-2) +
  258    ("0" + parseInt(color[3],10).toString(16)).slice(-2) : '';
  259
  260    return hex.toUpperCase();
  261  }
  262
  263  /**
  264   * Converts an RGB color to RGBA
  265   * @param color {string}
  266   * @returns {string}
  267   */
  268  function rgbToRgba (color) {
  269    return color.replace(')', ', 0.1)').replace('(', 'a(');
  270  }
  271
  272  /**
  273   * Converts an RGBA color to RGB
  274   * @param color {string}
  275   * @returns {string}
  276   */
  277  function rgbaToRgb (color) {
  278    return color
  279      ? color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')')
  280      : 'rgb(0,0,0)';
  281  }
  282
  283  return {
  284    rgbaToHex: rgbaToHex,
  285    hexToRgba: hexToRgba,
  286    rgbToRgba: rgbToRgba,
  287    rgbaToRgb: rgbaToRgb
  288  }
  289}
  290})();
  291(function(){
  292"use strict";
  293
  294
  295MdConstantFactory.$inject = ["$sniffer", "$window", "$document"];angular.module('material.core')
  296.factory('$mdConstant', MdConstantFactory);
  297
  298/**
  299 * Factory function that creates the grab-bag $mdConstant service.
  300 * @ngInject
  301 */
  302function MdConstantFactory($sniffer, $window, $document) {
  303
  304  var vendorPrefix = $sniffer.vendorPrefix;
  305  var isWebkit = /webkit/i.test(vendorPrefix);
  306  var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g;
  307  var prefixTestEl = document.createElement('div');
  308
  309  function vendorProperty(name) {
  310    // Add a dash between the prefix and name, to be able to transform the string into camelcase.
  311    var prefixedName = vendorPrefix + '-' + name;
  312    var ucPrefix = camelCase(prefixedName);
  313    var lcPrefix = ucPrefix.charAt(0).toLowerCase() + ucPrefix.substring(1);
  314
  315    return hasStyleProperty(name)     ? name     :       // The current browser supports the un-prefixed property
  316           hasStyleProperty(ucPrefix) ? ucPrefix :       // The current browser only supports the prefixed property.
  317           hasStyleProperty(lcPrefix) ? lcPrefix : name; // Some browsers are only supporting the prefix in lowercase.
  318  }
  319
  320  function hasStyleProperty(property) {
  321    return angular.isDefined(prefixTestEl.style[property]);
  322  }
  323
  324  function camelCase(input) {
  325    return input.replace(SPECIAL_CHARS_REGEXP, function(matches, separator, letter, offset) {
  326      return offset ? letter.toUpperCase() : letter;
  327    });
  328  }
  329
  330  var self = {
  331    isInputKey : function(e) { return (e.keyCode >= 31 && e.keyCode <= 90); },
  332    isNumPadKey : function (e){ return (3 === e.location && e.keyCode >= 97 && e.keyCode <= 105); },
  333    isNavigationKey : function(e) {
  334      var kc = self.KEY_CODE, NAVIGATION_KEYS =  [kc.SPACE, kc.ENTER, kc.UP_ARROW, kc.DOWN_ARROW];
  335      return (NAVIGATION_KEYS.indexOf(e.keyCode) != -1);    
  336    },
  337
  338    KEY_CODE: {
  339      COMMA: 188,
  340      SEMICOLON : 186,
  341      ENTER: 13,
  342      ESCAPE: 27,
  343      SPACE: 32,
  344      PAGE_UP: 33,
  345      PAGE_DOWN: 34,
  346      END: 35,
  347      HOME: 36,
  348      LEFT_ARROW : 37,
  349      UP_ARROW : 38,
  350      RIGHT_ARROW : 39,
  351      DOWN_ARROW : 40,
  352      TAB : 9,
  353      BACKSPACE: 8,
  354      DELETE: 46
  355    },
  356    CSS: {
  357      /* Constants */
  358      TRANSITIONEND: 'transitionend' + (isWebkit ? ' webkitTransitionEnd' : ''),
  359      ANIMATIONEND: 'animationend' + (isWebkit ? ' webkitAnimationEnd' : ''),
  360
  361      TRANSFORM: vendorProperty('transform'),
  362      TRANSFORM_ORIGIN: vendorProperty('transformOrigin'),
  363      TRANSITION: vendorProperty('transition'),
  364      TRANSITION_DURATION: vendorProperty('transitionDuration'),
  365      ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'),
  366      ANIMATION_DURATION: vendorProperty('animationDuration'),
  367      ANIMATION_NAME: vendorProperty('animationName'),
  368      ANIMATION_TIMING: vendorProperty('animationTimingFunction'),
  369      ANIMATION_DIRECTION: vendorProperty('animationDirection')
  370    },
  371    /**
  372     * As defined in core/style/variables.scss
  373     *
  374     * $layout-breakpoint-xs:     600px !default;
  375     * $layout-breakpoint-sm:     960px !default;
  376     * $layout-breakpoint-md:     1280px !default;
  377     * $layout-breakpoint-lg:     1920px !default;
  378     *
  379     */
  380    MEDIA: {
  381      'xs'        : '(max-width: 599px)'                         ,
  382      'gt-xs'     : '(min-width: 600px)'                         ,
  383      'sm'        : '(min-width: 600px) and (max-width: 959px)'  ,
  384      'gt-sm'     : '(min-width: 960px)'                         ,
  385      'md'        : '(min-width: 960px) and (max-width: 1279px)' ,
  386      'gt-md'     : '(min-width: 1280px)'                        ,
  387      'lg'        : '(min-width: 1280px) and (max-width: 1919px)',
  388      'gt-lg'     : '(min-width: 1920px)'                        ,
  389      'xl'        : '(min-width: 1920px)'                        ,
  390      'landscape' : '(orientation: landscape)'                   ,
  391      'portrait'  : '(orientation: portrait)'                    ,
  392      'print' : 'print'
  393    },
  394    MEDIA_PRIORITY: [
  395      'xl',
  396      'gt-lg',
  397      'lg',
  398      'gt-md',
  399      'md',
  400      'gt-sm',
  401      'sm',
  402      'gt-xs',
  403      'xs',
  404      'landscape',
  405      'portrait',
  406      'print'
  407    ]
  408  };
  409
  410  return self;
  411}
  412
  413})();
  414(function(){
  415"use strict";
  416
  417  angular
  418    .module('material.core')
  419    .config( ["$provide", function($provide){
  420       $provide.decorator('$mdUtil', ['$delegate', function ($delegate){
  421           /**
  422            * Inject the iterator facade to easily support iteration and accessors
  423            * @see iterator below
  424            */
  425           $delegate.iterator = MdIterator;
  426
  427           return $delegate;
  428         }
  429       ]);
  430     }]);
  431
  432  /**
  433   * iterator is a list facade to easily support iteration and accessors
  434   *
  435   * @param items Array list which this iterator will enumerate
  436   * @param reloop Boolean enables iterator to consider the list as an endless reloop
  437   */
  438  function MdIterator(items, reloop) {
  439    var trueFn = function() { return true; };
  440
  441    if (items && !angular.isArray(items)) {
  442      items = Array.prototype.slice.call(items);
  443    }
  444
  445    reloop = !!reloop;
  446    var _items = items || [ ];
  447
  448    // Published API
  449    return {
  450      items: getItems,
  451      count: count,
  452
  453      inRange: inRange,
  454      contains: contains,
  455      indexOf: indexOf,
  456      itemAt: itemAt,
  457
  458      findBy: findBy,
  459
  460      add: add,
  461      remove: remove,
  462
  463      first: first,
  464      last: last,
  465      next: angular.bind(null, findSubsequentItem, false),
  466      previous: angular.bind(null, findSubsequentItem, true),
  467
  468      hasPrevious: hasPrevious,
  469      hasNext: hasNext
  470
  471    };
  472
  473    /**
  474     * Publish copy of the enumerable set
  475     * @returns {Array|*}
  476     */
  477    function getItems() {
  478      return [].concat(_items);
  479    }
  480
  481    /**
  482     * Determine length of the list
  483     * @returns {Array.length|*|number}
  484     */
  485    function count() {
  486      return _items.length;
  487    }
  488
  489    /**
  490     * Is the index specified valid
  491     * @param index
  492     * @returns {Array.length|*|number|boolean}
  493     */
  494    function inRange(index) {
  495      return _items.length && ( index > -1 ) && (index < _items.length );
  496    }
  497
  498    /**
  499     * Can the iterator proceed to the next item in the list; relative to
  500     * the specified item.
  501     *
  502     * @param item
  503     * @returns {Array.length|*|number|boolean}
  504     */
  505    function hasNext(item) {
  506      return item ? inRange(indexOf(item) + 1) : false;
  507    }
  508
  509    /**
  510     * Can the iterator proceed to the previous item in the list; relative to
  511     * the specified item.
  512     *
  513     * @param item
  514     * @returns {Array.length|*|number|boolean}
  515     */
  516    function hasPrevious(item) {
  517      return item ? inRange(indexOf(item) - 1) : false;
  518    }
  519
  520    /**
  521     * Get item at specified index/position
  522     * @param index
  523     * @returns {*}
  524     */
  525    function itemAt(index) {
  526      return inRange(index) ? _items[index] : null;
  527    }
  528
  529    /**
  530     * Find all elements matching the key/value pair
  531     * otherwise return null
  532     *
  533     * @param val
  534     * @param key
  535     *
  536     * @return array
  537     */
  538    function findBy(key, val) {
  539      return _items.filter(function(item) {
  540        return item[key] === val;
  541      });
  542    }
  543
  544    /**
  545     * Add item to list
  546     * @param item
  547     * @param index
  548     * @returns {*}
  549     */
  550    function add(item, index) {
  551      if ( !item ) return -1;
  552
  553      if (!angular.isNumber(index)) {
  554        index = _items.length;
  555      }
  556
  557      _items.splice(index, 0, item);
  558
  559      return indexOf(item);
  560    }
  561
  562    /**
  563     * Remove item from list...
  564     * @param item
  565     */
  566    function remove(item) {
  567      if ( contains(item) ){
  568        _items.splice(indexOf(item), 1);
  569      }
  570    }
  571
  572    /**
  573     * Get the zero-based index of the target item
  574     * @param item
  575     * @returns {*}
  576     */
  577    function indexOf(item) {
  578      return _items.indexOf(item);
  579    }
  580
  581    /**
  582     * Boolean existence check
  583     * @param item
  584     * @returns {boolean}
  585     */
  586    function contains(item) {
  587      return item && (indexOf(item) > -1);
  588    }
  589
  590    /**
  591     * Return first item in the list
  592     * @returns {*}
  593     */
  594    function first() {
  595      return _items.length ? _items[0] : null;
  596    }
  597
  598    /**
  599     * Return last item in the list...
  600     * @returns {*}
  601     */
  602    function last() {
  603      return _items.length ? _items[_items.length - 1] : null;
  604    }
  605
  606    /**
  607     * Find the next item. If reloop is true and at the end of the list, it will go back to the
  608     * first item. If given, the `validate` callback will be used to determine whether the next item
  609     * is valid. If not valid, it will try to find the next item again.
  610     *
  611     * @param {boolean} backwards Specifies the direction of searching (forwards/backwards)
  612     * @param {*} item The item whose subsequent item we are looking for
  613     * @param {Function=} validate The `validate` function
  614     * @param {integer=} limit The recursion limit
  615     *
  616     * @returns {*} The subsequent item or null
  617     */
  618    function findSubsequentItem(backwards, item, validate, limit) {
  619      validate = validate || trueFn;
  620
  621      var curIndex = indexOf(item);
  622      while (true) {
  623        if (!inRange(curIndex)) return null;
  624
  625        var nextIndex = curIndex + (backwards ? -1 : 1);
  626        var foundItem = null;
  627        if (inRange(nextIndex)) {
  628          foundItem = _items[nextIndex];
  629        } else if (reloop) {
  630          foundItem = backwards ? last() : first();
  631          nextIndex = indexOf(foundItem);
  632        }
  633
  634        if ((foundItem === null) || (nextIndex === limit)) return null;
  635        if (validate(foundItem)) return foundItem;
  636
  637        if (angular.isUndefined(limit)) limit = nextIndex;
  638
  639        curIndex = nextIndex;
  640      }
  641    }
  642  }
  643
  644
  645})();
  646(function(){
  647"use strict";
  648
  649
  650mdMediaFactory.$inject = ["$mdConstant", "$rootScope", "$window"];angular.module('material.core')
  651.factory('$mdMedia', mdMediaFactory);
  652
  653/**
  654 * @ngdoc service
  655 * @name $mdMedia
  656 * @module material.core
  657 *
  658 * @description
  659 * `$mdMedia` is used to evaluate whether a given media query is true or false given the
  660 * current device's screen / window size. The media query will be re-evaluated on resize, allowing
  661 * you to register a watch.
  662 *
  663 * `$mdMedia` also has pre-programmed support for media queries that match the layout breakpoints:
  664 *
  665 *  <table class="md-api-table">
  666 *    <thead>
  667 *    <tr>
  668 *      <th>Breakpoint</th>
  669 *      <th>mediaQuery</th>
  670 *    </tr>
  671 *    </thead>
  672 *    <tbody>
  673 *    <tr>
  674 *      <td>xs</td>
  675 *      <td>(max-width: 599px)</td>
  676 *    </tr>
  677 *    <tr>
  678 *      <td>gt-xs</td>
  679 *      <td>(min-width: 600px)</td>
  680 *    </tr>
  681 *    <tr>
  682 *      <td>sm</td>
  683 *      <td>(min-width: 600px) and (max-width: 959px)</td>
  684 *    </tr>
  685 *    <tr>
  686 *      <td>gt-sm</td>
  687 *      <td>(min-width: 960px)</td>
  688 *    </tr>
  689 *    <tr>
  690 *      <td>md</td>
  691 *      <td>(min-width: 960px) and (max-width: 1279px)</td>
  692 *    </tr>
  693 *    <tr>
  694 *      <td>gt-md</td>
  695 *      <td>(min-width: 1280px)</td>
  696 *    </tr>
  697 *    <tr>
  698 *      <td>lg</td>
  699 *      <td>(min-width: 1280px) and (max-width: 1919px)</td>
  700 *    </tr>
  701 *    <tr>
  702 *      <td>gt-lg</td>
  703 *      <td>(min-width: 1920px)</td>
  704 *    </tr>
  705 *    <tr>
  706 *      <td>xl</td>
  707 *      <td>(min-width: 1920px)</td>
  708 *    </tr>
  709 *    <tr>
  710 *      <td>landscape</td>
  711 *      <td>landscape</td>
  712 *    </tr>
  713 *    <tr>
  714 *      <td>portrait</td>
  715 *      <td>portrait</td>
  716 *    </tr>
  717 *    <tr>
  718 *      <td>print</td>
  719 *      <td>print</td>
  720 *    </tr>
  721 *    </tbody>
  722 *  </table>
  723 *
  724 *  See Material Design's <a href="https://material.google.com/layout/responsive-ui.html">Layout - Adaptive UI</a> for more details.
  725 *
  726 *  <a href="https://www.google.com/design/spec/layout/adaptive-ui.html">
  727 *  <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>
  728 *  </a>
  729 *
  730 * @returns {boolean} a boolean representing whether or not the given media query is true or false.
  731 *
  732 * @usage
  733 * <hljs lang="js">
  734 * app.controller('MyController', function($mdMedia, $scope) {
  735 *   $scope.$watch(function() { return $mdMedia('lg'); }, function(big) {
  736 *     $scope.bigScreen = big;
  737 *   });
  738 *
  739 *   $scope.screenIsSmall = $mdMedia('sm');
  740 *   $scope.customQuery = $mdMedia('(min-width: 1234px)');
  741 *   $scope.anotherCustom = $mdMedia('max-width: 300px');
  742 * });
  743 * </hljs>
  744 */
  745
  746/* @ngInject */
  747function mdMediaFactory($mdConstant, $rootScope, $window) {
  748  var queries = {};
  749  var mqls = {};
  750  var results = {};
  751  var normalizeCache = {};
  752
  753  $mdMedia.getResponsiveAttribute = getResponsiveAttribute;
  754  $mdMedia.getQuery = getQuery;
  755  $mdMedia.watchResponsiveAttributes = watchResponsiveAttributes;
  756
  757  return $mdMedia;
  758
  759  function $mdMedia(query) {
  760    var validated = queries[query];
  761    if (angular.isUndefined(validated)) {
  762      validated = queries[query] = validate(query);
  763    }
  764
  765    var result = results[validated];
  766    if (angular.isUndefined(result)) {
  767      result = add(validated);
  768    }
  769
  770    return result;
  771  }
  772
  773  function validate(query) {
  774    return $mdConstant.MEDIA[query] ||
  775           ((query.charAt(0) !== '(') ? ('(' + query + ')') : query);
  776  }
  777
  778  function add(query) {
  779    var result = mqls[query];
  780    if ( !result ) {
  781      result = mqls[query] = $window.matchMedia(query);
  782    }
  783
  784    result.addListener(onQueryChange);
  785    return (results[result.media] = !!result.matches);
  786  }
  787
  788  function onQueryChange(query) {
  789    $rootScope.$evalAsync(function() {
  790      results[query.media] = !!query.matches;
  791    });
  792  }
  793
  794  function getQuery(name) {
  795    return mqls[name];
  796  }
  797
  798  function getResponsiveAttribute(attrs, attrName) {
  799    for (var i = 0; i < $mdConstant.MEDIA_PRIORITY.length; i++) {
  800      var mediaName = $mdConstant.MEDIA_PRIORITY[i];
  801      if (!mqls[queries[mediaName]].matches) {
  802        continue;
  803      }
  804
  805      var normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
  806      if (attrs[normalizedName]) {
  807        return attrs[normalizedName];
  808      }
  809    }
  810
  811    // fallback on unprefixed
  812    return attrs[getNormalizedName(attrs, attrName)];
  813  }
  814
  815  function watchResponsiveAttributes(attrNames, attrs, watchFn) {
  816    var unwatchFns = [];
  817    attrNames.forEach(function(attrName) {
  818      var normalizedName = getNormalizedName(attrs, attrName);
  819      if (angular.isDefined(attrs[normalizedName])) {
  820        unwatchFns.push(
  821            attrs.$observe(normalizedName, angular.bind(void 0, watchFn, null)));
  822      }
  823
  824      for (var mediaName in $mdConstant.MEDIA) {
  825        normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
  826        if (angular.isDefined(attrs[normalizedName])) {
  827          unwatchFns.push(
  828              attrs.$observe(normalizedName, angular.bind(void 0, watchFn, mediaName)));
  829        }
  830      }
  831    });
  832
  833    return function unwatch() {
  834      unwatchFns.forEach(function(fn) { fn(); })
  835    };
  836  }
  837
  838  // Improves performance dramatically
  839  function getNormalizedName(attrs, attrName) {
  840    return normalizeCache[attrName] ||
  841        (normalizeCache[attrName] = attrs.$normalize(attrName));
  842  }
  843}
  844
  845})();
  846(function(){
  847"use strict";
  848
  849angular
  850  .module('material.core')
  851  .config( ["$provide", function($provide) {
  852    $provide.decorator('$mdUtil', ['$delegate', function ($delegate) {
  853
  854      // Inject the prefixer into our original $mdUtil service.
  855      $delegate.prefixer = MdPrefixer;
  856
  857      return $delegate;
  858    }]);
  859  }]);
  860
  861function MdPrefixer(initialAttributes, buildSelector) {
  862  var PREFIXES = ['data', 'x'];
  863
  864  if (initialAttributes) {
  865    // The prefixer also accepts attributes as a parameter, and immediately builds a list or selector for
  866    // the specified attributes.
  867    return buildSelector ? _buildSelector(initialAttributes) : _buildList(initialAttributes);
  868  }
  869
  870  return {
  871    buildList: _buildList,
  872    buildSelector: _buildSelector,
  873    hasAttribute: _hasAttribute,
  874    removeAttribute: _removeAttribute
  875  };
  876
  877  function _buildList(attributes) {
  878    attributes = angular.isArray(attributes) ? attributes : [attributes];
  879
  880    attributes.forEach(function(item) {
  881      PREFIXES.forEach(function(prefix) {
  882        attributes.push(prefix + '-' + item);
  883      });
  884    });
  885
  886    return attributes;
  887  }
  888
  889  function _buildSelector(attributes) {
  890    attributes = angular.isArray(attributes) ? attributes : [attributes];
  891
  892    return _buildList(attributes)
  893      .map(function(item) {
  894        return '[' + item + ']'
  895      })
  896      .join(',');
  897  }
  898
  899  function _hasAttribute(element, attribute) {
  900    element = _getNativeElement(element);
  901
  902    if (!element) {
  903      return false;
  904    }
  905
  906    var prefixedAttrs = _buildList(attribute);
  907
  908    for (var i = 0; i < prefixedAttrs.length; i++) {
  909      if (element.hasAttribute(prefixedAttrs[i])) {
  910        return true;
  911      }
  912    }
  913
  914    return false;
  915  }
  916
  917  function _removeAttribute(element, attribute) {
  918    element = _getNativeElement(element);
  919
  920    if (!element) {
  921      return;
  922    }
  923
  924    _buildList(attribute).forEach(function(prefixedAttribute) {
  925      element.removeAttribute(prefixedAttribute);
  926    });
  927  }
  928
  929  /**
  930   * Transforms a jqLite or DOM element into a HTML element.
  931   * This is useful when supporting jqLite elements and DOM elements at
  932   * same time.
  933   * @param element {JQLite|Element} Element to be parsed
  934   * @returns {HTMLElement} Parsed HTMLElement
  935   */
  936  function _getNativeElement(element) {
  937    element =  element[0] || element;
  938
  939    if (element.nodeType) {
  940      return element;
  941    }
  942  }
  943
  944}
  945})();
  946(function(){
  947"use strict";
  948
  949/*
  950 * This var has to be outside the angular factory, otherwise when
  951 * there are multiple material apps on the same page, each app
  952 * will create its own instance of this array and the app's IDs
  953 * will not be unique.
  954 */
  955UtilFactory.$inject = ["$document", "$timeout", "$compile", "$rootScope", "$$mdAnimate", "$interpolate", "$log", "$rootElement", "$window", "$$rAF"];
  956var nextUniqueId = 0;
  957
  958/**
  959 * @ngdoc module
  960 * @name material.core.util
  961 * @description
  962 * Util
  963 */
  964angular
  965  .module('material.core')
  966  .factory('$mdUtil', UtilFactory);
  967
  968/**
  969 * @ngInject
  970 */
  971function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $interpolate, $log, $rootElement, $window, $$rAF) {
  972  // Setup some core variables for the processTemplate method
  973  var startSymbol = $interpolate.startSymbol(),
  974    endSymbol = $interpolate.endSymbol(),
  975    usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}'));
  976
  977  /**
  978   * Checks if the target element has the requested style by key
  979   * @param {DOMElement|JQLite} target Target element
  980   * @param {string} key Style key
  981   * @param {string=} expectedVal Optional expected value
  982   * @returns {boolean} Whether the target element has the style or not
  983   */
  984  var hasComputedStyle = function (target, key, expectedVal) {
  985    var hasValue = false;
  986
  987    if ( target && target.length  ) {
  988      var computedStyles = $window.getComputedStyle(target[0]);
  989      hasValue = angular.isDefined(computedStyles[key]) && (expectedVal ? computedStyles[key] == expectedVal : true);
  990    }
  991
  992    return hasValue;
  993  };
  994
  995  function validateCssValue(value) {
  996    return !value       ? '0'   :
  997      hasPx(value) || hasPercent(value) ? value : value + 'px';
  998  }
  999
 1000  function hasPx(value) {
 1001    return String(value).indexOf('px') > -1;
 1002  }
 1003
 1004  function hasPercent(value) {
 1005    return String(value).indexOf('%') > -1;
 1006
 1007  }
 1008
 1009  var $mdUtil = {
 1010    dom: {},
 1011    now: window.performance ?
 1012      angular.bind(window.performance, window.performance.now) : Date.now || function() {
 1013      return new Date().getTime();
 1014    },
 1015
 1016    /**
 1017     * Bi-directional accessor/mutator used to easily update an element's
 1018     * property based on the current 'dir'ectional value.
 1019     */
 1020    bidi : function(element, property, lValue, rValue) {
 1021      var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl');
 1022
 1023      // If accessor
 1024      if ( arguments.length == 0 ) return ltr ? 'ltr' : 'rtl';
 1025
 1026      // If mutator
 1027      var elem = angular.element(element);
 1028
 1029      if ( ltr && angular.isDefined(lValue)) {
 1030        elem.css(property, validateCssValue(lValue));
 1031      }
 1032      else if ( !ltr && angular.isDefined(rValue)) {
 1033        elem.css(property, validateCssValue(rValue) );
 1034      }
 1035    },
 1036
 1037    bidiProperty: function (element, lProperty, rProperty, value) {
 1038      var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl');
 1039
 1040      var elem = angular.element(element);
 1041
 1042      if ( ltr && angular.isDefined(lProperty)) {
 1043        elem.css(lProperty, validateCssValue(value));
 1044        elem.css(rProperty, '');
 1045      }
 1046      else if ( !ltr && angular.isDefined(rProperty)) {
 1047        elem.css(rProperty, validateCssValue(value) );
 1048        elem.css(lProperty, '');
 1049      }
 1050    },
 1051
 1052    clientRect: function(element, offsetParent, isOffsetRect) {
 1053      var node = getNode(element);
 1054      offsetParent = getNode(offsetParent || node.offsetParent || document.body);
 1055      var nodeRect = node.getBoundingClientRect();
 1056
 1057      // The user can ask for an offsetRect: a rect relative to the offsetParent,
 1058      // or a clientRect: a rect relative to the page
 1059      var offsetRect = isOffsetRect ?
 1060        offsetParent.getBoundingClientRect() :
 1061      {left: 0, top: 0, width: 0, height: 0};
 1062      return {
 1063        left: nodeRect.left - offsetRect.left,
 1064        top: nodeRect.top - offsetRect.top,
 1065        width: nodeRect.width,
 1066        height: nodeRect.height
 1067      };
 1068    },
 1069    offsetRect: function(element, offsetParent) {
 1070      return $mdUtil.clientRect(element, offsetParent, true);
 1071    },
 1072
 1073    // Annoying method to copy nodes to an array, thanks to IE
 1074    nodesToArray: function(nodes) {
 1075      nodes = nodes || [];
 1076
 1077      var results = [];
 1078      for (var i = 0; i < nodes.length; ++i) {
 1079        results.push(nodes.item(i));
 1080      }
 1081      return results;
 1082    },
 1083
 1084    /**
 1085     * Calculate the positive scroll offset
 1086     * TODO: Check with pinch-zoom in IE/Chrome;
 1087     *       https://code.google.com/p/chromium/issues/detail?id=496285
 1088     */
 1089    scrollTop: function(element) {
 1090      element = angular.element(element || $document[0].body);
 1091
 1092      var body = (element[0] == $document[0].body) ? $document[0].body : undefined;
 1093      var scrollTop = body ? body.scrollTop + body.parentElement.scrollTop : 0;
 1094
 1095      // Calculate the positive scroll offset
 1096      return scrollTop || Math.abs(element[0].getBoundingClientRect().top);
 1097    },
 1098
 1099    /**
 1100     * Finds the proper focus target by searching the DOM.
 1101     *
 1102     * @param containerEl
 1103     * @param attributeVal
 1104     * @returns {*}
 1105     */
 1106    findFocusTarget: function(containerEl, attributeVal) {
 1107      var AUTO_FOCUS = this.prefixer('md-autofocus', true);
 1108      var elToFocus;
 1109
 1110      elToFocus = scanForFocusable(containerEl, attributeVal || AUTO_FOCUS);
 1111
 1112      if ( !elToFocus && attributeVal != AUTO_FOCUS) {
 1113        // Scan for deprecated attribute
 1114        elToFocus = scanForFocusable(containerEl, this.prefixer('md-auto-focus', true));
 1115
 1116        if ( !elToFocus ) {
 1117          // Scan for fallback to 'universal' API
 1118          elToFocus = scanForFocusable(containerEl, AUTO_FOCUS);
 1119        }
 1120      }
 1121
 1122      return elToFocus;
 1123
 1124      /**
 1125       * Can target and nested children for specified Selector (attribute)
 1126       * whose value may be an expression that evaluates to True/False.
 1127       */
 1128      function scanForFocusable(target, selector) {
 1129        var elFound, items = target[0].querySelectorAll(selector);
 1130
 1131        // Find the last child element with the focus attribute
 1132        if ( items && items.length ){
 1133          items.length && angular.forEach(items, function(it) {
 1134            it = angular.element(it);
 1135
 1136            // Check the element for the md-autofocus class to ensure any associated expression
 1137            // evaluated to true.
 1138            var isFocusable = it.hasClass('md-autofocus');
 1139            if (isFocusable) elFound = it;
 1140          });
 1141        }
 1142        return elFound;
 1143      }
 1144    },
 1145
 1146    /**
 1147     * Disables scroll around the passed parent element.
 1148     * @param element Unused
 1149     * @param {!Element|!angular.JQLite} parent Element to disable scrolling within.
 1150     *   Defaults to body if none supplied.
 1151     * @param options Object of options to modify functionality
 1152     *   - disableScrollMask Boolean of whether or not to create a scroll mask element or
 1153     *     use the passed parent element.
 1154     */
 1155    disableScrollAround: function(element, parent, options) {
 1156      $mdUtil.disableScrollAround._count = $mdUtil.disableScrollAround._count || 0;
 1157      ++$mdUtil.disableScrollAround._count;
 1158      if ($mdUtil.disableScrollAround._enableScrolling) return $mdUtil.disableScrollAround._enableScrolling;
 1159      var body = $document[0].body,
 1160        restoreBody = disableBodyScroll(),
 1161        restoreElement = disableElementScroll(parent);
 1162
 1163      return $mdUtil.disableScrollAround._enableScrolling = function() {
 1164        if (!--$mdUtil.disableScrollAround._count) {
 1165          restoreBody();
 1166          restoreElement();
 1167          delete $mdUtil.disableScrollAround._enableScrolling;
 1168        }
 1169      };
 1170
 1171      // Creates a virtual scrolling mask to absorb touchmove, keyboard, scrollbar clicking, and wheel events
 1172      function disableElementScroll(element) {
 1173        element = angular.element(element || body);
 1174        var scrollMask;
 1175        if (options && options.disableScrollMask) {
 1176          scrollMask = element;
 1177        } else {
 1178          element = element[0];
 1179          scrollMask = angular.element(
 1180            '<div class="md-scroll-mask">' +
 1181            '  <div class="md-scroll-mask-bar"></div>' +
 1182            '</div>');
 1183          element.appendChild(scrollMask[0]);
 1184        }
 1185
 1186        scrollMask.on('wheel', preventDefault);
 1187        scrollMask.on('touchmove', preventDefault);
 1188
 1189        return function restoreScroll() {
 1190          scrollMask.off('wheel');
 1191          scrollMask.off('touchmove');
 1192          scrollMask[0].parentNode.removeChild(scrollMask[0]);
 1193          delete $mdUtil.disableScrollAround._enableScrolling;
 1194        };
 1195
 1196        function preventDefault(e) {
 1197          e.preventDefault();
 1198        }
 1199      }
 1200
 1201      // Converts the body to a position fixed block and translate it to the proper scroll position
 1202      function disableBodyScroll() {
 1203        var htmlNode = body.parentNode;
 1204        var restoreHtmlStyle = htmlNode.style.cssText || '';
 1205        var restoreBodyStyle = body.style.cssText || '';
 1206        var scrollOffset = $mdUtil.scrollTop(body);
 1207        var clientWidth = body.clientWidth;
 1208
 1209        if (body.scrollHeight > body.clientHeight + 1) {
 1210          applyStyles(body, {
 1211            position: 'fixed',
 1212            width: '100%',
 1213            top: -scrollOffset + 'px'
 1214          });
 1215
 1216          htmlNode.style.overflowY = 'scroll';
 1217        }
 1218
 1219        if (body.clientWidth < clientWidth) applyStyles(body, {overflow: 'hidden'});
 1220
 1221        return function restoreScroll() {
 1222          body.style.cssText = restoreBodyStyle;
 1223          htmlNode.style.cssText = restoreHtmlStyle;
 1224          body.scrollTop = scrollOffset;
 1225          htmlNode.scrollTop = scrollOffset;
 1226        };
 1227      }
 1228
 1229      function applyStyles(el, styles) {
 1230        for (var key in styles) {
 1231          el.style[key] = styles[key];
 1232        }
 1233      }
 1234    },
 1235    enableScrolling: function() {
 1236      var method = this.disableScrollAround._enableScrolling;
 1237      method && method();
 1238    },
 1239    floatingScrollbars: function() {
 1240      if (this.floatingScrollbars.cached === undefined) {
 1241        var tempNode = angular.element('<div><div></div></div>').css({
 1242          width: '100%',
 1243          'z-index': -1,
 1244          position: 'absolute',
 1245          height: '35px',
 1246          'overflow-y': 'scroll'
 1247        });
 1248        tempNode.children().css('height', '60px');
 1249
 1250        $document[0].body.appendChild(tempNode[0]);
 1251        this.floatingScrollbars.cached = (tempNode[0].offsetWidth == tempNode[0].childNodes[0].offsetWidth);
 1252        tempNode.remove();
 1253      }
 1254      return this.floatingScrollbars.cached;
 1255    },
 1256
 1257    // Mobile safari only allows you to set focus in click event listeners...
 1258    forceFocus: function(element) {
 1259      var node = element[0] || element;
 1260
 1261      document.addEventListener('click', function focusOnClick(ev) {
 1262        if (ev.target === node && ev.$focus) {
 1263          node.focus();
 1264          ev.stopImmediatePropagation();
 1265          ev.preventDefault();
 1266          node.removeEventListener('click', focusOnClick);
 1267        }
 1268      }, true);
 1269
 1270      var newEvent = document.createEvent('MouseEvents');
 1271      newEvent.initMouseEvent('click', false, true, window, {}, 0, 0, 0, 0,
 1272        false, false, false, false, 0, null);
 1273      newEvent.$material = true;
 1274      newEvent.$focus = true;
 1275      node.dispatchEvent(newEvent);
 1276    },
 1277
 1278    /**
 1279     * facade to build md-backdrop element with desired styles
 1280     * NOTE: Use $compile to trigger backdrop postLink function
 1281     */
 1282    createBackdrop: function(scope, addClass) {
 1283      return $compile($mdUtil.supplant('<md-backdrop class="{0}">', [addClass]))(scope);
 1284    },
 1285
 1286    /**
 1287     * supplant() method from Crockford's `Remedial Javascript`
 1288     * Equivalent to use of $interpolate; without dependency on
 1289     * interpolation symbols and scope. Note: the '{<token>}' can
 1290     * be property names, property chains, or array indices.
 1291     */
 1292    supplant: function(template, values, pattern) {
 1293      pattern = pattern || /\{([^\{\}]*)\}/g;
 1294      return template.replace(pattern, function(a, b) {
 1295        var p = b.split('.'),
 1296          r = values;
 1297        try {
 1298          for (var s in p) {
 1299            if (p.hasOwnProperty(s) ) {
 1300              r = r[p[s]];
 1301            }
 1302          }
 1303        } catch (e) {
 1304          r = a;
 1305        }
 1306        return (typeof r === 'string' || typeof r === 'number') ? r : a;
 1307      });
 1308    },
 1309
 1310    fakeNgModel: function() {
 1311      return {
 1312        $fake: true,
 1313        $setTouched: angular.noop,
 1314        $setViewValue: function(value) {
 1315          this.$viewValue = value;
 1316          this.$render(value);
 1317          this.$viewChangeListeners.forEach(function(cb) {
 1318            cb();
 1319          });
 1320        },
 1321        $isEmpty: function(value) {
 1322          return ('' + value).length === 0;
 1323        },
 1324        $parsers: [],
 1325        $formatters: [],
 1326        $viewChangeListeners: [],
 1327        $render: angular.noop
 1328      };
 1329    },
 1330
 1331    // Returns a function, that, as long as it continues to be invoked, will not
 1332    // be triggered. The function will be called after it stops being called for
 1333    // N milliseconds.
 1334    // @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs
 1335    // @param invokeApply should the $timeout trigger $digest() dirty checking
 1336    debounce: function(func, wait, scope, invokeApply) {
 1337      var timer;
 1338
 1339      return function debounced() {
 1340        var context = scope,
 1341          args = Array.prototype.slice.call(arguments);
 1342
 1343        $timeout.cancel(timer);
 1344        timer = $timeout(function() {
 1345
 1346          timer = undefined;
 1347          func.apply(context, args);
 1348
 1349        }, wait || 10, invokeApply);
 1350      };
 1351    },
 1352
 1353    // Returns a function that can only be triggered every `delay` milliseconds.
 1354    // In other words, the function will not be called unless it has been more
 1355    // than `delay` milliseconds since the last call.
 1356    throttle: function throttle(func, delay) {
 1357      var recent;
 1358      return function throttled() {
 1359        var context = this;
 1360        var args = arguments;
 1361        var now = $mdUtil.now();
 1362
 1363        if (!recent || (now - recent > delay)) {
 1364          func.apply(context, args);
 1365          recent = now;
 1366        }
 1367      };
 1368    },
 1369
 1370    /**
 1371     * Measures the number of milliseconds taken to run the provided callback
 1372     * function. Uses a high-precision timer if available.
 1373     */
 1374    time: function time(cb) {
 1375      var start = $mdUtil.now();
 1376      cb();
 1377      return $mdUtil.now() - start;
 1378    },
 1379
 1380    /**
 1381     * Create an implicit getter that caches its `getter()`
 1382     * lookup value
 1383     */
 1384    valueOnUse : function (scope, key, getter) {
 1385      var value = null, args = Array.prototype.slice.call(arguments);
 1386      var params = (args.length > 3) ? args.slice(3) : [ ];
 1387
 1388      Object.defineProperty(scope, key, {
 1389        get: function () {
 1390          if (value === null) value = getter.apply(scope, params);
 1391          return value;
 1392        }
 1393      });
 1394    },
 1395
 1396    /**
 1397     * Get a unique ID.
 1398     *
 1399     * @returns {string} an unique numeric string
 1400     */
 1401    nextUid: function() {
 1402      return '' + nextUniqueId++;
 1403    },
 1404
 1405    // Stop watchers and events from firing on a scope without destroying it,
 1406    // by disconnecting it from its parent and its siblings' linked lists.
 1407    disconnectScope: function disconnectScope(scope) {
 1408      if (!scope) return;
 1409
 1410      // we can't destroy the root scope or a scope that has been already destroyed
 1411      if (scope.$root === scope) return;
 1412      if (scope.$$destroyed) return;
 1413
 1414      var parent = scope.$parent;
 1415      scope.$$disconnected = true;
 1416
 1417      // See Scope.$destroy
 1418      if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling;
 1419      if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling;
 1420      if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
 1421      if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
 1422
 1423      scope.$$nextSibling = scope.$$prevSibling = null;
 1424
 1425    },
 1426
 1427    // Undo the effects of disconnectScope above.
 1428    reconnectScope: function reconnectScope(scope) {
 1429      if (!scope) return;
 1430
 1431      // we can't disconnect the root node or scope already disconnected
 1432      if (scope.$root === scope) return;
 1433      if (!scope.$$disconnected) return;
 1434
 1435      var child = scope;
 1436
 1437      var parent = child.$parent;
 1438      child.$$disconnected = false;
 1439      // See Scope.$new for this logic...
 1440      child.$$prevSibling = parent.$$childTail;
 1441      if (parent.$$childHead) {
 1442        parent.$$childTail.$$nextSibling = child;
 1443        parent.$$childTail = child;
 1444      } else {
 1445        parent.$$childHead = parent.$$childTail = child;
 1446      }
 1447    },
 1448
 1449    /*
 1450     * getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName
 1451     *
 1452     * @param el Element to start walking the DOM from
 1453     * @param check Either a string or a function. If a string is passed, it will be evaluated against
 1454     * each of the parent nodes' tag name. If a function is passed, the loop will call it with each of
 1455     * the parents and will use the return value to determine whether the node is a match.
 1456     * @param onlyParent Only start checking from the parent element, not `el`.
 1457     */
 1458    getClosest: function getClosest(el, validateWith, onlyParent) {
 1459      if ( angular.isString(validateWith) ) {
 1460        var tagName = validateWith.toUpperCase();
 1461        validateWith = function(el) {
 1462          return el.nodeName === tagName;
 1463        };
 1464      }
 1465
 1466      if (el instanceof angular.element) el = el[0];
 1467      if (onlyParent) el = el.parentNode;
 1468      if (!el) return null;
 1469
 1470      do {
 1471        if (validateWith(el)) {
 1472          return el;
 1473        }
 1474      } while (el = el.parentNode);
 1475
 1476      return null;
 1477    },
 1478
 1479    /**
 1480     * Build polyfill for the Node.contains feature (if needed)
 1481     */
 1482    elementContains: function(node, child) {
 1483      var hasContains = (window.Node && window.Node.prototype && Node.prototype.contains);
 1484      var findFn = hasContains ? angular.bind(node, node.contains) : angular.bind(node, function(arg) {
 1485        // compares the positions of two nodes and returns a bitmask
 1486        return (node === child) || !!(this.compareDocumentPosition(arg) & 16)
 1487      });
 1488
 1489      return findFn(child);
 1490    },
 1491
 1492    /**
 1493     * Functional equivalent for $element.filter(‘md-bottom-sheet’)
 1494     * useful with interimElements where the element and its container are important...
 1495     *
 1496     * @param {[]} elements to scan
 1497     * @param {string} name of node to find (e.g. 'md-dialog')
 1498     * @param {boolean=} optional flag to allow deep scans; defaults to 'false'.
 1499     * @param {boolean=} optional flag to enable log warnings; defaults to false
 1500     */
 1501    extractElementByName: function(element, nodeName, scanDeep, warnNotFound) {
 1502      var found = scanTree(element);
 1503      if (!found && !!warnNotFound) {
 1504        $log.warn( $mdUtil.supplant("Unable to find node '{0}' in element '{1}'.",[nodeName, element[0].outerHTML]) );
 1505      }
 1506
 1507      return angular.element(found || element);
 1508
 1509      /**
 1510       * Breadth-First tree scan for element with matching `nodeName`
 1511       */
 1512      function scanTree(element) {
 1513        return scanLevel(element) || (!!scanDeep ? scanChildren(element) : null);
 1514      }
 1515
 1516      /**
 1517       * Case-insensitive scan of current elements only (do not descend).
 1518       */
 1519      function scanLevel(element) {
 1520        if ( element ) {
 1521          for (var i = 0, len = element.length; i < len; i++) {
 1522            if (element[i].nodeName.toLowerCase() === nodeName) {
 1523              return element[i];
 1524            }
 1525          }
 1526        }
 1527        return null;
 1528      }
 1529
 1530      /**
 1531       * Scan children of specified node
 1532       */
 1533      function scanChildren(element) {
 1534        var found;
 1535        if ( element ) {
 1536          for (var i = 0, len = element.length; i < len; i++) {
 1537            var target = element[i];
 1538            if ( !found ) {
 1539              for (var j = 0, numChild = target.childNodes.length; j < numChild; j++) {
 1540                found = found || scanTree([target.childNodes[j]]);
 1541              }
 1542            }
 1543          }
 1544        }
 1545        return found;
 1546      }
 1547
 1548    },
 1549
 1550    /**
 1551     * Give optional properties with no value a boolean true if attr provided or false otherwise
 1552     */
 1553    initOptionalProperties: function(scope, attr, defaults) {
 1554      defaults = defaults || {};
 1555      angular.forEach(scope.$$isolateBindings, function(binding, key) {
 1556        if (binding.optional && angular.isUndefined(scope[key])) {
 1557          var attrIsDefined = angular.isDefined(attr[binding.attrName]);
 1558          scope[key] = angular.isDefined(defaults[key]) ? defaults[key] : attrIsDefined;
 1559        }
 1560      });
 1561    },
 1562
 1563    /**
 1564     * Alternative to $timeout calls with 0 delay.
 1565     * nextTick() coalesces all calls within a single frame
 1566     * to minimize $digest thrashing
 1567     *
 1568     * @param callback
 1569     * @param digest
 1570     * @returns {*}
 1571     */
 1572    nextTick: function(callback, digest, scope) {
 1573      //-- grab function reference for storing state details
 1574      var nextTick = $mdUtil.nextTick;
 1575      var timeout = nextTick.timeout;
 1576      var queue = nextTick.queue || [];
 1577
 1578      //-- add callback to the queue
 1579      queue.push({scope: scope, callback: callback});
 1580
 1581      //-- set default value for digest
 1582      if (digest == null) digest = true;
 1583
 1584      //-- store updated digest/queue values
 1585      nextTick.digest = nextTick.digest || digest;
 1586      nextTick.queue = queue;
 1587
 1588      //-- either return existing timeout or create a new one
 1589      return timeout || (nextTick.timeout = $timeout(processQueue, 0, false));
 1590
 1591      /**
 1592       * Grab a copy of the current queue
 1593       * Clear the queue for future use
 1594       * Process the existing queue
 1595       * Trigger digest if necessary
 1596       */
 1597      function processQueue() {
 1598        var queue = nextTick.queue;
 1599        var digest = nextTick.digest;
 1600
 1601        nextTick.queue = [];
 1602        nextTick.timeout = null;
 1603        nextTick.digest = false;
 1604
 1605        queue.forEach(function(queueItem) {
 1606          var skip = queueItem.scope && queueItem.scope.$$destroyed;
 1607          if (!skip) {
 1608            queueItem.callback();
 1609          }
 1610        });
 1611
 1612        if (digest) $rootScope.$digest();
 1613      }
 1614    },
 1615
 1616    /**
 1617     * Processes a template and replaces the start/end symbols if the application has
 1618     * overriden them.
 1619     *
 1620     * @param template The template to process whose start/end tags may be replaced.
 1621     * @returns {*}
 1622     */
 1623    processTemplate: function(template) {
 1624      if (usesStandardSymbols) {
 1625        return template;
 1626      } else {
 1627        if (!template || !angular.isString(template)) return template;
 1628        return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
 1629      }
 1630    },
 1631
 1632    /**
 1633     * Scan up dom hierarchy for enabled parent;
 1634     */
 1635    getParentWithPointerEvents: function (element) {
 1636      var parent = element.parent();
 1637
 1638      // jqLite might return a non-null, but still empty, parent; so check for parent and length
 1639      while (hasComputedStyle(parent, 'pointer-events', 'none')) {
 1640        parent = parent.parent();
 1641      }
 1642
 1643      return parent;
 1644    },
 1645
 1646    getNearestContentElement: function (element) {
 1647      var current = element.parent()[0];
 1648      // Look for the nearest parent md-content, stopping at the rootElement.
 1649      while (current && current !== $rootElement[0] && current !== document.body && current.nodeName.toUpperCase() !== 'MD-CONTENT') {
 1650        current = current.parentNode;
 1651      }
 1652      return current;
 1653    },
 1654
 1655    /**
 1656     * Checks if the current browser is natively supporting the `sticky` position.
 1657     * @returns {string} supported sticky property name
 1658     */
 1659    checkStickySupport: function() {
 1660      var stickyProp;
 1661      var testEl = angular.element('<div>');
 1662      $document[0].body.appendChild(testEl[0]);
 1663
 1664      var stickyProps = ['sticky', '-webkit-sticky'];
 1665      for (var i = 0; i < stickyProps.length; ++i) {
 1666        testEl.css({
 1667          position: stickyProps[i],
 1668          top: 0,
 1669          'z-index': 2
 1670        });
 1671
 1672        if (testEl.css('position') == stickyProps[i]) {
 1673          stickyProp = stickyProps[i];
 1674          break;
 1675        }
 1676      }
 1677
 1678      testEl.remove();
 1679
 1680      return stickyProp;
 1681    },
 1682
 1683    /**
 1684     * Parses an attribute value, mostly a string.
 1685     * By default checks for negated values and returns `false´ if present.
 1686     * Negated values are: (native falsy) and negative strings like:
 1687     * `false` or `0`.
 1688     * @param value Attribute value which should be parsed.
 1689     * @param negatedCheck When set to false, won't check for negated values.
 1690     * @returns {boolean}
 1691     */
 1692    parseAttributeBoolean: function(value, negatedCheck) {
 1693      return value === '' || !!value && (negatedCheck === false || value !== 'false' && value !== '0');
 1694    },
 1695
 1696    hasComputedStyle: hasComputedStyle,
 1697
 1698    /**
 1699     * Returns true if the parent form of the element has been submitted.
 1700     *
 1701     * @param element An Angular or HTML5 element.
 1702     *
 1703     * @returns {boolean}
 1704     */
 1705    isParentFormSubmitted: function(element) {
 1706      var parent = $mdUtil.getClosest(element, 'form');
 1707      var form = parent ? angular.element(parent).controller('form') : null;
 1708
 1709      return form ? form.$submitted : false;
 1710    },
 1711
 1712    /**
 1713     * Animate the requested element's scrollTop to the requested scrollPosition with basic easing.
 1714     *
 1715     * @param element The element to scroll.
 1716     * @param scrollEnd The new/final scroll position.
 1717     */
 1718    animateScrollTo: function(element, scrollEnd) {
 1719      var scrollStart = element.scrollTop;
 1720      var scrollChange = scrollEnd - scrollStart;
 1721      var scrollingDown = scrollStart < scrollEnd;
 1722      var startTime = $mdUtil.now();
 1723
 1724      $$rAF(scrollChunk);
 1725
 1726      function scrollChunk() {
 1727        var newPosition = calculateNewPosition();
 1728        
 1729        element.scrollTop = newPosition;
 1730        
 1731        if (scrollingDown ? newPosition < scrollEnd : newPosition > scrollEnd) {
 1732          $$rAF(scrollChunk);
 1733        }
 1734      }
 1735      
 1736      function calculateNewPosition() {
 1737        var duration = 1000;
 1738        var currentTime = $mdUtil.now() - startTime;
 1739        
 1740        return ease(currentTime, scrollStart, scrollChange, duration);
 1741      }
 1742
 1743      function ease(currentTime, start, change, duration) {
 1744        // If the duration has passed (which can occur if our app loses focus due to $$rAF), jump
 1745        // straight to the proper position
 1746        if (currentTime > duration) {
 1747          return start + change;
 1748        }
 1749        
 1750        var ts = (currentTime /= duration) * currentTime;
 1751        var tc = ts * currentTime;
 1752
 1753        return start + change * (-2 * tc + 3 * ts);
 1754      }
 1755    }
 1756  };
 1757
 1758
 1759// Instantiate other namespace utility methods
 1760
 1761  $mdUtil.dom.animator = $$mdAnimate($mdUtil);
 1762
 1763  return $mdUtil;
 1764
 1765  function getNode(el) {
 1766    return el[0] || el;
 1767  }
 1768
 1769}
 1770
 1771/*
 1772 * Since removing jQuery from the demos, some code that uses `element.focus()` is broken.
 1773 * We need to add `element.focus()`, because it's testable unlike `element[0].focus`.
 1774 */
 1775
 1776angular.element.prototype.focus = angular.element.prototype.focus || function() {
 1777    if (this.length) {
 1778      this[0].focus();
 1779    }
 1780    return this;
 1781  };
 1782angular.element.prototype.blur = angular.element.prototype.blur || function() {
 1783    if (this.length) {
 1784      this[0].blur();
 1785    }
 1786    return this;
 1787  };
 1788
 1789})();
 1790(function(){
 1791"use strict";
 1792
 1793/**
 1794 * @ngdoc module
 1795 * @name material.core.aria
 1796 * @description
 1797 * Aria Expectations for ngMaterial components.
 1798 */
 1799MdAriaService.$inject = ["$$rAF", "$log", "$window", "$interpolate"];
 1800angular
 1801  .module('material.core')
 1802  .provider('$mdAria', MdAriaProvider);
 1803
 1804/**
 1805 * @ngdoc service
 1806 * @name $mdAriaProvider
 1807 * @module material.core.aria
 1808 *
 1809 * @description
 1810 *
 1811 * Modify options of the `$mdAria` service, which will be used by most of the Angular Material
 1812 * components.
 1813 *
 1814 * You are able to disable `$mdAria` warnings, by using the following markup.
 1815 *
 1816 * <hljs lang="js">
 1817 *   app.config(function($mdAriaProvider) {
 1818 *     // Globally disables all ARIA warnings.
 1819 *     $mdAriaProvider.disableWarnings();
 1820 *   });
 1821 * </hljs>
 1822 *
 1823 */
 1824function MdAriaProvider() {
 1825
 1826  var self = this;
 1827
 1828  /**
 1829   * Whether we should show ARIA warnings in the console, if labels are missing on the element
 1830   * By default the warnings are enabled
 1831   */
 1832  self.showWarnings = true;
 1833
 1834  return {
 1835    disableWarnings: disableWarnings,
 1836    $get: ["$$rAF", "$log", "$window", "$interpolate", function($$rAF, $log, $window, $interpolate) {
 1837      return MdAriaService.apply(self, arguments);
 1838    }]
 1839  };
 1840
 1841  /**
 1842   * @ngdoc method
 1843   * @name $mdAriaProvider#disableWarnings
 1844   * @description Disables all ARIA warnings generated by Angular Material.
 1845   */
 1846  function disableWarnings() {
 1847    self.showWarnings = false;
 1848  }
 1849}
 1850
 1851/*
 1852 * @ngInject
 1853 */
 1854function MdAriaService($$rAF, $log, $window, $interpolate) {
 1855
 1856  // Load the showWarnings option from the current context and store it inside of a scope variable,
 1857  // because the context will be probably lost in some function calls.
 1858  var showWarnings = this.showWarnings;
 1859
 1860  return {
 1861    expect: expect,
 1862    expectAsync: expectAsync,
 1863    expectWithText: expectWithText,
 1864    expectWithoutText: expectWithoutText
 1865  };
 1866
 1867  /**
 1868   * Check if expected attribute has been specified on the target element or child
 1869   * @param element
 1870   * @param attrName
 1871   * @param {optional} defaultValue What to set the attr to if no value is found
 1872   */
 1873  function expect(element, attrName, defaultValue) {
 1874
 1875    var node = angular.element(element)[0] || element;
 1876
 1877    // if node exists and neither it nor its children have the attribute
 1878    if (node &&
 1879       ((!node.hasAttribute(attrName) ||
 1880        node.getAttribute(attrName).length === 0) &&
 1881        !childHasAttribute(node, attrName))) {
 1882
 1883      defaultValue = angular.isString(defaultValue) ? defaultValue.trim() : '';
 1884      if (defaultValue.length) {
 1885        element.attr(attrName, defaultValue);
 1886      } else if (showWarnings) {
 1887        $log.warn('ARIA: Attribute "', attrName, '", required for accessibility, is missing on node:', node);
 1888      }
 1889
 1890    }
 1891  }
 1892
 1893  function expectAsync(element, attrName, defaultValueGetter) {
 1894    // Problem: when retrieving the element's contents synchronously to find the label,
 1895    // the text may not be defined yet in the case of a binding.
 1896    // There is a higher chance that a binding will be defined if we wait one frame.
 1897    $$rAF(function() {
 1898        expect(element, attrName, defaultValueGetter());
 1899    });
 1900  }
 1901
 1902  function expectWithText(element, attrName) {
 1903    var content = getText(element) || "";
 1904    var hasBinding = content.indexOf($interpolate.startSymbol()) > -1;
 1905
 1906    if ( hasBinding ) {
 1907      expectAsync(element, attrName, function() {
 1908        return getText(element);
 1909      });
 1910    } else {
 1911      expect(element, attrName, content);
 1912    }
 1913  }
 1914
 1915  function expectWithoutText(element, attrName) {
 1916    var content = getText(element);
 1917    var hasBinding = content.indexOf($interpolate.startSymbol()) > -1;
 1918
 1919    if ( !hasBinding && !content) {
 1920      expect(element, attrName, content);
 1921    }
 1922  }
 1923
 1924  function getText(element) {
 1925    element = element[0] || element;
 1926    var walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
 1927    var text = '';
 1928
 1929    var node;
 1930    while (node = walker.nextNode()) {
 1931      if (!isAriaHiddenNode(node)) {
 1932        text += node.textContent;
 1933      }
 1934    }
 1935
 1936    return text.trim() || '';
 1937
 1938    function isAriaHiddenNode(node) {
 1939      while (node.parentNode && (node = node.parentNode) !== element) {
 1940        if (node.getAttribute && node.getAttribute('aria-hidden') === 'true') {
 1941          return true;
 1942        }
 1943      }
 1944    }
 1945  }
 1946
 1947  function childHasAttribute(node, attrName) {
 1948    var hasChildren = node.hasChildNodes(),
 1949        hasAttr = false;
 1950
 1951    function isHidden(el) {
 1952      var style = el.currentStyle ? el.currentStyle : $window.getComputedStyle(el);
 1953      return (style.display === 'none');
 1954    }
 1955
 1956    if (hasChildren) {
 1957      var children = node.childNodes;
 1958      for (var i=0; i < children.length; i++) {
 1959        var child = children[i];
 1960        if (child.nodeType === 1 && child.hasAttribute(attrName)) {
 1961          if (!isHidden(child)) {
 1962            hasAttr = true;
 1963          }
 1964        }
 1965      }
 1966    }
 1967
 1968    return hasAttr;
 1969  }
 1970}
 1971
 1972})();
 1973(function(){
 1974"use strict";
 1975
 1976
 1977mdCompilerService.$inject = ["$q", "$templateRequest", "$injector", "$compile", "$controller"];angular
 1978  .module('material.core')
 1979  .service('$mdCompiler', mdCompilerService);
 1980
 1981function mdCompilerService($q, $templateRequest, $injector, $compile, $controller) {
 1982  /* jshint validthis: true */
 1983
 1984  /*
 1985   * @ngdoc service
 1986   * @name $mdCompiler
 1987   * @module material.core
 1988   * @description
 1989   * The $mdCompiler service is an abstraction of angular's compiler, that allows the developer
 1990   * to easily compile an element with a templateUrl, controller, and locals.
 1991   *
 1992   * @usage
 1993   * <hljs lang="js">
 1994   * $mdCompiler.compile({
 1995   *   templateUrl: 'modal.html',
 1996   *   controller: 'ModalCtrl',
 1997   *   locals: {
 1998   *     modal: myModalInstance;
 1999   *   }
 2000   * }).then(function(compileData) {
 2001   *   compileData.element; // modal.html's template in an element
 2002   *   compileData.link(myScope); //attach controller & scope to element
 2003   * });
 2004   * </hljs>
 2005   */
 2006
 2007   /*
 2008    * @ngdoc method
 2009    * @name $mdCompiler#compile
 2010    * @description A helper to compile an HTML template/templateUrl with a given controller,
 2011    * locals, and scope.
 2012    * @param {object} options An options object, with the following properties:
 2013    *
 2014    *    - `controller` - `{(string=|function()=}` Controller fn that should be associated with
 2015    *      newly created scope or the name of a registered controller if passed as a string.
 2016    *    - `controllerAs` - `{string=}` A controller alias name. If present the controller will be
 2017    *      published to scope under the `controllerAs` name.
 2018    *    - `template` - `{string=}` An html template as a string.
 2019    *    - `templateUrl` - `{string=}` A path to an html template.
 2020    *    - `transformTemplate` - `{function(template)=}` A function which transforms the template after
 2021    *      it is loaded. It will be given the template string as a parameter, and should
 2022    *      return a a new string representing the transformed template.
 2023    *    - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
 2024    *      be injected into the controller. If any of these dependencies are promises, the compiler
 2025    *      will wait for them all to be resolved, or if one is rejected before the controller is
 2026    *      instantiated `compile()` will fail..
 2027    *      * `key` - `{string}`: a name of a dependency to be injected into the controller.
 2028    *      * `factory` - `{string|function}`: If `string` then it is an alias for a service.
 2029    *        Otherwise if function, then it is injected and the return value is treated as the
 2030    *        dependency. If the result is a promise, it is resolved before its value is
 2031    *        injected into the controller.
 2032    *
 2033    * @returns {object=} promise A promise, which will be resolved with a `compileData` object.
 2034    * `compileData` has the following properties:
 2035    *
 2036    *   - `element` - `{element}`: an uncompiled element matching the provided template.
 2037    *   - `link` - `{function(scope)}`: A link function, which, when called, will compile
 2038    *     the element and instantiate the provided controller (if given).
 2039    *   - `locals` - `{object}`: The locals which will be passed into the controller once `link` is
 2040    *     called. If `bindToController` is true, they will be coppied to the ctrl instead
 2041    *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
 2042    */
 2043  this.compile = function(options) {
 2044    var templateUrl = options.templateUrl;
 2045    var template = options.template || '';
 2046    var controller = options.controller;
 2047    var controllerAs = options.controllerAs;
 2048    var resolve = angular.extend({}, options.resolve || {});
 2049    var locals = angular.extend({}, options.locals || {});
 2050    var transformTemplate = options.transformTemplate || angular.identity;
 2051    var bindToController = options.bindToController;
 2052
 2053    // Take resolve values and invoke them.
 2054    // Resolves can either be a string (value: 'MyRegisteredAngularConst'),
 2055    // or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {})
 2056    angular.forEach(resolve, function(value, key) {
 2057      if (angular.isString(value)) {
 2058        resolve[key] = $injector.get(value);
 2059      } else {
 2060        resolve[key] = $injector.invoke(value);
 2061      }
 2062    });
 2063    //Add the locals, which are just straight values to inject
 2064    //eg locals: { three: 3 }, will inject three into the controller
 2065    angular.extend(resolve, locals);
 2066
 2067    if (templateUrl) {
 2068      resolve.$template = $templateRequest(templateUrl)
 2069        .then(function(response) {
 2070          return response;
 2071        });
 2072    } else {
 2073      resolve.$template = $q.when(template);
 2074    }
 2075
 2076    // Wait for all the resolves to finish if they are promises
 2077    return $q.all(resolve).then(function(locals) {
 2078
 2079      var compiledData;
 2080      var template = transformTemplate(locals.$template, options);
 2081      var element = options.element || angular.element('<div>').html(template.trim()).contents();
 2082      var linkFn = $compile(element);
 2083
 2084      // Return a linking function that can be used later when the element is ready
 2085      return compiledData = {
 2086        locals: locals,
 2087        element: element,
 2088        link: function link(scope) {
 2089          locals.$scope = scope;
 2090
 2091          //Instantiate controller if it exists, because we have scope
 2092          if (controller) {
 2093            var invokeCtrl = $controller(controller, locals, true, controllerAs);
 2094            if (bindToController) {
 2095              angular.extend(invokeCtrl.instance, locals);
 2096            }
 2097            var ctrl = invokeCtrl();
 2098            //See angular-route source for this logic
 2099            element.data('$ngControllerController', ctrl);
 2100            element.children().data('$ngControllerController', ctrl);
 2101
 2102            // Publish reference to this controller
 2103            compiledData.controller = ctrl;
 2104          }
 2105          return linkFn(scope);
 2106        }
 2107      };
 2108    });
 2109
 2110  };
 2111}
 2112
 2113})();
 2114(function(){
 2115"use strict";
 2116
 2117
 2118MdGesture.$inject = ["$$MdGestureHandler", "$$rAF", "$timeout"];
 2119attachToDocument.$inject = ["$mdGesture", "$$MdGestureHandler"];var HANDLERS = {};
 2120
 2121/* The state of the current 'pointer'
 2122 * The pointer represents the state of the current touch.
 2123 * It contains normalized x and y coordinates from DOM events,
 2124 * as well as other information abstracted from the DOM.
 2125 */
 2126
 2127var pointer, lastPointer, forceSkipClickHijack = false;
 2128
 2129/**
 2130 * The position of the most recent click if that click was on a label element.
 2131 * @type {{x: number, y: number}?}
 2132 */
 2133var lastLabelClickPos = null;
 2134
 2135// Used to attach event listeners once when multiple ng-apps are running.
 2136var isInitialized = false;
 2137
 2138angular
 2139  .module('material.core.gestures', [ ])
 2140  .provider('$mdGesture', MdGestureProvider)
 2141  .factory('$$MdGestureHandler', MdGestureHandler)
 2142  .run( attachToDocument );
 2143
 2144/**
 2145   * @ngdoc service
 2146   * @name $mdGestureProvider
 2147   * @module material.core.gestures
 2148   *
 2149   * @description
 2150   * In some scenarios on Mobile devices (without jQuery), the click events should NOT be hijacked.
 2151   * `$mdGestureProvider` is used to configure the Gesture module to ignore or skip click hijacking on mobile
 2152   * devices.
 2153   *
 2154   * <hljs lang="js">
 2155   *   app.config(function($mdGestureProvider) {
 2156   *
 2157   *     // For mobile devices without jQuery loaded, do not
 2158   *     // intercept click events during the capture phase.
 2159   *     $mdGestureProvider.skipClickHijack();
 2160   *
 2161   *   });
 2162   * </hljs>
 2163   *
 2164   */
 2165function MdGestureProvider() { }
 2166
 2167MdGestureProvider.prototype = {
 2168
 2169  // Publish access to setter to configure a variable  BEFORE the
 2170  // $mdGesture service is instantiated...
 2171  skipClickHijack: function() {
 2172    return forceSkipClickHijack = true;
 2173  },
 2174
 2175  /**
 2176   * $get is used to build an instance of $mdGesture
 2177   * @ngInject
 2178   */
 2179  $get : ["$$MdGestureHandler", "$$rAF", "$timeout", function($$MdGestureHandler, $$rAF, $timeout) {
 2180       return new MdGesture($$MdGestureHandler, $$rAF, $timeout);
 2181  }]
 2182};
 2183
 2184
 2185
 2186/**
 2187 * MdGesture factory construction function
 2188 * @ngInject
 2189 */
 2190function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
 2191  var userAgent = navigator.userAgent || navigator.vendor || window.opera;
 2192  var isIos = userAgent.match(/ipad|iphone|ipod/i);
 2193  var isAndroid = userAgent.match(/android/i);
 2194  var touchActionProperty = getTouchAction();
 2195  var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
 2196
 2197  var self = {
 2198    handler: addHandler,
 2199    register: register,
 2200    isIos: isIos,
 2201    isAndroid: isAndroid,
 2202    // On mobile w/out jQuery, we normally intercept clicks. Should we skip that?
 2203    isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack
 2204  };
 2205
 2206  if (self.isHijackingClicks) {
 2207    var maxClickDistance = 6;
 2208    self.handler('click', {
 2209      options: {
 2210        maxDistance: maxClickDistance
 2211      },
 2212      onEnd: checkDistanceAndEmit('click')
 2213    });
 2214
 2215    self.handler('focus', {
 2216      options: {
 2217        maxDistance: maxClickDistance
 2218      },
 2219      onEnd: function(ev, pointer) {
 2220        if (pointer.distance < this.state.options.maxDistance) {
 2221          if (canFocus(ev.target)) {
 2222            this.dispatchEvent(ev, 'focus', pointer);
 2223            ev.target.focus();
 2224          }
 2225        }
 2226
 2227        function canFocus(element) {
 2228          var focusableElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA', 'VIDEO', 'AUDIO'];
 2229
 2230          return (element.getAttribute('tabindex') != '-1') &&
 2231              !element.hasAttribute('DISABLED') &&
 2232              (element.hasAttribute('tabindex') || element.hasAttribute('href') || element.isContentEditable ||
 2233              (focusableElements.indexOf(element.nodeName) != -1));
 2234        }
 2235      }
 2236    });
 2237
 2238    self.handler('mouseup', {
 2239      options: {
 2240        maxDistance: maxClickDistance
 2241      },
 2242      onEnd: checkDistanceAndEmit('mouseup')
 2243    });
 2244
 2245    self.handler('mousedown', {
 2246      onStart: function(ev) {
 2247        this.dispatchEvent(ev, 'mousedown');
 2248      }
 2249    });
 2250  }
 2251
 2252  function checkDistanceAndEmit(eventName) {
 2253    return function(ev, pointer) {
 2254      if (pointer.distance < this.state.options.maxDistance) {
 2255        this.dispatchEvent(ev, eventName, pointer);
 2256      }
 2257    };
 2258  }
 2259
 2260  /*
 2261   * Register an element to listen for a handler.
 2262   * This allows an element to override the default options for a handler.
 2263   * Additionally, some handlers like drag and hold only dispatch events if
 2264   * the domEvent happens inside an element that's registered to listen for these events.
 2265   *
 2266   * @see GestureHandler for how overriding of default options works.
 2267   * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horziontal: false })
 2268   */
 2269  function register(element, handlerName, options) {
 2270    var handler = HANDLERS[handlerName.replace(/^\$md./, '')];
 2271    if (!handler) {
 2272      throw new Error('Failed to register element with handler ' + handlerName + '. ' +
 2273      'Available handlers: ' + Object.keys(HANDLERS).join(', '));
 2274    }
 2275    return handler.registerElement(element, options);
 2276  }
 2277
 2278  /*
 2279   * add a handler to $mdGesture. see below.
 2280   */
 2281  function addHandler(name, definition) {
 2282    var handler = new $$MdGestureHandler(name);
 2283    angular.extend(handler, definition);
 2284    HANDLERS[name] = handler;
 2285
 2286    return self;
 2287  }
 2288
 2289  /*
 2290   * Register handlers. These listen to touch/start/move events, interpret them,
 2291   * and dispatch gesture events depending on options & conditions. These are all
 2292   * instances of GestureHandler.
 2293   * @see GestureHandler
 2294   */
 2295  return self
 2296    /*
 2297     * The press handler dispatches an event on touchdown/touchend.
 2298     * It's a simple abstraction of touch/mouse/pointer start and end.
 2299     */
 2300    .handler('press', {
 2301      onStart: function (ev, pointer) {
 2302        this.dispatchEvent(ev, '$md.pressdown');
 2303      },
 2304      onEnd: function (ev, pointer) {
 2305        this.dispatchEvent(ev, '$md.pressup');
 2306      }
 2307    })
 2308
 2309    /*
 2310     * The hold handler dispatches an event if the user keeps their finger within
 2311     * the same <maxDistance> area for <delay> ms.
 2312     * The hold handler will only run if a parent of the touch target is registered
 2313     * to listen for hold events through $mdGesture.register()
 2314     */
 2315    .handler('hold', {
 2316      options: {
 2317        maxDistance: 6,
 2318        delay: 500
 2319      },
 2320      onCancel: function () {
 2321        $timeout.cancel(this.state.timeout);
 2322      },
 2323      onStart: function (ev, pointer) {
 2324        // For hold, require a parent to be registered with $mdGesture.register()
 2325        // Because we prevent scroll events, this is necessary.
 2326        if (!this.state.registeredParent) return this.cancel();
 2327
 2328        this.state.pos = {x: pointer.x, y: pointer.y};
 2329        this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() {
 2330          this.dispatchEvent(ev, '$md.hold');
 2331          this.cancel(); //we're done!
 2332        }), this.state.options.delay, false);
 2333      },
 2334      onMove: function (ev, pointer) {
 2335        // Don't scroll while waiting for hold.
 2336        // If we don't preventDefault touchmove events here, Android will assume we don't
 2337        // want to listen to anymore touch events. It will start scrolling and stop sending
 2338        // touchmove events.
 2339        if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
 2340
 2341        // If the user moves greater than <maxDistance> pixels, stop the hold timer
 2342        // set in onStart
 2343        var dx = this.state.pos.x - pointer.x;
 2344        var dy = this.state.pos.y - pointer.y;
 2345        if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) {
 2346          this.cancel();
 2347        }
 2348      },
 2349      onEnd: function () {
 2350        this.onCancel();
 2351      }
 2352    })
 2353
 2354    /*
 2355     * The drag handler dispatches a drag event if the user holds and moves his finger greater than
 2356     * <minDistance> px in the x or y direction, depending on options.horizontal.
 2357     * The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in
 2358     * the perpendicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>
 2359     * pixels vertically, this handler won't consider the move part of a drag.
 2360     */
 2361    .handler('drag', {
 2362      options: {
 2363        minDistance: 6,
 2364        horizontal: true,
 2365        cancelMultiplier: 1.5
 2366      },
 2367      onSetup: function(element, options) {
 2368        if (touchActionProperty) {
 2369          // We check for horizontal to be false, because otherwise we would overwrite the default opts.
 2370          this.oldTouchAction = element[0].style[touchActionProperty];
 2371          element[0].style[touchActionProperty] = options.horizontal === false ? 'pan-y' : 'pan-x';
 2372        }
 2373      },
 2374      onCleanup: function(element) {
 2375        if (this.oldTouchAction) {
 2376          element[0].style[touchActionProperty] = this.oldTouchAction;
 2377        }
 2378      },
 2379      onStart: function (ev) {
 2380        // For drag, require a parent to be registered with $mdGesture.register()
 2381        if (!this.state.registeredParent) this.cancel();
 2382      },
 2383      onMove: function (ev, pointer) {
 2384        var shouldStartDrag, shouldCancel;
 2385        // Don't scroll while deciding if this touchmove qualifies as a drag event.
 2386        // If we don't preventDefault touchmove events here, Android will assume we don't
 2387        // want to listen to anymore touch events. It will start scrolling and stop sending
 2388        // touchmove events.
 2389        if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
 2390
 2391        if (!this.state.dragPointer) {
 2392          if (this.state.options.horizontal) {
 2393            shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance;
 2394            shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier;
 2395          } else {
 2396            shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance;
 2397            shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier;
 2398          }
 2399
 2400          if (shouldStartDrag) {
 2401            // Create a new pointer representing this drag, starting at this point where the drag started.
 2402            this.state.dragPointer = makeStartPointer(ev);
 2403            updatePointerState(ev, this.state.dragPointer);
 2404            this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer);
 2405
 2406          } else if (shouldCancel) {
 2407            this.cancel();
 2408          }
 2409        } else {
 2410          this.dispatchDragMove(ev);
 2411        }
 2412      },
 2413      // Only dispatch dragmove events every frame; any more is unnecessary
 2414      dispatchDragMove: $$rAF.throttle(function (ev) {
 2415        // Make sure the drag didn't stop while waiting for the next frame
 2416        if (this.state.isRunning) {
 2417          updatePointerState(ev, this.state.dragPointer);
 2418          this.dispatchEvent(ev, '$md.drag', this.state.dragPointer);
 2419        }
 2420      }),
 2421      onEnd: function (ev, pointer) {
 2422        if (this.state.dragPointer) {
 2423          updatePointerState(ev, this.state.dragPointer);
 2424          this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer);
 2425        }
 2426      }
 2427    })
 2428
 2429    /*
 2430     * The swipe handler will dispatch a swipe event if, on the end of a touch,
 2431     * the velocity and distance were high enough.
 2432     */
 2433    .handler('swipe', {
 2434      options: {
 2435        minVelocity: 0.65,
 2436        minDistance: 10
 2437      },
 2438      onEnd: function (ev, pointer) {
 2439        var eventType;
 2440
 2441        if (Math.abs(pointer.velocityX) > this.state.options.minVelocity &&
 2442          Math.abs(pointer.distanceX) > this.state.options.minDistance) {
 2443          eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight';
 2444          this.dispatchEvent(ev, eventType);
 2445        }
 2446        else if (Math.abs(pointer.velocityY) > this.state.options.minVelocity &&
 2447          Math.abs(pointer.distanceY) > this.state.options.minDistance) {
 2448          eventType = pointer.directionY == 'up' ? '$md.swipeup' : '$md.swipedown';
 2449          this.dispatchEvent(ev, eventType);
 2450        }
 2451      }
 2452    });
 2453
 2454  function getTouchAction() {
 2455    var testEl = document.createElement('div');
 2456    var vendorPrefixes = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
 2457
 2458    for (var i = 0; i < vendorPrefixes.length; i++) {
 2459      var prefix = vendorPrefixes[i];
 2460      var property = prefix ? prefix + 'TouchAction' : 'touchAction';
 2461      if (angular.isDefined(testEl.style[property])) {
 2462        return property;
 2463      }
 2464    }
 2465  }
 2466
 2467}
 2468
 2469/**
 2470 * MdGestureHandler
 2471 * A GestureHandler is an object which is able to dispatch custom dom events
 2472 * based on native dom {touch,pointer,mouse}{start,move,end} events.
 2473 *
 2474 * A gesture will manage its lifecycle through the start,move,end, and cancel
 2475 * functions, which are called by native dom events.
 2476 *
 2477 * A gesture has the concept of 'options' (eg a swipe's required velocity), which can be
 2478 * overridden by elements registering through $mdGesture.register()
 2479 */
 2480function GestureHandler (name) {
 2481  this.name = name;
 2482  this.state = {};
 2483}
 2484
 2485function MdGestureHandler() {
 2486  var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
 2487
 2488  GestureHandler.prototype = {
 2489    options: {},
 2490    // jQuery listeners don't work with custom DOMEvents, so we have to dispatch events
 2491    // differently when jQuery is loaded
 2492    dispatchEvent: hasJQuery ?  jQueryDispatchEvent : nativeDispatchEvent,
 2493
 2494    // These are overridden by the registered handler
 2495    onSetup: angular.noop,
 2496    onCleanup: angular.noop,
 2497    onStart: angular.noop,
 2498    onMove: angular.noop,
 2499    onEnd: angular.noop,
 2500    onCancel: angular.noop,
 2501
 2502    // onStart sets up a new state for the handler, which includes options from the
 2503    // nearest registered parent element of ev.target.
 2504    start: function (ev, pointer) {
 2505      if (this.state.isRunning) return;
 2506      var parentTarget = this.getNearestParent(ev.target);
 2507      // Get the options from the nearest registered parent
 2508      var parentTargetOptions = parentTarget && parentTarget.$mdGesture[this.name] || {};
 2509
 2510      this.state = {
 2511        isRunning: true,
 2512        // Override the default options with the nearest registered parent's options
 2513        options: angular.extend({}, this.options, parentTargetOptions),
 2514        // Pass in the registered parent node to the state so the onStart listener can use
 2515        registeredParent: parentTarget
 2516      };
 2517      this.onStart(ev, pointer);
 2518    },
 2519    move: function (ev, pointer) {
 2520      if (!this.state.isRunning) return;
 2521      this.onMove(ev, pointer);
 2522    },
 2523    end: function (ev, pointer) {
 2524      if (!this.state.isRunning) return;
 2525      this.onEnd(ev, pointer);
 2526      this.state.isRunning = false;
 2527    },
 2528    cancel: function (ev, pointer) {
 2529      this.onCancel(ev, pointer);
 2530      this.state = {};
 2531    },
 2532
 2533    // Find and return the nearest parent element that has been registered to
 2534    // listen for this handler via $mdGesture.register(element, 'handlerName').
 2535    getNearestParent: function (node) {
 2536      var current = node;
 2537      while (current) {
 2538        if ((current.$mdGesture || {})[this.name]) {
 2539          return current;
 2540        }
 2541        current = current.parentNode;
 2542      }
 2543      return null;
 2544    },
 2545
 2546    // Called from $mdGesture.register when an element registers itself with a handler.
 2547    // Store the options the user gave on the DOMElement itself. These options will
 2548    // be retrieved with getNearestParent when the handler starts.
 2549    registerElement: function (element, options) {
 2550      var self = this;
 2551      element[0].$mdGesture = element[0].$mdGesture || {};
 2552      element[0].$mdGesture[this.name] = options || {};
 2553      element.on('$destroy', onDestroy);
 2554
 2555      self.onSetup(element, options || {});
 2556
 2557      return onDestroy;
 2558
 2559      function onDestroy() {
 2560        delete element[0].$mdGesture[self.name];
 2561        element.off('$destroy', onDestroy);
 2562
 2563        self.onCleanup(element, options || {});
 2564      }
 2565    }
 2566  };
 2567
 2568  return GestureHandler;
 2569
 2570  /*
 2571   * Dispatch an event with jQuery
 2572   * TODO: Make sure this sends bubbling events
 2573   *
 2574   * @param srcEvent the original DOM touch event that started this.
 2575   * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
 2576   * @param eventPointer the pointer object that matches this event.
 2577   */
 2578  function jQueryDispatchEvent(srcEvent, eventType, eventPointer) {
 2579    eventPointer = eventPointer || pointer;
 2580    var eventObj = new angular.element.Event(eventType);
 2581
 2582    eventObj.$material = true;
 2583    eventObj.pointer = eventPointer;
 2584    eventObj.srcEvent = srcEvent;
 2585
 2586    angular.extend(eventObj, {
 2587      clientX: eventPointer.x,
 2588      clientY: eventPointer.y,
 2589      screenX: eventPointer.x,
 2590      screenY: eventPointer.y,
 2591      pageX: eventPointer.x,
 2592      pageY: eventPointer.y,
 2593      ctrlKey: srcEvent.ctrlKey,
 2594      altKey: srcEvent.altKey,
 2595      shiftKey: srcEvent.shiftKey,
 2596      metaKey: srcEvent.metaKey
 2597    });
 2598    angular.element(eventPointer.target).trigger(eventObj);
 2599  }
 2600
 2601  /*
 2602   * NOTE: nativeDispatchEvent is very performance sensitive.
 2603   * @param srcEvent the original DOM touch event that started this.
 2604   * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
 2605   * @param eventPointer the pointer object that matches this event.
 2606   */
 2607  function nativeDispatchEvent(srcEvent, eventType, eventPointer) {
 2608    eventPointer = eventPointer || pointer;
 2609    var eventObj;
 2610
 2611    if (eventType === 'click' || eventType == 'mouseup' || eventType == 'mousedown' ) {
 2612      eventObj = document.createEvent('MouseEvents');
 2613      eventObj.initMouseEvent(
 2614        eventType, true, true, window, srcEvent.detail,
 2615        eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y,
 2616        srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey,
 2617        srcEvent.button, srcEvent.relatedTarget || null
 2618      );
 2619
 2620    } else {
 2621      eventObj = document.createEvent('CustomEvent');
 2622      eventObj.initCustomEvent(eventType, true, true, {});
 2623    }
 2624    eventObj.$material = true;
 2625    eventObj.pointer = eventPointer;
 2626    eventObj.srcEvent = srcEvent;
 2627    eventPointer.target.dispatchEvent(eventObj);
 2628  }
 2629
 2630}
 2631
 2632/**
 2633 * Attach Gestures: hook document and check shouldHijack clicks
 2634 * @ngInject
 2635 */
 2636function attachToDocument( $mdGesture, $$MdGestureHandler ) {
 2637
 2638  // Polyfill document.contains for IE11.
 2639  // TODO: move to util
 2640  document.contains || (document.contains = function (node) {
 2641    return document.body.contains(node);
 2642  });
 2643
 2644  if (!isInitialized && $mdGesture.isHijackingClicks ) {
 2645    /*
 2646     * If hijack clicks is true, we preventDefault any click that wasn't
 2647     * sent by ngMaterial. This is because on older Android & iOS, a false, or 'ghost',
 2648     * click event will be sent ~400ms after a touchend event happens.
 2649     * The only way to know if this click is real is to prevent any normal
 2650     * click events, and add a flag to events sent by material so we know not to prevent those.
 2651     *
 2652     * Two exceptions to click events that should be prevented are:
 2653     *  - click events sent by the keyboard (eg form submit)
 2654     *  - events that originate from an Ionic app
 2655     */
 2656    document.addEventListener('click'    , clickHijacker     , true);
 2657    document.addEventListener('mouseup'  , mouseInputHijacker, true);
 2658    document.addEventListener('mousedown', mouseInputHijacker, true);
 2659    document.addEventListener('focus'    , mouseInputHijacker, true);
 2660
 2661    isInitialized = true;
 2662  }
 2663
 2664  function mouseInputHijacker(ev) {
 2665    var isKeyClick = !ev.clientX && !ev.clientY;
 2666    if (!isKeyClick && !ev.$material && !ev.isIonicTap
 2667      && !isInputEventFromLabelClick(ev)) {
 2668      ev.preventDefault();
 2669      ev.stopPropagation();
 2670    }
 2671  }
 2672
 2673  function clickHijacker(ev) {
 2674    var isKeyClick = ev.clientX === 0 && ev.clientY === 0;
 2675    if (!isKeyClick && !ev.$material && !ev.isIonicTap
 2676      && !isInputEventFromLabelClick(ev)) {
 2677      ev.preventDefault();
 2678      ev.stopPropagation();
 2679      lastLabelClickPos = null;
 2680    } else {
 2681      lastLabelClickPos = null;
 2682      if (ev.target.tagName.toLowerCase() == 'label') {
 2683        lastLabelClickPos = {x: ev.x, y: ev.y};
 2684      }
 2685    }
 2686  }
 2687
 2688
 2689  // Listen to all events to cover all platforms.
 2690  var START_EVENTS = 'mousedown touchstart pointerdown';
 2691  var MOVE_EVENTS = 'mousemove touchmove pointermove';
 2692  var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel';
 2693
 2694  angular.element(document)
 2695    .on(START_EVENTS, gestureStart)
 2696    .on(MOVE_EVENTS, gestureMove)
 2697    .on(END_EVENTS, gestureEnd)
 2698    // For testing
 2699    .on('$$mdGestureReset', function gestureClearCache () {
 2700      lastPointer = pointer = null;
 2701    });
 2702
 2703  /*
 2704   * When a DOM event happens, run all registered gesture handlers' lifecycle
 2705   * methods which match the DOM event.
 2706   * Eg when a 'touchstart' event happens, runHandlers('start') will call and
 2707   * run `handler.cancel()` and `handler.start()` on all registered handlers.
 2708   */
 2709  function runHandlers(handlerEvent, event) {
 2710    var handler;
 2711    for (var name in HANDLERS) {
 2712      handler = HANDLERS[name];
 2713      if( handler instanceof $$MdGestureHandler ) {
 2714
 2715        if (handlerEvent === 'start') {
 2716          // Run cancel to reset any handlers' state
 2717          handler.cancel();
 2718        }
 2719        handler[handlerEvent](event, pointer);
 2720
 2721      }
 2722    }
 2723  }
 2724
 2725  /*
 2726   * gestureStart vets if a start event is legitimate (and not part of a 'ghost click' from iOS/Android)
 2727   * If it is legitimate, we initiate the pointer state and mark the current pointer's type
 2728   * For example, for a touchstart event, mark the current pointer as a 'touch' pointer, so mouse events
 2729   * won't effect it.
 2730   */
 2731  function gestureStart(ev) {
 2732    // If we're already touched down, abort
 2733    if (pointer) return;
 2734
 2735    var now = +Date.now();
 2736
 2737    // iOS & old android bug: after a touch event, a click event is sent 350 ms later.
 2738    // If <400ms have passed, don't allow an event of a different type than the previous event
 2739    if (lastPointer && !typesMatch(ev, lastPointer) && (now - lastPointer.endTime < 1500)) {
 2740      return;
 2741    }
 2742
 2743    pointer = makeStartPointer(ev);
 2744
 2745    runHandlers('start', ev);
 2746  }
 2747  /*
 2748   * If a move event happens of the right type, update the pointer and run all the move handlers.
 2749   * "of the right type": if a mousemove happens but our pointer started with a touch event, do nothing.
 2750   */
 2751  function gestureMove(ev) {
 2752    if (!pointer || !typesMatch(ev, pointer)) return;
 2753
 2754    updatePointerState(ev, pointer);
 2755    runHandlers('move', ev);
 2756  }
 2757  /*
 2758   * If an end event happens of the right type, update the pointer, run endHandlers, and save the pointer as 'lastPointer'
 2759   */
 2760  function gestureEnd(ev) {
 2761    if (!pointer || !typesMatch(ev, pointer)) return;
 2762
 2763    updatePointerState(ev, pointer);
 2764    pointer.endTime = +Date.now();
 2765
 2766    runHandlers('end', ev);
 2767
 2768    lastPointer = pointer;
 2769    pointer = null;
 2770  }
 2771
 2772}
 2773
 2774// ********************
 2775// Module Functions
 2776// ********************
 2777
 2778/*
 2779 * Initiate the pointer. x, y, and the pointer's type.
 2780 */
 2781function makeStartPointer(ev) {
 2782  var point = getEventPoint(ev);
 2783  var startPointer = {
 2784    startTime: +Date.now(),
 2785    target: ev.target,
 2786    // 'p' for pointer events, 'm' for mouse, 't' for touch
 2787    type: ev.type.charAt(0)
 2788  };
 2789  startPointer.startX = startPointer.x = point.pageX;
 2790  startPointer.startY = startPointer.y = point.pageY;
 2791  return startPointer;
 2792}
 2793
 2794/*
 2795 * return whether the pointer's type matches the event's type.
 2796 * Eg if a touch event happens but the pointer has a mouse type, return false.
 2797 */
 2798function typesMatch(ev, pointer) {
 2799  return ev && pointer && ev.type.charAt(0) === pointer.type;
 2800}
 2801
 2802/**
 2803 * Gets whether the given event is an input event that was caused by clicking on an
 2804 * associated label element.
 2805 *
 2806 * This is necessary because the browser will, upon clicking on a label element, fire an
 2807 * *extra* click event on its associated input (if any). mdGesture is able to flag the label
 2808 * click as with `$material` correctly, but not the second input click.
 2809 *
 2810 * In order to determine whether an input event is from a label click, we compare the (x, y) for
 2811 * the event to the (x, y) for the most recent label click (which is cleared whenever a non-label
 2812 * click occurs). Unfortunately, there are no event properties that tie the input and the label
 2813 * together (such as relatedTarget).
 2814 *
 2815 * @param {MouseEvent} event
 2816 * @returns {boolean}
 2817 */
 2818function isInputEventFromLabelClick(event) {
 2819  return lastLabelClickPos
 2820      && lastLabelClickPos.x == event.x
 2821      && lastLabelClickPos.y == event.y;
 2822}
 2823
 2824/*
 2825 * Update the given pointer based upon the given DOMEvent.
 2826 * Distance, velocity, direction, duration, etc
 2827 */
 2828function updatePointerState(ev, pointer) {
 2829  var point = getEventPoint(ev);
 2830  var x = pointer.x = point.pageX;
 2831  var y = pointer.y = point.pageY;
 2832
 2833  pointer.distanceX = x - pointer.startX;
 2834  pointer.distanceY = y - pointer.startY;
 2835  pointer.distance = Math.sqrt(
 2836    pointer.distanceX * pointer.distanceX + pointer.distanceY * pointer.distanceY
 2837  );
 2838
 2839  pointer.directionX = pointer.distanceX > 0 ? 'right' : pointer.distanceX < 0 ? 'left' : '';
 2840  pointer.directionY = pointer.distanceY > 0 ? 'down' : pointer.distanceY < 0 ? 'up' : '';
 2841
 2842  pointer.duration = +Date.now() - pointer.startTime;
 2843  pointer.velocityX = pointer.distanceX / pointer.duration;
 2844  pointer.velocityY = pointer.distanceY / pointer.duration;
 2845}
 2846
 2847/*
 2848 * Normalize the point where the DOM event happened whether it's touch or mouse.
 2849 * @returns point event obj with pageX and pageY on it.
 2850 */
 2851function getEventPoint(ev) {
 2852  ev = ev.originalEvent || ev; // support jQuery events
 2853  return (ev.touches && ev.touches[0]) ||
 2854    (ev.changedTouches && ev.changedTouches[0]) ||
 2855    ev;
 2856}
 2857
 2858})();
 2859(function(){
 2860"use strict";
 2861
 2862angular.module('material.core')
 2863  .provider('$$interimElement', InterimElementProvider);
 2864
 2865/*
 2866 * @ngdoc service
 2867 * @name $$interimElement
 2868 * @module material.core
 2869 *
 2870 * @description
 2871 *
 2872 * Factory that contructs `$$interimElement.$service` services.
 2873 * Used internally in material design for elements that appear on screen temporarily.
 2874 * The service provides a promise-like API for interacting with the temporary
 2875 * elements.
 2876 *
 2877 * ```js
 2878 * app.service('$mdToast', function($$interimElement) {
 2879 *   var $mdToast = $$interimElement(toastDefaultOptions);
 2880 *   return $mdToast;
 2881 * });
 2882 * ```
 2883 * @param {object=} defaultOptions Options used by default for the `show` method on the service.
 2884 *
 2885 * @returns {$$interimElement.$service}
 2886 *
 2887 */
 2888
 2889function InterimElementProvider() {
 2890  InterimElementFactory.$inject = ["$document", "$q", "$$q", "$rootScope", "$timeout", "$rootElement", "$animate", "$mdUtil", "$mdCompiler", "$mdTheming", "$injector"];
 2891  createInterimElementProvider.$get = InterimElementFactory;
 2892  return createInterimElementProvider;
 2893
 2894  /**
 2895   * Returns a new provider which allows configuration of a new interimElement
 2896   * service. Allows configuration of default options & methods for options,
 2897   * as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method)
 2898   */
 2899  function createInterimElementProvider(interimFactoryName) {
 2900    factory.$inject = ["$$interimElement", "$injector"];
 2901    var EXPOSED_METHODS = ['onHide', 'onShow', 'onRemove'];
 2902
 2903    var customMethods = {};
 2904    var providerConfig = {
 2905      presets: {}
 2906    };
 2907
 2908    var provider = {
 2909      setDefaults: setDefaults,
 2910      addPreset: addPreset,
 2911      addMethod: addMethod,
 2912      $get: factory
 2913    };
 2914
 2915    /**
 2916     * all interim elements will come with the 'build' preset
 2917     */
 2918    provider.addPreset('build', {
 2919      methods: ['controller', 'controllerAs', 'resolve',
 2920        'template', 'templateUrl', 'themable', 'transformTemplate', 'parent']
 2921    });
 2922
 2923    return provider;
 2924
 2925    /**
 2926     * Save the configured defaults to be used when the factory is instantiated
 2927     */
 2928    function setDefaults(definition) {
 2929      providerConfig.optionsFactory = definition.options;
 2930      providerConfig.methods = (definition.methods || []).concat(EXPOSED_METHODS);
 2931      return provider;
 2932    }
 2933
 2934    /**
 2935     * Add a method to the factory that isn't specific to any interim element operations
 2936     */
 2937
 2938    function addMethod(name, fn) {
 2939      customMethods[name] = fn;
 2940      return provider;
 2941    }
 2942
 2943    /**
 2944     * Save the configured preset to be used when the factory is instantiated
 2945     */
 2946    function addPreset(name, definition) {
 2947      definition = definition || {};
 2948      definition.methods = definition.methods || [];
 2949      definition.options = definition.options || function() { return {}; };
 2950
 2951      if (/^cancel|hide|show$/.test(name)) {
 2952        throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!");
 2953      }
 2954      if (definition.methods.indexOf('_options') > -1) {
 2955        throw new Error("Method '_options' in " + interimFactoryName + " is reserved!");
 2956      }
 2957      providerConfig.presets[name] = {
 2958        methods: definition.methods.concat(EXPOSED_METHODS),
 2959        optionsFactory: definition.options,
 2960        argOption: definition.argOption
 2961      };
 2962      return provider;
 2963    }
 2964
 2965    function addPresetMethod(presetName, methodName, method) {
 2966      providerConfig.presets[presetName][methodName] = method;
 2967    }
 2968
 2969    /**
 2970     * Create a factory that has the given methods & defaults implementing interimElement
 2971     */
 2972    /* @ngInject */
 2973    function factory($$interimElement, $injector) {
 2974      var defaultMethods;
 2975      var defaultOptions;
 2976      var interimElementService = $$interimElement();
 2977
 2978      /*
 2979       * publicService is what the developer will be using.
 2980       * It has methods hide(), cancel(), show(), build(), and any other
 2981       * presets which were set during the config phase.
 2982       */
 2983      var publicService = {
 2984        hide: interimElementService.hide,
 2985        cancel: interimElementService.cancel,
 2986        show: showInterimElement,
 2987
 2988        // Special internal method to destroy an interim element without animations
 2989        // used when navigation changes causes a $scope.$destroy() action
 2990        destroy : destroyInterimElement
 2991      };
 2992
 2993
 2994      defaultMethods = providerConfig.methods || [];
 2995      // This must be invoked after the publicService is initialized
 2996      defaultOptions = invokeFactory(providerConfig.optionsFactory, {});
 2997
 2998      // Copy over the simple custom methods
 2999      angular.forEach(customMethods, function(fn, name) {
 3000        publicService[name] = fn;
 3001      });
 3002
 3003      angular.forEach(providerConfig.presets, function(definition, name) {
 3004        var presetDefaults = invokeFactory(definition.optionsFactory, {});
 3005        var presetMethods = (definition.methods || []).concat(defaultMethods);
 3006
 3007        // Every interimElement built with a preset has a field called `$type`,
 3008        // which matches the name of the preset.
 3009        // Eg in preset 'confirm', options.$type === 'confirm'
 3010        angular.extend(presetDefaults, { $type: name });
 3011
 3012        // This creates a preset class which has setter methods for every
 3013        // method given in the `.addPreset()` function, as well as every
 3014        // method given in the `.setDefaults()` function.
 3015        //
 3016        // @example
 3017        // .setDefaults({
 3018        //   methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'],
 3019        //   options: dialogDefaultOptions
 3020        // })
 3021        // .addPreset('alert', {
 3022        //   methods: ['title', 'ok'],
 3023        //   options: alertDialogOptions
 3024        // })
 3025        //
 3026        // Set values will be passed to the options when interimElement.show() is called.
 3027        function Preset(opts) {
 3028          this._options = angular.extend({}, presetDefaults, opts);
 3029        }
 3030        angular.forEach(presetMethods, function(name) {
 3031          Preset.prototype[name] = function(value) {
 3032            this._options[name] = value;
 3033            return this;
 3034          };
 3035        });
 3036
 3037        // Create shortcut method for one-linear methods
 3038        if (definition.argOption) {
 3039          var methodName = 'show' + name.charAt(0).toUpperCase() + name.slice(1);
 3040          publicService[methodName] = function(arg) {
 3041            var config = publicService[name](arg);
 3042            return publicService.show(config);
 3043          };
 3044        }
 3045
 3046        // eg $mdDialog.alert() will return a new alert preset
 3047        publicService[name] = function(arg) {
 3048          // If argOption is supplied, eg `argOption: 'content'`, then we assume
 3049          // if the argument is not an options object then it is the `argOption` option.
 3050          //
 3051          // @example `$mdToast.simple('hello')` // sets options.content to hello
 3052          //                                     // because argOption === 'content'
 3053          if (arguments.length && definition.argOption &&
 3054              !angular.isObject(arg) && !angular.isArray(arg))  {
 3055
 3056            return (new Preset())[definition.argOption](arg);
 3057
 3058          } else {
 3059            return new Preset(arg);
 3060          }
 3061
 3062        };
 3063      });
 3064
 3065      return publicService;
 3066
 3067      /**
 3068       *
 3069       */
 3070      function showInterimElement(opts) {
 3071        // opts is either a preset which stores its options on an _options field,
 3072        // or just an object made up of options
 3073        opts = opts || { };
 3074        if (opts._options) opts = opts._options;
 3075
 3076        return interimElementService.show(
 3077          angular.extend({}, defaultOptions, opts)
 3078        );
 3079      }
 3080
 3081      /**
 3082       *  Special method to hide and destroy an interimElement WITHOUT
 3083       *  any 'leave` or hide animations ( an immediate force hide/remove )
 3084       *
 3085       *  NOTE: This calls the onRemove() subclass method for each component...
 3086       *  which must have code to respond to `options.$destroy == true`
 3087       */
 3088      function destroyInterimElement(opts) {
 3089          return interimElementService.destroy(opts);
 3090      }
 3091
 3092      /**
 3093       * Helper to call $injector.invoke with a local of the factory name for
 3094       * this provider.
 3095       * If an $mdDialog is providing options for a dialog and tries to inject
 3096       * $mdDialog, a circular dependency error will happen.
 3097       * We get around that by manually injecting $mdDialog as a local.
 3098       */
 3099      function invokeFactory(factory, defaultVal) {
 3100        var locals = {};
 3101        locals[interimFactoryName] = publicService;
 3102        return $injector.invoke(factory || function() { return defaultVal; }, {}, locals);
 3103      }
 3104
 3105    }
 3106
 3107  }
 3108
 3109  /* @ngInject */
 3110  function InterimElementFactory($document, $q, $$q, $rootScope, $timeout, $rootElement, $animate,
 3111                                 $mdUtil, $mdCompiler, $mdTheming, $injector ) {
 3112    return function createInterimElementService() {
 3113      var SHOW_CANCELLED = false;
 3114
 3115      /*
 3116       * @ngdoc service
 3117       * @name $$interimElement.$service
 3118       *
 3119       * @description
 3120       * A service used to control inserting and removing an element into the DOM.
 3121       *
 3122       */
 3123      var service, stack = [];
 3124
 3125      // Publish instance $$interimElement service;
 3126      // ... used as $mdDialog, $mdToast, $mdMenu, and $mdSelect
 3127
 3128      return service = {
 3129        show: show,
 3130        hide: hide,
 3131        cancel: cancel,
 3132        destroy : destroy,
 3133        $injector_: $injector
 3134      };
 3135
 3136      /*
 3137       * @ngdoc method
 3138       * @name $$interimElement.$service#show
 3139       * @kind function
 3140       *
 3141       * @description
 3142       * Adds the `$interimElement` to the DOM and returns a special promise that will be resolved or rejected
 3143       * with hide or cancel, respectively. To external cancel/hide, developers should use the
 3144       *
 3145       * @param {*} options is hashMap of settings
 3146       * @returns a Promise
 3147       *
 3148       */
 3149      function show(options) {
 3150        options = options || {};
 3151        var interimElement = new InterimElement(options || {});
 3152        // When an interim element is currently showing, we have to cancel it.
 3153        // Just hiding it, will resolve the InterimElement's promise, the promise should be
 3154        // rejected instead.
 3155        var hideExisting = !options.skipHide && stack.length ? service.cancel() : $q.when(true);
 3156
 3157        // This hide()s only the current interim element before showing the next, new one
 3158        // NOTE: this is not reversible (e.g. interim elements are not stackable)
 3159
 3160        hideExisting.finally(function() {
 3161
 3162          stack.push(interimElement);
 3163          interimElement
 3164            .show()
 3165            .catch(function( reason ) {
 3166              //$log.error("InterimElement.show() error: " + reason );
 3167              return reason;
 3168            });
 3169
 3170        });
 3171
 3172        // Return a promise that will be resolved when the interim
 3173        // element is hidden or cancelled...
 3174
 3175        return interimElement.deferred.promise;
 3176      }
 3177
 3178      /*
 3179       * @ngdoc method
 3180       * @name $$interimElement.$service#hide
 3181       * @kind function
 3182       *
 3183       * @description
 3184       * Removes the `$interimElement` from the DOM and resolves the promise returned from `show`
 3185       *
 3186       * @param {*} resolveParam Data to resolve the promise with
 3187       * @returns a Promise that will be resolved after the element has been removed.
 3188       *
 3189       */
 3190      function hide(reason, options) {
 3191        if ( !stack.length ) return $q.when(reason);
 3192        options = options || {};
 3193
 3194        if (options.closeAll) {
 3195          var promise = $q.all(stack.reverse().map(closeElement));
 3196          stack = [];
 3197          return promise;
 3198        } else if (options.closeTo !== undefined) {
 3199          return $q.all(stack.splice(options.closeTo).map(closeElement));
 3200        } else {
 3201          var interim = stack.pop();
 3202          return closeElement(interim);
 3203        }
 3204
 3205        function closeElement(interim) {
 3206          interim
 3207            .remove(reason, false, options || { })
 3208            .catch(function( reason ) {
 3209              //$log.error("InterimElement.hide() error: " + reason );
 3210              return reason;
 3211            });
 3212          return interim.deferred.promise;
 3213        }
 3214      }
 3215
 3216      /*
 3217       * @ngdoc method
 3218       * @name $$interimElement.$service#cancel
 3219       * @kind function
 3220       *
 3221       * @description
 3222       * Removes the `$interimElement` from the DOM and rejects the promise returned from `show`
 3223       *
 3224       * @param {*} reason Data to reject the promise with
 3225       * @returns Promise that will be resolved after the element has been removed.
 3226       *
 3227       */
 3228      function cancel(reason, options) {
 3229        var interim = stack.pop();
 3230        if ( !interim ) return $q.when(reason);
 3231
 3232        interim
 3233          .remove(reason, true, options || { })
 3234          .catch(function( reason ) {
 3235            //$log.error("InterimElement.cancel() error: " + reason );
 3236            return reason;
 3237          });
 3238
 3239        // Since Angular 1.6.7, promises will be logged to $exceptionHandler when the promise
 3240        // is not handling the rejection. We create a pseudo catch handler, which will prevent the
 3241        // promise from being logged to the $exceptionHandler.
 3242        return interim.deferred.promise.catch(angular.noop);
 3243      }
 3244
 3245      /*
 3246       * Special method to quick-remove the interim element without animations
 3247       * Note: interim elements are in "interim containers"
 3248       */
 3249      function destroy(target) {
 3250        var interim = !target ? stack.shift() : null;
 3251        var cntr = angular.element(target).length ? angular.element(target)[0].parentNode : null;
 3252
 3253        if (cntr) {
 3254            // Try to find the interim element in the stack which corresponds to the supplied DOM element.
 3255            var filtered = stack.filter(function(entry) {
 3256                  var currNode = entry.options.element[0];
 3257                  return  (currNode === cntr);
 3258                });
 3259
 3260            // Note: this function might be called when the element already has been removed, in which
 3261            //       case we won't find any matches. That's ok.
 3262            if (filtered.length > 0) {
 3263              interim = filtered[0];
 3264              stack.splice(stack.indexOf(interim), 1);
 3265            }
 3266        }
 3267
 3268        return interim ? interim.remove(SHOW_CANCELLED, false, {'$destroy':true}) : $q.when(SHOW_CANCELLED);
 3269      }
 3270
 3271      /*
 3272       * Internal Interim Element Object
 3273       * Used internally to manage the DOM element and related data
 3274       */
 3275      function InterimElement(options) {
 3276        var self, element, showAction = $q.when(true);
 3277
 3278        options = configureScopeAndTransitions(options);
 3279
 3280        return self = {
 3281          options : options,
 3282          deferred: $q.defer(),
 3283          show    : createAndTransitionIn,
 3284          remove  : transitionOutAndRemove
 3285        };
 3286
 3287        /**
 3288         * Compile, link, and show this interim element
 3289         * Use optional autoHided and transition-in effects
 3290         */
 3291        function createAndTransitionIn() {
 3292          return $q(function(resolve, reject) {
 3293
 3294            // Trigger onCompiling callback before the compilation starts.
 3295            // This is useful, when modifying options, which can be influenced by developers.
 3296            options.onCompiling && options.onCompiling(options);
 3297
 3298            compileElement(options)
 3299              .then(function( compiledData ) {
 3300                element = linkElement( compiledData, options );
 3301
 3302                showAction = showElement(element, options, compiledData.controller)
 3303                  .then(resolve, rejectAll);
 3304
 3305              }, rejectAll);
 3306
 3307            function rejectAll(fault) {
 3308              // Force the '$md<xxx>.show()' promise to reject
 3309              self.deferred.reject(fault);
 3310
 3311              // Continue rejection propagation
 3312              reject(fault);
 3313            }
 3314          });
 3315        }
 3316
 3317        /**
 3318         * After the show process has finished/rejected:
 3319         * - announce 'removing',
 3320         * - perform the transition-out, and
 3321         * - perform optional clean up scope.
 3322         */
 3323        function transitionOutAndRemove(response, isCancelled, opts) {
 3324
 3325          // abort if the show() and compile failed
 3326          if ( !element ) return $q.when(false);
 3327
 3328          options = angular.extend(options || {}, opts || {});
 3329          options.cancelAutoHide && options.cancelAutoHide();
 3330          options.element.triggerHandler('$mdInterimElementRemove');
 3331
 3332          if ( options.$destroy === true ) {
 3333
 3334            return hideElement(options.element, options).then(function(){
 3335              (isCancelled && rejectAll(response)) || resolveAll(response);
 3336            });
 3337
 3338          } else {
 3339
 3340            $q.when(showAction)
 3341                .finally(function() {
 3342                  hideElement(options.element, options).then(function() {
 3343
 3344                    (isCancelled && rejectAll(response)) || resolveAll(response);
 3345
 3346                  }, rejectAll);
 3347                });
 3348
 3349            return self.deferred.promise;
 3350          }
 3351
 3352
 3353          /**
 3354           * The `show()` returns a promise that will be resolved when the interim
 3355           * element is hidden or cancelled...
 3356           */
 3357          function resolveAll(response) {
 3358            self.deferred.resolve(response);
 3359          }
 3360
 3361          /**
 3362           * Force the '$md<xxx>.show()' promise to reject
 3363           */
 3364          function rejectAll(fault) {
 3365            self.deferred.reject(fault);
 3366          }
 3367        }
 3368
 3369        /**
 3370         * Prepare optional isolated scope and prepare $animate with default enter and leave
 3371         * transitions for the new element instance.
 3372         */
 3373        function configureScopeAndTransitions(options) {
 3374          options = options || { };
 3375          if ( options.template ) {
 3376            options.template = $mdUtil.processTemplate(options.template);
 3377          }
 3378
 3379          return angular.extend({
 3380            preserveScope: false,
 3381            cancelAutoHide : angular.noop,
 3382            scope: options.scope || $rootScope.$new(options.isolateScope),
 3383
 3384            /**
 3385             * Default usage to enable $animate to transition-in; can be easily overridden via 'options'
 3386             */
 3387            onShow: function transitionIn(scope, element, options) {
 3388              return $animate.enter(element, options.parent);
 3389            },
 3390
 3391            /**
 3392             * Default usage to enable $animate to transition-out; can be easily overridden via 'options'
 3393             */
 3394            onRemove: function transitionOut(scope, element) {
 3395              // Element could be undefined if a new element is shown before
 3396              // the old one finishes compiling.
 3397              return element && $animate.leave(element) || $q.when();
 3398            }
 3399          }, options );
 3400
 3401        }
 3402
 3403        /**
 3404         * Compile an element with a templateUrl, controller, and locals
 3405         */
 3406        function compileElement(options) {
 3407
 3408          var compiled = !options.skipCompile ? $mdCompiler.compile(options) : null;
 3409
 3410          return compiled || $q(function (resolve) {
 3411              resolve({
 3412                locals: {},
 3413                link: function () {
 3414                  return options.element;
 3415                }
 3416              });
 3417            });
 3418        }
 3419
 3420        /**
 3421         *  Link an element with compiled configuration
 3422         */
 3423        function linkElement(compileData, options){
 3424          angular.extend(compileData.locals, options);
 3425
 3426          var element = compileData.link(options.scope);
 3427
 3428          // Search for parent at insertion time, if not specified
 3429          options.element = element;
 3430          options.parent = findParent(element, options);
 3431          if (options.themable) $mdTheming(element);
 3432
 3433          return element;
 3434        }
 3435
 3436        /**
 3437         * Search for parent at insertion time, if not specified
 3438         */
 3439        function findParent(element, options) {
 3440          var parent = options.parent;
 3441
 3442          // Search for parent at insertion time, if not specified
 3443          if (angular.isFunction(parent)) {
 3444            parent = parent(options.scope, element, options);
 3445          } else if (angular.isString(parent)) {
 3446            parent = angular.element($document[0].querySelector(parent));
 3447          } else {
 3448            parent = angular.element(parent);
 3449          }
 3450
 3451          // If parent querySelector/getter function fails, or it's just null,
 3452          // find a default.
 3453          if (!(parent || {}).length) {
 3454            var el;
 3455            if ($rootElement[0] && $rootElement[0].querySelector) {
 3456              el = $rootElement[0].querySelector(':not(svg) > body');
 3457            }
 3458            if (!el) el = $rootElement[0];
 3459            if (el.nodeName == '#comment') {
 3460              el = $document[0].body;
 3461            }
 3462            return angular.element(el);
 3463          }
 3464
 3465          return parent;
 3466        }
 3467
 3468        /**
 3469         * If auto-hide is enabled, start timer and prepare cancel function
 3470         */
 3471        function startAutoHide() {
 3472          var autoHideTimer, cancelAutoHide = angular.noop;
 3473
 3474          if (options.hideDelay) {
 3475            autoHideTimer = $timeout(service.hide, options.hideDelay) ;
 3476            cancelAutoHide = function() {
 3477              $timeout.cancel(autoHideTimer);
 3478            }
 3479          }
 3480
 3481          // Cache for subsequent use
 3482          options.cancelAutoHide = function() {
 3483            cancelAutoHide();
 3484            options.cancelAutoHide = undefined;
 3485          }
 3486        }
 3487
 3488        /**
 3489         * Show the element ( with transitions), notify complete and start
 3490         * optional auto-Hide
 3491         */
 3492        function showElement(element, options, controller) {
 3493          // Trigger onShowing callback before the `show()` starts
 3494          var notifyShowing = options.onShowing || angular.noop;
 3495          // Trigger onComplete callback when the `show()` finishes
 3496          var notifyComplete = options.onComplete || angular.noop;
 3497
 3498          notifyShowing(options.scope, element, options, controller);
 3499
 3500          return $q(function (resolve, reject) {
 3501            try {
 3502              // Start transitionIn
 3503              $q.when(options.onShow(options.scope, element, options, controller))
 3504                .then(function () {
 3505                  notifyComplete(options.scope, element, options);
 3506                  startAutoHide();
 3507
 3508                  resolve(element);
 3509
 3510                }, reject );
 3511
 3512            } catch(e) {
 3513              reject(e.message);
 3514            }
 3515          });
 3516        }
 3517
 3518        function hideElement(element, options) {
 3519          var announceRemoving = options.onRemoving || angular.noop;
 3520
 3521          return $$q(function (resolve, reject) {
 3522            try {
 3523              // Start transitionIn
 3524              var action = $$q.when( options.onRemove(options.scope, element, options) || true );
 3525
 3526              // Trigger callback *before* the remove operation starts
 3527              announceRemoving(element, action);
 3528
 3529              if ( options.$destroy == true ) {
 3530
 3531                // For $destroy, onRemove should be synchronous
 3532                resolve(element);
 3533
 3534              } else {
 3535
 3536                // Wait until transition-out is done
 3537                action.then(function () {
 3538
 3539                  if (!options.preserveScope && options.scope ) {
 3540                    options.scope.$destroy();
 3541                  }
 3542
 3543                  resolve(element);
 3544
 3545                }, reject );
 3546              }
 3547
 3548            } catch(e) {
 3549              reject(e);
 3550            }
 3551          });
 3552        }
 3553
 3554      }
 3555    };
 3556
 3557  }
 3558
 3559}
 3560
 3561})();
 3562(function(){
 3563"use strict";
 3564
 3565(function() {
 3566  'use strict';
 3567
 3568  var $mdUtil, $interpolate, $log;
 3569
 3570  var SUFFIXES = /(-gt)?-(sm|md|lg|print)/g;
 3571  var WHITESPACE = /\s+/g;
 3572
 3573  var FLEX_OPTIONS = ['grow', 'initial', 'auto', 'none', 'noshrink', 'nogrow' ];
 3574  var LAYOUT_OPTIONS = ['row', 'column'];
 3575  var ALIGNMENT_MAIN_AXIS= [ "", "start", "center", "end", "stretch", "space-around", "space-between" ];
 3576  var ALIGNMENT_CROSS_AXIS= [ "", "start", "center", "end", "stretch" ];
 3577
 3578  var config = {
 3579    /**
 3580     * Enable directive attribute-to-class conversions
 3581     * Developers can use `<body md-layout-css />` to quickly
 3582     * disable the Layout directives and prohibit the injection of Layout classNames
 3583     */
 3584    enabled: true,
 3585
 3586    /**
 3587     * List of mediaQuery breakpoints and associated suffixes
 3588     *
 3589     *   [
 3590     *    { suffix: "sm", mediaQuery: "screen and (max-width: 599px)" },
 3591     *    { suffix: "md", mediaQuery: "screen and (min-width: 600px) and (max-width: 959px)" }
 3592     *   ]
 3593     */
 3594    breakpoints: []
 3595  };
 3596
 3597  registerLayoutAPI( angular.module('material.core.layout', ['ng']) );
 3598
 3599  /**
 3600   *   registerLayoutAPI()
 3601   *
 3602   *   The original ngMaterial Layout solution used attribute selectors and CSS.
 3603   *
 3604   *  ```html
 3605   *  <div layout="column"> My Content </div>
 3606   *  ```
 3607   *
 3608   *  ```css
 3609   *  [layout] {
 3610   *    box-sizing: border-box;
 3611   *    display:flex;
 3612   *  }
 3613   *  [layout=column] {
 3614   *    flex-direction : column
 3615   *  }
 3616   *  ```
 3617   *
 3618   *  Use of attribute selectors creates significant performance impacts in some
 3619   *  browsers... mainly IE.
 3620   *
 3621   *  This module registers directives that allow the same layout attributes to be
 3622   *  interpreted and converted to class selectors. The directive will add equivalent classes to each element that
 3623   *  contains a Layout directive.
 3624   *
 3625   * ```html
 3626   *   <div layout="column" class="layout layout-column"> My Content </div>
 3627   *```
 3628   *
 3629   *  ```css
 3630   *  .layout {
 3631   *    box-sizing: border-box;
 3632   *    display:flex;
 3633   *  }
 3634   *  .layout-column {
 3635   *    flex-direction : column
 3636   *  }
 3637   *  ```
 3638   */
 3639  function registerLayoutAPI(module){
 3640    var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
 3641    var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
 3642
 3643    // NOTE: these are also defined in constants::MEDIA_PRIORITY and constants::MEDIA
 3644    var BREAKPOINTS     = [ "", "xs", "gt-xs", "sm", "gt-sm", "md", "gt-md", "lg", "gt-lg", "xl", "print" ];
 3645    var API_WITH_VALUES = [ "layout", "flex", "flex-order", "flex-offset", "layout-align" ];
 3646    var API_NO_VALUES   = [ "show", "hide", "layout-padding", "layout-margin" ];
 3647
 3648
 3649    // Build directive registration functions for the standard Layout API... for all breakpoints.
 3650    angular.forEach(BREAKPOINTS, function(mqb) {
 3651
 3652      // Attribute directives with expected, observable value(s)
 3653      angular.forEach( API_WITH_VALUES, function(name){
 3654        var fullName = mqb ? name + "-" + mqb : name;
 3655        module.directive( directiveNormalize(fullName), attributeWithObserve(fullName));
 3656      });
 3657
 3658      // Attribute directives with no expected value(s)
 3659      angular.forEach( API_NO_VALUES, function(name){
 3660        var fullName = mqb ? name + "-" + mqb : name;
 3661        module.directive( directiveNormalize(fullName), attributeWithoutValue(fullName));
 3662      });
 3663
 3664    });
 3665
 3666    // Register other, special directive functions for the Layout features:
 3667    module
 3668
 3669      .provider('$$mdLayout'     , function() {
 3670        // Publish internal service for Layouts
 3671        return {
 3672          $get : angular.noop,
 3673          validateAttributeValue : validateAttributeValue,
 3674          validateAttributeUsage : validateAttributeUsage,
 3675          /**
 3676           * Easy way to disable/enable the Layout API.
 3677           * When disabled, this stops all attribute-to-classname generations
 3678           */
 3679          disableLayouts  : function(isDisabled) {
 3680            config.enabled =  (isDisabled !== true);
 3681          }
 3682        };
 3683      })
 3684
 3685      .directive('mdLayoutCss'        , disableLayoutDirective )
 3686      .directive('ngCloak'            , buildCloakInterceptor('ng-cloak'))
 3687
 3688      .directive('layoutWrap'   , attributeWithoutValue('layout-wrap'))
 3689      .directive('layoutNowrap' , attributeWithoutValue('layout-nowrap'))
 3690      .directive('layoutNoWrap' , attributeWithoutValue('layout-no-wrap'))
 3691      .directive('layoutFill'   , attributeWithoutValue('layout-fill'))
 3692
 3693      // !! Deprecated attributes: use the `-lt` (aka less-than) notations
 3694
 3695      .directive('layoutLtMd'     , warnAttrNotSupported('layout-lt-md', true))
 3696      .directive('layoutLtLg'     , warnAttrNotSupported('layout-lt-lg', true))
 3697      .directive('flexLtMd'       , warnAttrNotSupported('flex-lt-md', true))
 3698      .directive('flexLtLg'       , warnAttrNotSupported('flex-lt-lg', true))
 3699
 3700      .directive('layoutAlignLtMd', warnAttrNotSupported('layout-align-lt-md'))
 3701      .directive('layoutAlignLtLg', warnAttrNotSupported('layout-align-lt-lg'))
 3702      .directive('flexOrderLtMd'  , warnAttrNotSupported('flex-order-lt-md'))
 3703      .directive('flexOrderLtLg'  , warnAttrNotSupported('flex-order-lt-lg'))
 3704      .directive('offsetLtMd'     , warnAttrNotSupported('flex-offset-lt-md'))
 3705      .directive('offsetLtLg'     , warnAttrNotSupported('flex-offset-lt-lg'))
 3706
 3707      .directive('hideLtMd'       , warnAttrNotSupported('hide-lt-md'))
 3708      .directive('hideLtLg'       , warnAttrNotSupported('hide-lt-lg'))
 3709      .directive('showLtMd'       , warnAttrNotSupported('show-lt-md'))
 3710      .directive('showLtLg'       , warnAttrNotSupported('show-lt-lg'))
 3711
 3712      // Determine if
 3713      .config( detectDisabledLayouts );
 3714
 3715    /**
 3716     * Converts snake_case to camelCase.
 3717     * Also there is special case for Moz prefix starting with upper case letter.
 3718     * @param name Name to normalize
 3719     */
 3720    function directiveNormalize(name) {
 3721      return name
 3722        .replace(PREFIX_REGEXP, '')
 3723        .replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
 3724          return offset ? letter.toUpperCase() : letter;
 3725        });
 3726    }
 3727
 3728  }
 3729
 3730
 3731  /**
 3732    * Detect if any of the HTML tags has a [md-layouts-disabled] attribute;
 3733    * If yes, then immediately disable all layout API features
 3734    *
 3735    * Note: this attribute should be specified on either the HTML or BODY tags
 3736    */
 3737   /**
 3738    * @ngInject
 3739    */
 3740   function detectDisabledLayouts() {
 3741     var isDisabled = !!document.querySelector('[md-layouts-disabled]');
 3742     config.enabled = !isDisabled;
 3743   }
 3744
 3745  /**
 3746   * Special directive that will disable ALL Layout conversions of layout
 3747   * attribute(s) to classname(s).
 3748   *
 3749   * <link rel="stylesheet" href="angular-material.min.css">
 3750   * <link rel="stylesheet" href="angular-material.layout.css">
 3751   *
 3752   * <body md-layout-css>
 3753   *  ...
 3754   * </body>
 3755   *
 3756   * Note: Using md-layout-css directive requires the developer to load the Material
 3757   * Layout Attribute stylesheet (which only uses attribute selectors):
 3758   *
 3759   *       `angular-material.layout.css`
 3760   *
 3761   * Another option is to use the LayoutProvider to configure and disable the attribute
 3762   * conversions; this would obviate the use of the `md-layout-css` directive
 3763   *
 3764   */
 3765  function disableLayoutDirective() {
 3766    // Return a 1x-only, first-match attribute directive
 3767    config.enabled = false;
 3768
 3769    return {
 3770      restrict : 'A',
 3771      priority : '900'
 3772    };
 3773  }
 3774
 3775  /**
 3776   * Tail-hook ngCloak to delay the uncloaking while Layout transformers
 3777   * finish processing. Eliminates flicker with Material.Layouts
 3778   */
 3779  function buildCloakInterceptor(className) {
 3780    return [ '$timeout', function($timeout){
 3781      return {
 3782        restrict : 'A',
 3783        priority : -10,   // run after normal ng-cloak
 3784        compile  : function( element ) {
 3785          if (!config.enabled) return angular.noop;
 3786
 3787          // Re-add the cloak
 3788          element.addClass(className);
 3789
 3790          return function( scope, element ) {
 3791            // Wait while layout injectors configure, then uncloak
 3792            // NOTE: $rAF does not delay enough... and this is a 1x-only event,
 3793            //       $timeout is acceptable.
 3794            $timeout( function(){
 3795              element.removeClass(className);
 3796            }, 10, false);
 3797          };
 3798        }
 3799      };
 3800    }];
 3801  }
 3802
 3803
 3804  // *********************************************************************************
 3805  //
 3806  // These functions create registration functions for ngMaterial Layout attribute directives
 3807  // This provides easy translation to switch ngMaterial attribute selectors to
 3808  // CLASS selectors and directives; which has huge performance implications
 3809  // for IE Browsers
 3810  //
 3811  // *********************************************************************************
 3812
 3813  /**
 3814   * Creates a directive registration function where a possible dynamic attribute
 3815   * value will be observed/watched.
 3816   * @param {string} className attribute name; eg `layout-gt-md` with value ="row"
 3817   */
 3818  function attributeWithObserve(className) {
 3819
 3820    return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) {
 3821      $mdUtil = _$mdUtil_;
 3822      $interpolate = _$interpolate_;
 3823      $log = _$log_;
 3824
 3825      return {
 3826        restrict: 'A',
 3827        compile: function(element, attr) {
 3828          var linkFn;
 3829          if (config.enabled) {
 3830            // immediately replace static (non-interpolated) invalid values...
 3831
 3832            validateAttributeUsage(className, attr, element, $log);
 3833
 3834            validateAttributeValue( className,
 3835              getNormalizedAttrValue(className, attr, ""),
 3836              buildUpdateFn(element, className, attr)
 3837            );
 3838
 3839            linkFn = translateWithValueToCssClass;
 3840          }
 3841
 3842          // Use for postLink to account for transforms after ng-transclude.
 3843          return linkFn || angular.noop;
 3844        }
 3845      };
 3846    }];
 3847
 3848    /**
 3849     * Add as transformed class selector(s), then
 3850     * remove the deprecated attribute selector
 3851     */
 3852    function translateWithValueToCssClass(scope, element, attrs) {
 3853      var updateFn = updateClassWithValue(element, className, attrs);
 3854      var unwatch = attrs.$observe(attrs.$normalize(className), updateFn);
 3855
 3856      updateFn(getNormalizedAttrValue(className, attrs, ""));
 3857      scope.$on("$destroy", function() { unwatch(); });
 3858    }
 3859  }
 3860
 3861  /**
 3862   * Creates a registration function for ngMaterial Layout attribute directive.
 3863   * This is a `simple` transpose of attribute usage to class usage; where we ignore
 3864   * any attribute value
 3865   */
 3866  function attributeWithoutValue(className) {
 3867    return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) {
 3868      $mdUtil = _$mdUtil_;
 3869      $interpolate = _$interpolate_;
 3870      $log = _$log_;
 3871
 3872      return {
 3873        restrict: 'A',
 3874        compile: function(element, attr) {
 3875          var linkFn;
 3876          if (config.enabled) {
 3877            // immediately replace static (non-interpolated) invalid values...
 3878
 3879            validateAttributeValue( className,
 3880              getNormalizedAttrValue(className, attr, ""),
 3881              buildUpdateFn(element, className, attr)
 3882            );
 3883
 3884            translateToCssClass(null, element);
 3885
 3886            // Use for postLink to account for transforms after ng-transclude.
 3887            linkFn = translateToCssClass;
 3888          }
 3889
 3890          return linkFn || angular.noop;
 3891        }
 3892      };
 3893    }];
 3894
 3895    /**
 3896     * Add as transformed class selector, then
 3897     * remove the deprecated attribute selector
 3898     */
 3899    function translateToCssClass(scope, element) {
 3900      element.addClass(className);
 3901    }
 3902  }
 3903
 3904
 3905
 3906  /**
 3907   * After link-phase, do NOT remove deprecated layout attribute selector.
 3908   * Instead watch the attribute so interpolated data-bindings to layout
 3909   * selectors will continue to be supported.
 3910   *
 3911   * $observe() the className and update with new class (after removing the last one)
 3912   *
 3913   * e.g. `layout="{{layoutDemo.direction}}"` will update...
 3914   *
 3915   * NOTE: The value must match one of the specified styles in the CSS.
 3916   * For example `flex-gt-md="{{size}}`  where `scope.size == 47` will NOT work since
 3917   * only breakpoints for 0, 5, 10, 15... 100, 33, 34, 66, 67 are defined.
 3918   *
 3919   */
 3920  function updateClassWithValue(element, className) {
 3921    var lastClass;
 3922
 3923    return function updateClassFn(newValue) {
 3924      var value = validateAttributeValue(className, newValue || "");
 3925      if ( angular.isDefined(value) ) {
 3926        if (lastClass) element.removeClass(lastClass);
 3927        lastClass = !value ? className : className + "-" + value.replace(WHITESPACE, "-");
 3928        element.addClass(lastClass);
 3929      }
 3930    };
 3931  }
 3932
 3933  /**
 3934   * Provide console warning that this layout attribute has been deprecated
 3935   *
 3936   */
 3937  function warnAttrNotSupported(className) {
 3938    var parts = className.split("-");
 3939    return ["$log", function($log) {
 3940      $log.warn(className + "has been deprecated. Please use a `" + parts[0] + "-gt-<xxx>` variant.");
 3941      return angular.noop;
 3942    }];
 3943  }
 3944
 3945  /**
 3946   * Centralize warnings for known flexbox issues (especially IE-related issues)
 3947   */
 3948  function validateAttributeUsage(className, attr, element, $log){
 3949    var message, usage, url;
 3950    var nodeName = element[0].nodeName.toLowerCase();
 3951
 3952    switch(className.replace(SUFFIXES,"")) {
 3953      case "flex":
 3954        if ((nodeName == "md-button") || (nodeName == "fieldset")){
 3955          // @see https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers
 3956          // Use <div flex> wrapper inside (preferred) or outside
 3957
 3958          usage = "<" + nodeName + " " + className + "></" + nodeName + ">";
 3959          url = "https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers";
 3960          message = "Markup '{0}' may not work as expected in IE Browsers. Consult '{1}' for details.";
 3961
 3962          $log.warn( $mdUtil.supplant(message, [usage, url]) );
 3963        }
 3964    }
 3965
 3966  }
 3967
 3968
 3969  /**
 3970   * For the Layout attribute value, validate or replace with default
 3971   * fallback value
 3972   */
 3973  function validateAttributeValue(className, value, updateFn) {
 3974    var origValue = value;
 3975
 3976    if (!needsInterpolation(value)) {
 3977      switch (className.replace(SUFFIXES,"")) {
 3978        case 'layout'        :
 3979          if ( !findIn(value, LAYOUT_OPTIONS) ) {
 3980            value = LAYOUT_OPTIONS[0];    // 'row';
 3981          }
 3982          break;
 3983
 3984        case 'flex'          :
 3985          if (!findIn(value, FLEX_OPTIONS)) {
 3986            if (isNaN(value)) {
 3987              value = '';
 3988            }
 3989          }
 3990          break;
 3991
 3992        case 'flex-offset' :
 3993        case 'flex-order'    :
 3994          if (!value || isNaN(+value)) {
 3995            value = '0';
 3996          }
 3997          break;
 3998
 3999        case 'layout-align'  :
 4000          var axis = extractAlignAxis(value);
 4001          value = $mdUtil.supplant("{main}-{cross}",axis);
 4002          break;
 4003
 4004        case 'layout-padding' :
 4005        case 'layout-margin'  :
 4006        case 'layout-fill'    :
 4007        case 'layout-wrap'    :
 4008        case 'layout-nowrap'  :
 4009        case 'layout-nowrap' :
 4010          value = '';
 4011          break;
 4012      }
 4013
 4014      if (value != origValue) {
 4015        (updateFn || angular.noop)(value);
 4016      }
 4017    }
 4018
 4019    return value;
 4020  }
 4021
 4022  /**
 4023   * Replace current attribute value with fallback value
 4024   */
 4025  function buildUpdateFn(element, className, attrs) {
 4026    return function updateAttrValue(fallback) {
 4027      if (!needsInterpolation(fallback)) {
 4028        // Do not modify the element's attribute value; so
 4029        // uses '<ui-layout layout="/api/sidebar.html" />' will not
 4030        // be affected. Just update the attrs value.
 4031        attrs[attrs.$normalize(className)] = fallback;
 4032      }
 4033    };
 4034  }
 4035
 4036  /**
 4037   * See if the original value has interpolation symbols:
 4038   * e.g.  flex-gt-md="{{triggerPoint}}"
 4039   */
 4040  function needsInterpolation(value) {
 4041    return (value || "").indexOf($interpolate.startSymbol()) > -1;
 4042  }
 4043
 4044  function getNormalizedAttrValue(className, attrs, defaultVal) {
 4045    var normalizedAttr = attrs.$normalize(className);
 4046    return attrs[normalizedAttr] ? attrs[normalizedAttr].replace(WHITESPACE, "-") : defaultVal || null;
 4047  }
 4048
 4049  function findIn(item, list, replaceWith) {
 4050    item = replaceWith && item ? item.replace(WHITESPACE, replaceWith) : item;
 4051
 4052    var found = false;
 4053    if (item) {
 4054      list.forEach(function(it) {
 4055        it = replaceWith ? it.replace(WHITESPACE, replaceWith) : it;
 4056        found = found || (it === item);
 4057      });
 4058    }
 4059    return found;
 4060  }
 4061
 4062  function extractAlignAxis(attrValue) {
 4063    var axis = {
 4064      main : "start",
 4065      cross: "stretch"
 4066    }, values;
 4067
 4068    attrValue = (attrValue || "");
 4069
 4070    if ( attrValue.indexOf("-") === 0 || attrValue.indexOf(" ") === 0) {
 4071      // For missing main-axis values
 4072      attrValue = "none" + attrValue;
 4073    }
 4074
 4075    values = attrValue.toLowerCase().trim().replace(WHITESPACE, "-").split("-");
 4076    if ( values.length && (values[0] === "space") ) {
 4077      // for main-axis values of "space-around" or "space-between"
 4078      values = [ values[0]+"-"+values[1],values[2] ];
 4079    }
 4080
 4081    if ( values.length > 0 ) axis.main  = values[0] || axis.main;
 4082    if ( values.length > 1 ) axis.cross = values[1] || axis.cross;
 4083
 4084    if ( ALIGNMENT_MAIN_AXIS.indexOf(axis.main) < 0 )   axis.main = "start";
 4085    if ( ALIGNMENT_CROSS_AXIS.indexOf(axis.cross) < 0 ) axis.cross = "stretch";
 4086
 4087    return axis;
 4088  }
 4089
 4090
 4091})();
 4092
 4093})();
 4094(function(){
 4095"use strict";
 4096
 4097/**
 4098 * @ngdoc service
 4099 * @name $$mdMeta
 4100 * @module material.core.meta
 4101 *
 4102 * @description
 4103 *
 4104 * A provider and a service that simplifies meta tags access
 4105 *
 4106 * Note: This is intended only for use with dynamic meta tags such as browser color and title.
 4107 * Tags that are only processed when the page is rendered (such as `charset`, and `http-equiv`)
 4108 * will not work since `$$mdMeta` adds the tags after the page has already been loaded.
 4109 *
 4110 * ```js
 4111 * app.config(function($$mdMetaProvider) {
 4112 *   var removeMeta = $$mdMetaProvider.setMeta('meta-name', 'content');
 4113 *   var metaValue  = $$mdMetaProvider.getMeta('meta-name'); // -> 'content'
 4114 *
 4115 *   removeMeta();
 4116 * });
 4117 *
 4118 * app.controller('myController', function($$mdMeta) {
 4119 *   var removeMeta = $$mdMeta.setMeta('meta-name', 'content');
 4120 *   var metaValue  = $$mdMeta.getMeta('meta-name'); // -> 'content'
 4121 *
 4122 *   removeMeta();
 4123 * });
 4124 * ```
 4125 *
 4126 * @returns {$$mdMeta.$service}
 4127 *
 4128 */
 4129angular.module('material.core.meta', [])
 4130  .provider('$$mdMeta', function () {
 4131    var head = angular.element(document.head);
 4132    var metaElements = {};
 4133
 4134    /**
 4135     * Checks if the requested element was written manually and maps it
 4136     *
 4137     * @param {string} name meta tag 'name' attribute value
 4138     * @returns {boolean} returns true if there is an element with the requested name
 4139     */
 4140    function mapExistingElement(name) {
 4141      if (metaElements[name]) {
 4142        return true;
 4143      }
 4144
 4145      var element = document.getElementsByName(name)[0];
 4146
 4147      if (!element) {
 4148        return false;
 4149      }
 4150
 4151      metaElements[name] = angular.element(element);
 4152
 4153      return true;
 4154    }
 4155
 4156    /**
 4157     * @ngdoc method
 4158     * @name $$mdMeta#setMeta
 4159     *
 4160     * @description
 4161     * Creates meta element with the 'name' and 'content' attributes,
 4162     * if the meta tag is already created than we replace the 'content' value
 4163     *
 4164     * @param {string} name meta tag 'name' attribute value
 4165     * @param {string} content meta tag 'content' attribute value
 4166     * @returns {function} remove function
 4167     *
 4168     */
 4169    function setMeta(name, content) {
 4170      mapExistingElement(name);
 4171
 4172      if (!metaElements[name]) {
 4173        var newMeta = angular.element('<meta name="' + name + '" content="' + content + '"/>');
 4174        head.append(newMeta);
 4175        metaElements[name] = newMeta;
 4176      }
 4177      else {
 4178        metaElements[name].attr('content', content);
 4179      }
 4180
 4181      return function () {
 4182        metaElements[name].attr('content', '');
 4183        metaElements[name].remove();
 4184        delete metaElements[name];
 4185      };
 4186    }
 4187
 4188    /**
 4189     * @ngdoc method
 4190     * @name $$mdMeta#getMeta
 4191     *
 4192     * @description
 4193     * Gets the 'content' attribute value of the wanted meta element
 4194     *
 4195     * @param {string} name meta tag 'name' attribute value
 4196     * @returns {string} content attribute value
 4197     */
 4198    function getMeta(name) {
 4199      if (!mapExistingElement(name)) {
 4200        throw Error('$$mdMeta: could not find a meta tag with the name \'' + name + '\'');
 4201      }
 4202
 4203      return metaElements[name].attr('content');
 4204    }
 4205
 4206    var module = {
 4207      setMeta: setMeta,
 4208      getMeta: getMeta
 4209    };
 4210
 4211    return angular.extend({}, module, {
 4212      $get: function () {
 4213        return module;
 4214      }
 4215    });
 4216  });
 4217})();
 4218(function(){
 4219"use strict";
 4220
 4221  /**
 4222   * @ngdoc module
 4223   * @name material.core.componentRegistry
 4224   *
 4225   * @description
 4226   * A component instance registration service.
 4227   * Note: currently this as a private service in the SideNav component.
 4228   */
 4229  ComponentRegistry.$inject = ["$log", "$q"];
 4230  angular.module('material.core')
 4231    .factory('$mdComponentRegistry', ComponentRegistry);
 4232
 4233  /*
 4234   * @private
 4235   * @ngdoc factory
 4236   * @name ComponentRegistry
 4237   * @module material.core.componentRegistry
 4238   *
 4239   */
 4240  function ComponentRegistry($log, $q) {
 4241
 4242    var self;
 4243    var instances = [ ];
 4244    var pendings = { };
 4245
 4246    return self = {
 4247      /**
 4248       * Used to print an error when an instance for a handle isn't found.
 4249       */
 4250      notFoundError: function(handle, msgContext) {
 4251        $log.error( (msgContext || "") + 'No instance found for handle', handle);
 4252      },
 4253      /**
 4254       * Return all registered instances as an array.
 4255       */
 4256      getInstances: function() {
 4257        return instances;
 4258      },
 4259
 4260      /**
 4261       * Get a registered instance.
 4262       * @param handle the String handle to look up for a registered instance.
 4263       */
 4264      get: function(handle) {
 4265        if ( !isValidID(handle) ) return null;
 4266
 4267        var i, j, instance;
 4268        for(i = 0, j = instances.length; i < j; i++) {
 4269          instance = instances[i];
 4270          if(instance.$$mdHandle === handle) {
 4271            return instance;
 4272          }
 4273        }
 4274        return null;
 4275      },
 4276
 4277      /**
 4278       * Register an instance.
 4279       * @param instance the instance to register
 4280       * @param handle the handle to identify the instance under.
 4281       */
 4282      register: function(instance, handle) {
 4283        if ( !handle ) return angular.noop;
 4284
 4285        instance.$$mdHandle = handle;
 4286        instances.push(instance);
 4287        resolveWhen();
 4288
 4289        return deregister;
 4290
 4291        /**
 4292         * Remove registration for an instance
 4293         */
 4294        function deregister() {
 4295          var index = instances.indexOf(instance);
 4296          if (index !== -1) {
 4297            instances.splice(index, 1);
 4298          }
 4299        }
 4300
 4301        /**
 4302         * Resolve any pending promises for this instance
 4303         */
 4304        function resolveWhen() {
 4305          var dfd = pendings[handle];
 4306          if ( dfd ) {
 4307            dfd.forEach(function (promise) {
 4308              promise.resolve(instance);
 4309            });
 4310            delete pendings[handle];
 4311          }
 4312        }
 4313      },
 4314
 4315      /**
 4316       * Async accessor to registered component instance
 4317       * If not available then a promise is created to notify
 4318       * all listeners when the instance is registered.
 4319       */
 4320      when : function(handle) {
 4321        if ( isValidID(handle) ) {
 4322          var deferred = $q.defer();
 4323          var instance = self.get(handle);
 4324
 4325          if ( instance )  {
 4326            deferred.resolve( instance );
 4327          } else {
 4328            if (pendings[handle] === undefined) {
 4329              pendings[handle] = [];
 4330            }
 4331            pendings[handle].push(deferred);
 4332          }
 4333
 4334          return deferred.promise;
 4335        }
 4336        return $q.reject("Invalid `md-component-id` value.");
 4337      }
 4338
 4339    };
 4340
 4341    function isValidID(handle){
 4342      return handle && (handle !== "");
 4343    }
 4344
 4345  }
 4346
 4347})();
 4348(function(){
 4349"use strict";
 4350
 4351(function() {
 4352  'use strict';
 4353
 4354  /**
 4355   * @ngdoc service
 4356   * @name $mdButtonInkRipple
 4357   * @module material.core
 4358   *
 4359   * @description
 4360   * Provides ripple effects for md-button.  See $mdInkRipple service for all possible configuration options.
 4361   *
 4362   * @param {object=} scope Scope within the current context
 4363   * @param {object=} element The element the ripple effect should be applied to
 4364   * @param {object=} options (Optional) Configuration options to override the default ripple configuration
 4365   */
 4366
 4367  MdButtonInkRipple.$inject = ["$mdInkRipple"];
 4368  angular.module('material.core')
 4369    .factory('$mdButtonInkRipple', MdButtonInkRipple);
 4370
 4371  function MdButtonInkRipple($mdInkRipple) {
 4372    return {
 4373      attach: function attachRipple(scope, element, options) {
 4374        options = angular.extend(optionsForElement(element), options);
 4375
 4376        return $mdInkRipple.attach(scope, element, options);
 4377      }
 4378    };
 4379
 4380    function optionsForElement(element) {
 4381      if (element.hasClass('md-icon-button')) {
 4382        return {
 4383          isMenuItem: element.hasClass('md-menu-item'),
 4384          fitRipple: true,
 4385          center: true
 4386        };
 4387      } else {
 4388        return {
 4389          isMenuItem: element.hasClass('md-menu-item'),
 4390          dimBackground: true
 4391        }
 4392      }
 4393    };
 4394  };
 4395})();
 4396
 4397})();
 4398(function(){
 4399"use strict";
 4400
 4401(function() {
 4402  'use strict';
 4403
 4404    /**
 4405   * @ngdoc service
 4406   * @name $mdCheckboxInkRipple
 4407   * @module material.core
 4408   *
 4409   * @description
 4410   * Provides ripple effects for md-checkbox.  See $mdInkRipple service for all possible configuration options.
 4411   *
 4412   * @param {object=} scope Scope within the current context
 4413   * @param {object=} element The element the ripple effect should be applied to
 4414   * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
 4415   */
 4416
 4417  MdCheckboxInkRipple.$inject = ["$mdInkRipple"];
 4418  angular.module('material.core')
 4419    .factory('$mdCheckboxInkRipple', MdCheckboxInkRipple);
 4420
 4421  function MdCheckboxInkRipple($mdInkRipple) {
 4422    return {
 4423      attach: attach
 4424    };
 4425
 4426    function attach(scope, element, options) {
 4427      return $mdInkRipple.attach(scope, element, angular.extend({
 4428        center: true,
 4429        dimBackground: false,
 4430        fitRipple: true
 4431      }, options));
 4432    };
 4433  };
 4434})();
 4435
 4436})();
 4437(function(){
 4438"use strict";
 4439
 4440(function() {
 4441  'use strict';
 4442
 4443  /**
 4444   * @ngdoc service
 4445   * @name $mdListInkRipple
 4446   * @module material.core
 4447   *
 4448   * @description
 4449   * Provides ripple effects for md-list.  See $mdInkRipple service for all possible configuration options.
 4450   *
 4451   * @param {object=} scope Scope within the current context
 4452   * @param {object=} element The element the ripple effect should be applied to
 4453   * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
 4454   */
 4455
 4456  MdListInkRipple.$inject = ["$mdInkRipple"];
 4457  angular.module('material.core')
 4458    .factory('$mdListInkRipple', MdListInkRipple);
 4459
 4460  function MdListInkRipple($mdInkRipple) {
 4461    return {
 4462      attach: attach
 4463    };
 4464
 4465    function attach(scope, element, options) {
 4466      return $mdInkRipple.attach(scope, element, angular.extend({
 4467        center: false,
 4468        dimBackground: true,
 4469        outline: false,
 4470        rippleSize: 'full'
 4471      }, options));
 4472    };
 4473  };
 4474})();
 4475
 4476})();
 4477(function(){
 4478"use strict";
 4479
 4480/**
 4481 * @ngdoc module
 4482 * @name material.core.ripple
 4483 * @description
 4484 * Ripple
 4485 */
 4486InkRippleCtrl.$inject = ["$scope", "$element", "rippleOptions", "$window", "$timeout", "$mdUtil", "$mdColorUtil"];
 4487InkRippleDirective.$inject = ["$mdButtonInkRipple", "$mdCheckboxInkRipple"];
 4488angular.module('material.core')
 4489    .provider('$mdInkRipple', InkRippleProvider)
 4490    .directive('mdInkRipple', InkRippleDirective)
 4491    .directive('mdNoInk', attrNoDirective)
 4492    .directive('mdNoBar', attrNoDirective)
 4493    .directive('mdNoStretch', attrNoDirective);
 4494
 4495var DURATION = 450;
 4496
 4497/**
 4498 * @ngdoc directive
 4499 * @name mdInkRipple
 4500 * @module material.core.ripple
 4501 *
 4502 * @description
 4503 * The `md-ink-ripple` directive allows you to specify the ripple color or id a ripple is allowed.
 4504 *
 4505 * @param {string|boolean} md-ink-ripple A color string `#FF0000` or boolean (`false` or `0`) for preventing ripple
 4506 *
 4507 * @usage
 4508 * ### String values
 4509 * <hljs lang="html">
 4510 *   <ANY md-ink-ripple="#FF0000">
 4511 *     Ripples in red
 4512 *   </ANY>
 4513 *
 4514 *   <ANY md-ink-ripple="false">
 4515 *     Not rippling
 4516 *   </ANY>
 4517 * </hljs>
 4518 *
 4519 * ### Interpolated values
 4520 * <hljs lang="html">
 4521 *   <ANY md-ink-ripple="{{ randomColor() }}">
 4522 *     Ripples with the return value of 'randomColor' function
 4523 *   </ANY>
 4524 *
 4525 *   <ANY md-ink-ripple="{{ canRipple() }}">
 4526 *     Ripples if 'canRipple' function return value is not 'false' or '0'
 4527 *   </ANY>
 4528 * </hljs>
 4529 */
 4530function InkRippleDirective ($mdButtonInkRipple, $mdCheckboxInkRipple) {
 4531  return {
 4532    controller: angular.noop,
 4533    link:       function (scope, element, attr) {
 4534      attr.hasOwnProperty('mdInkRippleCheckbox')
 4535          ? $mdCheckboxInkRipple.attach(scope, element)
 4536          : $mdButtonInkRipple.attach(scope, element);
 4537    }
 4538  };
 4539}
 4540
 4541/**
 4542 * @ngdoc service
 4543 * @name $mdInkRipple
 4544 * @module material.core.ripple
 4545 *
 4546 * @description
 4547 * `$mdInkRipple` is a service for adding ripples to any element
 4548 *
 4549 * @usage
 4550 * <hljs lang="js">
 4551 * app.factory('$myElementInkRipple', function($mdInkRipple) {
 4552 *   return {
 4553 *     attach: function (scope, element, options) {
 4554 *       return $mdInkRipple.attach(scope, element, angular.extend({
 4555 *         center: false,
 4556 *         dimBackground: true
 4557 *       }, options));
 4558 *     }
 4559 *   };
 4560 * });
 4561 *
 4562 * app.controller('myController', function ($scope, $element, $myElementInkRipple) {
 4563 *   $scope.onClick = function (ev) {
 4564 *     $myElementInkRipple.attach($scope, angular.element(ev.target), { center: true });
 4565 *   }
 4566 * });
 4567 * </hljs>
 4568 *
 4569 * ### Disabling ripples globally
 4570 * If you want to disable ink ripples globally, for all components, you can call the
 4571 * `disableInkRipple` method in your app's config.
 4572 *
 4573 * <hljs lang="js">
 4574 * app.config(function ($mdInkRippleProvider) {
 4575 *   $mdInkRippleProvider.disableInkRipple();
 4576 * });
 4577 */
 4578
 4579function InkRippleProvider () {
 4580  var isDisabledGlobally = false;
 4581
 4582  return {
 4583    disableInkRipple: disableInkRipple,
 4584    $get: ["$injector", function($injector) {
 4585      return { attach: attach };
 4586
 4587      /**
 4588       * @ngdoc method
 4589       * @name $mdInkRipple#attach
 4590       *
 4591       * @description
 4592       * Attaching given scope, element and options to inkRipple controller
 4593       *
 4594       * @param {object=} scope Scope within the current context
 4595       * @param {object=} element The element the ripple effect should be applied to
 4596       * @param {object=} options (Optional) Configuration options to override the defaultRipple configuration
 4597       * * `center` -  Whether the ripple should start from the center of the container element
 4598       * * `dimBackground` - Whether the background should be dimmed with the ripple color
 4599       * * `colorElement` - The element the ripple should take its color from, defined by css property `color`
 4600       * * `fitRipple` - Whether the ripple should fill the element
 4601       */
 4602      function attach (scope, element, options) {
 4603        if (isDisabledGlobally || element.controller('mdNoInk')) return angular.noop;
 4604        return $injector.instantiate(InkRippleCtrl, {
 4605          $scope:        scope,
 4606          $element:      element,
 4607          rippleOptions: options
 4608        });
 4609      }
 4610    }]
 4611  };
 4612
 4613  /**
 4614   * @ngdoc method
 4615   * @name $mdInkRipple#disableInkRipple
 4616   *
 4617   * @description
 4618   * A config-time method that, when called, disables ripples globally.
 4619   */
 4620  function disableInkRipple () {
 4621    isDisabledGlobally = true;
 4622  }
 4623}
 4624
 4625/**
 4626 * Controller used by the ripple service in order to apply ripples
 4627 * @ngInject
 4628 */
 4629function InkRippleCtrl ($scope, $element, rippleOptions, $window, $timeout, $mdUtil, $mdColorUtil) {
 4630  this.$window    = $window;
 4631  this.$timeout   = $timeout;
 4632  this.$mdUtil    = $mdUtil;
 4633  this.$mdColorUtil    = $mdColorUtil;
 4634  this.$scope     = $scope;
 4635  this.$element   = $element;
 4636  this.options    = rippleOptions;
 4637  this.mousedown  = false;
 4638  this.ripples    = [];
 4639  this.timeout    = null; // Stores a reference to the most-recent ripple timeout
 4640  this.lastRipple = null;
 4641
 4642  $mdUtil.valueOnUse(this, 'container', this.createContainer);
 4643
 4644  this.$element.addClass('md-ink-ripple');
 4645
 4646  // attach method for unit tests
 4647  ($element.controller('mdInkRipple') || {}).createRipple = angular.bind(this, this.createRipple);
 4648  ($element.controller('mdInkRipple') || {}).setColor = angular.bind(this, this.color);
 4649
 4650  this.bindEvents();
 4651}
 4652
 4653
 4654/**
 4655 * Either remove or unlock any remaining ripples when the user mouses off of the element (either by
 4656 * mouseup or mouseleave event)
 4657 */
 4658function autoCleanup (self, cleanupFn) {
 4659
 4660  if ( self.mousedown || self.lastRipple ) {
 4661    self.mousedown = false;
 4662    self.$mdUtil.nextTick( angular.bind(self, cleanupFn), false);
 4663  }
 4664
 4665}
 4666
 4667
 4668/**
 4669 * Returns the color that the ripple should be (either based on CSS or hard-coded)
 4670 * @returns {string}
 4671 */
 4672InkRippleCtrl.prototype.color = function (value) {
 4673  var self = this;
 4674
 4675  // If assigning a color value, apply it to background and the ripple color
 4676  if (angular.isDefined(value)) {
 4677    self._color = self._parseColor(value);
 4678  }
 4679
 4680  // If color lookup, use assigned, defined, or inherited
 4681  return self._color || self._parseColor( self.inkRipple() ) || self._parseColor( getElementColor() );
 4682
 4683  /**
 4684   * Finds the color element and returns its text color for use as default ripple color
 4685   * @returns {string}
 4686   */
 4687  function getElementColor () {
 4688    var items = self.options && self.options.colorElement ? self.options.colorElement : [];
 4689    var elem =  items.length ? items[ 0 ] : self.$element[ 0 ];
 4690
 4691    return elem ? self.$window.getComputedStyle(elem).color : 'rgb(0,0,0)';
 4692  }
 4693};
 4694
 4695/**
 4696 * Updating the ripple colors based on the current inkRipple value
 4697 * or the element's computed style color
 4698 */
 4699InkRippleCtrl.prototype.calculateColor = function () {
 4700  return this.color();
 4701};
 4702
 4703
 4704/**
 4705 * Takes a string color and converts it to RGBA format
 4706 * @param color {string}
 4707 * @param [multiplier] {int}
 4708 * @returns {string}
 4709 */
 4710
 4711InkRippleCtrl.prototype._parseColor = function parseColor (color, multiplier) {
 4712  multiplier = multiplier || 1;
 4713  var colorUtil = this.$mdColorUtil;
 4714
 4715  if (!color) return;
 4716  if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, (0.1 * multiplier).toString() + ')');
 4717  if (color.indexOf('rgb') === 0) return colorUtil.rgbToRgba(color);
 4718  if (color.indexOf('#') === 0) return colorUtil.hexToRgba(color);
 4719
 4720};
 4721
 4722/**
 4723 * Binds events to the root element for
 4724 */
 4725InkRippleCtrl.prototype.bindEvents = function () {
 4726  this.$element.on('mousedown', angular.bind(this, this.handleMousedown));
 4727  this.$element.on('mouseup touchend', angular.bind(this, this.handleMouseup));
 4728  this.$element.on('mouseleave', angular.bind(this, this.handleMouseup));
 4729  this.$element.on('touchmove', angular.bind(this, this.handleTouchmove));
 4730};
 4731
 4732/**
 4733 * Create a new ripple on every mousedown event from the root element
 4734 * @param event {MouseEvent}
 4735 */
 4736InkRippleCtrl.prototype.handleMousedown = function (event) {
 4737  if ( this.mousedown ) return;
 4738
 4739  // When jQuery is loaded, we have to get the original event
 4740  if (event.hasOwnProperty('originalEvent')) event = event.originalEvent;
 4741  this.mousedown = true;
 4742  if (this.options.center) {
 4743    this.createRipple(this.container.prop('clientWidth') / 2, this.container.prop('clientWidth') / 2);
 4744  } else {
 4745
 4746    // We need to calculate the relative coordinates if the target is a sublayer of the ripple element
 4747    if (event.srcElement !== this.$element[0]) {
 4748      var layerRect = this.$element[0].getBoundingClientRect();
 4749      var layerX = event.clientX - layerRect.left;
 4750      var layerY = event.clientY - layerRect.top;
 4751
 4752      this.createRipple(layerX, layerY);
 4753    } else {
 4754      this.createRipple(event.offsetX, event.offsetY);
 4755    }
 4756  }
 4757};
 4758
 4759/**
 4760 * Either remove or unlock any remaining ripples when the user mouses off of the element (either by
 4761 * mouseup, touchend or mouseleave event)
 4762 */
 4763InkRippleCtrl.prototype.handleMouseup = function () {
 4764  autoCleanup(this, this.clearRipples);
 4765};
 4766
 4767/**
 4768 * Either remove or unlock any remaining ripples when the user mouses off of the element (by
 4769 * touchmove)
 4770 */
 4771InkRippleCtrl.prototype.handleTouchmove = function () {
 4772  autoCleanup(this, this.deleteRipples);
 4773};
 4774
 4775/**
 4776 * Cycles through all ripples and attempts to remove them.
 4777 */
 4778InkRippleCtrl.prototype.deleteRipples = function () {
 4779  for (var i = 0; i < this.ripples.length; i++) {
 4780    this.ripples[ i ].remove();
 4781  }
 4782};
 4783
 4784/**
 4785 * Cycles through all ripples and attempts to remove them with fade.
 4786 * Depending on logic within `fadeInComplete`, some removals will be postponed.
 4787 */
 4788InkRippleCtrl.prototype.clearRipples = function () {
 4789  for (var i = 0; i < this.ripples.length; i++) {
 4790    this.fadeInComplete(this.ripples[ i ]);
 4791  }
 4792};
 4793
 4794/**
 4795 * Creates the ripple container element
 4796 * @returns {*}
 4797 */
 4798InkRippleCtrl.prototype.createContainer = function () {
 4799  var container = angular.element('<div class="md-ripple-container"></div>');
 4800  this.$element.append(container);
 4801  return container;
 4802};
 4803
 4804InkRippleCtrl.prototype.clearTimeout = function () {
 4805  if (this.timeout) {
 4806    this.$timeout.cancel(this.timeout);
 4807    this.timeout = null;
 4808  }
 4809};
 4810
 4811InkRippleCtrl.prototype.isRippleAllowed = function () {
 4812  var element = this.$element[0];
 4813  do {
 4814    if (!element.tagName || element.tagName === 'BODY') break;
 4815
 4816    if (element && angular.isFunction(element.hasAttribute)) {
 4817      if (element.hasAttribute('disabled')) return false;
 4818      if (this.inkRipple() === 'false' || this.inkRipple() === '0') return false;
 4819    }
 4820
 4821  } while (element = element.parentNode);
 4822  return true;
 4823};
 4824
 4825/**
 4826 * The attribute `md-ink-ripple` may be a static or interpolated
 4827 * color value OR a boolean indicator (used to disable ripples)
 4828 */
 4829InkRippleCtrl.prototype.inkRipple = function () {
 4830  return this.$element.attr('md-ink-ripple');
 4831};
 4832
 4833/**
 4834 * Creates a new ripple and adds it to the container.  Also tracks ripple in `this.ripples`.
 4835 * @param left
 4836 * @param top
 4837 */
 4838InkRippleCtrl.prototype.createRipple = function (left, top) {
 4839  if (!this.isRippleAllowed()) return;
 4840
 4841  var ctrl        = this;
 4842  var colorUtil   = ctrl.$mdColorUtil;
 4843  var ripple      = angular.element('<div class="md-ripple"></div>');
 4844  var width       = this.$element.prop('clientWidth');
 4845  var height      = this.$element.prop('clientHeight');
 4846  var x           = Math.max(Math.abs(width - left), left) * 2;
 4847  var y           = Math.max(Math.abs(height - top), top) * 2;
 4848  var size        = getSize(this.options.fitRipple, x, y);
 4849  var color       = this.calculateColor();
 4850
 4851  ripple.css({
 4852    left:            left + 'px',
 4853    top:             top + 'px',
 4854    background:      'black',
 4855    width:           size + 'px',
 4856    height:          size + 'px',
 4857    backgroundColor: colorUtil.rgbaToRgb(color),
 4858    borderColor:     colorUtil.rgbaToRgb(color)
 4859  });
 4860  this.lastRipple = ripple;
 4861
 4862  // we only want one timeout to be running at a time
 4863  this.clearTimeout();
 4864  this.timeout    = this.$timeout(function () {
 4865    ctrl.clearTimeout();
 4866    if (!ctrl.mousedown) ctrl.fadeInComplete(ripple);
 4867  }, DURATION * 0.35, false);
 4868
 4869  if (this.options.dimBackground) this.container.css({ backgroundColor: color });
 4870  this.container.append(ripple);
 4871  this.ripples.push(ripple);
 4872  ripple.addClass('md-ripple-placed');
 4873
 4874  this.$mdUtil.nextTick(function () {
 4875
 4876    ripple.addClass('md-ripple-scaled md-ripple-active');
 4877    ctrl.$timeout(function () {
 4878      ctrl.clearRipples();
 4879    }, DURATION, false);
 4880
 4881  }, false);
 4882
 4883  function getSize (fit, x, y) {
 4884    return fit
 4885        ? Math.max(x, y)
 4886        : Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
 4887  }
 4888};
 4889
 4890
 4891
 4892/**
 4893 * After fadeIn finishes, either kicks off the fade-out animation or queues the element for removal on mouseup
 4894 * @param ripple
 4895 */
 4896InkRippleCtrl.prototype.fadeInComplete = function (ripple) {
 4897  if (this.lastRipple === ripple) {
 4898    if (!this.timeout && !this.mousedown) {
 4899      this.removeRipple(ripple);
 4900    }
 4901  } else {
 4902    this.removeRipple(ripple);
 4903  }
 4904};
 4905
 4906/**
 4907 * Kicks off the animation for removing a ripple
 4908 * @param ripple {Element}
 4909 */
 4910InkRippleCtrl.prototype.removeRipple = function (ripple) {
 4911  var ctrl  = this;
 4912  var index = this.ripples.indexOf(ripple);
 4913  if (index < 0) return;
 4914  this.ripples.splice(this.ripples.indexOf(ripple), 1);
 4915  ripple.removeClass('md-ripple-active');
 4916  ripple.addClass('md-ripple-remove');
 4917  if (this.ripples.length === 0) this.container.css({ backgroundColor: '' });
 4918  // use a 2-second timeout in order to allow for the animation to finish
 4919  // we don't actually care how long the animation takes
 4920  this.$timeout(function () {
 4921    ctrl.fadeOutComplete(ripple);
 4922  }, DURATION, false);
 4923};
 4924
 4925/**
 4926 * Removes the provided ripple from the DOM
 4927 * @param ripple
 4928 */
 4929InkRippleCtrl.prototype.fadeOutComplete = function (ripple) {
 4930  ripple.remove();
 4931  this.lastRipple = null;
 4932};
 4933
 4934/**
 4935 * Used to create an empty directive.  This is used to track flag-directives whose children may have
 4936 * functionality based on them.
 4937 *
 4938 * Example: `md-no-ink` will potentially be used by all child directives.
 4939 */
 4940function attrNoDirective () {
 4941  return { controller: angular.noop };
 4942}
 4943
 4944})();
 4945(function(){
 4946"use strict";
 4947
 4948(function() {
 4949  'use strict';
 4950
 4951    /**
 4952   * @ngdoc service
 4953   * @name $mdTabInkRipple
 4954   * @module material.core
 4955   *
 4956   * @description
 4957   * Provides ripple effects for md-tabs.  See $mdInkRipple service for all possible configuration options.
 4958   *
 4959   * @param {object=} scope Scope within the current context
 4960   * @param {object=} element The element the ripple effect should be applied to
 4961   * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
 4962   */
 4963
 4964  MdTabInkRipple.$inject = ["$mdInkRipple"];
 4965  angular.module('material.core')
 4966    .factory('$mdTabInkRipple', MdTabInkRipple);
 4967
 4968  function MdTabInkRipple($mdInkRipple) {
 4969    return {
 4970      attach: attach
 4971    };
 4972
 4973    function attach(scope, element, options) {
 4974      return $mdInkRipple.attach(scope, element, angular.extend({
 4975        center: false,
 4976        dimBackground: true,
 4977        outline: false,
 4978        rippleSize: 'full'
 4979      }, options));
 4980    };
 4981  };
 4982})();
 4983
 4984})();
 4985(function(){
 4986"use strict";
 4987
 4988angular.module('material.core.theming.palette', [])
 4989.constant('$mdColorPalette', {
 4990  'red': {
 4991    '50': '#ffebee',
 4992    '100': '#ffcdd2',
 4993    '200': '#ef9a9a',
 4994    '300': '#e57373',
 4995    '400': '#ef5350',
 4996    '500': '#f44336',
 4997    '600': '#e53935',
 4998    '700': '#d32f2f',
 4999    '800': '#c62828',
 5000    '900': '#b71c1c',
 5001    'A100': '#ff8a80',
 5002    'A200': '#ff5252',
 5003    'A400': '#ff1744',
 5004    'A700': '#d50000',
 5005    'contrastDefaultColor': 'light',
 5006    'contrastDarkColors': '50 100 200 300 A100',
 5007    'contrastStrongLightColors': '400 500 600 700 A200 A400 A700'
 5008  },
 5009  'pink': {
 5010    '50': '#fce4ec',
 5011    '100': '#f8bbd0',
 5012    '200': '#f48fb1',
 5013    '300': '#f06292',
 5014    '400': '#ec407a',
 5015    '500': '#e91e63',
 5016    '600': '#d81b60',
 5017    '700': '#c2185b',
 5018    '800': '#ad1457',
 5019    '900': '#880e4f',
 5020    'A100': '#ff80ab',
 5021    'A200': '#ff4081',
 5022    'A400': '#f50057',
 5023    'A700': '#c51162',
 5024    'contrastDefaultColor': 'light',
 5025    'contrastDarkColors': '50 100 200 A100',
 5026    'contrastStrongLightColors': '500 600 A200 A400 A700'
 5027  },
 5028  'purple': {
 5029    '50': '#f3e5f5',
 5030    '100': '#e1bee7',
 5031    '200': '#ce93d8',
 5032    '300': '#ba68c8',
 5033    '400': '#ab47bc',
 5034    '500': '#9c27b0',
 5035    '600': '#8e24aa',
 5036    '700': '#7b1fa2',
 5037    '800': '#6a1b9a',
 5038    '900': '#4a148c',
 5039    'A100': '#ea80fc',
 5040    'A200': '#e040fb',
 5041    'A400': '#d500f9',
 5042    'A700': '#aa00ff',
 5043    'contrastDefaultColor': 'light',
 5044    'contrastDarkColors': '50 100 200 A100',
 5045    'contrastStrongLightColors': '300 400 A200 A400 A700'
 5046  },
 5047  'deep-purple': {
 5048    '50': '#ede7f6',
 5049    '100': '#d1c4e9',
 5050    '200': '#b39ddb',
 5051    '300': '#9575cd',
 5052    '400': '#7e57c2',
 5053    '500': '#673ab7',
 5054    '600': '#5e35b1',
 5055    '700': '#512da8',
 5056    '800': '#4527a0',
 5057    '900': '#311b92',
 5058    'A100': '#b388ff',
 5059    'A200': '#7c4dff',
 5060    'A400': '#651fff',
 5061    'A700': '#6200ea',
 5062    'contrastDefaultColor': 'light',
 5063    'contrastDarkColors': '50 100 200 A100',
 5064    'contrastStrongLightColors': '300 400 A200'
 5065  },
 5066  'indigo': {
 5067    '50': '#e8eaf6',
 5068    '100': '#c5cae9',
 5069    '200': '#9fa8da',
 5070    '300': '#7986cb',
 5071    '400': '#5c6bc0',
 5072    '500': '#3f51b5',
 5073    '600': '#3949ab',
 5074    '700': '#303f9f',
 5075    '800': '#283593',
 5076    '900': '#1a237e',
 5077    'A100': '#8c9eff',
 5078    'A200': '#536dfe',
 5079    'A400': '#3d5afe',
 5080    'A700': '#304ffe',
 5081    'contrastDefaultColor': 'light',
 5082    'contrastDarkColors': '50 100 200 A100',
 5083    'contrastStrongLightColors': '300 400 A200 A400'
 5084  },
 5085  'blue': {
 5086    '50': '#e3f2fd',
 5087    '100': '#bbdefb',
 5088    '200': '#90caf9',
 5089    '300': '#64b5f6',
 5090    '400': '#42a5f5',
 5091    '500': '#2196f3',
 5092    '600': '#1e88e5',
 5093    '700': '#1976d2',
 5094    '800': '#1565c0',
 5095    '900': '#0d47a1',
 5096    'A100': '#82b1ff',
 5097    'A200': '#448aff',
 5098    'A400': '#2979ff',
 5099    'A700': '#2962ff',
 5100    'contrastDefaultColor': 'light',
 5101    'contrastDarkColors': '50 100 200 300 400 A100',
 5102    'contrastStrongLightColors': '500 600 700 A200 A400 A700'
 5103  },
 5104  'light-blue': {
 5105    '50': '#e1f5fe',
 5106    '100': '#b3e5fc',
 5107    '200': '#81d4fa',
 5108    '300': '#4fc3f7',
 5109    '400': '#29b6f6',
 5110    '500': '#03a9f4',
 5111    '600': '#039be5',
 5112    '700': '#0288d1',
 5113    '800': '#0277bd',
 5114    '900': '#01579b',
 5115    'A100': '#80d8ff',
 5116    'A200': '#40c4ff',
 5117    'A400': '#00b0ff',
 5118    'A700': '#0091ea',
 5119    'contrastDefaultColor': 'dark',
 5120    'contrastLightColors': '600 700 800 900 A700',
 5121    'contrastStrongLightColors': '600 700 800 A700'
 5122  },
 5123  'cyan': {
 5124    '50': '#e0f7fa',
 5125    '100': '#b2ebf2',
 5126    '200': '#80deea',
 5127    '300': '#4dd0e1',
 5128    '400': '#26c6da',
 5129    '500': '#00bcd4',
 5130    '600': '#00acc1',
 5131    '700': '#0097a7',
 5132    '800': '#00838f',
 5133    '900': '#006064',
 5134    'A100': '#84ffff',
 5135    'A200': '#18ffff',
 5136    'A400': '#00e5ff',
 5137    'A700': '#00b8d4',
 5138    'contrastDefaultColor': 'dark',
 5139    'contrastLightColors': '700 800 900',
 5140    'contrastStrongLightColors': '700 800 900'
 5141  },
 5142  'teal': {
 5143    '50': '#e0f2f1',
 5144    '100': '#b2dfdb',
 5145    '200': '#80cbc4',
 5146    '300': '#4db6ac',
 5147    '400': '#26a69a',
 5148    '500': '#009688',
 5149    '600': '#00897b',
 5150    '700': '#00796b',
 5151    '800': '#00695c',
 5152    '900': '#004d40',
 5153    'A100': '#a7ffeb',
 5154    'A200': '#64ffda',
 5155    'A400': '#1de9b6',
 5156    'A700': '#00bfa5',
 5157    'contrastDefaultColor': 'dark',
 5158    'contrastLightColors': '500 600 700 800 900',
 5159    'contrastStrongLightColors': '500 600 700'
 5160  },
 5161  'green': {
 5162    '50': '#e8f5e9',
 5163    '100': '#c8e6c9',
 5164    '200': '#a5d6a7',
 5165    '300': '#81c784',
 5166    '400': '#66bb6a',
 5167    '500': '#4caf50',
 5168    '600': '#43a047',
 5169    '700': '#388e3c',
 5170    '800': '#2e7d32',
 5171    '900': '#1b5e20',
 5172    'A100': '#b9f6ca',
 5173    'A200': '#69f0ae',
 5174    'A400': '#00e676',
 5175    'A700': '#00c853',
 5176    'contrastDefaultColor': 'dark',
 5177    'contrastLightColors': '500 600 700 800 900',
 5178    'contrastStrongLightColors': '500 600 700'
 5179  },
 5180  'light-green': {
 5181    '50': '#f1f8e9',
 5182    '100': '#dcedc8',
 5183    '200': '#c5e1a5',
 5184    '300': '#aed581',
 5185    '400': '#9ccc65',
 5186    '500': '#8bc34a',
 5187    '600': '#7cb342',
 5188    '700': '#689f38',
 5189    '800': '#558b2f',
 5190    '900': '#33691e',
 5191    'A100': '#ccff90',
 5192    'A200': '#b2ff59',
 5193    'A400': '#76ff03',
 5194    'A700': '#64dd17',
 5195    'contrastDefaultColor': 'dark',
 5196    'contrastLightColors': '700 800 900',
 5197    'contrastStrongLightColors': '700 800 900'
 5198  },
 5199  'lime': {
 5200    '50': '#f9fbe7',
 5201    '100': '#f0f4c3',
 5202    '200': '#e6ee9c',
 5203    '300': '#dce775',
 5204    '400': '#d4e157',
 5205    '500': '#cddc39',
 5206    '600': '#c0ca33',
 5207    '700': '#afb42b',
 5208    '800': '#9e9d24',
 5209    '900': '#827717',
 5210    'A100': '#f4ff81',
 5211    'A200': '#eeff41',
 5212    'A400': '#c6ff00',
 5213    'A700': '#aeea00',
 5214    'contrastDefaultColor': 'dark',
 5215    'contrastLightColors': '900',
 5216    'contrastStrongLightColors': '900'
 5217  },
 5218  'yellow': {
 5219    '50': '#fffde7',
 5220    '100': '#fff9c4',
 5221    '200': '#fff59d',
 5222    '300': '#fff176',
 5223    '400': '#ffee58',
 5224    '500': '#ffeb3b',
 5225    '600': '#fdd835',
 5226    '700': '#fbc02d',
 5227    '800': '#f9a825',
 5228    '900': '#f57f17',
 5229    'A100': '#ffff8d',
 5230    'A200': '#ffff00',
 5231    'A400': '#ffea00',
 5232    'A700': '#ffd600',
 5233    'contrastDefaultColor': 'dark'
 5234  },
 5235  'amber': {
 5236    '50': '#fff8e1',
 5237    '100': '#ffecb3',
 5238    '200': '#ffe082',
 5239    '300': '#ffd54f',
 5240    '400': '#ffca28',
 5241    '500': '#ffc107',
 5242    '600': '#ffb300',
 5243    '700': '#ffa000',
 5244    '800': '#ff8f00',
 5245    '900': '#ff6f00',
 5246    'A100': '#ffe57f',
 5247    'A200': '#ffd740',
 5248    'A400': '#ffc400',
 5249    'A700': '#ffab00',
 5250    'contrastDefaultColor': 'dark'
 5251  },
 5252  'orange': {
 5253    '50': '#fff3e0',
 5254    '100': '#ffe0b2',
 5255    '200': '#ffcc80',
 5256    '300': '#ffb74d',
 5257    '400': '#ffa726',
 5258    '500': '#ff9800',
 5259    '600': '#fb8c00',
 5260    '700': '#f57c00',
 5261    '800': '#ef6c00',
 5262    '900': '#e65100',
 5263    'A100': '#ffd180',
 5264    'A200': '#ffab40',
 5265    'A400': '#ff9100',
 5266    'A700': '#ff6d00',
 5267    'contrastDefaultColor': 'dark',
 5268    'contrastLightColors': '800 900',
 5269    'contrastStrongLightColors': '800 900'
 5270  },
 5271  'deep-orange': {
 5272    '50': '#fbe9e7',
 5273    '100': '#ffccbc',
 5274    '200': '#ffab91',
 5275    '300': '#ff8a65',
 5276    '400': '#ff7043',
 5277    '500': '#ff5722',
 5278    '600': '#f4511e',
 5279    '700': '#e64a19',
 5280    '800': '#d84315',
 5281    '900': '#bf360c',
 5282    'A100': '#ff9e80',
 5283    'A200': '#ff6e40',
 5284    'A400': '#ff3d00',
 5285    'A700': '#dd2c00',
 5286    'contrastDefaultColor': 'light',
 5287    'contrastDarkColors': '50 100 200 300 400 A100 A200',
 5288    'contrastStrongLightColors': '500 600 700 800 900 A400 A700'
 5289  },
 5290  'brown': {
 5291    '50': '#efebe9',
 5292    '100': '#d7ccc8',
 5293    '200': '#bcaaa4',
 5294    '300': '#a1887f',
 5295    '400': '#8d6e63',
 5296    '500': '#795548',
 5297    '600': '#6d4c41',
 5298    '700': '#5d4037',
 5299    '800': '#4e342e',
 5300    '900': '#3e2723',
 5301    'A100': '#d7ccc8',
 5302    'A200': '#bcaaa4',
 5303    'A400': '#8d6e63',
 5304    'A700': '#5d4037',
 5305    'contrastDefaultColor': 'light',
 5306    'contrastDarkColors': '50 100 200 A100 A200',
 5307    'contrastStrongLightColors': '300 400'
 5308  },
 5309  'grey': {
 5310    '50': '#fafafa',
 5311    '100': '#f5f5f5',
 5312    '200': '#eeeeee',
 5313    '300': '#e0e0e0',
 5314    '400': '#bdbdbd',
 5315    '500': '#9e9e9e',
 5316    '600': '#757575',
 5317    '700': '#616161',
 5318    '800': '#424242',
 5319    '900': '#212121',
 5320    'A100': '#ffffff',
 5321    'A200': '#000000',
 5322    'A400': '#303030',
 5323    'A700': '#616161',
 5324    'contrastDefaultColor': 'dark',
 5325    'contrastLightColors': '600 700 800 900 A200 A400 A700'
 5326  },
 5327  'blue-grey': {
 5328    '50': '#eceff1',
 5329    '100': '#cfd8dc',
 5330    '200': '#b0bec5',
 5331    '300': '#90a4ae',
 5332    '400': '#78909c',
 5333    '500': '#607d8b',
 5334    '600': '#546e7a',
 5335    '700': '#455a64',
 5336    '800': '#37474f',
 5337    '900': '#263238',
 5338    'A100': '#cfd8dc',
 5339    'A200': '#b0bec5',
 5340    'A400': '#78909c',
 5341    'A700': '#455a64',
 5342    'contrastDefaultColor': 'light',
 5343    'contrastDarkColors': '50 100 200 300 A100 A200',
 5344    'contrastStrongLightColors': '400 500 700'
 5345  }
 5346});
 5347
 5348})();
 5349(function(){
 5350"use strict";
 5351
 5352(function(angular) {
 5353  'use strict';
 5354/**
 5355 * @ngdoc module
 5356 * @name material.core.theming
 5357 * @description
 5358 * Theming
 5359 */
 5360detectDisabledThemes.$inject = ["$mdThemingProvider"];
 5361ThemingDirective.$inject = ["$mdTheming", "$interpolate", "$log"];
 5362ThemableDirective.$inject = ["$mdTheming"];
 5363ThemingProvider.$inject = ["$mdColorPalette", "$$mdMetaProvider"];
 5364generateAllThemes.$inject = ["$injector", "$mdTheming"];
 5365angular.module('material.core.theming', ['material.core.theming.palette', 'material.core.meta'])
 5366  .directive('mdTheme', ThemingDirective)
 5367  .directive('mdThemable', ThemableDirective)
 5368  .directive('mdThemesDisabled', disableThemesDirective )
 5369  .provider('$mdTheming', ThemingProvider)
 5370  .config( detectDisabledThemes )
 5371  .run(generateAllThemes);
 5372
 5373/**
 5374 * Detect if the HTML or the BODY tags has a [md-themes-disabled] attribute
 5375 * If yes, then immediately disable all theme stylesheet generation and DOM injection
 5376 */
 5377/**
 5378 * @ngInject
 5379 */
 5380function detectDisabledThemes($mdThemingProvider) {
 5381  var isDisabled = !!document.querySelector('[md-themes-disabled]');
 5382  $mdThemingProvider.disableTheming(isDisabled);
 5383}
 5384
 5385/**
 5386 * @ngdoc service
 5387 * @name $mdThemingProvider
 5388 * @module material.core.theming
 5389 *
 5390 * @description Provider to configure the `$mdTheming` service.
 5391 *
 5392 * ### Default Theme
 5393 * The `$mdThemingProvider` uses by default the following theme configuration:
 5394 *
 5395 * - Primary Palette: `Primary`
 5396 * - Accent Palette: `Pink`
 5397 * - Warn Palette: `Deep-Orange`
 5398 * - Background Palette: `Grey`
 5399 *
 5400 * If you don't want to use the `md-theme` directive on the elements itself, you may want to overwrite
 5401 * the default theme.<br/>
 5402 * This can be done by using the following markup.
 5403 *
 5404 * <hljs lang="js">
 5405 *   myAppModule.config(function($mdThemingProvider) {
 5406 *     $mdThemingProvider
 5407 *       .theme('default')
 5408 *       .primaryPalette('blue')
 5409 *       .accentPalette('teal')
 5410 *       .warnPalette('red')
 5411 *       .backgroundPalette('grey');
 5412 *   });
 5413 * </hljs>
 5414 *
 5415
 5416 * ### Dynamic Themes
 5417 *
 5418 * By default, if you change a theme at runtime, the `$mdTheming` service will not detect those changes.<br/>
 5419 * If you have an application, which changes its theme on runtime, you have to enable theme watching.
 5420 *
 5421 * <hljs lang="js">
 5422 *   myAppModule.config(function($mdThemingProvider) {
 5423 *     // Enable theme watching.
 5424 *     $mdThemingProvider.alwaysWatchTheme(true);
 5425 *   });
 5426 * </hljs>
 5427 *
 5428 * ### Custom Theme Styles
 5429 *
 5430 * Sometimes you may want to use your own theme styles for some custom components.<br/>
 5431 * You are able to register your own styles by using the following markup.
 5432 *
 5433 * <hljs lang="js">
 5434 *   myAppModule.config(function($mdThemingProvider) {
 5435 *     // Register our custom stylesheet into the theming provider.
 5436 *     $mdThemingProvider.registerStyles(STYLESHEET);
 5437 *   });
 5438 * </hljs>
 5439 *
 5440 * The `registerStyles` method only accepts strings as value, so you're actually not able to load an external
 5441 * stylesheet file into the `$mdThemingProvider`.
 5442 *
 5443 * If it's necessary to load an external stylesheet, we suggest using a bundler, which supports including raw content,
 5444 * like [raw-loader](https://github.com/webpack/raw-loader) for `webpack`.
 5445 *
 5446 * <hljs lang="js">
 5447 *   myAppModule.config(function($mdThemingProvider) {
 5448 *     // Register your custom stylesheet into the theming provider.
 5449 *     $mdThemingProvider.registerStyles(require('../styles/my-component.theme.css'));
 5450 *   });
 5451 * </hljs>
 5452 *
 5453 * ### Browser color
 5454 *
 5455 * Enables browser header coloring
 5456 * for more info please visit:
 5457 * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
 5458 *
 5459 * Options parameter: <br/>
 5460 * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
 5461 * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
 5462 *             'accent', 'background' and 'warn'. Default is `primary`. <br/>
 5463 * `hue`     - The hue from the selected palette. Default is `800`<br/>
 5464 *
 5465 * <hljs lang="js">
 5466 *   myAppModule.config(function($mdThemingProvider) {
 5467 *     // Enable browser color
 5468 *     $mdThemingProvider.enableBrowserColor({
 5469 *       theme: 'myTheme', // Default is 'default'
 5470 *       palette: 'accent', // Default is 'primary', any basic material palette and extended palettes are available
 5471 *       hue: '200' // Default is '800'
 5472 *     });
 5473 *   });
 5474 * </hljs>
 5475 */
 5476
 5477/**
 5478 * @ngdoc method
 5479 * @name $mdThemingProvider#registerStyles
 5480 * @param {string} styles The styles to be appended to Angular Material's built in theme css.
 5481 */
 5482/**
 5483 * @ngdoc method
 5484 * @name $mdThemingProvider#setNonce
 5485 * @param {string} nonceValue The nonce to be added as an attribute to the theme style tags.
 5486 * Setting a value allows the use of CSP policy without using the unsafe-inline directive.
 5487 */
 5488
 5489/**
 5490 * @ngdoc method
 5491 * @name $mdThemingProvider#setDefaultTheme
 5492 * @param {string} themeName Default theme name to be applied to elements. Default value is `default`.
 5493 */
 5494
 5495/**
 5496 * @ngdoc method
 5497 * @name $mdThemingProvider#alwaysWatchTheme
 5498 * @param {boolean} watch Whether or not to always watch themes for changes and re-apply
 5499 * classes when they change. Default is `false`. Enabling can reduce performance.
 5500 */
 5501
 5502/**
 5503 * @ngdoc method
 5504 * @name $mdThemingProvider#enableBrowserColor
 5505 * @param {Object=} options Options object for the browser color<br/>
 5506 * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
 5507 * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
 5508 *             'accent', 'background' and 'warn'. Default is `primary`. <br/>
 5509 * `hue`     - The hue from the selected palette. Default is `800`<br/>
 5510 * @returns {Function} remove function of the browser color
 5511 */
 5512
 5513/* Some Example Valid Theming Expressions
 5514 * =======================================
 5515 *
 5516 * Intention group expansion: (valid for primary, accent, warn, background)
 5517 *
 5518 * {{primary-100}} - grab shade 100 from the primary palette
 5519 * {{primary-100-0.7}} - grab shade 100, apply opacity of 0.7
 5520 * {{primary-100-contrast}} - grab shade 100's contrast color
 5521 * {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette
 5522 * {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-1
 5523 * {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue
 5524 * {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules
 5525 * {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue
 5526 * {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules
 5527 *
 5528 * Foreground expansion: Applies rgba to black/white foreground text
 5529 *
 5530 * {{foreground-1}} - used for primary text
 5531 * {{foreground-2}} - used for secondary text/divider
 5532 * {{foreground-3}} - used for disabled text
 5533 * {{foreground-4}} - used for dividers
 5534 *
 5535 */
 5536
 5537// In memory generated CSS rules; registered by theme.name
 5538var GENERATED = { };
 5539
 5540// In memory storage of defined themes and color palettes (both loaded by CSS, and user specified)
 5541var PALETTES;
 5542
 5543// Text Colors on light and dark backgrounds
 5544// @see https://www.google.com/design/spec/style/color.html#color-text-background-colors
 5545var DARK_FOREGROUND = {
 5546  name: 'dark',
 5547  '1': 'rgba(0,0,0,0.87)',
 5548  '2': 'rgba(0,0,0,0.54)',
 5549  '3': 'rgba(0,0,0,0.38)',
 5550  '4': 'rgba(0,0,0,0.12)'
 5551};
 5552var LIGHT_FOREGROUND = {
 5553  name: 'light',
 5554  '1': 'rgba(255,255,255,1.0)',
 5555  '2': 'rgba(255,255,255,0.7)',
 5556  '3': 'rgba(255,255,255,0.5)',
 5557  '4': 'rgba(255,255,255,0.12)'
 5558};
 5559
 5560var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)';
 5561var LIGHT_SHADOW = '';
 5562
 5563var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)');
 5564var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87)');
 5565var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)');
 5566
 5567var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background'];
 5568var DEFAULT_COLOR_TYPE = 'primary';
 5569
 5570// A color in a theme will use these hues by default, if not specified by user.
 5571var LIGHT_DEFAULT_HUES = {
 5572  'accent': {
 5573    'default': 'A200',
 5574    'hue-1': 'A100',
 5575    'hue-2': 'A400',
 5576    'hue-3': 'A700'
 5577  },
 5578  'background': {
 5579    'default': '50',
 5580    'hue-1': 'A100',
 5581    'hue-2': '100',
 5582    'hue-3': '300'
 5583  }
 5584};
 5585
 5586var DARK_DEFAULT_HUES = {
 5587  'background': {
 5588    'default': 'A400',
 5589    'hue-1': '800',
 5590    'hue-2': '900',
 5591    'hue-3': 'A200'
 5592  }
 5593};
 5594THEME_COLOR_TYPES.forEach(function(colorType) {
 5595  // Color types with unspecified default hues will use these default hue values
 5596  var defaultDefaultHues = {
 5597    'default': '500',
 5598    'hue-1': '300',
 5599    'hue-2': '800',
 5600    'hue-3': 'A100'
 5601  };
 5602  if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues;
 5603  if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues;
 5604});
 5605
 5606var VALID_HUE_VALUES = [
 5607  '50', '100', '200', '300', '400', '500', '600',
 5608  '700', '800', '900', 'A100', 'A200', 'A400', 'A700'
 5609];
 5610
 5611var themeConfig = {
 5612  disableTheming : false,   // Generate our themes at run time; also disable stylesheet DOM injection
 5613  generateOnDemand : false, // Whether or not themes are to be generated on-demand (vs. eagerly).
 5614  registeredStyles : [],    // Custom styles registered to be used in the theming of custom components.
 5615  nonce : null              // Nonce to be added as an attribute to the generated themes style tags.
 5616};
 5617
 5618/**
 5619 *
 5620 */
 5621function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
 5622  ThemingService.$inject = ["$rootScope", "$log"];
 5623  PALETTES = { };
 5624  var THEMES = { };
 5625
 5626  var themingProvider;
 5627
 5628  var alwaysWatchTheme = false;
 5629  var defaultTheme = 'default';
 5630
 5631  // Load JS Defined Palettes
 5632  angular.extend(PALETTES, $mdColorPalette);
 5633
 5634  // Default theme defined in core.js
 5635
 5636  /**
 5637   * Adds `theme-color` and `msapplication-navbutton-color` meta tags with the color parameter
 5638   * @param {string} color Hex value of the wanted browser color
 5639   * @returns {Function} Remove function of the meta tags
 5640   */
 5641  var setBrowserColor = function (color) {
 5642    // Chrome, Firefox OS and Opera
 5643    var removeChrome = $$mdMetaProvider.setMeta('theme-color', color);
 5644    // Windows Phone
 5645    var removeWindows = $$mdMetaProvider.setMeta('msapplication-navbutton-color', color);
 5646
 5647    return function () {
 5648      removeChrome();
 5649      removeWindows();
 5650    }
 5651  };
 5652
 5653  /**
 5654   * Enables browser header coloring
 5655   * for more info please visit:
 5656   * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
 5657   *
 5658   * The default color is `800` from `primary` palette of the `default` theme
 5659   *
 5660   * options are:
 5661   * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme
 5662   * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
 5663   *             'accent', 'background' and 'warn'. Default is `primary`
 5664   * `hue`     - The hue from the selected palette. Default is `800`
 5665   *
 5666   * @param {Object=} options Options object for the browser color
 5667   * @returns {Function} remove function of the browser color
 5668   */
 5669  var enableBrowserColor = function (options) {
 5670    options = angular.isObject(options) ? options : {};
 5671
 5672    var theme = options.theme || 'default';
 5673    var hue = options.hue || '800';
 5674
 5675    var palette = PALETTES[options.palette] ||
 5676      PALETTES[THEMES[theme].colors[options.palette || 'primary'].name];
 5677
 5678    var color = angular.isObject(palette[hue]) ? palette[hue].hex : palette[hue];
 5679
 5680    return setBrowserColor(color);
 5681  };
 5682
 5683  return themingProvider = {
 5684    definePalette: definePalette,
 5685    extendPalette: extendPalette,
 5686    theme: registerTheme,
 5687
 5688    /**
 5689     * return a read-only clone of the current theme configuration
 5690     */
 5691    configuration : function() {
 5692      return angular.extend( { }, themeConfig, {
 5693        defaultTheme : defaultTheme,
 5694        alwaysWatchTheme : alwaysWatchTheme,
 5695        registeredStyles : [].concat(themeConfig.registeredStyles)
 5696      });
 5697    },
 5698
 5699    /**
 5700     * Easy way to disable theming without having to use
 5701     * `.constant("$MD_THEME_CSS","");` This disables
 5702     * all dynamic theme style sheet generations and injections...
 5703     */
 5704    disableTheming: function(isDisabled) {
 5705      themeConfig.disableTheming = angular.isUndefined(isDisabled) || !!isDisabled;
 5706    },
 5707
 5708    registerStyles: function(styles) {
 5709      themeConfig.registeredStyles.push(styles);
 5710    },
 5711
 5712    setNonce: function(nonceValue) {
 5713      themeConfig.nonce = nonceValue;
 5714    },
 5715
 5716    generateThemesOnDemand: function(onDemand) {
 5717      themeConfig.generateOnDemand = onDemand;
 5718    },
 5719
 5720    setDefaultTheme: function(theme) {
 5721      defaultTheme = theme;
 5722    },
 5723
 5724    alwaysWatchTheme: function(alwaysWatch) {
 5725      alwaysWatchTheme = alwaysWatch;
 5726    },
 5727
 5728    enableBrowserColor: enableBrowserColor,
 5729
 5730    $get: ThemingService,
 5731    _LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,
 5732    _DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,
 5733    _PALETTES: PALETTES,
 5734    _THEMES: THEMES,
 5735    _parseRules: parseRules,
 5736    _rgba: rgba
 5737  };
 5738
 5739  // Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... });
 5740  function definePalette(name, map) {
 5741    map = map || {};
 5742    PALETTES[name] = checkPaletteValid(name, map);
 5743    return themingProvider;
 5744  }
 5745
 5746  // Returns an new object which is a copy of a given palette `name` with variables from
 5747  // `map` overwritten
 5748  // Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' });
 5749  function extendPalette(name, map) {
 5750    return checkPaletteValid(name,  angular.extend({}, PALETTES[name] || {}, map) );
 5751  }
 5752
 5753  // Make sure that palette has all required hues
 5754  function checkPaletteValid(name, map) {
 5755    var missingColors = VALID_HUE_VALUES.filter(function(field) {
 5756      return !map[field];
 5757    });
 5758    if (missingColors.length) {
 5759      throw new Error("Missing colors %1 in palette %2!"
 5760                      .replace('%1', missingColors.join(', '))
 5761                      .replace('%2', name));
 5762    }
 5763
 5764    return map;
 5765  }
 5766
 5767  // Register a theme (which is a collection of color palettes to use with various states
 5768  // ie. warn, accent, primary )
 5769  // Optionally inherit from an existing theme
 5770  // $mdThemingProvider.theme('custom-theme').primaryPalette('red');
 5771  function registerTheme(name, inheritFrom) {
 5772    if (THEMES[name]) return THEMES[name];
 5773
 5774    inheritFrom = inheritFrom || 'default';
 5775
 5776    var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom;
 5777    var theme = new Theme(name);
 5778
 5779    if (parentTheme) {
 5780      angular.forEach(parentTheme.colors, function(color, colorType) {
 5781        theme.colors[colorType] = {
 5782          name: color.name,
 5783          // Make sure a COPY of the hues is given to the child color,
 5784          // not the same reference.
 5785          hues: angular.extend({}, color.hues)
 5786        };
 5787      });
 5788    }
 5789    THEMES[name] = theme;
 5790
 5791    return theme;
 5792  }
 5793
 5794  function Theme(name) {
 5795    var self = this;
 5796    self.name = name;
 5797    self.colors = {};
 5798
 5799    self.dark = setDark;
 5800    setDark(false);
 5801
 5802    function setDark(isDark) {
 5803      isDark = arguments.length === 0 ? true : !!isDark;
 5804
 5805      // If no change, abort
 5806      if (isDark === self.isDark) return;
 5807
 5808      self.isDark = isDark;
 5809
 5810      self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND;
 5811      self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW;
 5812
 5813      // Light and dark themes have different default hues.
 5814      // Go through each existing color type for this theme, and for every
 5815      // hue value that is still the default hue value from the previous light/dark setting,
 5816      // set it to the default hue value from the new light/dark setting.
 5817      var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES;
 5818      var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES;
 5819      angular.forEach(newDefaultHues, function(newDefaults, colorType) {
 5820        var color = self.colors[colorType];
 5821        var oldDefaults = oldDefaultHues[colorType];
 5822        if (color) {
 5823          for (var hueName in color.hues) {
 5824            if (color.hues[hueName] === oldDefaults[hueName]) {
 5825              color.hues[hueName] = newDefaults[hueName];
 5826            }
 5827          }
 5828        }
 5829      });
 5830
 5831      return self;
 5832    }
 5833
 5834    THEME_COLOR_TYPES.forEach(function(colorType) {
 5835      var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType];
 5836      self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) {
 5837        var color = self.colors[colorType] = {
 5838          name: paletteName,
 5839          hues: angular.extend({}, defaultHues, hues)
 5840        };
 5841
 5842        Object.keys(color.hues).forEach(function(name) {
 5843          if (!defaultHues[name]) {
 5844            throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4"
 5845              .replace('%1', name)
 5846              .replace('%2', self.name)
 5847              .replace('%3', paletteName)
 5848              .replace('%4', Object.keys(defaultHues).join(', '))
 5849            );
 5850          }
 5851        });
 5852        Object.keys(color.hues).map(function(key) {
 5853          return color.hues[key];
 5854        }).forEach(function(hueValue) {
 5855          if (VALID_HUE_VALUES.indexOf(hueValue) == -1) {
 5856            throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5"
 5857              .replace('%1', hueValue)
 5858              .replace('%2', self.name)
 5859              .replace('%3', colorType)
 5860              .replace('%4', paletteName)
 5861              .replace('%5', VALID_HUE_VALUES.join(', '))
 5862            );
 5863          }
 5864        });
 5865        return self;
 5866      };
 5867
 5868      self[colorType + 'Color'] = function() {
 5869        var args = Array.prototype.slice.call(arguments);
 5870        console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' +
 5871                     'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.');
 5872        return self[colorType + 'Palette'].apply(self, args);
 5873      };
 5874    });
 5875  }
 5876
 5877  /**
 5878   * @ngdoc service
 5879   * @name $mdTheming
 5880   *
 5881   * @description
 5882   *
 5883   * Service that makes an element apply theming related classes to itself.
 5884   *
 5885   * ```js
 5886   * app.directive('myFancyDirective', function($mdTheming) {
 5887   *   return {
 5888   *     restrict: 'e',
 5889   *     link: function(scope, el, attrs) {
 5890   *       $mdTheming(el);
 5891   *     }
 5892   *   };
 5893   * });
 5894   * ```
 5895   * @param {el=} element to apply theming to
 5896   */
 5897  /* @ngInject */
 5898  function ThemingService($rootScope, $log) {
 5899        // Allow us to be invoked via a linking function signature.
 5900    var applyTheme = function (scope, el) {
 5901          if (el === undefined) { el = scope; scope = undefined; }
 5902          if (scope === undefined) { scope = $rootScope; }
 5903          applyTheme.inherit(el, el);
 5904        };
 5905
 5906    applyTheme.THEMES = angular.extend({}, THEMES);
 5907    applyTheme.PALETTES = angular.extend({}, PALETTES);
 5908    applyTheme.inherit = inheritTheme;
 5909    applyTheme.registered = registered;
 5910    applyTheme.defaultTheme = function() { return defaultTheme; };
 5911    applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };
 5912    applyTheme.setBrowserColor = enableBrowserColor;
 5913
 5914    return applyTheme;
 5915
 5916    /**
 5917     * Determine is specified theme name is a valid, registered theme
 5918     */
 5919    function registered(themeName) {
 5920      if (themeName === undefined || themeName === '') return true;
 5921      return applyTheme.THEMES[themeName] !== undefined;
 5922    }
 5923
 5924    /**
 5925     * Get theme name for the element, then update with Theme CSS class
 5926     */
 5927    function inheritTheme (el, parent) {
 5928      var ctrl = parent.controller('mdTheme');
 5929      var attrThemeValue = el.attr('md-theme-watch');
 5930      var watchTheme = (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false';
 5931
 5932      updateThemeClass(lookupThemeName());
 5933
 5934      if ((alwaysWatchTheme && !registerChangeCallback()) || (!alwaysWatchTheme && watchTheme)) {
 5935        el.on('$destroy', $rootScope.$watch(lookupThemeName, updateThemeClass) );
 5936      }
 5937
 5938      /**
 5939       * Find the theme name from the parent controller or element data
 5940       */
 5941      function lookupThemeName() {
 5942        // As a few components (dialog) add their controllers later, we should also watch for a controller init.
 5943        ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');
 5944        return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme);
 5945      }
 5946
 5947      /**
 5948       * Remove old theme class and apply a new one
 5949       * NOTE: if not a valid theme name, then the current name is not changed
 5950       */
 5951      function updateThemeClass(theme) {
 5952        if (!theme) return;
 5953        if (!registered(theme)) {
 5954          $log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' +
 5955                    'Register it with $mdThemingProvider.theme().');
 5956        }
 5957
 5958        var oldTheme = el.data('$mdThemeName');
 5959        if (oldTheme) el.removeClass('md-' + oldTheme +'-theme');
 5960        el.addClass('md-' + theme + '-theme');
 5961        el.data('$mdThemeName', theme);
 5962        if (ctrl) {
 5963          el.data('$mdThemeController', ctrl);
 5964        }
 5965      }
 5966
 5967      /**
 5968       * Register change callback with parent mdTheme controller
 5969       */
 5970      function registerChangeCallback() {
 5971        var parentController = parent.controller('mdTheme');
 5972        if (!parentController) return false;
 5973        el.on('$destroy', parentController.registerChanges( function() {
 5974          updateThemeClass(lookupThemeName());
 5975        }));
 5976        return true;
 5977      }
 5978    }
 5979
 5980  }
 5981}
 5982
 5983function ThemingDirective($mdTheming, $interpolate, $log) {
 5984  return {
 5985    priority: 100,
 5986    link: {
 5987      pre: function(scope, el, attrs) {
 5988        var registeredCallbacks = [];
 5989        var ctrl = {
 5990          registerChanges: function (cb, context) {
 5991            if (context) {
 5992              cb = angular.bind(context, cb);
 5993            }
 5994
 5995            registeredCallbacks.push(cb);
 5996
 5997            return function () {
 5998              var index = registeredCallbacks.indexOf(cb);
 5999
 6000              if (index > -1) {
 6001                registeredCallbacks.splice(index, 1);
 6002              }
 6003            };
 6004          },
 6005          $setTheme: function (theme) {
 6006            if (!$mdTheming.registered(theme)) {
 6007              $log.warn('attempted to use unregistered theme \'' + theme + '\'');
 6008            }
 6009            ctrl.$mdTheme = theme;
 6010
 6011            registeredCallbacks.forEach(function (cb) {
 6012              cb();
 6013            });
 6014          }
 6015        };
 6016        el.data('$mdThemeController', ctrl);
 6017        ctrl.$setTheme($interpolate(attrs.mdTheme)(scope));
 6018        attrs.$observe('mdTheme', ctrl.$setTheme);
 6019      }
 6020    }
 6021  };
 6022}
 6023
 6024/**
 6025 * Special directive that will disable ALL runtime Theme style generation and DOM injection
 6026 *
 6027 * <link rel="stylesheet" href="angular-material.min.css">
 6028 * <link rel="stylesheet" href="angular-material.themes.css">
 6029 *
 6030 * <body md-themes-disabled>
 6031 *  ...
 6032 * </body>
 6033 *
 6034 * Note: Using md-themes-css directive requires the developer to load external
 6035 * theme stylesheets; e.g. custom themes from Material-Tools:
 6036 *
 6037 *       `angular-material.themes.css`
 6038 *
 6039 * Another option is to use the ThemingProvider to configure and disable the attribute
 6040 * conversions; this would obviate the use of the `md-themes-css` directive
 6041 *
 6042 */
 6043function disableThemesDirective() {
 6044  themeConfig.disableTheming = true;
 6045
 6046  // Return a 1x-only, first-match attribute directive
 6047  return {
 6048    restrict : 'A',
 6049    priority : '900'
 6050  };
 6051}
 6052
 6053function ThemableDirective($mdTheming) {
 6054  return $mdTheming;
 6055}
 6056
 6057function parseRules(theme, colorType, rules) {
 6058  checkValidPalette(theme, colorType);
 6059
 6060  rules = rules.replace(/THEME_NAME/g, theme.name);
 6061  var generatedRules = [];
 6062  var color = theme.colors[colorType];
 6063
 6064  var themeNameRegex = new RegExp('\\.md-' + theme.name + '-theme', 'g');
 6065  // Matches '{{ primary-color }}', etc
 6066  var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g');
 6067  var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow|default)-?(\d\.?\d*)?(contrast)?\s*\}\}'?"?/g;
 6068  var palette = PALETTES[color.name];
 6069
 6070  // find and replace simple variables where we use a specific hue, not an entire palette
 6071  // eg. "{{primary-100}}"
 6072  //\(' + THEME_COLOR_TYPES.join('\|') + '\)'
 6073  rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity, contrast) {
 6074    if (colorType === 'foreground') {
 6075      if (hue == 'shadow') {
 6076        return theme.foregroundShadow;
 6077      } else {
 6078        return theme.foregroundPalette[hue] || theme.foregroundPalette['1'];
 6079      }
 6080    }
 6081
 6082    // `default` is also accepted as a hue-value, because the background palettes are
 6083    // using it as a name for the default hue.
 6084    if (hue.indexOf('hue') === 0 || hue === 'default') {
 6085      hue = theme.colors[colorType].hues[hue];
 6086    }
 6087
 6088    return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '')[contrast ? 'contrast' : 'value'], opacity );
 6089  });
 6090
 6091  // For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3)
 6092  angular.forEach(color.hues, function(hueValue, hueName) {
 6093    var newRule = rules
 6094      .replace(hueRegex, function(match, _, colorType, hueType, opacity) {
 6095        return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity);
 6096      });
 6097    if (hueName !== 'default') {
 6098      newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName);
 6099    }
 6100
 6101    // Don't apply a selector rule to the default theme, making it easier to override
 6102    // styles of the base-component
 6103    if (theme.name == 'default') {
 6104      var themeRuleRegex = /((?:(?:(?: |>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)+) )?)((?:(?:\w|\.|-)+)?)\.md-default-theme((?: |>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)/g;
 6105      newRule = newRule.replace(themeRuleRegex, function(match, prefix, target, suffix) {
 6106        return match + ', ' + prefix + target + suffix;
 6107      });
 6108    }
 6109    generatedRules.push(newRule);
 6110  });
 6111
 6112  return generatedRules;
 6113}
 6114
 6115var rulesByType = {};
 6116
 6117// Generate our themes at run time given the state of THEMES and PALETTES
 6118function generateAllThemes($injector, $mdTheming) {
 6119  var head = document.head;
 6120  var firstChild = head ? head.firstElementChild : null;
 6121  var themeCss = !themeConfig.disableTheming && $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : '';
 6122
 6123  // Append our custom registered styles to the theme stylesheet.
 6124  themeCss += themeConfig.registeredStyles.join('');
 6125
 6126  if ( !firstChild ) return;
 6127  if (themeCss.length === 0) return; // no rules, so no point in running this expensive task
 6128
 6129  // Expose contrast colors for palettes to ensure that text is always readable
 6130  angular.forEach(PALETTES, sanitizePalette);
 6131
 6132  // MD_THEME_CSS is a string generated by the build process that includes all the themable
 6133  // components as templates
 6134
 6135  // Break the CSS into individual rules
 6136  var rules = themeCss
 6137                  .split(/\}(?!(\}|'|"|;))/)
 6138                  .filter(function(rule) { return rule && rule.trim().length; })
 6139                  .map(function(rule) { return rule.trim() + '}'; });
 6140
 6141
 6142  var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g');
 6143
 6144  THEME_COLOR_TYPES.forEach(function(type) {
 6145    rulesByType[type] = '';
 6146  });
 6147
 6148
 6149  // Sort the rules based on type, allowing us to do color substitution on a per-type basis
 6150  rules.forEach(function(rule) {
 6151    var match = rule.match(ruleMatchRegex);
 6152    // First: test that if the rule has '.md-accent', it goes into the accent set of rules
 6153    for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) {
 6154      if (rule.indexOf('.md-' + type) > -1) {
 6155        return rulesByType[type] += rule;
 6156      }
 6157    }
 6158
 6159    // If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from
 6160    // there
 6161    for (i = 0; type = THEME_COLOR_TYPES[i]; i++) {
 6162      if (rule.indexOf(type) > -1) {
 6163        return rulesByType[type] += rule;
 6164      }
 6165    }
 6166
 6167    // Default to the primary array
 6168    return rulesByType[DEFAULT_COLOR_TYPE] += rule;
 6169  });
 6170
 6171  // If themes are being generated on-demand, quit here. The user will later manually
 6172  // call generateTheme to do this on a theme-by-theme basis.
 6173  if (themeConfig.generateOnDemand) return;
 6174
 6175  angular.forEach($mdTheming.THEMES, function(theme) {
 6176    if (!GENERATED[theme.name] && !($mdTheming.defaultTheme() !== 'default' && theme.name === 'default')) {
 6177      generateTheme(theme, theme.name, themeConfig.nonce);
 6178    }
 6179  });
 6180
 6181
 6182  // *************************
 6183  // Internal functions
 6184  // *************************
 6185
 6186  // The user specifies a 'default' contrast color as either light or dark,
 6187  // then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light)
 6188  function sanitizePalette(palette, name) {
 6189    var defaultContrast = palette.contrastDefaultColor;
 6190    var lightColors = palette.contrastLightColors || [];
 6191    var strongLightColors = palette.contrastStrongLightColors || [];
 6192    var darkColors = palette.contrastDarkColors || [];
 6193
 6194    // These colors are provided as space-separated lists
 6195    if (typeof lightColors === 'string') lightColors = lightColors.split(' ');
 6196    if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' ');
 6197    if (typeof darkColors === 'string') darkColors = darkColors.split(' ');
 6198
 6199    // Cleanup after ourselves
 6200    delete palette.contrastDefaultColor;
 6201    delete palette.contrastLightColors;
 6202    delete palette.contrastStrongLightColors;
 6203    delete palette.contrastDarkColors;
 6204
 6205    // Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR }
 6206    angular.forEach(palette, function(hueValue, hueName) {
 6207      if (angular.isObject(hueValue)) return; // Already converted
 6208      // Map everything to rgb colors
 6209      var rgbValue = colorToRgbaArray(hueValue);
 6210      if (!rgbValue) {
 6211        throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected."
 6212                        .replace('%1', hueValue)
 6213                        .replace('%2', palette.name)
 6214                        .replace('%3', hueName));
 6215      }
 6216
 6217      palette[hueName] = {
 6218        hex: palette[hueName],
 6219        value: rgbValue,
 6220        contrast: getContrastColor()
 6221      };
 6222      function getContrastColor() {
 6223        if (defaultContrast === 'light') {
 6224          if (darkColors.indexOf(hueName) > -1) {
 6225            return DARK_CONTRAST_COLOR;
 6226          } else {
 6227            return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
 6228              : LIGHT_CONTRAST_COLOR;
 6229          }
 6230        } else {
 6231          if (lightColors.indexOf(hueName) > -1) {
 6232            return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
 6233              : LIGHT_CONTRAST_COLOR;
 6234          } else {
 6235            return DARK_CONTRAST_COLOR;
 6236          }
 6237        }
 6238      }
 6239    });
 6240  }
 6241}
 6242
 6243function generateTheme(theme, name, nonce) {
 6244  var head = document.head;
 6245  var firstChild = head ? head.firstElementChild : null;
 6246
 6247  if (!GENERATED[name]) {
 6248    // For each theme, use the color palettes specified for
 6249    // `primary`, `warn` and `accent` to generate CSS rules.
 6250    THEME_COLOR_TYPES.forEach(function(colorType) {
 6251      var styleStrings = parseRules(theme, colorType, rulesByType[colorType]);
 6252      while (styleStrings.length) {
 6253        var styleContent = styleStrings.shift();
 6254        if (styleContent) {
 6255          var style = document.createElement('style');
 6256          style.setAttribute('md-theme-style', '');
 6257          if (nonce) {
 6258            style.setAttribute('nonce', nonce);
 6259          }
 6260          style.appendChild(document.createTextNode(styleContent));
 6261          head.insertBefore(style, firstChild);
 6262        }
 6263      }
 6264    });
 6265
 6266    GENERATED[theme.name] = true;
 6267  }
 6268
 6269}
 6270
 6271
 6272function checkValidPalette(theme, colorType) {
 6273  // If theme attempts to use a palette that doesnt exist, throw error
 6274  if (!PALETTES[ (theme.colors[colorType] || {}).name ]) {
 6275    throw new Error(
 6276      "You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3"
 6277                    .replace('%1', theme.name)
 6278                    .replace('%2', colorType)
 6279                    .replace('%3', Object.keys(PALETTES).join(', '))
 6280    );
 6281  }
 6282}
 6283
 6284function colorToRgbaArray(clr) {
 6285  if (angular.isArray(clr) && clr.length == 3) return clr;
 6286  if (/^rgb/.test(clr)) {
 6287    return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) {
 6288      return i == 3 ? parseFloat(value, 10) : parseInt(value, 10);
 6289    });
 6290  }
 6291  if (clr.charAt(0) == '#') clr = clr.substring(1);
 6292  if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return;
 6293
 6294  var dig = clr.length / 3;
 6295  var red = clr.substr(0, dig);
 6296  var grn = clr.substr(dig, dig);
 6297  var blu = clr.substr(dig * 2);
 6298  if (dig === 1) {
 6299    red += red;
 6300    grn += grn;
 6301    blu += blu;
 6302  }
 6303  return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)];
 6304}
 6305
 6306function rgba(rgbArray, opacity) {
 6307  if ( !rgbArray ) return "rgb('0,0,0')";
 6308
 6309  if (rgbArray.length == 4) {
 6310    rgbArray = angular.copy(rgbArray);
 6311    opacity ? rgbArray.pop() : opacity = rgbArray.pop();
 6312  }
 6313  return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ?
 6314    'rgba(' + rgbArray.join(',') + ',' + opacity + ')' :
 6315    'rgb(' + rgbArray.join(',') + ')';
 6316}
 6317
 6318
 6319})(window.angular);
 6320
 6321})();
 6322(function(){
 6323"use strict";
 6324
 6325// Polyfill angular < 1.4 (provide $animateCss)
 6326angular
 6327  .module('material.core')
 6328  .factory('$$mdAnimate', ["$q", "$timeout", "$mdConstant", "$animateCss", function($q, $timeout, $mdConstant, $animateCss){
 6329
 6330     // Since $$mdAnimate is injected into $mdUtil... use a wrapper function
 6331     // to subsequently inject $mdUtil as an argument to the AnimateDomUtils
 6332
 6333     return function($mdUtil) {
 6334       return AnimateDomUtils( $mdUtil, $q, $timeout, $mdConstant, $animateCss);
 6335     };
 6336   }]);
 6337
 6338/**
 6339 * Factory function that requires special injections
 6340 */
 6341function AnimateDomUtils($mdUtil, $q, $timeout, $mdConstant, $animateCss) {
 6342  var self;
 6343  return self = {
 6344    /**
 6345     *
 6346     */
 6347    translate3d : function( target, from, to, options ) {
 6348      return $animateCss(target,{
 6349        from:from,
 6350        to:to,
 6351        addClass:options.transitionInClass,
 6352        removeClass:options.transitionOutClass
 6353      })
 6354      .start()
 6355      .then(function(){
 6356          // Resolve with reverser function...
 6357          return reverseTranslate;
 6358      });
 6359
 6360      /**
 6361       * Specific reversal of the request translate animation above...
 6362       */
 6363      function reverseTranslate (newFrom) {
 6364        return $animateCss(target, {
 6365           to: newFrom || from,
 6366           addClass: options.transitionOutClass,
 6367           removeClass: options.transitionInClass
 6368        }).start();
 6369
 6370      }
 6371    },
 6372
 6373    /**
 6374     * Listen for transitionEnd event (with optional timeout)
 6375     * Announce completion or failure via promise handlers
 6376     */
 6377    waitTransitionEnd: function (element, opts) {
 6378      var TIMEOUT = 3000; // fallback is 3 secs
 6379
 6380      return $q(function(resolve, reject){
 6381        opts = opts || { };
 6382
 6383        // If there is no transition is found, resolve immediately
 6384        //
 6385        // NOTE: using $mdUtil.nextTick() causes delays/issues
 6386        if (noTransitionFound(opts.cachedTransitionStyles)) {
 6387          TIMEOUT = 0;
 6388        }
 6389
 6390        var timer = $timeout(finished, opts.timeout || TIMEOUT);
 6391        element.on($mdConstant.CSS.TRANSITIONEND, finished);
 6392
 6393        /**
 6394         * Upon timeout or transitionEnd, reject or resolve (respectively) this promise.
 6395         * NOTE: Make sure this transitionEnd didn't bubble up from a child
 6396         */
 6397        function finished(ev) {
 6398          if ( ev && ev.target !== element[0]) return;
 6399
 6400          if ( ev  ) $timeout.cancel(timer);
 6401          element.off($mdConstant.CSS.TRANSITIONEND, finished);
 6402
 6403          // Never reject since ngAnimate may cause timeouts due missed transitionEnd events
 6404          resolve();
 6405
 6406        }
 6407
 6408        /**
 6409         * Checks whether or not there is a transition.
 6410         *
 6411         * @param styles The cached styles to use for the calculation. If null, getComputedStyle()
 6412         * will be used.
 6413         *
 6414         * @returns {boolean} True if there is no transition/duration; false otherwise.
 6415         */
 6416        function noTransitionFound(styles) {
 6417          styles = styles || window.getComputedStyle(element[0]);
 6418
 6419          return styles.transitionDuration == '0s' || (!styles.transition && !styles.transitionProperty);
 6420        }
 6421
 6422      });
 6423    },
 6424
 6425    calculateTransformValues: function (element, originator) {
 6426      var origin = originator.element;
 6427      var bounds = originator.bounds;
 6428
 6429      if (origin || bounds) {
 6430        var originBnds = origin ? self.clientRect(origin) || currentBounds() : self.copyRect(bounds);
 6431        var dialogRect = self.copyRect(element[0].getBoundingClientRect());
 6432        var dialogCenterPt = self.centerPointFor(dialogRect);
 6433        var originCenterPt = self.centerPointFor(originBnds);
 6434
 6435        return {
 6436          centerX: originCenterPt.x - dialogCenterPt.x,
 6437          centerY: originCenterPt.y - dialogCenterPt.y,
 6438          scaleX: Math.round(100 * Math.min(0.5, originBnds.width / dialogRect.width)) / 100,
 6439          scaleY: Math.round(100 * Math.min(0.5, originBnds.height / dialogRect.height)) / 100
 6440        };
 6441      }
 6442      return {centerX: 0, centerY: 0, scaleX: 0.5, scaleY: 0.5};
 6443
 6444      /**
 6445       * This is a fallback if the origin information is no longer valid, then the
 6446       * origin bounds simply becomes the current bounds for the dialogContainer's parent
 6447       */
 6448      function currentBounds() {
 6449        var cntr = element ? element.parent() : null;
 6450        var parent = cntr ? cntr.parent() : null;
 6451
 6452        return parent ? self.clientRect(parent) : null;
 6453      }
 6454    },
 6455
 6456    /**
 6457     * Calculate the zoom transform from dialog to origin.
 6458     *
 6459     * We use this to set the dialog position immediately;
 6460     * then the md-transition-in actually translates back to
 6461     * `translate3d(0,0,0) scale(1.0)`...
 6462     *
 6463     * NOTE: all values are rounded to the nearest integer
 6464     */
 6465    calculateZoomToOrigin: function (element, originator) {
 6466      var zoomTemplate = "translate3d( {centerX}px, {centerY}px, 0 ) scale( {scaleX}, {scaleY} )";
 6467      var buildZoom = angular.bind(null, $mdUtil.supplant, zoomTemplate);
 6468
 6469      return buildZoom(self.calculateTransformValues(element, originator));
 6470    },
 6471
 6472    /**
 6473     * Calculate the slide transform from panel to origin.
 6474     * NOTE: all values are rounded to the nearest integer
 6475     */
 6476    calculateSlideToOrigin: function (element, originator) {
 6477      var slideTemplate = "translate3d( {centerX}px, {centerY}px, 0 )";
 6478      var buildSlide = angular.bind(null, $mdUtil.supplant, slideTemplate);
 6479
 6480      return buildSlide(self.calculateTransformValues(element, originator));
 6481    },
 6482
 6483    /**
 6484     * Enhance raw values to represent valid css stylings...
 6485     */
 6486    toCss : function( raw ) {
 6487      var css = { };
 6488      var lookups = 'left top right bottom width height x y min-width min-height max-width max-height';
 6489
 6490      angular.forEach(raw, function(value,key) {
 6491        if ( angular.isUndefined(value) ) return;
 6492
 6493        if ( lookups.indexOf(key) >= 0 ) {
 6494          css[key] = value + 'px';
 6495        } else {
 6496          switch (key) {
 6497            case 'transition':
 6498              convertToVendor(key, $mdConstant.CSS.TRANSITION, value);
 6499              break;
 6500            case 'transform':
 6501              convertToVendor(key, $mdConstant.CSS.TRANSFORM, value);
 6502              break;
 6503            case 'transformOrigin':
 6504              convertToVendor(key, $mdConstant.CSS.TRANSFORM_ORIGIN, value);
 6505              break;
 6506            case 'font-size':
 6507              css['font-size'] = value; // font sizes aren't always in px
 6508              break;
 6509          }
 6510        }
 6511      });
 6512
 6513      return css;
 6514
 6515      function convertToVendor(key, vendor, value) {
 6516        angular.forEach(vendor.split(' '), function (key) {
 6517          css[key] = value;
 6518        });
 6519      }
 6520    },
 6521
 6522    /**
 6523     * Convert the translate CSS value to key/value pair(s).
 6524     */
 6525    toTransformCss: function (transform, addTransition, transition) {
 6526      var css = {};
 6527      angular.forEach($mdConstant.CSS.TRANSFORM.split(' '), function (key) {
 6528        css[key] = transform;
 6529      });
 6530
 6531      if (addTransition) {
 6532        transition = transition || "all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) !important";
 6533        css.transition = transition;
 6534      }
 6535
 6536      return css;
 6537    },
 6538
 6539    /**
 6540     *  Clone the Rect and calculate the height/width if needed
 6541     */
 6542    copyRect: function (source, destination) {
 6543      if (!source) return null;
 6544
 6545      destination = destination || {};
 6546
 6547      angular.forEach('left top right bottom width height'.split(' '), function (key) {
 6548        destination[key] = Math.round(source[key]);
 6549      });
 6550
 6551      destination.width = destination.width || (destination.right - destination.left);
 6552      destination.height = destination.height || (destination.bottom - destination.top);
 6553
 6554      return destination;
 6555    },
 6556
 6557    /**
 6558     * Calculate ClientRect of element; return null if hidden or zero size
 6559     */
 6560    clientRect: function (element) {
 6561      var bounds = angular.element(element)[0].getBoundingClientRect();
 6562      var isPositiveSizeClientRect = function (rect) {
 6563        return rect && (rect.width > 0) && (rect.height > 0);
 6564      };
 6565
 6566      // If the event origin element has zero size, it has probably been hidden.
 6567      return isPositiveSizeClientRect(bounds) ? self.copyRect(bounds) : null;
 6568    },
 6569
 6570    /**
 6571     *  Calculate 'rounded' center point of Rect
 6572     */
 6573    centerPointFor: function (targetRect) {
 6574      return targetRect ? {
 6575        x: Math.round(targetRect.left + (targetRect.width / 2)),
 6576        y: Math.round(targetRect.top + (targetRect.height / 2))
 6577      } : { x : 0, y : 0 };
 6578    }
 6579
 6580  };
 6581}
 6582
 6583
 6584})();
 6585(function(){
 6586"use strict";
 6587
 6588"use strict";
 6589
 6590if (angular.version.minor >= 4) {
 6591  angular.module('material.core.animate', []);
 6592} else {
 6593(function() {
 6594
 6595  var forEach = angular.forEach;
 6596
 6597  var WEBKIT = angular.isDefined(document.documentElement.style.WebkitAppearance);
 6598  var TRANSITION_PROP = WEBKIT ? 'WebkitTransition' : 'transition';
 6599  var ANIMATION_PROP = WEBKIT ? 'WebkitAnimation' : 'animation';
 6600  var PREFIX = WEBKIT ? '-webkit-' : '';
 6601
 6602  var TRANSITION_EVENTS = (WEBKIT ? 'webkitTransitionEnd ' : '') + 'transitionend';
 6603  var ANIMATION_EVENTS = (WEBKIT ? 'webkitAnimationEnd ' : '') + 'animationend';
 6604
 6605  var $$ForceReflowFactory = ['$document', function($document) {
 6606    return function() {
 6607      return $document[0].body.clientWidth + 1;
 6608    }
 6609  }];
 6610
 6611  var $$rAFMutexFactory = ['$$rAF', function($$rAF) {
 6612    return function() {
 6613      var passed = false;
 6614      $$rAF(function() {
 6615        passed = true;
 6616      });
 6617      return function(fn) {
 6618        passed ? fn() : $$rAF(fn);
 6619      };
 6620    };
 6621  }];
 6622
 6623  var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
 6624    var INITIAL_STATE = 0;
 6625    var DONE_PENDING_STATE = 1;
 6626    var DONE_COMPLETE_STATE = 2;
 6627
 6628    function AnimateRunner(host) {
 6629      this.setHost(host);
 6630
 6631      this._doneCallbacks = [];
 6632      this._runInAnimationFrame = $$rAFMutex();
 6633      this._state = 0;
 6634    }
 6635
 6636    AnimateRunner.prototype = {
 6637      setHost: function(host) {
 6638        this.host = host || {};
 6639      },
 6640
 6641      done: function(fn) {
 6642        if (this._state === DONE_COMPLETE_STATE) {
 6643          fn();
 6644        } else {
 6645          this._doneCallbacks.push(fn);
 6646        }
 6647      },
 6648
 6649      progress: angular.noop,
 6650
 6651      getPromise: function() {
 6652        if (!this.promise) {
 6653          var self = this;
 6654          this.promise = $q(function(resolve, reject) {
 6655            self.done(function(status) {
 6656              status === false ? reject() : resolve();
 6657            });
 6658          });
 6659        }
 6660        return this.promise;
 6661      },
 6662
 6663      then: function(resolveHandler, rejectHandler) {
 6664        return this.getPromise().then(resolveHandler, rejectHandler);
 6665      },
 6666
 6667      'catch': function(handler) {
 6668        return this.getPromise()['catch'](handler);
 6669      },
 6670
 6671      'finally': function(handler) {
 6672        return this.getPromise()['finally'](handler);
 6673      },
 6674
 6675      pause: function() {
 6676        if (this.host.pause) {
 6677          this.host.pause();
 6678        }
 6679      },
 6680
 6681      resume: function() {
 6682        if (this.host.resume) {
 6683          this.host.resume();
 6684        }
 6685      },
 6686
 6687      end: function() {
 6688        if (this.host.end) {
 6689          this.host.end();
 6690        }
 6691        this._resolve(true);
 6692      },
 6693
 6694      cancel: function() {
 6695        if (this.host.cancel) {
 6696          this.host.cancel();
 6697        }
 6698        this._resolve(false);
 6699      },
 6700
 6701      complete: function(response) {
 6702        var self = this;
 6703        if (self._state === INITIAL_STATE) {
 6704          self._state = DONE_PENDING_STATE;
 6705          self._runInAnimationFrame(function() {
 6706            self._resolve(response);
 6707          });
 6708        }
 6709      },
 6710
 6711      _resolve: function(response) {
 6712        if (this._state !== DONE_COMPLETE_STATE) {
 6713          forEach(this._doneCallbacks, function(fn) {
 6714            fn(response);
 6715          });
 6716          this._doneCallbacks.length = 0;
 6717          this._state = DONE_COMPLETE_STATE;
 6718        }
 6719      }
 6720    };
 6721
 6722    // Polyfill AnimateRunner.all which is used by input animations
 6723    AnimateRunner.all = function(runners, callback) {
 6724      var count = 0;
 6725      var status = true;
 6726      forEach(runners, function(runner) {
 6727        runner.done(onProgress);
 6728      });
 6729
 6730      function onProgress(response) {
 6731        status = status && response;
 6732        if (++count === runners.length) {
 6733          callback(status);
 6734        }
 6735      }
 6736    };
 6737
 6738    return AnimateRunner;
 6739  }];
 6740
 6741  angular
 6742    .module('material.core.animate', [])
 6743    .factory('$$forceReflow', $$ForceReflowFactory)
 6744    .factory('$$AnimateRunner', $$AnimateRunnerFactory)
 6745    .factory('$$rAFMutex', $$rAFMutexFactory)
 6746    .factory('$animateCss', ['$window', '$$rAF', '$$AnimateRunner', '$$forceReflow', '$$jqLite', '$timeout', '$animate',
 6747                     function($window,   $$rAF,   $$AnimateRunner,   $$forceReflow,   $$jqLite,   $timeout, $animate) {
 6748
 6749      function init(element, options) {
 6750
 6751        var temporaryStyles = [];
 6752        var node = getDomNode(element);
 6753        var areAnimationsAllowed = node && $animate.enabled();
 6754
 6755        var hasCompleteStyles = false;
 6756        var hasCompleteClasses = false;
 6757
 6758        if (areAnimationsAllowed) {
 6759          if (options.transitionStyle) {
 6760            temporaryStyles.push([PREFIX + 'transition', options.transitionStyle]);
 6761          }
 6762
 6763          if (options.keyframeStyle) {
 6764            temporaryStyles.push([PREFIX + 'animation', options.keyframeStyle]);
 6765          }
 6766
 6767          if (options.delay) {
 6768            temporaryStyles.push([PREFIX + 'transition-delay', options.delay + 's']);
 6769          }
 6770
 6771          if (options.duration) {
 6772            temporaryStyles.push([PREFIX + 'transition-duration', options.duration + 's']);
 6773          }
 6774
 6775          hasCompleteStyles = options.keyframeStyle ||
 6776              (options.to && (options.duration > 0 || options.transitionStyle));
 6777          hasCompleteClasses = !!options.addClass || !!options.removeClass;
 6778
 6779          blockTransition(element, true);
 6780        }
 6781
 6782        var hasCompleteAnimation = areAnimationsAllowed && (hasCompleteStyles || hasCompleteClasses);
 6783
 6784        applyAnimationFromStyles(element, options);
 6785
 6786        var animationClosed = false;
 6787        var events, eventFn;
 6788
 6789        return {
 6790          close: $window.close,
 6791          start: function() {
 6792            var runner = new $$AnimateRunner();
 6793            waitUntilQuiet(function() {
 6794              blockTransition(element, false);
 6795              if (!hasCompleteAnimation) {
 6796                return close();
 6797              }
 6798
 6799              forEach(temporaryStyles, function(entry) {
 6800                var key = entry[0];
 6801                var value = entry[1];
 6802                node.style[camelCase(key)] = value;
 6803              });
 6804
 6805              applyClasses(element, options);
 6806
 6807              var timings = computeTimings(element);
 6808              if (timings.duration === 0) {
 6809                return close();
 6810              }
 6811
 6812              var moreStyles = [];
 6813
 6814              if (options.easing) {
 6815                if (timings.transitionDuration) {
 6816                  moreStyles.push([PREFIX + 'transition-timing-function', options.easing]);
 6817                }
 6818                if (timings.animationDuration) {
 6819                  moreStyles.push([PREFIX + 'animation-timing-function', options.easing]);
 6820                }
 6821              }
 6822
 6823              if (options.delay && timings.animationDelay) {
 6824                moreStyles.push([PREFIX + 'animation-delay', options.delay + 's']);
 6825              }
 6826
 6827              if (options.duration && timings.animationDuration) {
 6828                moreStyles.push([PREFIX + 'animation-duration', options.duration + 's']);
 6829              }
 6830
 6831              forEach(moreStyles, function(entry) {
 6832                var key = entry[0];
 6833                var value = entry[1];
 6834                node.style[camelCase(key)] = value;
 6835                temporaryStyles.push(entry);
 6836              });
 6837
 6838              var maxDelay = timings.delay;
 6839              var maxDelayTime = maxDelay * 1000;
 6840              var maxDuration = timings.duration;
 6841              var maxDurationTime = maxDuration * 1000;
 6842              var startTime = Date.now();
 6843
 6844              events = [];
 6845              if (timings.transitionDuration) {
 6846                events.push(TRANSITION_EVENTS);
 6847              }
 6848              if (timings.animationDuration) {
 6849                events.push(ANIMATION_EVENTS);
 6850              }
 6851              events = events.join(' ');
 6852              eventFn = function(event) {
 6853                event.stopPropagation();
 6854                var ev = event.originalEvent || event;
 6855                var timeStamp = ev.timeStamp || Date.now();
 6856                var elapsedTime = parseFloat(ev.elapsedTime.toFixed(3));
 6857                if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
 6858                  close();
 6859                }
 6860              };
 6861              element.on(events, eventFn);
 6862
 6863              applyAnimationToStyles(element, options);
 6864
 6865              $timeout(close, maxDelayTime + maxDurationTime * 1.5, false);
 6866            });
 6867
 6868            return runner;
 6869
 6870            function close() {
 6871              if (animationClosed) return;
 6872              animationClosed = true;
 6873
 6874              if (events && eventFn) {
 6875                element.off(events, eventFn);
 6876              }
 6877              applyClasses(element, options);
 6878              applyAnimationStyles(element, options);
 6879              forEach(temporaryStyles, function(entry) {
 6880                node.style[camelCase(entry[0])] = '';
 6881              });
 6882              runner.complete(true);
 6883              return runner;
 6884            }
 6885          }
 6886        }
 6887      }
 6888
 6889      function applyClasses(element, options) {
 6890        if (options.addClass) {
 6891          $$jqLite.addClass(element, options.addClass);
 6892          options.addClass = null;
 6893        }
 6894        if (options.removeClass) {
 6895          $$jqLite.removeClass(element, options.removeClass);
 6896          options.removeClass = null;
 6897        }
 6898      }
 6899
 6900      function computeTimings(element) {
 6901        var node = getDomNode(element);
 6902        var cs = $window.getComputedStyle(node)
 6903        var tdr = parseMaxTime(cs[prop('transitionDuration')]);
 6904        var adr = parseMaxTime(cs[prop('animationDuration')]);
 6905        var tdy = parseMaxTime(cs[prop('transitionDelay')]);
 6906        var ady = parseMaxTime(cs[prop('animationDelay')]);
 6907
 6908        adr *= (parseInt(cs[prop('animationIterationCount')], 10) || 1);
 6909        var duration = Math.max(adr, tdr);
 6910        var delay = Math.max(ady, tdy);
 6911
 6912        return {
 6913          duration: duration,
 6914          delay: delay,
 6915          animationDuration: adr,
 6916          transitionDuration: tdr,
 6917          animationDelay: ady,
 6918          transitionDelay: tdy
 6919        };
 6920
 6921        function prop(key) {
 6922          return WEBKIT ? 'Webkit' + key.charAt(0).toUpperCase() + key.substr(1)
 6923                        : key;
 6924        }
 6925      }
 6926
 6927      function parseMaxTime(str) {
 6928        var maxValue = 0;
 6929        var values = (str || "").split(/\s*,\s*/);
 6930        forEach(values, function(value) {
 6931          // it's always safe to consider only second values and omit `ms` values since
 6932          // getComputedStyle will always handle the conversion for us
 6933          if (value.charAt(value.length - 1) == 's') {
 6934            value = value.substring(0, value.length - 1);
 6935          }
 6936          value = parseFloat(value) || 0;
 6937          maxValue = maxValue ? Math.max(value, maxValue) : value;
 6938        });
 6939        return maxValue;
 6940      }
 6941
 6942      var cancelLastRAFRequest;
 6943      var rafWaitQueue = [];
 6944      function waitUntilQuiet(callback) {
 6945        if (cancelLastRAFRequest) {
 6946          cancelLastRAFRequest(); //cancels the request
 6947        }
 6948        rafWaitQueue.push(callback);
 6949        cancelLastRAFRequest = $$rAF(function() {
 6950          cancelLastRAFRequest = null;
 6951
 6952          // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
 6953          // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
 6954          var pageWidth = $$forceReflow();
 6955
 6956          // we use a for loop to ensure that if the queue is changed
 6957          // during this looping then it will consider new requests
 6958          for (var i = 0; i < rafWaitQueue.length; i++) {
 6959            rafWaitQueue[i](pageWidth);
 6960          }
 6961          rafWaitQueue.length = 0;
 6962        });
 6963      }
 6964
 6965      function applyAnimationStyles(element, options) {
 6966        applyAnimationFromStyles(element, options);
 6967        applyAnimationToStyles(element, options);
 6968      }
 6969
 6970      function applyAnimationFromStyles(element, options) {
 6971        if (options.from) {
 6972          element.css(options.from);
 6973          options.from = null;
 6974        }
 6975      }
 6976
 6977      function applyAnimationToStyles(element, options) {
 6978        if (options.to) {
 6979          element.css(options.to);
 6980          options.to = null;
 6981        }
 6982      }
 6983
 6984      function getDomNode(element) {
 6985        for (var i = 0; i < element.length; i++) {
 6986          if (element[i].nodeType === 1) return element[i];
 6987        }
 6988      }
 6989
 6990      function blockTransition(element, bool) {
 6991        var node = getDomNode(element);
 6992        var key = camelCase(PREFIX + 'transition-delay');
 6993        node.style[key] = bool ? '-9999s' : '';
 6994      }
 6995
 6996      return init;
 6997    }]);
 6998
 6999  /**
 7000   * Older browsers [FF31] expect camelCase
 7001   * property keys.
 7002   * e.g.
 7003   *  animation-duration --> animationDuration
 7004   */
 7005  function camelCase(str) {
 7006    return str.replace(/-[a-z]/g, function(str) {
 7007      return str.charAt(1).toUpperCase();
 7008    });
 7009  }
 7010
 7011})();
 7012
 7013}
 7014
 7015})();
 7016(function(){
 7017"use strict";
 7018
 7019/**
 7020 * @ngdoc module
 7021 * @name material.components.autocomplete
 7022 */
 7023/*
 7024 * @see js folder for autocomplete implementation
 7025 */
 7026angular.module('material.components.autocomplete', [
 7027  'material.core',
 7028  'material.components.icon',
 7029  'material.components.virtualRepeat'
 7030]);
 7031
 7032})();
 7033(function(){
 7034"use strict";
 7035
 7036/*
 7037 * @ngdoc module
 7038 * @name material.components.backdrop
 7039 * @description Backdrop
 7040 */
 7041
 7042/**
 7043 * @ngdoc directive
 7044 * @name mdBackdrop
 7045 * @module material.components.backdrop
 7046 *
 7047 * @restrict E
 7048 *
 7049 * @description
 7050 * `<md-backdrop>` is a backdrop element used by other components, such as dialog and bottom sheet.
 7051 * Apply class `opaque` to make the backdrop use the theme backdrop color.
 7052 *
 7053 */
 7054
 7055angular
 7056  .module('material.components.backdrop', ['material.core'])
 7057  .directive('mdBackdrop', ["$mdTheming", "$mdUtil", "$animate", "$rootElement", "$window", "$log", "$$rAF", "$document", function BackdropDirective($mdTheming, $mdUtil, $animate, $rootElement, $window, $log, $$rAF, $document) {
 7058    var ERROR_CSS_POSITION = '<md-backdrop> may not work properly in a scrolled, static-positioned parent container.';
 7059
 7060    return {
 7061      restrict: 'E',
 7062      link: postLink
 7063    };
 7064
 7065    function postLink(scope, element, attrs) {
 7066      // backdrop may be outside the $rootElement, tell ngAnimate to animate regardless
 7067      if ($animate.pin) $animate.pin(element, $rootElement);
 7068
 7069      var bodyStyles;
 7070
 7071      $$rAF(function() {
 7072        // If body scrolling has been disabled using mdUtil.disableBodyScroll(),
 7073        // adjust the 'backdrop' height to account for the fixed 'body' top offset.
 7074        // Note that this can be pretty expensive and is better done inside the $$rAF.
 7075        bodyStyles = $window.getComputedStyle($document[0].body);
 7076
 7077        if (bodyStyles.position === 'fixed') {
 7078          var resizeHandler = $mdUtil.debounce(function(){
 7079            bodyStyles = $window.getComputedStyle($document[0].body);
 7080            resize();
 7081          }, 60, null, false);
 7082
 7083          resize();
 7084          angular.element($window).on('resize', resizeHandler);
 7085
 7086          scope.$on('$destroy', function() {
 7087            angular.element($window).off('resize', resizeHandler);
 7088          });
 7089        }
 7090
 7091        // Often $animate.enter() is used to append the backDrop element
 7092        // so let's wait until $animate is done...
 7093        var parent = element.parent();
 7094
 7095        if (parent.length) {
 7096          if (parent[0].nodeName === 'BODY') {
 7097            element.css('position', 'fixed');
 7098          }
 7099
 7100          var styles = $window.getComputedStyle(parent[0]);
 7101
 7102          if (styles.position === 'static') {
 7103            // backdrop uses position:absolute and will not work properly with parent position:static (default)
 7104            $log.warn(ERROR_CSS_POSITION);
 7105          }
 7106
 7107          // Only inherit the parent if the backdrop has a parent.
 7108          $mdTheming.inherit(element, parent);
 7109        }
 7110      });
 7111
 7112      function resize() {
 7113        var viewportHeight = parseInt(bodyStyles.height, 10) + Math.abs(parseInt(bodyStyles.top, 10));
 7114        element.css('height', viewportHeight + 'px');
 7115      }
 7116    }
 7117
 7118  }]);
 7119
 7120})();
 7121(function(){
 7122"use strict";
 7123
 7124/**
 7125 * @ngdoc module
 7126 * @name material.components.bottomSheet
 7127 * @description
 7128 * BottomSheet
 7129 */
 7130MdBottomSheetDirective.$inject = ["$mdBottomSheet"];
 7131MdBottomSheetProvider.$inject = ["$$interimElementProvider"];
 7132angular
 7133  .module('material.components.bottomSheet', [
 7134    'material.core',
 7135    'material.components.backdrop'
 7136  ])
 7137  .directive('mdBottomSheet', MdBottomSheetDirective)
 7138  .provider('$mdBottomSheet', MdBottomSheetProvider);
 7139
 7140/* @ngInject */
 7141function MdBottomSheetDirective($mdBottomSheet) {
 7142  return {
 7143    restrict: 'E',
 7144    link : function postLink(scope, element) {
 7145      element.addClass('_md');     // private md component indicator for styling
 7146
 7147      // When navigation force destroys an interimElement, then
 7148      // listen and $destroy() that interim instance...
 7149      scope.$on('$destroy', function() {
 7150        $mdBottomSheet.destroy();
 7151      });
 7152    }
 7153  };
 7154}
 7155
 7156
 7157/**
 7158 * @ngdoc service
 7159 * @name $mdBottomSheet
 7160 * @module material.components.bottomSheet
 7161 *
 7162 * @description
 7163 * `$mdBottomSheet` opens a bottom sheet over the app and provides a simple promise API.
 7164 *
 7165 * ## Restrictions
 7166 *
 7167 * - The bottom sheet's template must have an outer `<md-bottom-sheet>` element.
 7168 * - Add the `md-grid` class to the bottom sheet for a grid layout.
 7169 * - Add the `md-list` class to the bottom sheet for a list layout.
 7170 *
 7171 * @usage
 7172 * <hljs lang="html">
 7173 * <div ng-controller="MyController">
 7174 *   <md-button ng-click="openBottomSheet()">
 7175 *     Open a Bottom Sheet!
 7176 *   </md-button>
 7177 * </div>
 7178 * </hljs>
 7179 * <hljs lang="js">
 7180 * var app = angular.module('app', ['ngMaterial']);
 7181 * app.controller('MyController', function($scope, $mdBottomSheet) {
 7182 *   $scope.openBottomSheet = function() {
 7183 *     $mdBottomSheet.show({
 7184 *       template: '<md-bottom-sheet>Hello!</md-bottom-sheet>'
 7185 *     });
 7186 *   };
 7187 * });
 7188 * </hljs>
 7189 */
 7190
 7191 /**
 7192 * @ngdoc method
 7193 * @name $mdBottomSheet#show
 7194 *
 7195 * @description
 7196 * Show a bottom sheet with the specified options.
 7197 *
 7198 * @param {object} options An options object, with the following properties:
 7199 *
 7200 *   - `templateUrl` - `{string=}`: The url of an html template file that will
 7201 *   be used as the content of the bottom sheet. Restrictions: the template must
 7202 *   have an outer `md-bottom-sheet` element.
 7203 *   - `template` - `{string=}`: Same as templateUrl, except this is an actual
 7204 *   template string.
 7205 *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
 7206 *     This scope will be destroyed when the bottom sheet is removed unless `preserveScope` is set to true.
 7207 *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
 7208 *   - `controller` - `{string=}`: The controller to associate with this bottom sheet.
 7209 *   - `locals` - `{string=}`: An object containing key/value pairs. The keys will
 7210 *   be used as names of values to inject into the controller. For example,
 7211 *   `locals: {three: 3}` would inject `three` into the controller with the value
 7212 *   of 3.
 7213 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the bottom sheet to
 7214 *     close it. Default true.
 7215 *   - `disableBackdrop` - `{boolean=}`: When set to true, the bottomsheet will not show a backdrop.
 7216 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the bottom sheet.
 7217 *     Default true.
 7218 *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
 7219 *   and the bottom sheet will not open until the promises resolve.
 7220 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
 7221 *   - `parent` - `{element=}`: The element to append the bottom sheet to. The `parent` may be a `function`, `string`,
 7222 *   `object`, or null. Defaults to appending to the body of the root element (or the root element) of the application.
 7223 *   e.g. angular.element(document.getElementById('content')) or "#content"
 7224 *   - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the bottom sheet is open.
 7225 *     Default true.
 7226 *
 7227 * @returns {promise} A promise that can be resolved with `$mdBottomSheet.hide()` or
 7228 * rejected with `$mdBottomSheet.cancel()`.
 7229 */
 7230
 7231/**
 7232 * @ngdoc method
 7233 * @name $mdBottomSheet#hide
 7234 *
 7235 * @description
 7236 * Hide the existing bottom sheet and resolve the promise returned from
 7237 * `$mdBottomSheet.show()`. This call will close the most recently opened/current bottomsheet (if any).
 7238 *
 7239 * @param {*=} response An argument for the resolved promise.
 7240 *
 7241 */
 7242
 7243/**
 7244 * @ngdoc method
 7245 * @name $mdBottomSheet#cancel
 7246 *
 7247 * @description
 7248 * Hide the existing bottom sheet and reject the promise returned from
 7249 * `$mdBottomSheet.show()`.
 7250 *
 7251 * @param {*=} response An argument for the rejected promise.
 7252 *
 7253 */
 7254
 7255function MdBottomSheetProvider($$interimElementProvider) {
 7256  // how fast we need to flick down to close the sheet, pixels/ms
 7257  bottomSheetDefaults.$inject = ["$animate", "$mdConstant", "$mdUtil", "$mdTheming", "$mdBottomSheet", "$rootElement", "$mdGesture", "$log"];
 7258  var CLOSING_VELOCITY = 0.5;
 7259  var PADDING = 80; // same as css
 7260
 7261  return $$interimElementProvider('$mdBottomSheet')
 7262    .setDefaults({
 7263      methods: ['disableParentScroll', 'escapeToClose', 'clickOutsideToClose'],
 7264      options: bottomSheetDefaults
 7265    });
 7266
 7267  /* @ngInject */
 7268  function bottomSheetDefaults($animate, $mdConstant, $mdUtil, $mdTheming, $mdBottomSheet, $rootElement,
 7269                               $mdGesture, $log) {
 7270    var backdrop;
 7271
 7272    return {
 7273      themable: true,
 7274      onShow: onShow,
 7275      onRemove: onRemove,
 7276      disableBackdrop: false,
 7277      escapeToClose: true,
 7278      clickOutsideToClose: true,
 7279      disableParentScroll: true
 7280    };
 7281
 7282
 7283    function onShow(scope, element, options, controller) {
 7284
 7285      element = $mdUtil.extractElementByName(element, 'md-bottom-sheet');
 7286
 7287      // prevent tab focus or click focus on the bottom-sheet container
 7288      element.attr('tabindex',"-1");
 7289
 7290      // Once the md-bottom-sheet has `ng-cloak` applied on his template the opening animation will not work properly.
 7291      // This is a very common problem, so we have to notify the developer about this.
 7292      if (element.hasClass('ng-cloak')) {
 7293        var message = '$mdBottomSheet: using `<md-bottom-sheet ng-cloak >` will affect the bottom-sheet opening animations.';
 7294        $log.warn( message, element[0] );
 7295      }
 7296
 7297      if (!options.disableBackdrop) {
 7298        // Add a backdrop that will close on click
 7299        backdrop = $mdUtil.createBackdrop(scope, "md-bottom-sheet-backdrop md-opaque");
 7300
 7301        // Prevent mouse focus on backdrop; ONLY programatic focus allowed.
 7302        // This allows clicks on backdrop to propogate to the $rootElement and
 7303        // ESC key events to be detected properly.
 7304        
 7305        backdrop[0].tabIndex = -1;
 7306
 7307        if (options.clickOutsideToClose) {
 7308          backdrop.on('click', function() {
 7309            $mdUtil.nextTick($mdBottomSheet.cancel,true);
 7310          });
 7311        }
 7312
 7313        $mdTheming.inherit(backdrop, options.parent);
 7314
 7315        $animate.enter(backdrop, options.parent, null);
 7316      }
 7317
 7318      var bottomSheet = new BottomSheet(element, options.parent);
 7319      options.bottomSheet = bottomSheet;
 7320
 7321      $mdTheming.inherit(bottomSheet.element, options.parent);
 7322
 7323      if (options.disableParentScroll) {
 7324        options.restoreScroll = $mdUtil.disableScrollAround(bottomSheet.element, options.parent);
 7325      }
 7326
 7327      return $animate.enter(bottomSheet.element, options.parent, backdrop)
 7328        .then(function() {
 7329          var focusable = $mdUtil.findFocusTarget(element) || angular.element(
 7330            element[0].querySelector('button') ||
 7331            element[0].querySelector('a') ||
 7332            element[0].querySelector($mdUtil.prefixer('ng-click', true))
 7333          ) || backdrop;
 7334
 7335          if (options.escapeToClose) {
 7336            options.rootElementKeyupCallback = function(e) {
 7337              if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
 7338                $mdUtil.nextTick($mdBottomSheet.cancel,true);
 7339              }
 7340            };
 7341
 7342            $rootElement.on('keyup', options.rootElementKeyupCallback);
 7343            focusable && focusable.focus();
 7344          }
 7345        });
 7346
 7347    }
 7348
 7349    function onRemove(scope, element, options) {
 7350
 7351      var bottomSheet = options.bottomSheet;
 7352
 7353      if (!options.disableBackdrop) $animate.leave(backdrop);
 7354      return $animate.leave(bottomSheet.element).then(function() {
 7355        if (options.disableParentScroll) {
 7356          options.restoreScroll();
 7357          delete options.restoreScroll;
 7358        }
 7359
 7360        bottomSheet.cleanup();
 7361      });
 7362    }
 7363
 7364    /**
 7365     * BottomSheet class to apply bottom-sheet behavior to an element
 7366     */
 7367    function BottomSheet(element, parent) {
 7368      var deregister = $mdGesture.register(parent, 'drag', { horizontal: false });
 7369      parent.on('$md.dragstart', onDragStart)
 7370        .on('$md.drag', onDrag)
 7371        .on('$md.dragend', onDragEnd);
 7372
 7373      return {
 7374        element: element,
 7375        cleanup: function cleanup() {
 7376          deregister();
 7377          parent.off('$md.dragstart', onDragStart);
 7378          parent.off('$md.drag', onDrag);
 7379          parent.off('$md.dragend', onDragEnd);
 7380        }
 7381      };
 7382
 7383      function onDragStart(ev) {
 7384        // Disable transitions on transform so that it feels fast
 7385        element.css($mdConstant.CSS.TRANSITION_DURATION, '0ms');
 7386      }
 7387
 7388      function onDrag(ev) {
 7389        var transform = ev.pointer.distanceY;
 7390        if (transform < 5) {
 7391          // Slow down drag when trying to drag up, and stop after PADDING
 7392          transform = Math.max(-PADDING, transform / 2);
 7393        }
 7394        element.css($mdConstant.CSS.TRANSFORM, 'translate3d(0,' + (PADDING + transform) + 'px,0)');
 7395      }
 7396
 7397      function onDragEnd(ev) {
 7398        if (ev.pointer.distanceY > 0 &&
 7399            (ev.pointer.distanceY > 20 || Math.abs(ev.pointer.velocityY) > CLOSING_VELOCITY)) {
 7400          var distanceRemaining = element.prop('offsetHeight') - ev.pointer.distanceY;
 7401          var transitionDuration = Math.min(distanceRemaining / ev.pointer.velocityY * 0.75, 500);
 7402          element.css($mdConstant.CSS.TRANSITION_DURATION, transitionDuration + 'ms');
 7403          $mdUtil.nextTick($mdBottomSheet.cancel,true);
 7404        } else {
 7405          element.css($mdConstant.CSS.TRANSITION_DURATION, '');
 7406          element.css($mdConstant.CSS.TRANSFORM, '');
 7407        }
 7408      }
 7409    }
 7410
 7411  }
 7412
 7413}
 7414
 7415})();
 7416(function(){
 7417"use strict";
 7418
 7419/**
 7420 * @ngdoc module
 7421 * @name material.components.button
 7422 * @description
 7423 *
 7424 * Button
 7425 */
 7426MdButtonDirective.$inject = ["$mdButtonInkRipple", "$mdTheming", "$mdAria", "$timeout"];
 7427MdAnchorDirective.$inject = ["$mdTheming"];
 7428angular
 7429    .module('material.components.button', [ 'material.core' ])
 7430    .directive('mdButton', MdButtonDirective)
 7431    .directive('a', MdAnchorDirective);
 7432
 7433
 7434/**
 7435 * @private
 7436 * @restrict E
 7437 *
 7438 * @description
 7439 * `a` is an anchor directive used to inherit theme colors for md-primary, md-accent, etc.
 7440 *
 7441 * @usage
 7442 *
 7443 * <hljs lang="html">
 7444 *  <md-content md-theme="myTheme">
 7445 *    <a href="#chapter1" class="md-accent"></a>
 7446 *  </md-content>
 7447 * </hljs>
 7448 */
 7449function MdAnchorDirective($mdTheming) {
 7450  return {
 7451    restrict : 'E',
 7452    link : function postLink(scope, element) {
 7453      // Make sure to inherit theme so stand-alone anchors
 7454      // support theme colors for md-primary, md-accent, etc.
 7455      $mdTheming(element);
 7456    }
 7457  };
 7458}
 7459
 7460
 7461/**
 7462 * @ngdoc directive
 7463 * @name mdButton
 7464 * @module material.components.button
 7465 *
 7466 * @restrict E
 7467 *
 7468 * @description
 7469 * `<md-button>` is a button directive with optional ink ripples (default enabled).
 7470 *
 7471 * If you supply a `href` or `ng-href` attribute, it will become an `<a>` element. Otherwise, it
 7472 * will become a `<button>` element. As per the
 7473 * [Material Design specifications](https://material.google.com/style/color.html#color-color-palette)
 7474 * the FAB button background is filled with the accent color [by default]. The primary color palette
 7475 * may be used with the `md-primary` class.
 7476 *
 7477 * Developers can also change the color palette of the button, by using the following classes
 7478 * - `md-primary`
 7479 * - `md-accent`
 7480 * - `md-warn`
 7481 *
 7482 * See for example
 7483 *
 7484 * <hljs lang="html">
 7485 *   <md-button class="md-primary">Primary Button</md-button>
 7486 * </hljs>
 7487 *
 7488 * Button can be also raised, which means that they will use the current color palette to fill the button.
 7489 *
 7490 * <hljs lang="html">
 7491 *   <md-button class="md-accent md-raised">Raised and Accent Button</md-button>
 7492 * </hljs>
 7493 *
 7494 * It is also possible to disable the focus effect on the button, by using the following markup.
 7495 *
 7496 * <hljs lang="html">
 7497 *   <md-button class="md-no-focus">No Focus Style</md-button>
 7498 * </hljs>
 7499 *
 7500 * @param {boolean=} md-no-ink If present, disable ripple ink effects.
 7501 * @param {expression=} ng-disabled En/Disable based on the expression
 7502 * @param {string=} md-ripple-size Overrides the default ripple size logic. Options: `full`, `partial`, `auto`
 7503 * @param {string=} aria-label Adds alternative text to button for accessibility, useful for icon buttons.
 7504 * If no default text is found, a warning will be logged.
 7505 *
 7506 * @usage
 7507 *
 7508 * Regular buttons:
 7509 *
 7510 * <hljs lang="html">
 7511 *  <md-button> Flat Button </md-button>
 7512 *  <md-button href="http://google.com"> Flat link </md-button>
 7513 *  <md-button class="md-raised"> Raised Button </md-button>
 7514 *  <md-button ng-disabled="true"> Disabled Button </md-button>
 7515 *  <md-button>
 7516 *    <md-icon md-svg-src="your/icon.svg"></md-icon>
 7517 *    Register Now
 7518 *  </md-button>
 7519 * </hljs>
 7520 *
 7521 * FAB buttons:
 7522 *
 7523 * <hljs lang="html">
 7524 *  <md-button class="md-fab" aria-label="FAB">
 7525 *    <md-icon md-svg-src="your/icon.svg"></md-icon>
 7526 *  </md-button>
 7527 *  <!-- mini-FAB -->
 7528 *  <md-button class="md-fab md-mini" aria-label="Mini FAB">
 7529 *    <md-icon md-svg-src="your/icon.svg"></md-icon>
 7530 *  </md-button>
 7531 *  <!-- Button with SVG Icon -->
 7532 *  <md-button class="md-icon-button" aria-label="Custom Icon Button">
 7533 *    <md-icon md-svg-icon="path/to/your.svg"></md-icon>
 7534 *  </md-button>
 7535 * </hljs>
 7536 */
 7537function MdButtonDirective($mdButtonInkRipple, $mdTheming, $mdAria, $timeout) {
 7538
 7539  return {
 7540    restrict: 'EA',
 7541    replace: true,
 7542    transclude: true,
 7543    template: getTemplate,
 7544    link: postLink
 7545  };
 7546
 7547  function isAnchor(attr) {
 7548    return angular.isDefined(attr.href) || angular.isDefined(attr.ngHref) || angular.isDefined(attr.ngLink) || angular.isDefined(attr.uiSref);
 7549  }
 7550
 7551  function getTemplate(element, attr) {
 7552    if (isAnchor(attr)) {
 7553      return '<a class="md-button" ng-transclude></a>';
 7554    } else {
 7555      //If buttons don't have type="button", they will submit forms automatically.
 7556      var btnType = (typeof attr.type === 'undefined') ? 'button' : attr.type;
 7557      return '<button class="md-button" type="' + btnType + '" ng-transclude></button>';
 7558    }
 7559  }
 7560
 7561  function postLink(scope, element, attr) {
 7562    $mdTheming(element);
 7563    $mdButtonInkRipple.attach(scope, element);
 7564
 7565    // Use async expect to support possible bindings in the button label
 7566    $mdAria.expectWithoutText(element, 'aria-label');
 7567
 7568    // For anchor elements, we have to set tabindex manually when the
 7569    // element is disabled
 7570    if (isAnchor(attr) && angular.isDefined(attr.ngDisabled) ) {
 7571      scope.$watch(attr.ngDisabled, function(isDisabled) {
 7572        element.attr('tabindex', isDisabled ? -1 : 0);
 7573      });
 7574    }
 7575
 7576    // disabling click event when disabled is true
 7577    element.on('click', function(e){
 7578      if (attr.disabled === true) {
 7579        e.preventDefault();
 7580        e.stopImmediatePropagation();
 7581      }
 7582    });
 7583
 7584    if (!element.hasClass('md-no-focus')) {
 7585      // restrict focus styles to the keyboard
 7586      scope.mouseActive = false;
 7587      element.on('mousedown', function() {
 7588        scope.mouseActive = true;
 7589        $timeout(function(){
 7590          scope.mouseActive = false;
 7591        }, 100);
 7592      })
 7593      .on('focus', function() {
 7594        if (scope.mouseActive === false) {
 7595          element.addClass('md-focused');
 7596        }
 7597      })
 7598      .on('blur', function(ev) {
 7599        element.removeClass('md-focused');
 7600      });
 7601    }
 7602
 7603  }
 7604
 7605}
 7606
 7607})();
 7608(function(){
 7609"use strict";
 7610
 7611/**
 7612 * @ngdoc module
 7613 * @name material.components.card
 7614 *
 7615 * @description
 7616 * Card components.
 7617 */
 7618mdCardDirective.$inject = ["$mdTheming"];
 7619angular.module('material.components.card', [
 7620    'material.core'
 7621  ])
 7622  .directive('mdCard', mdCardDirective);
 7623
 7624
 7625/**
 7626 * @ngdoc directive
 7627 * @name mdCard
 7628 * @module material.components.card
 7629 *
 7630 * @restrict E
 7631 *
 7632 * @description
 7633 * The `<md-card>` directive is a container element used within `<md-content>` containers.
 7634 *
 7635 * An image included as a direct descendant will fill the card's width. If you want to avoid this,
 7636 * you can add the `md-image-no-fill` class to the parent element. The `<md-card-content>`
 7637 * container will wrap text content and provide padding. An `<md-card-footer>` element can be
 7638 * optionally included to put content flush against the bottom edge of the card.
 7639 *
 7640 * Action buttons can be included in an `<md-card-actions>` element, similar to `<md-dialog-actions>`.
 7641 * You can then position buttons using layout attributes.
 7642 *
 7643 * Card is built with:
 7644 * * `<md-card-header>` - Header for the card, holds avatar, text and squared image
 7645 *  - `<md-card-avatar>` - Card avatar
 7646 *    - `md-user-avatar` - Class for user image
 7647 *    - `<md-icon>`
 7648 *  - `<md-card-header-text>` - Contains elements for the card description
 7649 *    - `md-title` - Class for the card title
 7650 *    - `md-subhead` - Class for the card sub header
 7651 * * `<img>` - Image for the card
 7652 * * `<md-card-title>` - Card content title
 7653 *  - `<md-card-title-text>`
 7654 *    - `md-headline` - Class for the card content title
 7655 *    - `md-subhead` - Class for the card content sub header
 7656 *  - `<md-card-title-media>` - Squared image within the title
 7657 *    - `md-media-sm` - Class for small image
 7658 *    - `md-media-md` - Class for medium image
 7659 *    - `md-media-lg` - Class for large image
 7660 * * `<md-card-content>` - Card content
 7661 *  - `md-media-xl` - Class for extra large image
 7662 * * `<md-card-actions>` - Card actions
 7663 *  - `<md-card-icon-actions>` - Icon actions
 7664 *
 7665 * Cards have constant width and variable heights; where the maximum height is limited to what can
 7666 * fit within a single view on a platform, but it can temporarily expand as needed.
 7667 *
 7668 * @usage
 7669 * ### Card with optional footer
 7670 * <hljs lang="html">
 7671 * <md-card>
 7672 *  <img src="card-image.png" class="md-card-image" alt="image caption">
 7673 *  <md-card-content>
 7674 *    <h2>Card headline</h2>
 7675 *    <p>Card content</p>
 7676 *  </md-card-content>
 7677 *  <md-card-footer>
 7678 *    Card footer
 7679 *  </md-card-footer>
 7680 * </md-card>
 7681 * </hljs>
 7682 *
 7683 * ### Card with actions
 7684 * <hljs lang="html">
 7685 * <md-card>
 7686 *  <img src="card-image.png" class="md-card-image" alt="image caption">
 7687 *  <md-card-content>
 7688 *    <h2>Card headline</h2>
 7689 *    <p>Card content</p>
 7690 *  </md-card-content>
 7691 *  <md-card-actions layout="row" layout-align="end center">
 7692 *    <md-button>Action 1</md-button>
 7693 *    <md-button>Action 2</md-button>
 7694 *  </md-card-actions>
 7695 * </md-card>
 7696 * </hljs>
 7697 *
 7698 * ### Card with header, image, title actions and content
 7699 * <hljs lang="html">
 7700 * <md-card>
 7701 *   <md-card-header>
 7702 *     <md-card-avatar>
 7703 *       <img class="md-user-avatar" src="avatar.png"/>
 7704 *     </md-card-avatar>
 7705 *     <md-card-header-text>
 7706 *       <span class="md-title">Title</span>
 7707 *       <span class="md-subhead">Sub header</span>
 7708 *     </md-card-header-text>
 7709 *   </md-card-header>
 7710 *   <img ng-src="card-image.png" class="md-card-image" alt="image caption">
 7711 *   <md-card-title>
 7712 *     <md-card-title-text>
 7713 *       <span class="md-headline">Card headline</span>
 7714 *       <span class="md-subhead">Card subheader</span>
 7715 *     </md-card-title-text>
 7716 *   </md-card-title>
 7717 *   <md-card-actions layout="row" layout-align="start center">
 7718 *     <md-button>Action 1</md-button>
 7719 *     <md-button>Action 2</md-button>
 7720 *     <md-card-icon-actions>
 7721 *       <md-button class="md-icon-button" aria-label="icon">
 7722 *         <md-icon md-svg-icon="icon"></md-icon>
 7723 *       </md-button>
 7724 *     </md-card-icon-actions>
 7725 *   </md-card-actions>
 7726 *   <md-card-content>
 7727 *     <p>
 7728 *      Card content
 7729 *     </p>
 7730 *   </md-card-content>
 7731 * </md-card>
 7732 * </hljs>
 7733 */
 7734function mdCardDirective($mdTheming) {
 7735  return {
 7736    restrict: 'E',
 7737    link: function ($scope, $element, attr) {
 7738      $element.addClass('_md');     // private md component indicator for styling
 7739      $mdTheming($element);
 7740    }
 7741  };
 7742}
 7743
 7744})();
 7745(function(){
 7746"use strict";
 7747
 7748/**
 7749 * @ngdoc module
 7750 * @name material.components.chips
 7751 */
 7752/*
 7753 * @see js folder for chips implementation
 7754 */
 7755angular.module('material.components.chips', [
 7756  'material.core',
 7757  'material.components.autocomplete'
 7758]);
 7759
 7760})();
 7761(function(){
 7762"use strict";
 7763
 7764/**
 7765 * @ngdoc module
 7766 * @name material.components.checkbox
 7767 * @description Checkbox module!
 7768 */
 7769MdCheckboxDirective.$inject = ["inputDirective", "$mdAria", "$mdConstant", "$mdTheming", "$mdUtil", "$timeout"];
 7770angular
 7771  .module('material.components.checkbox', ['material.core'])
 7772  .directive('mdCheckbox', MdCheckboxDirective);
 7773
 7774/**
 7775 * @ngdoc directive
 7776 * @name mdCheckbox
 7777 * @module material.components.checkbox
 7778 * @restrict E
 7779 *
 7780 * @description
 7781 * The checkbox directive is used like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
 7782 *
 7783 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-color-schemes)
 7784 * the checkbox is in the accent color by default. The primary color palette may be used with
 7785 * the `md-primary` class.
 7786 *
 7787 * @param {string} ng-model Assignable angular expression to data-bind to.
 7788 * @param {string=} name Property name of the form under which the control is published.
 7789 * @param {expression=} ng-true-value The value to which the expression should be set when selected.
 7790 * @param {expression=} ng-false-value The value to which the expression should be set when not selected.
 7791 * @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element.
 7792 * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects
 7793 * @param {string=} aria-label Adds label to checkbox for accessibility.
 7794 *     Defaults to checkbox's text. If no default text is found, a warning will be logged.
 7795 * @param {expression=} md-indeterminate This determines when the checkbox should be rendered as 'indeterminate'.
 7796 *     If a truthy expression or no value is passed in the checkbox renders in the md-indeterminate state.
 7797 *     If falsy expression is passed in it just looks like a normal unchecked checkbox.
 7798 *     The indeterminate, checked, and unchecked states are mutually exclusive. A box cannot be in any two states at the same time.
 7799 *     Adding the 'md-indeterminate' attribute overrides any checked/unchecked rendering logic.
 7800 *     When using the 'md-indeterminate' attribute use 'ng-checked' to define rendering logic instead of using 'ng-model'.
 7801 * @param {expression=} ng-checked If this expression evaluates as truthy, the 'md-checked' css class is added to the checkbox and it
 7802 *     will appear checked.
 7803 *
 7804 * @usage
 7805 * <hljs lang="html">
 7806 * <md-checkbox ng-model="isChecked" aria-label="Finished?">
 7807 *   Finished ?
 7808 * </md-checkbox>
 7809 *
 7810 * <md-checkbox md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
 7811 *   No Ink Effects
 7812 * </md-checkbox>
 7813 *
 7814 * <md-checkbox ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
 7815 *   Disabled
 7816 * </md-checkbox>
 7817 *
 7818 * </hljs>
 7819 *
 7820 */
 7821function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $mdUtil, $timeout) {
 7822  inputDirective = inputDirective[0];
 7823
 7824  return {
 7825    restrict: 'E',
 7826    transclude: true,
 7827    require: '?ngModel',
 7828    priority: 210, // Run before ngAria
 7829    template:
 7830      '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
 7831        '<div class="md-icon"></div>' +
 7832      '</div>' +
 7833      '<div ng-transclude class="md-label"></div>',
 7834    compile: compile
 7835  };
 7836
 7837  // **********************************************************
 7838  // Private Methods
 7839  // **********************************************************
 7840
 7841  function compile (tElement, tAttrs) {
 7842    tAttrs.$set('tabindex', tAttrs.tabindex || '0');
 7843    tAttrs.$set('type', 'checkbox');
 7844    tAttrs.$set('role', tAttrs.type);
 7845
 7846    return  {
 7847      pre: function(scope, element) {
 7848        // Attach a click handler during preLink, in order to immediately stop propagation
 7849        // (especially for ng-click) when the checkbox is disabled.
 7850        element.on('click', function(e) {
 7851          if (this.hasAttribute('disabled')) {
 7852            e.stopImmediatePropagation();
 7853          }
 7854        });
 7855      },
 7856      post: postLink
 7857    };
 7858
 7859    function postLink(scope, element, attr, ngModelCtrl) {
 7860      var isIndeterminate;
 7861      ngModelCtrl = ngModelCtrl || $mdUtil.fakeNgModel();
 7862      $mdTheming(element);
 7863
 7864      // Redirect focus events to the root element, because IE11 is always focusing the container element instead
 7865      // of the md-checkbox element. This causes issues when using ngModelOptions: `updateOnBlur`
 7866      element.children().on('focus', function() {
 7867        element.focus();
 7868      });
 7869
 7870      if ($mdUtil.parseAttributeBoolean(attr.mdIndeterminate)) {
 7871        setIndeterminateState();
 7872        scope.$watch(attr.mdIndeterminate, setIndeterminateState);
 7873      }
 7874
 7875      if (attr.ngChecked) {
 7876        scope.$watch(scope.$eval.bind(scope, attr.ngChecked), function(value) {
 7877          ngModelCtrl.$setViewValue(value);
 7878          ngModelCtrl.$render();
 7879        });
 7880      }
 7881
 7882      $$watchExpr('ngDisabled', 'tabindex', {
 7883        true: '-1',
 7884        false: attr.tabindex
 7885      });
 7886
 7887      $mdAria.expectWithText(element, 'aria-label');
 7888
 7889      // Reuse the original input[type=checkbox] directive from Angular core.
 7890      // This is a bit hacky as we need our own event listener and own render
 7891      // function.
 7892      inputDirective.link.pre(scope, {
 7893        on: angular.noop,
 7894        0: {}
 7895      }, attr, [ngModelCtrl]);
 7896
 7897      scope.mouseActive = false;
 7898      element.on('click', listener)
 7899        .on('keypress', keypressHandler)
 7900        .on('mousedown', function() {
 7901          scope.mouseActive = true;
 7902          $timeout(function() {
 7903            scope.mouseActive = false;
 7904          }, 100);
 7905        })
 7906        .on('focus', function() {
 7907          if (scope.mouseActive === false) {
 7908            element.addClass('md-focused');
 7909          }
 7910        })
 7911        .on('blur', function() {
 7912          element.removeClass('md-focused');
 7913        });
 7914
 7915      ngModelCtrl.$render = render;
 7916
 7917      function $$watchExpr(expr, htmlAttr, valueOpts) {
 7918        if (attr[expr]) {
 7919          scope.$watch(attr[expr], function(val) {
 7920            if (valueOpts[val]) {
 7921              element.attr(htmlAttr, valueOpts[val]);
 7922            }
 7923          });
 7924        }
 7925      }
 7926
 7927      function keypressHandler(ev) {
 7928        var keyCode = ev.which || ev.keyCode;
 7929        if (keyCode === $mdConstant.KEY_CODE.SPACE || keyCode === $mdConstant.KEY_CODE.ENTER) {
 7930          ev.preventDefault();
 7931          element.addClass('md-focused');
 7932          listener(ev);
 7933        }
 7934      }
 7935
 7936      function listener(ev) {
 7937        // skipToggle boolean is used by the switch directive to prevent the click event
 7938        // when releasing the drag. There will be always a click if releasing the drag over the checkbox
 7939        if (element[0].hasAttribute('disabled') || scope.skipToggle) {
 7940          return;
 7941        }
 7942
 7943        scope.$apply(function() {
 7944          // Toggle the checkbox value...
 7945          var viewValue = attr.ngChecked ? attr.checked : !ngModelCtrl.$viewValue;
 7946
 7947          ngModelCtrl.$setViewValue(viewValue, ev && ev.type);
 7948          ngModelCtrl.$render();
 7949        });
 7950      }
 7951
 7952      function render() {
 7953        // Cast the $viewValue to a boolean since it could be undefined
 7954        element.toggleClass('md-checked', !!ngModelCtrl.$viewValue && !isIndeterminate);
 7955      }
 7956
 7957      function setIndeterminateState(newValue) {
 7958        isIndeterminate = newValue !== false;
 7959        if (isIndeterminate) {
 7960          element.attr('aria-checked', 'mixed');
 7961        }
 7962        element.toggleClass('md-indeterminate', isIndeterminate);
 7963      }
 7964    }
 7965  }
 7966}
 7967
 7968})();
 7969(function(){
 7970"use strict";
 7971
 7972(function () {
 7973  "use strict";
 7974
 7975  /**
 7976   *  Use a RegExp to check if the `md-colors="<expression>"` is static string
 7977   *  or one that should be observed and dynamically interpolated.
 7978   */
 7979  MdColorsDirective.$inject = ["$mdColors", "$mdUtil", "$log", "$parse"];
 7980  MdColorsService.$inject = ["$mdTheming", "$mdUtil", "$log"];
 7981  var STATIC_COLOR_EXPRESSION = /^{((\s|,)*?["'a-zA-Z-]+?\s*?:\s*?('|")[a-zA-Z0-9-.]*('|"))+\s*}$/;
 7982  var colorPalettes = null;
 7983
 7984  /**
 7985   * @ngdoc module
 7986   * @name material.components.colors
 7987   *
 7988   * @description
 7989   * Define $mdColors service and a `md-colors=""` attribute directive
 7990   */
 7991  angular
 7992    .module('material.components.colors', ['material.core'])
 7993    .directive('mdColors', MdColorsDirective)
 7994    .service('$mdColors', MdColorsService);
 7995
 7996  /**
 7997   * @ngdoc service
 7998   * @name $mdColors
 7999   * @module material.components.colors
 8000   *
 8001   * @description
 8002   * With only defining themes, one couldn't get non ngMaterial elements colored with Material colors,
 8003   * `$mdColors` service is used by the md-color directive to convert the 1..n color expressions to RGBA values and will apply
 8004   * those values to element as CSS property values.
 8005   *
 8006   *  @usage
 8007   *  <hljs lang="js">
 8008   *    angular.controller('myCtrl', function ($mdColors) {
 8009   *      var color = $mdColors.getThemeColor('myTheme-red-200-0.5');
 8010   *      ...
 8011   *    });
 8012   *  </hljs>
 8013   *
 8014   */
 8015  function MdColorsService($mdTheming, $mdUtil, $log) {
 8016    colorPalettes = colorPalettes || Object.keys($mdTheming.PALETTES);
 8017
 8018    // Publish service instance
 8019    return {
 8020      applyThemeColors: applyThemeColors,
 8021      getThemeColor: getThemeColor,
 8022      hasTheme: hasTheme
 8023    };
 8024
 8025    // ********************************************
 8026    // Internal Methods
 8027    // ********************************************
 8028
 8029    /**
 8030     * @ngdoc method
 8031     * @name $mdColors#applyThemeColors
 8032     *
 8033     * @description
 8034     * Gets a color json object, keys are css properties and values are string of the wanted color
 8035     * Then calculate the rgba() values based on the theme color parts
 8036     *
 8037     * @param {DOMElement} element the element to apply the styles on.
 8038     * @param {object} colorExpression json object, keys are css properties and values are string of the wanted color,
 8039     * for example: `{color: 'red-A200-0.3'}`.
 8040     *
 8041     * @usage
 8042     * <hljs lang="js">
 8043     *   app.directive('myDirective', function($mdColors) {
 8044     *     return {
 8045     *       ...
 8046     *       link: function (scope, elem) {
 8047     *         $mdColors.applyThemeColors(elem, {color: 'red'});
 8048     *       }
 8049     *    }
 8050     *   });
 8051     * </hljs>
 8052     */
 8053    function applyThemeColors(element, colorExpression) {
 8054      try {
 8055        if (colorExpression) {
 8056          // Assign the calculate RGBA color values directly as inline CSS
 8057          element.css(interpolateColors(colorExpression));
 8058        }
 8059      } catch (e) {
 8060        $log.error(e.message);
 8061      }
 8062
 8063    }
 8064
 8065    /**
 8066     * @ngdoc method
 8067     * @name $mdColors#getThemeColor
 8068     *
 8069     * @description
 8070     * Get parsed color from expression
 8071     *
 8072     * @param {string} expression string of a color expression (for instance `'red-700-0.8'`)
 8073     *
 8074     * @returns {string} a css color expression (for instance `rgba(211, 47, 47, 0.8)`)
 8075     *
 8076     * @usage
 8077     *  <hljs lang="js">
 8078     *    angular.controller('myCtrl', function ($mdColors) {
 8079     *      var color = $mdColors.getThemeColor('myTheme-red-200-0.5');
 8080     *      ...
 8081     *    });
 8082     *  </hljs>
 8083     */
 8084    function getThemeColor(expression) {
 8085      var color = extractColorOptions(expression);
 8086
 8087      return parseColor(color);
 8088    }
 8089
 8090    /**
 8091     * Return the parsed color
 8092     * @param color hashmap of color definitions
 8093     * @param contrast whether use contrast color for foreground
 8094     * @returns rgba color string
 8095     */
 8096    function parseColor(color, contrast) {
 8097      contrast = contrast || false;
 8098      var rgbValues = $mdTheming.PALETTES[color.palette][color.hue];
 8099
 8100      rgbValues = contrast ? rgbValues.contrast : rgbValues.value;
 8101
 8102      return $mdUtil.supplant('rgba({0}, {1}, {2}, {3})',
 8103        [rgbValues[0], rgbValues[1], rgbValues[2], rgbValues[3] || color.opacity]
 8104      );
 8105    }
 8106
 8107    /**
 8108     * Convert the color expression into an object with scope-interpolated values
 8109     * Then calculate the rgba() values based on the theme color parts
 8110     *
 8111     * @results Hashmap of CSS properties with associated `rgba( )` string vales
 8112     *
 8113     *
 8114     */
 8115    function interpolateColors(themeColors) {
 8116      var rgbColors = {};
 8117
 8118      var hasColorProperty = themeColors.hasOwnProperty('color');
 8119
 8120      angular.forEach(themeColors, function (value, key) {
 8121        var color = extractColorOptions(value);
 8122        var hasBackground = key.indexOf('background') > -1;
 8123
 8124        rgbColors[key] = parseColor(color);
 8125        if (hasBackground && !hasColorProperty) {
 8126          rgbColors.color = parseColor(color, true);
 8127        }
 8128      });
 8129
 8130      return rgbColors;
 8131    }
 8132
 8133    /**
 8134     * Check if expression has defined theme
 8135     * e.g.
 8136     * 'myTheme-primary' => true
 8137     * 'red-800' => false
 8138     */
 8139    function hasTheme(expression) {
 8140      return angular.isDefined($mdTheming.THEMES[expression.split('-')[0]]);
 8141    }
 8142
 8143    /**
 8144     * For the evaluated expression, extract the color parts into a hash map
 8145     */
 8146    function extractColorOptions(expression) {
 8147      var parts = expression.split('-');
 8148      var hasTheme = angular.isDefined($mdTheming.THEMES[parts[0]]);
 8149      var theme = hasTheme ? parts.splice(0, 1)[0] : $mdTheming.defaultTheme();
 8150
 8151      return {
 8152        theme: theme,
 8153        palette: extractPalette(parts, theme),
 8154        hue: extractHue(parts, theme),
 8155        opacity: parts[2] || 1
 8156      };
 8157    }
 8158
 8159    /**
 8160     * Calculate the theme palette name
 8161     */
 8162    function extractPalette(parts, theme) {
 8163      // If the next section is one of the palettes we assume it's a two word palette
 8164      // Two word palette can be also written in camelCase, forming camelCase to dash-case
 8165
 8166      var isTwoWord = parts.length > 1 && colorPalettes.indexOf(parts[1]) !== -1;
 8167      var palette = parts[0].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
 8168
 8169      if (isTwoWord)  palette = parts[0] + '-' + parts.splice(1, 1);
 8170
 8171      if (colorPalettes.indexOf(palette) === -1) {
 8172        // If the palette is not in the palette list it's one of primary/accent/warn/background
 8173        var scheme = $mdTheming.THEMES[theme].colors[palette];
 8174        if (!scheme) {
 8175          throw new Error($mdUtil.supplant('mdColors: couldn\'t find \'{palette}\' in the palettes.', {palette: palette}));
 8176        }
 8177        palette = scheme.name;
 8178      }
 8179
 8180      return palette;
 8181    }
 8182
 8183    function extractHue(parts, theme) {
 8184      var themeColors = $mdTheming.THEMES[theme].colors;
 8185
 8186      if (parts[1] === 'hue') {
 8187        var hueNumber = parseInt(parts.splice(2, 1)[0], 10);
 8188
 8189        if (hueNumber < 1 || hueNumber > 3) {
 8190          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}));
 8191        }
 8192        parts[1] = 'hue-' + hueNumber;
 8193
 8194        if (!(parts[0] in themeColors)) {
 8195          throw new Error($mdUtil.supplant('mdColors: \'hue-x\' can only be used with [{availableThemes}], but was used with \'{usedTheme}\'', {
 8196            availableThemes: Object.keys(themeColors).join(', '),
 8197            usedTheme: parts[0]
 8198          }));
 8199        }
 8200
 8201        return themeColors[parts[0]].hues[parts[1]];
 8202      }
 8203
 8204      return parts[1] || themeColors[parts[0] in themeColors ? parts[0] : 'primary'].hues['default'];
 8205    }
 8206  }
 8207
 8208  /**
 8209   * @ngdoc directive
 8210   * @name mdColors
 8211   * @module material.components.colors
 8212   *
 8213   * @restrict A
 8214   *
 8215   * @description
 8216   * `mdColors` directive will apply the theme-based color expression as RGBA CSS style values.
 8217   *
 8218   *   The format will be similar to our color defining in the scss files:
 8219   *
 8220   *   ## `[?theme]-[palette]-[?hue]-[?opacity]`
 8221   *   - [theme]    - default value is the default theme
 8222   *   - [palette]  - can be either palette name or primary/accent/warn/background
 8223   *   - [hue]      - default is 500 (hue-x can be used with primary/accent/warn/background)
 8224   *   - [opacity]  - default is 1
 8225   *
 8226   *   > `?` indicates optional parameter
 8227   *
 8228   * @usage
 8229   * <hljs lang="html">
 8230   *   <div md-colors="{background: 'myTheme-accent-900-0.43'}">
 8231   *     <div md-colors="{color: 'red-A100', 'border-color': 'primary-600'}">
 8232   *       <span>Color demo</span>
 8233   *     </div>
 8234   *   </div>
 8235   * </hljs>
 8236   *
 8237   * `mdColors` directive will automatically watch for changes in the expression if it recognizes an interpolation
 8238   * expression or a function. For performance options, you can use `::` prefix to the `md-colors` expression
 8239   * to indicate a one-time data binding.
 8240   * <hljs lang="html">
 8241   *   <md-card md-colors="::{background: '{{theme}}-primary-700'}">
 8242   *   </md-card>
 8243   * </hljs>
 8244   *
 8245   */
 8246  function MdColorsDirective($mdColors, $mdUtil, $log, $parse) {
 8247    return {
 8248      restrict: 'A',
 8249      require: ['^?mdTheme'],
 8250      compile: function (tElem, tAttrs) {
 8251        var shouldWatch = shouldColorsWatch();
 8252
 8253        return function (scope, element, attrs, ctrl) {
 8254          var mdThemeController = ctrl[0];
 8255
 8256          var lastColors = {};
 8257
 8258          var parseColors = function (theme) {
 8259            if (typeof theme !== 'string') {
 8260              theme = '';
 8261            }
 8262
 8263            if (!attrs.mdColors) {
 8264              attrs.mdColors = '{}';
 8265            }
 8266
 8267            /**
 8268             * Json.parse() does not work because the keys are not quoted;
 8269             * use $parse to convert to a hash map
 8270             */
 8271            var colors = $parse(attrs.mdColors)(scope);
 8272
 8273            /**
 8274             * If mdTheme is defined up the DOM tree
 8275             * we add mdTheme theme to colors who doesn't specified a theme
 8276             *
 8277             * # example
 8278             * <hljs lang="html">
 8279             *   <div md-theme="myTheme">
 8280             *     <div md-colors="{background: 'primary-600'}">
 8281             *       <span md-colors="{background: 'mySecondTheme-accent-200'}">Color demo</span>
 8282             *     </div>
 8283             *   </div>
 8284             * </hljs>
 8285             *
 8286             * 'primary-600' will be 'myTheme-primary-600',
 8287             * but 'mySecondTheme-accent-200' will stay the same cause it has a theme prefix
 8288             */
 8289            if (mdThemeController) {
 8290              Object.keys(colors).forEach(function (prop) {
 8291                var color = colors[prop];
 8292                if (!$mdColors.hasTheme(color)) {
 8293                  colors[prop] = (theme || mdThemeController.$mdTheme) + '-' + color;
 8294                }
 8295              });
 8296            }
 8297
 8298            cleanElement(colors);
 8299
 8300            return colors;
 8301          };
 8302
 8303          var cleanElement = function (colors) {
 8304            if (!angular.equals(colors, lastColors)) {
 8305              var keys = Object.keys(lastColors);
 8306
 8307              if (lastColors.background && !keys.color) {
 8308                keys.push('color');
 8309              }
 8310
 8311              keys.forEach(function (key) {
 8312                element.css(key, '');
 8313              });
 8314            }
 8315
 8316            lastColors = colors;
 8317          };
 8318
 8319          /**
 8320           * Registering for mgTheme changes and asking mdTheme controller run our callback whenever a theme changes
 8321           */
 8322          var unregisterChanges = angular.noop;
 8323
 8324          if (mdThemeController) {
 8325            unregisterChanges = mdThemeController.registerChanges(function (theme) {
 8326              $mdColors.applyThemeColors(element, parseColors(theme));
 8327            });
 8328          }
 8329
 8330          scope.$on('$destroy', function () {
 8331            unregisterChanges();
 8332          });
 8333
 8334          try {
 8335            if (shouldWatch) {
 8336              scope.$watch(parseColors, angular.bind(this,
 8337                $mdColors.applyThemeColors, element
 8338              ), true);
 8339            }
 8340            else {
 8341              $mdColors.applyThemeColors(element, parseColors());
 8342            }
 8343
 8344          }
 8345          catch (e) {
 8346            $log.error(e.message);
 8347          }
 8348
 8349        };
 8350
 8351        function shouldColorsWatch() {
 8352          // Simulate 1x binding and mark mdColorsWatch == false
 8353          var rawColorExpression = tAttrs.mdColors;
 8354          var bindOnce = rawColorExpression.indexOf('::') > -1;
 8355          var isStatic = bindOnce ? true : STATIC_COLOR_EXPRESSION.test(tAttrs.mdColors);
 8356
 8357          // Remove it for the postLink...
 8358          tAttrs.mdColors = rawColorExpression.replace('::', '');
 8359
 8360          var hasWatchAttr = angular.isDefined(tAttrs.mdColorsWatch);
 8361
 8362          return (bindOnce || isStatic) ? false :
 8363            hasWatchAttr ? $mdUtil.parseAttributeBoolean(tAttrs.mdColorsWatch) : true;
 8364        }
 8365      }
 8366    };
 8367
 8368  }
 8369
 8370
 8371})();
 8372
 8373})();
 8374(function(){
 8375"use strict";
 8376
 8377/**
 8378 * @ngdoc module
 8379 * @name material.components.content
 8380 *
 8381 * @description
 8382 * Scrollable content
 8383 */
 8384mdContentDirective.$inject = ["$mdTheming"];
 8385angular.module('material.components.content', [
 8386  'material.core'
 8387])
 8388  .directive('mdContent', mdContentDirective);
 8389
 8390/**
 8391 * @ngdoc directive
 8392 * @name mdContent
 8393 * @module material.components.content
 8394 *
 8395 * @restrict E
 8396 *
 8397 * @description
 8398 *
 8399 * The `<md-content>` directive is a container element useful for scrollable content. It achieves
 8400 * this by setting the CSS `overflow` property to `auto` so that content can properly scroll.
 8401 *
 8402 * In general, `<md-content>` components are not designed to be nested inside one another. If
 8403 * possible, it is better to make them siblings. This often results in a better user experience as
 8404 * having nested scrollbars may confuse the user.
 8405 *
 8406 * ## Troubleshooting
 8407 *
 8408 * In some cases, you may wish to apply the `md-no-momentum` class to ensure that Safari's
 8409 * momentum scrolling is disabled. Momentum scrolling can cause flickering issues while scrolling
 8410 * SVG icons and some other components.
 8411 *
 8412 * Additionally, we now also offer the `md-no-flicker` class which can be applied to any element
 8413 * and uses a Webkit-specific filter of `blur(0px)` that forces GPU rendering of all elements
 8414 * inside (which eliminates the flicker on iOS devices).
 8415 *
 8416 * _<b>Note:</b> Forcing an element to render on the GPU can have unintended side-effects, especially
 8417 * related to the z-index of elements. Please use with caution and only on the elements needed._
 8418 *
 8419 * @usage
 8420 *
 8421 * Add the `[layout-padding]` attribute to make the content padded.
 8422 *
 8423 * <hljs lang="html">
 8424 *  <md-content layout-padding>
 8425 *      Lorem ipsum dolor sit amet, ne quod novum mei.
 8426 *  </md-content>
 8427 * </hljs>
 8428 */
 8429
 8430function mdContentDirective($mdTheming) {
 8431  return {
 8432    restrict: 'E',
 8433    controller: ['$scope', '$element', ContentController],
 8434    link: function(scope, element) {
 8435      element.addClass('_md');     // private md component indicator for styling
 8436
 8437      $mdTheming(element);
 8438      scope.$broadcast('$mdContentLoaded', element);
 8439
 8440      iosScrollFix(element[0]);
 8441    }
 8442  };
 8443
 8444  function ContentController($scope, $element) {
 8445    this.$scope = $scope;
 8446    this.$element = $element;
 8447  }
 8448}
 8449
 8450function iosScrollFix(node) {
 8451  // IOS FIX:
 8452  // If we scroll where there is no more room for the webview to scroll,
 8453  // by default the webview itself will scroll up and down, this looks really
 8454  // bad.  So if we are scrolling to the very top or bottom, add/subtract one
 8455  angular.element(node).on('$md.pressdown', function(ev) {
 8456    // Only touch events
 8457    if (ev.pointer.type !== 't') return;
 8458    // Don't let a child content's touchstart ruin it for us.
 8459    if (ev.$materialScrollFixed) return;
 8460    ev.$materialScrollFixed = true;
 8461
 8462    if (node.scrollTop === 0) {
 8463      node.scrollTop = 1;
 8464    } else if (node.scrollHeight === node.scrollTop + node.offsetHeight) {
 8465      node.scrollTop -= 1;
 8466    }
 8467  });
 8468}
 8469
 8470})();
 8471(function(){
 8472"use strict";
 8473
 8474/**
 8475 * @ngdoc module
 8476 * @name material.components.datepicker
 8477 * @description Module for the datepicker component.
 8478 */
 8479
 8480angular.module('material.components.datepicker', [
 8481  'material.core',
 8482  'material.components.icon',
 8483  'material.components.virtualRepeat'
 8484]);
 8485
 8486})();
 8487(function(){
 8488"use strict";
 8489
 8490/**
 8491 * @ngdoc module
 8492 * @name material.components.dialog
 8493 */
 8494MdDialogDirective.$inject = ["$$rAF", "$mdTheming", "$mdDialog"];
 8495MdDialogProvider.$inject = ["$$interimElementProvider"];
 8496angular
 8497  .module('material.components.dialog', [
 8498    'material.core',
 8499    'material.components.backdrop'
 8500  ])
 8501  .directive('mdDialog', MdDialogDirective)
 8502  .provider('$mdDialog', MdDialogProvider);
 8503
 8504/**
 8505 * @ngdoc directive
 8506 * @name mdDialog
 8507 * @module material.components.dialog
 8508 *
 8509 * @restrict E
 8510 *
 8511 * @description
 8512 * `<md-dialog>` - The dialog's template must be inside this element.
 8513 *
 8514 * Inside, use an `<md-dialog-content>` element for the dialog's content, and use
 8515 * an `<md-dialog-actions>` element for the dialog's actions.
 8516 *
 8517 * ## CSS
 8518 * - `.md-dialog-content` - class that sets the padding on the content as the spec file
 8519 *
 8520 * ## Notes
 8521 * - If you specify an `id` for the `<md-dialog>`, the `<md-dialog-content>` will have the same `id`
 8522 * prefixed with `dialogContent_`.
 8523 *
 8524 * @usage
 8525 * ### Dialog template
 8526 * <hljs lang="html">
 8527 * <md-dialog aria-label="List dialog">
 8528 *   <md-dialog-content>
 8529 *     <md-list>
 8530 *       <md-list-item ng-repeat="item in items">
 8531 *         <p>Number {{item}}</p>
 8532 *       </md-list-item>
 8533 *     </md-list>
 8534 *   </md-dialog-content>
 8535 *   <md-dialog-actions>
 8536 *     <md-button ng-click="closeDialog()" class="md-primary">Close Dialog</md-button>
 8537 *   </md-dialog-actions>
 8538 * </md-dialog>
 8539 * </hljs>
 8540 */
 8541function MdDialogDirective($$rAF, $mdTheming, $mdDialog) {
 8542  return {
 8543    restrict: 'E',
 8544    link: function(scope, element) {
 8545      element.addClass('_md');     // private md component indicator for styling
 8546
 8547      $mdTheming(element);
 8548      $$rAF(function() {
 8549        var images;
 8550        var content = element[0].querySelector('md-dialog-content');
 8551
 8552        if (content) {
 8553          images = content.getElementsByTagName('img');
 8554          addOverflowClass();
 8555          //-- delayed image loading may impact scroll height, check after images are loaded
 8556          angular.element(images).on('load', addOverflowClass);
 8557        }
 8558
 8559        scope.$on('$destroy', function() {
 8560          $mdDialog.destroy(element);
 8561        });
 8562
 8563        /**
 8564         *
 8565         */
 8566        function addOverflowClass() {
 8567          element.toggleClass('md-content-overflow', content.scrollHeight > content.clientHeight);
 8568        }
 8569
 8570
 8571      });
 8572    }
 8573  };
 8574}
 8575
 8576/**
 8577 * @ngdoc service
 8578 * @name $mdDialog
 8579 * @module material.components.dialog
 8580 *
 8581 * @description
 8582 * `$mdDialog` opens a dialog over the app to inform users about critical information or require
 8583 *  them to make decisions. There are two approaches for setup: a simple promise API
 8584 *  and regular object syntax.
 8585 *
 8586 * ## Restrictions
 8587 *
 8588 * - The dialog is always given an isolate scope.
 8589 * - The dialog's template must have an outer `<md-dialog>` element.
 8590 *   Inside, use an `<md-dialog-content>` element for the dialog's content, and use
 8591 *   an `<md-dialog-actions>` element for the dialog's actions.
 8592 * - Dialogs must cover the entire application to keep interactions inside of them.
 8593 * Use the `parent` option to change where dialogs are appended.
 8594 *
 8595 * ## Sizing
 8596 * - Complex dialogs can be sized with `flex="percentage"`, i.e. `flex="66"`.
 8597 * - Default max-width is 80% of the `rootElement` or `parent`.
 8598 *
 8599 * ## CSS
 8600 * - `.md-dialog-content` - class that sets the padding on the content as the spec file
 8601 *
 8602 * @usage
 8603 * <hljs lang="html">
 8604 * <div  ng-app="demoApp" ng-controller="EmployeeController">
 8605 *   <div>
 8606 *     <md-button ng-click="showAlert()" class="md-raised md-warn">
 8607 *       Employee Alert!
 8608 *       </md-button>
 8609 *   </div>
 8610 *   <div>
 8611 *     <md-button ng-click="showDialog($event)" class="md-raised">
 8612 *       Custom Dialog
 8613 *       </md-button>
 8614 *   </div>
 8615 *   <div>
 8616 *     <md-button ng-click="closeAlert()" ng-disabled="!hasAlert()" class="md-raised">
 8617 *       Close Alert
 8618 *     </md-button>
 8619 *   </div>
 8620 *   <div>
 8621 *     <md-button ng-click="showGreeting($event)" class="md-raised md-primary" >
 8622 *       Greet Employee
 8623 *       </md-button>
 8624 *   </div>
 8625 * </div>
 8626 * </hljs>
 8627 *
 8628 * ### JavaScript: object syntax
 8629 * <hljs lang="js">
 8630 * (function(angular, undefined){
 8631 *   "use strict";
 8632 *
 8633 *   angular
 8634 *    .module('demoApp', ['ngMaterial'])
 8635 *    .controller('AppCtrl', AppController);
 8636 *
 8637 *   function AppController($scope, $mdDialog) {
 8638 *     var alert;
 8639 *     $scope.showAlert = showAlert;
 8640 *     $scope.showDialog = showDialog;
 8641 *     $scope.items = [1, 2, 3];
 8642 *
 8643 *     // Internal method
 8644 *     function showAlert() {
 8645 *       alert = $mdDialog.alert({
 8646 *         title: 'Attention',
 8647 *         textContent: 'This is an example of how easy dialogs can be!',
 8648 *         ok: 'Close'
 8649 *       });
 8650 *
 8651 *       $mdDialog
 8652 *         .show( alert )
 8653 *         .finally(function() {
 8654 *           alert = undefined;
 8655 *         });
 8656 *     }
 8657 *
 8658 *     function showDialog($event) {
 8659 *        var parentEl = angular.element(document.body);
 8660 *        $mdDialog.show({
 8661 *          parent: parentEl,
 8662 *          targetEvent: $event,
 8663 *          template:
 8664 *            '<md-dialog aria-label="List dialog">' +
 8665 *            '  <md-dialog-content>'+
 8666 *            '    <md-list>'+
 8667 *            '      <md-list-item ng-repeat="item in items">'+
 8668 *            '       <p>Number {{item}}</p>' +
 8669 *            '      </md-item>'+
 8670 *            '    </md-list>'+
 8671 *            '  </md-dialog-content>' +
 8672 *            '  <md-dialog-actions>' +
 8673 *            '    <md-button ng-click="closeDialog()" class="md-primary">' +
 8674 *            '      Close Dialog' +
 8675 *            '    </md-button>' +
 8676 *            '  </md-dialog-actions>' +
 8677 *            '</md-dialog>',
 8678 *          locals: {
 8679 *            items: $scope.items
 8680 *          },
 8681 *          controller: DialogController
 8682 *       });
 8683 *       function DialogController($scope, $mdDialog, items) {
 8684 *         $scope.items = items;
 8685 *         $scope.closeDialog = function() {
 8686 *           $mdDialog.hide();
 8687 *         }
 8688 *       }
 8689 *     }
 8690 *   }
 8691 * })(angular);
 8692 * </hljs>
 8693 *
 8694 * ### Pre-Rendered Dialogs
 8695 * By using the `contentElement` option, it is possible to use an already existing element in the DOM.
 8696 *
 8697 * <hljs lang="js">
 8698 *   $scope.showPrerenderedDialog = function() {
 8699 *     $mdDialog.show({
 8700 *       contentElement: '#myStaticDialog',
 8701 *       parent: angular.element(document.body)
 8702 *     });
 8703 *   };
 8704 * </hljs>
 8705 *
 8706 * When using a string as value, `$mdDialog` will automatically query the DOM for the specified CSS selector.
 8707 *
 8708 * <hljs lang="html">
 8709 *   <div style="visibility: hidden">
 8710 *     <div class="md-dialog-container" id="myStaticDialog">
 8711 *       <md-dialog>
 8712 *         This is a pre-rendered dialog.
 8713 *       </md-dialog>
 8714 *     </div>
 8715 *   </div>
 8716 * </hljs>
 8717 *
 8718 * **Notice**: It is important, to use the `.md-dialog-container` as the content element, otherwise the dialog
 8719 * will not show up.
 8720 *
 8721 * It also possible to use a DOM element for the `contentElement` option.
 8722 * - `contentElement: document.querySelector('#myStaticDialog')`
 8723 * - `contentElement: angular.element(TEMPLATE)`
 8724 *
 8725 * When using a `template` as content element, it will be not compiled upon open.
 8726 * This allows you to compile the element yourself and use it each time the dialog opens.
 8727 *
 8728 * ### Custom Presets
 8729 * Developers are also able to create their own preset, which can be easily used without repeating
 8730 * their options each time.
 8731 *
 8732 * <hljs lang="js">
 8733 *   $mdDialogProvider.addPreset('testPreset', {
 8734 *     options: function() {
 8735 *       return {
 8736 *         template:
 8737 *           '<md-dialog>' +
 8738 *             'This is a custom preset' +
 8739 *           '</md-dialog>',
 8740 *         controllerAs: 'dialog',
 8741 *         bindToController: true,
 8742 *         clickOutsideToClose: true,
 8743 *         escapeToClose: true
 8744 *       };
 8745 *     }
 8746 *   });
 8747 * </hljs>
 8748 *
 8749 * After you created your preset at config phase, you can easily access it.
 8750 *
 8751 * <hljs lang="js">
 8752 *   $mdDialog.show(
 8753 *     $mdDialog.testPreset()
 8754 *   );
 8755 * </hljs>
 8756 *
 8757 * ### JavaScript: promise API syntax, custom dialog template
 8758 * <hljs lang="js">
 8759 * (function(angular, undefined){
 8760 *   "use strict";
 8761 *
 8762 *   angular
 8763 *     .module('demoApp', ['ngMaterial'])
 8764 *     .controller('EmployeeController', EmployeeEditor)
 8765 *     .controller('GreetingController', GreetingController);
 8766 *
 8767 *   // Fictitious Employee Editor to show how to use simple and complex dialogs.
 8768 *
 8769 *   function EmployeeEditor($scope, $mdDialog) {
 8770 *     var alert;
 8771 *
 8772 *     $scope.showAlert = showAlert;
 8773 *     $scope.closeAlert = closeAlert;
 8774 *     $scope.showGreeting = showCustomGreeting;
 8775 *
 8776 *     $scope.hasAlert = function() { return !!alert };
 8777 *     $scope.userName = $scope.userName || 'Bobby';
 8778 *
 8779 *     // Dialog #1 - Show simple alert dialog and cache
 8780 *     // reference to dialog instance
 8781 *
 8782 *     function showAlert() {
 8783 *       alert = $mdDialog.alert()
 8784 *         .title('Attention, ' + $scope.userName)
 8785 *         .textContent('This is an example of how easy dialogs can be!')
 8786 *         .ok('Close');
 8787 *
 8788 *       $mdDialog
 8789 *           .show( alert )
 8790 *           .finally(function() {
 8791 *             alert = undefined;
 8792 *           });
 8793 *     }
 8794 *
 8795 *     // Close the specified dialog instance and resolve with 'finished' flag
 8796 *     // Normally this is not needed, just use '$mdDialog.hide()' to close
 8797 *     // the most recent dialog popup.
 8798 *
 8799 *     function closeAlert() {
 8800 *       $mdDialog.hide( alert, "finished" );
 8801 *       alert = undefined;
 8802 *     }
 8803 *
 8804 *     // Dialog #2 - Demonstrate more complex dialogs construction and popup.
 8805 *
 8806 *     function showCustomGreeting($event) {
 8807 *         $mdDialog.show({
 8808 *           targetEvent: $event,
 8809 *           template:
 8810 *             '<md-dialog>' +
 8811 *
 8812 *             '  <md-dialog-content>Hello {{ employee }}!</md-dialog-content>' +
 8813 *
 8814 *             '  <md-dialog-actions>' +
 8815 *             '    <md-button ng-click="closeDialog()" class="md-primary">' +
 8816 *             '      Close Greeting' +
 8817 *             '    </md-button>' +
 8818 *             '  </md-dialog-actions>' +
 8819 *             '</md-dialog>',
 8820 *           controller: 'GreetingController',
 8821 *           onComplete: afterShowAnimation,
 8822 *           locals: { employee: $scope.userName }
 8823 *         });
 8824 *
 8825 *         // When the 'enter' animation finishes...
 8826 *
 8827 *         function afterShowAnimation(scope, element, options) {
 8828 *            // post-show code here: DOM element focus, etc.
 8829 *         }
 8830 *     }
 8831 *
 8832 *     // Dialog #3 - Demonstrate use of ControllerAs and passing $scope to dialog
 8833 *     //             Here we used ng-controller="GreetingController as vm" and
 8834 *     //             $scope.vm === <controller instance>
 8835 *
 8836 *     function showCustomGreeting() {
 8837 *
 8838 *        $mdDialog.show({
 8839 *           clickOutsideToClose: true,
 8840 *
 8841 *           scope: $scope,        // use parent scope in template
 8842 *           preserveScope: true,  // do not forget this if use parent scope
 8843
 8844 *           // Since GreetingController is instantiated with ControllerAs syntax
 8845 *           // AND we are passing the parent '$scope' to the dialog, we MUST
 8846 *           // use 'vm.<xxx>' in the template markup
 8847 *
 8848 *           template: '<md-dialog>' +
 8849 *                     '  <md-dialog-content>' +
 8850 *                     '     Hi There {{vm.employee}}' +
 8851 *                     '  </md-dialog-content>' +
 8852 *                     '</md-dialog>',
 8853 *
 8854 *           controller: function DialogController($scope, $mdDialog) {
 8855 *             $scope.closeDialog = function() {
 8856 *               $mdDialog.hide();
 8857 *             }
 8858 *           }
 8859 *        });
 8860 *     }
 8861 *
 8862 *   }
 8863 *
 8864 *   // Greeting controller used with the more complex 'showCustomGreeting()' custom dialog
 8865 *
 8866 *   function GreetingController($scope, $mdDialog, employee) {
 8867 *     // Assigned from construction <code>locals</code> options...
 8868 *     $scope.employee = employee;
 8869 *
 8870 *     $scope.closeDialog = function() {
 8871 *       // Easily hides most recent dialog shown...
 8872 *       // no specific instance reference is needed.
 8873 *       $mdDialog.hide();
 8874 *     };
 8875 *   }
 8876 *
 8877 * })(angular);
 8878 * </hljs>
 8879 */
 8880
 8881/**
 8882 * @ngdoc method
 8883 * @name $mdDialog#alert
 8884 *
 8885 * @description
 8886 * Builds a preconfigured dialog with the specified message.
 8887 *
 8888 * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
 8889 *
 8890 * - $mdDialogPreset#title(string) - Sets the alert title.
 8891 * - $mdDialogPreset#textContent(string) - Sets the alert message.
 8892 * - $mdDialogPreset#htmlContent(string) - Sets the alert message as HTML. Requires ngSanitize
 8893 *     module to be loaded. HTML is not run through Angular's compiler.
 8894 * - $mdDialogPreset#ok(string) - Sets the alert "Okay" button text.
 8895 * - $mdDialogPreset#theme(string) - Sets the theme of the alert dialog.
 8896 * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option,
 8897 *     the location of the click will be used as the starting point for the opening animation
 8898 *     of the the dialog.
 8899 *
 8900 */
 8901
 8902/**
 8903 * @ngdoc method
 8904 * @name $mdDialog#confirm
 8905 *
 8906 * @description
 8907 * Builds a preconfigured dialog with the specified message. You can call show and the promise returned
 8908 * will be resolved only if the user clicks the confirm action on the dialog.
 8909 *
 8910 * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
 8911 *
 8912 * Additionally, it supports the following methods:
 8913 *
 8914 * - $mdDialogPreset#title(string) - Sets the confirm title.
 8915 * - $mdDialogPreset#textContent(string) - Sets the confirm message.
 8916 * - $mdDialogPreset#htmlContent(string) - Sets the confirm message as HTML. Requires ngSanitize
 8917 *     module to be loaded. HTML is not run through Angular's compiler.
 8918 * - $mdDialogPreset#ok(string) - Sets the confirm "Okay" button text.
 8919 * - $mdDialogPreset#cancel(string) - Sets the confirm "Cancel" button text.
 8920 * - $mdDialogPreset#theme(string) - Sets the theme of the confirm dialog.
 8921 * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option,
 8922 *     the location of the click will be used as the starting point for the opening animation
 8923 *     of the the dialog.
 8924 *
 8925 */
 8926
 8927/**
 8928 * @ngdoc method
 8929 * @name $mdDialog#prompt
 8930 *
 8931 * @description
 8932 * Builds a preconfigured dialog with the specified message and input box. You can call show and the promise returned
 8933 * will be resolved only if the user clicks the prompt action on the dialog, passing the input value as the first argument.
 8934 *
 8935 * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
 8936 *
 8937 * Additionally, it supports the following methods:
 8938 *
 8939 * - $mdDialogPreset#title(string) - Sets the prompt title.
 8940 * - $mdDialogPreset#textContent(string) - Sets the prompt message.
 8941 * - $mdDialogPreset#htmlContent(string) - Sets the prompt message as HTML. Requires ngSanitize
 8942 *     module to be loaded. HTML is not run through Angular's compiler.
 8943 * - $mdDialogPreset#placeholder(string) - Sets the placeholder text for the input.
 8944 * - $mdDialogPreset#initialValue(string) - Sets the initial value for the prompt input.
 8945 * - $mdDialogPreset#ok(string) - Sets the prompt "Okay" button text.
 8946 * - $mdDialogPreset#cancel(string) - Sets the prompt "Cancel" button text.
 8947 * - $mdDialogPreset#theme(string) - Sets the theme of the prompt dialog.
 8948 * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option,
 8949 *     the location of the click will be used as the starting point for the opening animation
 8950 *     of the the dialog.
 8951 *
 8952 */
 8953
 8954/**
 8955 * @ngdoc method
 8956 * @name $mdDialog#show
 8957 *
 8958 * @description
 8959 * Show a dialog with the specified options.
 8960 *
 8961 * @param {object} optionsOrPreset Either provide an `$mdDialogPreset` returned from `alert()`, and
 8962 * `confirm()`, or an options object with the following properties:
 8963 *   - `templateUrl` - `{string=}`: The url of a template that will be used as the content
 8964 *   of the dialog.
 8965 *   - `template` - `{string=}`: HTML template to show in the dialog. This **must** be trusted HTML
 8966 *      with respect to Angular's [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
 8967 *      This template should **never** be constructed with any kind of user input or user data.
 8968 *   - `contentElement` - `{string|Element}`: Instead of using a template, which will be compiled each time a
 8969 *     dialog opens, you can also use a DOM element.<br/>
 8970 *     * When specifying an element, which is present on the DOM, `$mdDialog` will temporary fetch the element into
 8971 *       the dialog and restores it at the old DOM position upon close.
 8972 *     * When specifying a string, the string be used as a CSS selector, to lookup for the element in the DOM.
 8973 *   - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template with a
 8974 *     `<md-dialog>` tag if one is not provided. Defaults to true. Can be disabled if you provide a
 8975 *     custom dialog directive.
 8976 *   - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option,
 8977 *     the location of the click will be used as the starting point for the opening animation
 8978 *     of the the dialog.
 8979 *   - `openFrom` - `{string|Element|object}`: The query selector, DOM element or the Rect object
 8980 *     that is used to determine the bounds (top, left, height, width) from which the Dialog will
 8981 *     originate.
 8982 *   - `closeTo` - `{string|Element|object}`: The query selector, DOM element or the Rect object
 8983 *     that is used to determine the bounds (top, left, height, width) to which the Dialog will
 8984 *     target.
 8985 *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified,
 8986 *     it will create a new isolate scope.
 8987 *     This scope will be destroyed when the dialog is removed unless `preserveScope` is set to true.
 8988 *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
 8989 *   - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the dialog is open.
 8990 *     Default true.
 8991 *   - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog.
 8992 *     Default true.
 8993 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the dialog to
 8994 *     close it. Default false.
 8995 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the dialog.
 8996 *     Default true.
 8997 *   - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on open. Only disable if
 8998 *     focusing some other way, as focus management is required for dialogs to be accessible.
 8999 *     Defaults to true.
 9000 *   - `controller` - `{function|string=}`: The controller to associate with the dialog. The controller
 9001 *     will be injected with the local `$mdDialog`, which passes along a scope for the dialog.
 9002 *   - `locals` - `{object=}`: An object containing key/value pairs. The keys will be used as names
 9003 *     of values to inject into the controller. For example, `locals: {three: 3}` would inject
 9004 *     `three` into the controller, with the value 3. If `bindToController` is true, they will be
 9005 *     copied to the controller instead.
 9006 *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
 9007 *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values, and the
 9008 *     dialog will not open until all of the promises resolve.
 9009 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
 9010 *   - `parent` - `{element=}`: The element to append the dialog to. Defaults to appending
 9011 *     to the root element of the application.
 9012 *   - `onShowing` - `function(scope, element)`: Callback function used to announce the show() action is
 9013 *     starting.
 9014 *   - `onComplete` - `function(scope, element)`: Callback function used to announce when the show() action is
 9015 *     finished.
 9016 *   - `onRemoving` - `function(element, removePromise)`: Callback function used to announce the
 9017 *      close/hide() action is starting. This allows developers to run custom animations
 9018 *      in parallel the close animations.
 9019 *   - `fullscreen` `{boolean=}`: An option to toggle whether the dialog should show in fullscreen
 9020 *      or not. Defaults to `false`.
 9021 * @returns {promise} A promise that can be resolved with `$mdDialog.hide()` or
 9022 * rejected with `$mdDialog.cancel()`.
 9023 */
 9024
 9025/**
 9026 * @ngdoc method
 9027 * @name $mdDialog#hide
 9028 *
 9029 * @description
 9030 * Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`.
 9031 *
 9032 * @param {*=} response An argument for the resolved promise.
 9033 *
 9034 * @returns {promise} A promise that is resolved when the dialog has been closed.
 9035 */
 9036
 9037/**
 9038 * @ngdoc method
 9039 * @name $mdDialog#cancel
 9040 *
 9041 * @description
 9042 * Hide an existing dialog and reject the promise returned from `$mdDialog.show()`.
 9043 *
 9044 * @param {*=} response An argument for the rejected promise.
 9045 *
 9046 * @returns {promise} A promise that is resolved when the dialog has been closed.
 9047 */
 9048
 9049function MdDialogProvider($$interimElementProvider) {
 9050  // Elements to capture and redirect focus when the user presses tab at the dialog boundary.
 9051  advancedDialogOptions.$inject = ["$mdDialog", "$mdConstant"];
 9052  dialogDefaultOptions.$inject = ["$mdDialog", "$mdAria", "$mdUtil", "$mdConstant", "$animate", "$document", "$window", "$rootElement", "$log", "$injector", "$mdTheming"];
 9053  var topFocusTrap, bottomFocusTrap;
 9054
 9055  return $$interimElementProvider('$mdDialog')
 9056    .setDefaults({
 9057      methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose',
 9058          'targetEvent', 'closeTo', 'openFrom', 'parent', 'fullscreen', 'contentElement'],
 9059      options: dialogDefaultOptions
 9060    })
 9061    .addPreset('alert', {
 9062      methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'theme',
 9063          'css'],
 9064      options: advancedDialogOptions
 9065    })
 9066    .addPreset('confirm', {
 9067      methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'cancel',
 9068          'theme', 'css'],
 9069      options: advancedDialogOptions
 9070    })
 9071    .addPreset('prompt', {
 9072      methods: ['title', 'htmlContent', 'textContent', 'initialValue', 'content', 'placeholder', 'ariaLabel',
 9073          'ok', 'cancel', 'theme', 'css'],
 9074      options: advancedDialogOptions
 9075    });
 9076
 9077  /* @ngInject */
 9078  function advancedDialogOptions($mdDialog, $mdConstant) {
 9079    return {
 9080      template: [
 9081        '<md-dialog md-theme="{{ dialog.theme }}" aria-label="{{ dialog.ariaLabel }}" ng-class="dialog.css">',
 9082        '  <md-dialog-content class="md-dialog-content" role="document" tabIndex="-1">',
 9083        '    <h2 class="md-title">{{ dialog.title }}</h2>',
 9084        '    <div ng-if="::dialog.mdHtmlContent" class="md-dialog-content-body" ',
 9085        '        ng-bind-html="::dialog.mdHtmlContent"></div>',
 9086        '    <div ng-if="::!dialog.mdHtmlContent" class="md-dialog-content-body">',
 9087        '      <p>{{::dialog.mdTextContent}}</p>',
 9088        '    </div>',
 9089        '    <md-input-container md-no-float ng-if="::dialog.$type == \'prompt\'" class="md-prompt-input-container">',
 9090        '      <input ng-keypress="dialog.keypress($event)" md-autofocus ng-model="dialog.result" ' +
 9091        '             placeholder="{{::dialog.placeholder}}">',
 9092        '    </md-input-container>',
 9093        '  </md-dialog-content>',
 9094        '  <md-dialog-actions>',
 9095        '    <md-button ng-if="dialog.$type === \'confirm\' || dialog.$type === \'prompt\'"' +
 9096        '               ng-click="dialog.abort()" class="md-primary md-cancel-button">',
 9097        '      {{ dialog.cancel }}',
 9098        '    </md-button>',
 9099        '    <md-button ng-click="dialog.hide()" class="md-primary md-confirm-button" md-autofocus="dialog.$type===\'alert\'">',
 9100        '      {{ dialog.ok }}',
 9101        '    </md-button>',
 9102        '  </md-dialog-actions>',
 9103        '</md-dialog>'
 9104      ].join('').replace(/\s\s+/g, ''),
 9105      controller: function mdDialogCtrl() {
 9106        var isPrompt = this.$type == 'prompt';
 9107
 9108        if (isPrompt && this.initialValue) {
 9109          this.result = this.initialValue;
 9110        }
 9111
 9112        this.hide = function() {
 9113          $mdDialog.hide(isPrompt ? this.result : true);
 9114        };
 9115        this.abort = function() {
 9116          $mdDialog.cancel();
 9117        };
 9118        this.keypress = function($event) {
 9119          if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) {
 9120            $mdDialog.hide(this.result);
 9121          }
 9122        };
 9123      },
 9124      controllerAs: 'dialog',
 9125      bindToController: true,
 9126    };
 9127  }
 9128
 9129  /* @ngInject */
 9130  function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document, $window, $rootElement,
 9131                                $log, $injector, $mdTheming) {
 9132
 9133    return {
 9134      hasBackdrop: true,
 9135      isolateScope: true,
 9136      onCompiling: beforeCompile,
 9137      onShow: onShow,
 9138      onShowing: beforeShow,
 9139      onRemove: onRemove,
 9140      clickOutsideToClose: false,
 9141      escapeToClose: true,
 9142      targetEvent: null,
 9143      contentElement: null,
 9144      closeTo: null,
 9145      openFrom: null,
 9146      focusOnOpen: true,
 9147      disableParentScroll: true,
 9148      autoWrap: true,
 9149      fullscreen: false,
 9150      transformTemplate: function(template, options) {
 9151        // Make the dialog container focusable, because otherwise the focus will be always redirected to
 9152        // an element outside of the container, and the focus trap won't work probably..
 9153        // Also the tabindex is needed for the `escapeToClose` functionality, because
 9154        // the keyDown event can't be triggered when the focus is outside of the container.
 9155        return '<div class="md-dialog-container" tabindex="-1">' + validatedTemplate(template) + '</div>';
 9156
 9157        /**
 9158         * The specified template should contain a <md-dialog> wrapper element....
 9159         */
 9160        function validatedTemplate(template) {
 9161          if (options.autoWrap && !/<\/md-dialog>/g.test(template)) {
 9162            return '<md-dialog>' + (template || '') + '</md-dialog>';
 9163          } else {
 9164            return template || '';
 9165          }
 9166        }
 9167      }
 9168    };
 9169
 9170    function beforeCompile(options) {
 9171      // Automatically apply the theme, if the user didn't specify a theme explicitly.
 9172      // Those option changes need to be done, before the compilation has started, because otherwise
 9173      // the option changes will be not available in the $mdCompilers locales.
 9174      detectTheming(options);
 9175
 9176      if (options.contentElement) {
 9177        options.restoreContentElement = installContentElement(options);
 9178      }
 9179    }
 9180
 9181    function beforeShow(scope, element, options, controller) {
 9182
 9183      if (controller) {
 9184        controller.mdHtmlContent = controller.htmlContent || options.htmlContent || '';
 9185        controller.mdTextContent = controller.textContent || options.textContent ||
 9186            controller.content || options.content || '';
 9187
 9188        if (controller.mdHtmlContent && !$injector.has('$sanitize')) {
 9189          throw Error('The ngSanitize module must be loaded in order to use htmlContent.');
 9190        }
 9191
 9192        if (controller.mdHtmlContent && controller.mdTextContent) {
 9193          throw Error('md-dialog cannot have both `htmlContent` and `textContent`');
 9194        }
 9195      }
 9196    }
 9197
 9198    /** Show method for dialogs */
 9199    function onShow(scope, element, options, controller) {
 9200      angular.element($document[0].body).addClass('md-dialog-is-showing');
 9201
 9202      var dialogElement = element.find('md-dialog');
 9203
 9204      // Once a dialog has `ng-cloak` applied on his template the dialog animation will not work properly.
 9205      // This is a very common problem, so we have to notify the developer about this.
 9206      if (dialogElement.hasClass('ng-cloak')) {
 9207        var message = '$mdDialog: using `<md-dialog ng-cloak >` will affect the dialog opening animations.';
 9208        $log.warn( message, element[0] );
 9209      }
 9210
 9211      captureParentAndFromToElements(options);
 9212      configureAria(dialogElement, options);
 9213      showBackdrop(scope, element, options);
 9214      activateListeners(element, options);
 9215
 9216      return dialogPopIn(element, options)
 9217        .then(function() {
 9218          lockScreenReader(element, options);
 9219          warnDeprecatedActions();
 9220          focusOnOpen();
 9221        });
 9222
 9223      /**
 9224       * Check to see if they used the deprecated .md-actions class and log a warning
 9225       */
 9226      function warnDeprecatedActions() {
 9227        if (element[0].querySelector('.md-actions')) {
 9228          $log.warn('Using a class of md-actions is deprecated, please use <md-dialog-actions>.');
 9229        }
 9230      }
 9231
 9232      /**
 9233       * For alerts, focus on content... otherwise focus on
 9234       * the close button (or equivalent)
 9235       */
 9236      function focusOnOpen() {
 9237        if (options.focusOnOpen) {
 9238          var target = $mdUtil.findFocusTarget(element) || findCloseButton() || dialogElement;
 9239          target.focus();
 9240        }
 9241
 9242        /**
 9243         * If no element with class dialog-close, try to find the last
 9244         * button child in md-actions and assume it is a close button.
 9245         *
 9246         * If we find no actions at all, log a warning to the console.
 9247         */
 9248        function findCloseButton() {
 9249          var closeButton = element[0].querySelector('.dialog-close');
 9250
 9251          if (!closeButton) {
 9252            var actionButtons = element[0].querySelectorAll('.md-actions button, md-dialog-actions button');
 9253            closeButton = actionButtons[actionButtons.length - 1];
 9254          }
 9255
 9256          return closeButton;
 9257        }
 9258      }
 9259    }
 9260
 9261    /**
 9262     * Remove function for all dialogs
 9263     */
 9264    function onRemove(scope, element, options) {
 9265      options.deactivateListeners();
 9266      options.unlockScreenReader();
 9267      options.hideBackdrop(options.$destroy);
 9268
 9269      // Remove the focus traps that we added earlier for keeping focus within the dialog.
 9270      if (topFocusTrap && topFocusTrap.parentNode) {
 9271        topFocusTrap.parentNode.removeChild(topFocusTrap);
 9272      }
 9273
 9274      if (bottomFocusTrap && bottomFocusTrap.parentNode) {
 9275        bottomFocusTrap.parentNode.removeChild(bottomFocusTrap);
 9276      }
 9277
 9278      // For navigation $destroy events, do a quick, non-animated removal,
 9279      // but for normal closes (from clicks, etc) animate the removal
 9280      return !!options.$destroy ? detachAndClean() : animateRemoval().then( detachAndClean );
 9281
 9282      /**
 9283       * For normal closes, animate the removal.
 9284       * For forced closes (like $destroy events), skip the animations
 9285       */
 9286      function animateRemoval() {
 9287        return dialogPopOut(element, options);
 9288      }
 9289
 9290      /**
 9291       * Detach the element
 9292       */
 9293      function detachAndClean() {
 9294        angular.element($document[0].body).removeClass('md-dialog-is-showing');
 9295        // Only remove the element, if it's not provided through the contentElement option.
 9296        if (!options.contentElement) {
 9297          element.remove();
 9298        } else {
 9299          options.reverseContainerStretch();
 9300          options.restoreContentElement();
 9301        }
 9302
 9303        if (!options.$destroy) options.origin.focus();
 9304      }
 9305    }
 9306
 9307    function detectTheming(options) {
 9308      // Only detect the theming, if the developer didn't specify the theme specifically.
 9309      if (options.theme) return;
 9310
 9311      options.theme = $mdTheming.defaultTheme();
 9312
 9313      if (options.targetEvent && options.targetEvent.target) {
 9314        var targetEl = angular.element(options.targetEvent.target);
 9315
 9316        // Once the user specifies a targetEvent, we will automatically try to find the correct
 9317        // nested theme.
 9318        options.theme = (targetEl.controller('mdTheme') || {}).$mdTheme || options.theme;
 9319      }
 9320
 9321    }
 9322
 9323    /**
 9324     * Installs a content element to the current $$interimElement provider options.
 9325     * @returns {Function} Function to restore the content element at its old DOM location.
 9326     */
 9327    function installContentElement(options) {
 9328      var contentEl = options.contentElement;
 9329      var restoreFn = null;
 9330
 9331      if (angular.isString(contentEl)) {
 9332        contentEl = document.querySelector(contentEl);
 9333        restoreFn = createRestoreFn(contentEl);
 9334      } else {
 9335        contentEl = contentEl[0] || contentEl;
 9336
 9337        // When the element is visible in the DOM, then we restore it at close of the dialog.
 9338        // Otherwise it will be removed from the DOM after close.
 9339        if (document.contains(contentEl)) {
 9340          restoreFn = createRestoreFn(contentEl);
 9341        } else {
 9342          restoreFn = function() {
 9343            contentEl.parentNode.removeChild(contentEl);
 9344          }
 9345        }
 9346      }
 9347
 9348      // Overwrite the options to use the content element.
 9349      options.element = angular.element(contentEl);
 9350      options.skipCompile = true;
 9351
 9352      return restoreFn;
 9353
 9354      function createRestoreFn(element) {
 9355        var parent = element.parentNode;
 9356        var nextSibling = element.nextElementSibling;
 9357
 9358        return function() {
 9359          if (!nextSibling) {
 9360            // When the element didn't had any sibling, then it can be simply appended to the
 9361            // parent, because it plays no role, which index it had before.
 9362            parent.appendChild(element);
 9363          } else {
 9364            // When the element had a sibling, which marks the previous position of the element
 9365            // in the DOM, we insert it correctly before the sibling, to have the same index as
 9366            // before.
 9367            parent.insertBefore(element, nextSibling);
 9368          }
 9369        }
 9370      }
 9371    }
 9372
 9373    /**
 9374     * Capture originator/trigger/from/to element information (if available)
 9375     * and the parent container for the dialog; defaults to the $rootElement
 9376     * unless overridden in the options.parent
 9377     */
 9378    function captureParentAndFromToElements(options) {
 9379          options.origin = angular.extend({
 9380            element: null,
 9381            bounds: null,
 9382            focus: angular.noop
 9383          }, options.origin || {});
 9384
 9385          options.parent   = getDomElement(options.parent, $rootElement);
 9386          options.closeTo  = getBoundingClientRect(getDomElement(options.closeTo));
 9387          options.openFrom = getBoundingClientRect(getDomElement(options.openFrom));
 9388
 9389          if ( options.targetEvent ) {
 9390            options.origin   = getBoundingClientRect(options.targetEvent.target, options.origin);
 9391          }
 9392
 9393
 9394          /**
 9395           * Identify the bounding RECT for the target element
 9396           *
 9397           */
 9398          function getBoundingClientRect (element, orig) {
 9399            var source = angular.element((element || {}));
 9400            if (source && source.length) {
 9401              // Compute and save the target element's bounding rect, so that if the
 9402              // element is hidden when the dialog closes, we can shrink the dialog
 9403              // back to the same position it expanded from.
 9404              //
 9405              // Checking if the source is a rect object or a DOM element
 9406              var bounds = {top:0,left:0,height:0,width:0};
 9407              var hasFn = angular.isFunction(source[0].getBoundingClientRect);
 9408
 9409              return angular.extend(orig || {}, {
 9410                  element : hasFn ? source : undefined,
 9411                  bounds  : hasFn ? source[0].getBoundingClientRect() : angular.extend({}, bounds, source[0]),
 9412                  focus   : angular.bind(source, source.focus),
 9413              });
 9414            }
 9415          }
 9416
 9417          /**
 9418           * If the specifier is a simple string selector, then query for
 9419           * the DOM element.
 9420           */
 9421          function getDomElement(element, defaultElement) {
 9422            if (angular.isString(element)) {
 9423              element = $document[0].querySelector(element);
 9424            }
 9425
 9426            // If we have a reference to a raw dom element, always wrap it in jqLite
 9427            return angular.element(element || defaultElement);
 9428          }
 9429
 9430        }
 9431
 9432    /**
 9433     * Listen for escape keys and outside clicks to auto close
 9434     */
 9435    function activateListeners(element, options) {
 9436      var window = angular.element($window);
 9437      var onWindowResize = $mdUtil.debounce(function() {
 9438        stretchDialogContainerToViewport(element, options);
 9439      }, 60);
 9440
 9441      var removeListeners = [];
 9442      var smartClose = function() {
 9443        // Only 'confirm' dialogs have a cancel button... escape/clickOutside will
 9444        // cancel or fallback to hide.
 9445        var closeFn = ( options.$type == 'alert' ) ? $mdDialog.hide : $mdDialog.cancel;
 9446        $mdUtil.nextTick(closeFn, true);
 9447      };
 9448
 9449      if (options.escapeToClose) {
 9450        var parentTarget = options.parent;
 9451        var keyHandlerFn = function(ev) {
 9452          if (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
 9453            ev.stopPropagation();
 9454            ev.preventDefault();
 9455
 9456            smartClose();
 9457          }
 9458        };
 9459
 9460        // Add keydown listeners
 9461        element.on('keydown', keyHandlerFn);
 9462        parentTarget.on('keydown', keyHandlerFn);
 9463
 9464        // Queue remove listeners function
 9465        removeListeners.push(function() {
 9466
 9467          element.off('keydown', keyHandlerFn);
 9468          parentTarget.off('keydown', keyHandlerFn);
 9469
 9470        });
 9471      }
 9472
 9473      // Register listener to update dialog on window resize
 9474      window.on('resize', onWindowResize);
 9475
 9476      removeListeners.push(function() {
 9477        window.off('resize', onWindowResize);
 9478      });
 9479
 9480      if (options.clickOutsideToClose) {
 9481        var target = element;
 9482        var sourceElem;
 9483
 9484        // Keep track of the element on which the mouse originally went down
 9485        // so that we can only close the backdrop when the 'click' started on it.
 9486        // A simple 'click' handler does not work,
 9487        // it sets the target object as the element the mouse went down on.
 9488        var mousedownHandler = function(ev) {
 9489          sourceElem = ev.target;
 9490        };
 9491
 9492        // We check if our original element and the target is the backdrop
 9493        // because if the original was the backdrop and the target was inside the dialog
 9494        // we don't want to dialog to close.
 9495        var mouseupHandler = function(ev) {
 9496          if (sourceElem === target[0] && ev.target === target[0]) {
 9497            ev.stopPropagation();
 9498            ev.preventDefault();
 9499
 9500            smartClose();
 9501          }
 9502        };
 9503
 9504        // Add listeners
 9505        target.on('mousedown', mousedownHandler);
 9506        target.on('mouseup', mouseupHandler);
 9507
 9508        // Queue remove listeners function
 9509        removeListeners.push(function() {
 9510          target.off('mousedown', mousedownHandler);
 9511          target.off('mouseup', mouseupHandler);
 9512        });
 9513      }
 9514
 9515      // Attach specific `remove` listener handler
 9516      options.deactivateListeners = function() {
 9517        removeListeners.forEach(function(removeFn) {
 9518          removeFn();
 9519        });
 9520        options.deactivateListeners = null;
 9521      };
 9522    }
 9523
 9524    /**
 9525     * Show modal backdrop element...
 9526     */
 9527    function showBackdrop(scope, element, options) {
 9528
 9529      if (options.disableParentScroll) {
 9530        // !! DO this before creating the backdrop; since disableScrollAround()
 9531        //    configures the scroll offset; which is used by mdBackDrop postLink()
 9532        options.restoreScroll = $mdUtil.disableScrollAround(element, options.parent);
 9533      }
 9534
 9535      if (options.hasBackdrop) {
 9536        options.backdrop = $mdUtil.createBackdrop(scope, "md-dialog-backdrop md-opaque");
 9537        $animate.enter(options.backdrop, options.parent);
 9538      }
 9539
 9540      /**
 9541       * Hide modal backdrop element...
 9542       */
 9543      options.hideBackdrop = function hideBackdrop($destroy) {
 9544        if (options.backdrop) {
 9545          if ( !!$destroy ) options.backdrop.remove();
 9546          else              $animate.leave(options.backdrop);
 9547        }
 9548
 9549
 9550        if (options.disableParentScroll) {
 9551          options.restoreScroll();
 9552          delete options.restoreScroll;
 9553        }
 9554
 9555        options.hideBackdrop = null;
 9556      };
 9557    }
 9558
 9559    /**
 9560     * Inject ARIA-specific attributes appropriate for Dialogs
 9561     */
 9562    function configureAria(element, options) {
 9563
 9564      var role = (options.$type === 'alert') ? 'alertdialog' : 'dialog';
 9565      var dialogContent = element.find('md-dialog-content');
 9566      var existingDialogId = element.attr('id');
 9567      var dialogContentId = 'dialogContent_' + (existingDialogId || $mdUtil.nextUid());
 9568
 9569      element.attr({
 9570        'role': role,
 9571        'tabIndex': '-1'
 9572      });
 9573
 9574      if (dialogContent.length === 0) {
 9575        dialogContent = element;
 9576        // If the dialog element already had an ID, don't clobber it.
 9577        if (existingDialogId) {
 9578          dialogContentId = existingDialogId;
 9579        }
 9580      }
 9581
 9582      dialogContent.attr('id', dialogContentId);
 9583      element.attr('aria-describedby', dialogContentId);
 9584
 9585      if (options.ariaLabel) {
 9586        $mdAria.expect(element, 'aria-label', options.ariaLabel);
 9587      }
 9588      else {
 9589        $mdAria.expectAsync(element, 'aria-label', function() {
 9590          var words = dialogContent.text().split(/\s+/);
 9591          if (words.length > 3) words = words.slice(0, 3).concat('...');
 9592          return words.join(' ');
 9593        });
 9594      }
 9595
 9596      // Set up elements before and after the dialog content to capture focus and
 9597      // redirect back into the dialog.
 9598      topFocusTrap = document.createElement('div');
 9599      topFocusTrap.classList.add('md-dialog-focus-trap');
 9600      topFocusTrap.tabIndex = 0;
 9601
 9602      bottomFocusTrap = topFocusTrap.cloneNode(false);
 9603
 9604      // When focus is about to move out of the dialog, we want to intercept it and redirect it
 9605      // back to the dialog element.
 9606      var focusHandler = function() {
 9607        element.focus();
 9608      };
 9609      topFocusTrap.addEventListener('focus', focusHandler);
 9610      bottomFocusTrap.addEventListener('focus', focusHandler);
 9611
 9612      // The top focus trap inserted immeidately before the md-dialog element (as a sibling).
 9613      // The bottom focus trap is inserted at the very end of the md-dialog element (as a child).
 9614      element[0].parentNode.insertBefore(topFocusTrap, element[0]);
 9615      element.after(bottomFocusTrap);
 9616    }
 9617
 9618    /**
 9619     * Prevents screen reader interaction behind modal window
 9620     * on swipe interfaces
 9621     */
 9622    function lockScreenReader(element, options) {
 9623      var isHidden = true;
 9624
 9625      // get raw DOM node
 9626      walkDOM(element[0]);
 9627
 9628      options.unlockScreenReader = function() {
 9629        isHidden = false;
 9630        walkDOM(element[0]);
 9631
 9632        options.unlockScreenReader = null;
 9633      };
 9634
 9635      /**
 9636       * Walk DOM to apply or remove aria-hidden on sibling nodes
 9637       * and parent sibling nodes
 9638       *
 9639       */
 9640      function walkDOM(element) {
 9641        while (element.parentNode) {
 9642          if (element === document.body) {
 9643            return;
 9644          }
 9645          var children = element.parentNode.children;
 9646          for (var i = 0; i < children.length; i++) {
 9647            // skip over child if it is an ascendant of the dialog
 9648            // or a script or style tag
 9649            if (element !== children[i] && !isNodeOneOf(children[i], ['SCRIPT', 'STYLE'])) {
 9650              children[i].setAttribute('aria-hidden', isHidden);
 9651            }
 9652          }
 9653
 9654          walkDOM(element = element.parentNode);
 9655        }
 9656      }
 9657    }
 9658
 9659    /**
 9660     * Ensure the dialog container fill-stretches to the viewport
 9661     */
 9662    function stretchDialogContainerToViewport(container, options) {
 9663      var isFixed = $window.getComputedStyle($document[0].body).position == 'fixed';
 9664      var backdrop = options.backdrop ? $window.getComputedStyle(options.backdrop[0]) : null;
 9665      var height = backdrop ? Math.min($document[0].body.clientHeight, Math.ceil(Math.abs(parseInt(backdrop.height, 10)))) : 0;
 9666
 9667      var previousStyles = {
 9668        top: container.css('top'),
 9669        height: container.css('height')
 9670      };
 9671
 9672      container.css({
 9673        top: (isFixed ? $mdUtil.scrollTop(options.parent) : 0) + 'px',
 9674        height: height ? height + 'px' : '100%'
 9675      });
 9676
 9677      return function() {
 9678        // Reverts the modified styles back to the previous values.
 9679        // This is needed for contentElements, which should have the same styles after close
 9680        // as before.
 9681        container.css(previousStyles);
 9682      };
 9683    }
 9684
 9685    /**
 9686     *  Dialog open and pop-in animation
 9687     */
 9688    function dialogPopIn(container, options) {
 9689      // Add the `md-dialog-container` to the DOM
 9690      options.parent.append(container);
 9691      options.reverseContainerStretch = stretchDialogContainerToViewport(container, options);
 9692
 9693      var dialogEl = container.find('md-dialog');
 9694      var animator = $mdUtil.dom.animator;
 9695      var buildTranslateToOrigin = animator.calculateZoomToOrigin;
 9696      var translateOptions = {transitionInClass: 'md-transition-in', transitionOutClass: 'md-transition-out'};
 9697      var from = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.openFrom || options.origin));
 9698      var to = animator.toTransformCss("");  // defaults to center display (or parent or $rootElement)
 9699
 9700      dialogEl.toggleClass('md-dialog-fullscreen', !!options.fullscreen);
 9701
 9702      return animator
 9703        .translate3d(dialogEl, from, to, translateOptions)
 9704        .then(function(animateReversal) {
 9705
 9706          // Build a reversal translate function synced to this translation...
 9707          options.reverseAnimate = function() {
 9708            delete options.reverseAnimate;
 9709
 9710            if (options.closeTo) {
 9711              // Using the opposite classes to create a close animation to the closeTo element
 9712              translateOptions = {transitionInClass: 'md-transition-out', transitionOutClass: 'md-transition-in'};
 9713              from = to;
 9714              to = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.closeTo));
 9715
 9716              return animator
 9717                .translate3d(dialogEl, from, to,translateOptions);
 9718            }
 9719
 9720            return animateReversal(
 9721              to = animator.toTransformCss(
 9722                // in case the origin element has moved or is hidden,
 9723                // let's recalculate the translateCSS
 9724                buildTranslateToOrigin(dialogEl, options.origin)
 9725              )
 9726            );
 9727
 9728          };
 9729
 9730          // Function to revert the generated animation styles on the dialog element.
 9731          // Useful when using a contentElement instead of a template.
 9732          options.clearAnimate = function() {
 9733            delete options.clearAnimate;
 9734
 9735            // Remove the transition classes, added from $animateCSS, since those can't be removed
 9736            // by reversely running the animator.
 9737            dialogEl.removeClass([
 9738              translateOptions.transitionOutClass,
 9739              translateOptions.transitionInClass
 9740            ].join(' '));
 9741
 9742            // Run the animation reversely to remove the previous added animation styles.
 9743            return animator.translate3d(dialogEl, to, animator.toTransformCss(''), {});
 9744          };
 9745
 9746          return true;
 9747        });
 9748    }
 9749
 9750    /**
 9751     * Dialog close and pop-out animation
 9752     */
 9753    function dialogPopOut(container, options) {
 9754      return options.reverseAnimate().then(function() {
 9755        if (options.contentElement) {
 9756          // When we use a contentElement, we want the element to be the same as before.
 9757          // That means, that we have to clear all the animation properties, like transform.
 9758          options.clearAnimate();
 9759        }
 9760      });
 9761    }
 9762
 9763    /**
 9764     * Utility function to filter out raw DOM nodes
 9765     */
 9766    function isNodeOneOf(elem, nodeTypeArray) {
 9767      if (nodeTypeArray.indexOf(elem.nodeName) !== -1) {
 9768        return true;
 9769      }
 9770    }
 9771
 9772  }
 9773}
 9774
 9775})();
 9776(function(){
 9777"use strict";
 9778
 9779/**
 9780 * @ngdoc module
 9781 * @name material.components.divider
 9782 * @description Divider module!
 9783 */
 9784MdDividerDirective.$inject = ["$mdTheming"];
 9785angular.module('material.components.divider', [
 9786  'material.core'
 9787])
 9788  .directive('mdDivider', MdDividerDirective);
 9789
 9790/**
 9791 * @ngdoc directive
 9792 * @name mdDivider
 9793 * @module material.components.divider
 9794 * @restrict E
 9795 *
 9796 * @description
 9797 * 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.
 9798 *
 9799 * @param {boolean=} md-inset Add this attribute to activate the inset divider style.
 9800 * @usage
 9801 * <hljs lang="html">
 9802 * <md-divider></md-divider>
 9803 *
 9804 * <md-divider md-inset></md-divider>
 9805 * </hljs>
 9806 *
 9807 */
 9808function MdDividerDirective($mdTheming) {
 9809  return {
 9810    restrict: 'E',
 9811    link: $mdTheming
 9812  };
 9813}
 9814
 9815})();
 9816(function(){
 9817"use strict";
 9818
 9819(function() {
 9820  'use strict';
 9821
 9822  /**
 9823   * @ngdoc module
 9824   * @name material.components.fabActions
 9825   */
 9826  MdFabActionsDirective.$inject = ["$mdUtil"];
 9827  angular
 9828    .module('material.components.fabActions', ['material.core'])
 9829    .directive('mdFabActions', MdFabActionsDirective);
 9830
 9831  /**
 9832   * @ngdoc directive
 9833   * @name mdFabActions
 9834   * @module material.components.fabActions
 9835   *
 9836   * @restrict E
 9837   *
 9838   * @description
 9839   * The `<md-fab-actions>` directive is used inside of a `<md-fab-speed-dial>` or
 9840   * `<md-fab-toolbar>` directive to mark an element (or elements) as the actions and setup the
 9841   * proper event listeners.
 9842   *
 9843   * @usage
 9844   * See the `<md-fab-speed-dial>` or `<md-fab-toolbar>` directives for example usage.
 9845   */
 9846  function MdFabActionsDirective($mdUtil) {
 9847    return {
 9848      restrict: 'E',
 9849
 9850      require: ['^?mdFabSpeedDial', '^?mdFabToolbar'],
 9851
 9852      compile: function(element, attributes) {
 9853        var children = element.children();
 9854
 9855        var hasNgRepeat = $mdUtil.prefixer().hasAttribute(children, 'ng-repeat');
 9856
 9857        // Support both ng-repeat and static content
 9858        if (hasNgRepeat) {
 9859          children.addClass('md-fab-action-item');
 9860        } else {
 9861          // Wrap every child in a new div and add a class that we can scale/fling independently
 9862          children.wrap('<div class="md-fab-action-item">');
 9863        }
 9864      }
 9865    }
 9866  }
 9867
 9868})();
 9869
 9870})();
 9871(function(){
 9872"use strict";
 9873
 9874(function() {
 9875  'use strict';
 9876
 9877  MdFabController.$inject = ["$scope", "$element", "$animate", "$mdUtil", "$mdConstant", "$timeout"];
 9878  angular.module('material.components.fabShared', ['material.core'])
 9879    .controller('MdFabController', MdFabController);
 9880
 9881  function MdFabController($scope, $element, $animate, $mdUtil, $mdConstant, $timeout) {
 9882    var vm = this;
 9883
 9884    // NOTE: We use async eval(s) below to avoid conflicts with any existing digest loops
 9885
 9886    vm.open = function() {
 9887      $scope.$evalAsync("vm.isOpen = true");
 9888    };
 9889
 9890    vm.close = function() {
 9891      // Async eval to avoid conflicts with existing digest loops
 9892      $scope.$evalAsync("vm.isOpen = false");
 9893
 9894      // Focus the trigger when the element closes so users can still tab to the next item
 9895      $element.find('md-fab-trigger')[0].focus();
 9896    };
 9897
 9898    // Toggle the open/close state when the trigger is clicked
 9899    vm.toggle = function() {
 9900      $scope.$evalAsync("vm.isOpen = !vm.isOpen");
 9901    };
 9902
 9903    setupDefaults();
 9904    setupListeners();
 9905    setupWatchers();
 9906
 9907    var initialAnimationAttempts = 0;
 9908    fireInitialAnimations();
 9909
 9910    function setupDefaults() {
 9911      // Set the default direction to 'down' if none is specified
 9912      vm.direction = vm.direction || 'down';
 9913
 9914      // Set the default to be closed
 9915      vm.isOpen = vm.isOpen || false;
 9916
 9917      // Start the keyboard interaction at the first action
 9918      resetActionIndex();
 9919
 9920      // Add an animations waiting class so we know not to run
 9921      $element.addClass('md-animations-waiting');
 9922    }
 9923
 9924    function setupListeners() {
 9925      var eventTypes = [
 9926        'click', 'focusin', 'focusout'
 9927      ];
 9928
 9929      // Add our listeners
 9930      angular.forEach(eventTypes, function(eventType) {
 9931        $element.on(eventType, parseEvents);
 9932      });
 9933
 9934      // Remove our listeners when destroyed
 9935      $scope.$on('$destroy', function() {
 9936        angular.forEach(eventTypes, function(eventType) {
 9937          $element.off(eventType, parseEvents);
 9938        });
 9939
 9940        // remove any attached keyboard handlers in case element is removed while
 9941        // speed dial is open
 9942        disableKeyboard();
 9943      });
 9944    }
 9945
 9946    var closeTimeout;
 9947    function parseEvents(event) {
 9948      // If the event is a click, just handle it
 9949      if (event.type == 'click') {
 9950        handleItemClick(event);
 9951      }
 9952
 9953      // If we focusout, set a timeout to close the element
 9954      if (event.type == 'focusout' && !closeTimeout) {
 9955        closeTimeout = $timeout(function() {
 9956          vm.close();
 9957        }, 100, false);
 9958      }
 9959
 9960      // If we see a focusin and there is a timeout about to run, cancel it so we stay open
 9961      if (event.type == 'focusin' && closeTimeout) {
 9962        $timeout.cancel(closeTimeout);
 9963        closeTimeout = null;
 9964      }
 9965    }
 9966
 9967    function resetActionIndex() {
 9968      vm.currentActionIndex = -1;
 9969    }
 9970
 9971    function setupWatchers() {
 9972      // Watch for changes to the direction and update classes/attributes
 9973      $scope.$watch('vm.direction', function(newDir, oldDir) {
 9974        // Add the appropriate classes so we can target the direction in the CSS
 9975        $animate.removeClass($element, 'md-' + oldDir);
 9976        $animate.addClass($element, 'md-' + newDir);
 9977
 9978        // Reset the action index since it may have changed
 9979        resetActionIndex();
 9980      });
 9981
 9982      var trigger, actions;
 9983
 9984      // Watch for changes to md-open
 9985      $scope.$watch('vm.isOpen', function(isOpen) {
 9986        // Reset the action index since it may have changed
 9987        resetActionIndex();
 9988
 9989        // We can't get the trigger/actions outside of the watch because the component hasn't been
 9990        // linked yet, so we wait until the first watch fires to cache them.
 9991        if (!trigger || !actions) {
 9992          trigger = getTriggerElement();
 9993          actions = getActionsElement();
 9994        }
 9995
 9996        if (isOpen) {
 9997          enableKeyboard();
 9998        } else {
 9999          disableKeyboard();
10000        }
10001
10002        var toAdd = isOpen ? 'md-is-open' : '';
10003        var toRemove = isOpen ? '' : 'md-is-open';
10004
10005        // Set the proper ARIA attributes
10006        trigger.attr('aria-haspopup', true);
10007        trigger.attr('aria-expanded', isOpen);
10008        actions.attr('aria-hidden', !isOpen);
10009
10010        // Animate the CSS classes
10011        $animate.setClass($element, toAdd, toRemove);
10012      });
10013    }
10014
10015    function fireInitialAnimations() {
10016      // If the element is actually visible on the screen
10017      if ($element[0].scrollHeight > 0) {
10018        // Fire our animation
10019        $animate.addClass($element, '_md-animations-ready').then(function() {
10020          // Remove the waiting class
10021          $element.removeClass('md-animations-waiting');
10022        });
10023      }
10024
10025      // Otherwise, try for up to 1 second before giving up
10026      else if (initialAnimationAttempts < 10) {
10027        $timeout(fireInitialAnimations, 100);
10028
10029        // Increment our counter
10030        initialAnimationAttempts = initialAnimationAttempts + 1;
10031      }
10032    }
10033
10034    function enableKeyboard() {
10035      $element.on('keydown', keyPressed);
10036
10037      // On the next tick, setup a check for outside clicks; we do this on the next tick to avoid
10038      // clicks/touches that result in the isOpen attribute changing (e.g. a bound radio button)
10039      $mdUtil.nextTick(function() {
10040        angular.element(document).on('click touchend', checkForOutsideClick);
10041      });
10042
10043      // TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but
10044      // this breaks accessibility, especially on mobile, since you have no arrow keys to press
10045      //resetActionTabIndexes();
10046    }
10047
10048    function disableKeyboard() {
10049      $element.off('keydown', keyPressed);
10050      angular.element(document).off('click touchend', checkForOutsideClick);
10051    }
10052
10053    function checkForOutsideClick(event) {
10054      if (event.target) {
10055        var closestTrigger = $mdUtil.getClosest(event.target, 'md-fab-trigger');
10056        var closestActions = $mdUtil.getClosest(event.target, 'md-fab-actions');
10057
10058        if (!closestTrigger && !closestActions) {
10059          vm.close();
10060        }
10061      }
10062    }
10063
10064    function keyPressed(event) {
10065      switch (event.which) {
10066        case $mdConstant.KEY_CODE.ESCAPE: vm.close(); event.preventDefault(); return false;
10067        case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false;
10068        case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false;
10069        case $mdConstant.KEY_CODE.RIGHT_ARROW: doKeyRight(event); return false;
10070        case $mdConstant.KEY_CODE.DOWN_ARROW: doKeyDown(event); return false;
10071      }
10072    }
10073
10074    function doActionPrev(event) {
10075      focusAction(event, -1);
10076    }
10077
10078    function doActionNext(event) {
10079      focusAction(event, 1);
10080    }
10081
10082    function focusAction(event, direction) {
10083      var actions = resetActionTabIndexes();
10084
10085      // Increment/decrement the counter with restrictions
10086      vm.currentActionIndex = vm.currentActionIndex + direction;
10087      vm.currentActionIndex = Math.min(actions.length - 1, vm.currentActionIndex);
10088      vm.currentActionIndex = Math.max(0, vm.currentActionIndex);
10089
10090      // Focus the element
10091      var focusElement =  angular.element(actions[vm.currentActionIndex]).children()[0];
10092      angular.element(focusElement).attr('tabindex', 0);
10093      focusElement.focus();
10094
10095      // Make sure the event doesn't bubble and cause something else
10096      event.preventDefault();
10097      event.stopImmediatePropagation();
10098    }
10099
10100    function resetActionTabIndexes() {
10101      // Grab all of the actions
10102      var actions = getActionsElement()[0].querySelectorAll('.md-fab-action-item');
10103
10104      // Disable all other actions for tabbing
10105      angular.forEach(actions, function(action) {
10106        angular.element(angular.element(action).children()[0]).attr('tabindex', -1);
10107      });
10108
10109      return actions;
10110    }
10111
10112    function doKeyLeft(event) {
10113      if (vm.direction === 'left') {
10114        doActionNext(event);
10115      } else {
10116        doActionPrev(event);
10117      }
10118    }
10119
10120    function doKeyUp(event) {
10121      if (vm.direction === 'down') {
10122        doActionPrev(event);
10123      } else {
10124        doActionNext(event);
10125      }
10126    }
10127
10128    function doKeyRight(event) {
10129      if (vm.direction === 'left') {
10130        doActionPrev(event);
10131      } else {
10132        doActionNext(event);
10133      }
10134    }
10135
10136    function doKeyDown(event) {
10137      if (vm.direction === 'up') {
10138        doActionPrev(event);
10139      } else {
10140        doActionNext(event);
10141      }
10142    }
10143
10144    function isTrigger(element) {
10145      return $mdUtil.getClosest(element, 'md-fab-trigger');
10146    }
10147
10148    function isAction(element) {
10149      return $mdUtil.getClosest(element, 'md-fab-actions');
10150    }
10151
10152    function handleItemClick(event) {
10153      if (isTrigger(event.target)) {
10154        vm.toggle();
10155      }
10156
10157      if (isAction(event.target)) {
10158        vm.close();
10159      }
10160    }
10161
10162    function getTriggerElement() {
10163      return $element.find('md-fab-trigger');
10164    }
10165
10166    function getActionsElement() {
10167      return $element.find('md-fab-actions');
10168    }
10169  }
10170})();
10171
10172})();
10173(function(){
10174"use strict";
10175
10176(function() {
10177  'use strict';
10178
10179  /**
10180   * The duration of the CSS animation in milliseconds.
10181   *
10182   * @type {number}
10183   */
10184  MdFabSpeedDialFlingAnimation.$inject = ["$timeout"];
10185  MdFabSpeedDialScaleAnimation.$inject = ["$timeout"];
10186  var cssAnimationDuration = 300;
10187
10188  /**
10189   * @ngdoc module
10190   * @name material.components.fabSpeedDial
10191   */
10192  angular
10193    // Declare our module
10194    .module('material.components.fabSpeedDial', [
10195      'material.core',
10196      'material.components.fabShared',
10197      'material.components.fabActions'
10198    ])
10199
10200    // Register our directive
10201    .directive('mdFabSpeedDial', MdFabSpeedDialDirective)
10202
10203    // Register our custom animations
10204    .animation('.md-fling', MdFabSpeedDialFlingAnimation)
10205    .animation('.md-scale', MdFabSpeedDialScaleAnimation)
10206
10207    // Register a service for each animation so that we can easily inject them into unit tests
10208    .service('mdFabSpeedDialFlingAnimation', MdFabSpeedDialFlingAnimation)
10209    .service('mdFabSpeedDialScaleAnimation', MdFabSpeedDialScaleAnimation);
10210
10211  /**
10212   * @ngdoc directive
10213   * @name mdFabSpeedDial
10214   * @module material.components.fabSpeedDial
10215   *
10216   * @restrict E
10217   *
10218   * @description
10219   * The `<md-fab-speed-dial>` directive is used to present a series of popup elements (usually
10220   * `<md-button>`s) for quick access to common actions.
10221   *
10222   * There are currently two animations available by applying one of the following classes to
10223   * the component:
10224   *
10225   *  - `md-fling` - The speed dial items appear from underneath the trigger and move into their
10226   *    appropriate positions.
10227   *  - `md-scale` - The speed dial items appear in their proper places by scaling from 0% to 100%.
10228   *
10229   * You may also easily position the trigger by applying one one of the following classes to the
10230   * `<md-fab-speed-dial>` element:
10231   *  - `md-fab-top-left`
10232   *  - `md-fab-top-right`
10233   *  - `md-fab-bottom-left`
10234   *  - `md-fab-bottom-right`
10235   *
10236   * These CSS classes use `position: absolute`, so you need to ensure that the container element
10237   * also uses `position: absolute` or `position: relative` in order for them to work.
10238   *
10239   * Additionally, you may use the standard `ng-mouseenter` and `ng-mouseleave` directives to
10240   * open or close the speed dial. However, if you wish to allow users to hover over the empty
10241   * space where the actions will appear, you must also add the `md-hover-full` class to the speed
10242   * dial element. Without this, the hover effect will only occur on top of the trigger.
10243   *
10244   * See the demos for more information.
10245   *
10246   * ## Troubleshooting
10247   *
10248   * If your speed dial shows the closing animation upon launch, you may need to use `ng-cloak` on
10249   * the parent container to ensure that it is only visible once ready. We have plans to remove this
10250   * necessity in the future.
10251   *
10252   * @usage
10253   * <hljs lang="html">
10254   * <md-fab-speed-dial md-direction="up" class="md-fling">
10255   *   <md-fab-trigger>
10256   *     <md-button aria-label="Add..."><md-icon icon="/img/icons/plus.svg"></md-icon></md-button>
10257   *   </md-fab-trigger>
10258   *
10259   *   <md-fab-actions>
10260   *     <md-button aria-label="Add User">
10261   *       <md-icon icon="/img/icons/user.svg"></md-icon>
10262   *     </md-button>
10263   *
10264   *     <md-button aria-label="Add Group">
10265   *       <md-icon icon="/img/icons/group.svg"></md-icon>
10266   *     </md-button>
10267   *   </md-fab-actions>
10268   * </md-fab-speed-dial>
10269   * </hljs>
10270   *
10271   * @param {string} md-direction From which direction you would like the speed dial to appear
10272   * relative to the trigger element.
10273   * @param {expression=} md-open Programmatically control whether or not the speed-dial is visible.
10274   */
10275  function MdFabSpeedDialDirective() {
10276    return {
10277      restrict: 'E',
10278
10279      scope: {
10280        direction: '@?mdDirection',
10281        isOpen: '=?mdOpen'
10282      },
10283
10284      bindToController: true,
10285      controller: 'MdFabController',
10286      controllerAs: 'vm',
10287
10288      link: FabSpeedDialLink
10289    };
10290
10291    function FabSpeedDialLink(scope, element) {
10292      // Prepend an element to hold our CSS variables so we can use them in the animations below
10293      element.prepend('<div class="_md-css-variables"></div>');
10294    }
10295  }
10296
10297  function MdFabSpeedDialFlingAnimation($timeout) {
10298    function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
10299
10300    function runAnimation(element) {
10301      // Don't run if we are still waiting and we are not ready
10302      if (element.hasClass('md-animations-waiting') && !element.hasClass('_md-animations-ready')) {
10303        return;
10304      }
10305
10306      var el = element[0];
10307      var ctrl = element.controller('mdFabSpeedDial');
10308      var items = el.querySelectorAll('.md-fab-action-item');
10309
10310      // Grab our trigger element
10311      var triggerElement = el.querySelector('md-fab-trigger');
10312
10313      // Grab our element which stores CSS variables
10314      var variablesElement = el.querySelector('._md-css-variables');
10315
10316      // Setup JS variables based on our CSS variables
10317      var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
10318
10319      // Always reset the items to their natural position/state
10320      angular.forEach(items, function(item, index) {
10321        var styles = item.style;
10322
10323        styles.transform = styles.webkitTransform = '';
10324        styles.transitionDelay = '';
10325        styles.opacity = 1;
10326
10327        // Make the items closest to the trigger have the highest z-index
10328        styles.zIndex = (items.length - index) + startZIndex;
10329      });
10330
10331      // Set the trigger to be above all of the actions so they disappear behind it.
10332      triggerElement.style.zIndex = startZIndex + items.length + 1;
10333
10334      // If the control is closed, hide the items behind the trigger
10335      if (!ctrl.isOpen) {
10336        angular.forEach(items, function(item, index) {
10337          var newPosition, axis;
10338          var styles = item.style;
10339
10340          // Make sure to account for differences in the dimensions of the trigger verses the items
10341          // so that we can properly center everything; this helps hide the item's shadows behind
10342          // the trigger.
10343          var triggerItemHeightOffset = (triggerElement.clientHeight - item.clientHeight) / 2;
10344          var triggerItemWidthOffset = (triggerElement.clientWidth - item.clientWidth) / 2;
10345
10346          switch (ctrl.direction) {
10347            case 'up':
10348              newPosition = (item.scrollHeight * (index + 1) + triggerItemHeightOffset);
10349              axis = 'Y';
10350              break;
10351            case 'down':
10352              newPosition = -(item.scrollHeight * (index + 1) + triggerItemHeightOffset);
10353              axis = 'Y';
10354              break;
10355            case 'left':
10356              newPosition = (item.scrollWidth * (index + 1) + triggerItemWidthOffset);
10357              axis = 'X';
10358              break;
10359            case 'right':
10360              newPosition = -(item.scrollWidth * (index + 1) + triggerItemWidthOffset);
10361              axis = 'X';
10362              break;
10363          }
10364
10365          var newTranslate = 'translate' + axis + '(' + newPosition + 'px)';
10366
10367          styles.transform = styles.webkitTransform = newTranslate;
10368        });
10369      }
10370    }
10371
10372    return {
10373      addClass: function(element, className, done) {
10374        if (element.hasClass('md-fling')) {
10375          runAnimation(element);
10376          delayDone(done);
10377        } else {
10378          done();
10379        }
10380      },
10381      removeClass: function(element, className, done) {
10382        runAnimation(element);
10383        delayDone(done);
10384      }
10385    }
10386  }
10387
10388  function MdFabSpeedDialScaleAnimation($timeout) {
10389    function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
10390
10391    var delay = 65;
10392
10393    function runAnimation(element) {
10394      var el = element[0];
10395      var ctrl = element.controller('mdFabSpeedDial');
10396      var items = el.querySelectorAll('.md-fab-action-item');
10397
10398      // Grab our element which stores CSS variables
10399      var variablesElement = el.querySelector('._md-css-variables');
10400
10401      // Setup JS variables based on our CSS variables
10402      var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
10403
10404      // Always reset the items to their natural position/state
10405      angular.forEach(items, function(item, index) {
10406        var styles = item.style,
10407          offsetDelay = index * delay;
10408
10409        styles.opacity = ctrl.isOpen ? 1 : 0;
10410        styles.transform = styles.webkitTransform = ctrl.isOpen ? 'scale(1)' : 'scale(0)';
10411        styles.transitionDelay = (ctrl.isOpen ? offsetDelay : (items.length - offsetDelay)) + 'ms';
10412
10413        // Make the items closest to the trigger have the highest z-index
10414        styles.zIndex = (items.length - index) + startZIndex;
10415      });
10416    }
10417
10418    return {
10419      addClass: function(element, className, done) {
10420        runAnimation(element);
10421        delayDone(done);
10422      },
10423
10424      removeClass: function(element, className, done) {
10425        runAnimation(element);
10426        delayDone(done);
10427      }
10428    }
10429  }
10430})();
10431
10432})();
10433(function(){
10434"use strict";
10435
10436(function() {
10437  'use strict';
10438
10439  /**
10440   * @ngdoc module
10441   * @name material.components.fabToolbar
10442   */
10443  angular
10444    // Declare our module
10445    .module('material.components.fabToolbar', [
10446      'material.core',
10447      'material.components.fabShared',
10448      'material.components.fabActions'
10449    ])
10450
10451    // Register our directive
10452    .directive('mdFabToolbar', MdFabToolbarDirective)
10453
10454    // Register our custom animations
10455    .animation('.md-fab-toolbar', MdFabToolbarAnimation)
10456
10457    // Register a service for the animation so that we can easily inject it into unit tests
10458    .service('mdFabToolbarAnimation', MdFabToolbarAnimation);
10459
10460  /**
10461   * @ngdoc directive
10462   * @name mdFabToolbar
10463   * @module material.components.fabToolbar
10464   *
10465   * @restrict E
10466   *
10467   * @description
10468   *
10469   * The `<md-fab-toolbar>` directive is used present a toolbar of elements (usually `<md-button>`s)
10470   * for quick access to common actions when a floating action button is activated (via click or
10471   * keyboard navigation).
10472   *
10473   * You may also easily position the trigger by applying one one of the following classes to the
10474   * `<md-fab-toolbar>` element:
10475   *  - `md-fab-top-left`
10476   *  - `md-fab-top-right`
10477   *  - `md-fab-bottom-left`
10478   *  - `md-fab-bottom-right`
10479   *
10480   * These CSS classes use `position: absolute`, so you need to ensure that the container element
10481   * also uses `position: absolute` or `position: relative` in order for them to work.
10482   *
10483   * @usage
10484   *
10485   * <hljs lang="html">
10486   * <md-fab-toolbar md-direction='left'>
10487   *   <md-fab-trigger>
10488   *     <md-button aria-label="Add..."><md-icon icon="/img/icons/plus.svg"></md-icon></md-button>
10489   *   </md-fab-trigger>
10490   *
10491   *   <md-toolbar>
10492   *    <md-fab-actions>
10493   *      <md-button aria-label="Add User">
10494   *        <md-icon icon="/img/icons/user.svg"></md-icon>
10495   *      </md-button>
10496   *
10497   *      <md-button aria-label="Add Group">
10498   *        <md-icon icon="/img/icons/group.svg"></md-icon>
10499   *      </md-button>
10500   *    </md-fab-actions>
10501   *   </md-toolbar>
10502   * </md-fab-toolbar>
10503   * </hljs>
10504   *
10505   * @param {string} md-direction From which direction you would like the toolbar items to appear
10506   * relative to the trigger element. Supports `left` and `right` directions.
10507   * @param {expression=} md-open Programmatically control whether or not the toolbar is visible.
10508   */
10509  function MdFabToolbarDirective() {
10510    return {
10511      restrict: 'E',
10512      transclude: true,
10513      template: '<div class="md-fab-toolbar-wrapper">' +
10514      '  <div class="md-fab-toolbar-content" ng-transclude></div>' +
10515      '</div>',
10516
10517      scope: {
10518        direction: '@?mdDirection',
10519        isOpen: '=?mdOpen'
10520      },
10521
10522      bindToController: true,
10523      controller: 'MdFabController',
10524      controllerAs: 'vm',
10525
10526      link: link
10527    };
10528
10529    function link(scope, element, attributes) {
10530      // Add the base class for animations
10531      element.addClass('md-fab-toolbar');
10532
10533      // Prepend the background element to the trigger's button
10534      element.find('md-fab-trigger').find('button')
10535        .prepend('<div class="md-fab-toolbar-background"></div>');
10536    }
10537  }
10538
10539  function MdFabToolbarAnimation() {
10540
10541    function runAnimation(element, className, done) {
10542      // If no className was specified, don't do anything
10543      if (!className) {
10544        return;
10545      }
10546
10547      var el = element[0];
10548      var ctrl = element.controller('mdFabToolbar');
10549
10550      // Grab the relevant child elements
10551      var backgroundElement = el.querySelector('.md-fab-toolbar-background');
10552      var triggerElement = el.querySelector('md-fab-trigger button');
10553      var toolbarElement = el.querySelector('md-toolbar');
10554      var iconElement = el.querySelector('md-fab-trigger button md-icon');
10555      var actions = element.find('md-fab-actions').children();
10556
10557      // If we have both elements, use them to position the new background
10558      if (triggerElement && backgroundElement) {
10559        // Get our variables
10560        var color = window.getComputedStyle(triggerElement).getPropertyValue('background-color');
10561        var width = el.offsetWidth;
10562        var height = el.offsetHeight;
10563
10564        // Make it twice as big as it should be since we scale from the center
10565        var scale = 2 * (width / triggerElement.offsetWidth);
10566
10567        // Set some basic styles no matter what animation we're doing
10568        backgroundElement.style.backgroundColor = color;
10569        backgroundElement.style.borderRadius = width + 'px';
10570
10571        // If we're open
10572        if (ctrl.isOpen) {
10573          // Turn on toolbar pointer events when closed
10574          toolbarElement.style.pointerEvents = 'inherit';
10575
10576          backgroundElement.style.width = triggerElement.offsetWidth + 'px';
10577          backgroundElement.style.height = triggerElement.offsetHeight + 'px';
10578          backgroundElement.style.transform = 'scale(' + scale + ')';
10579
10580          // Set the next close animation to have the proper delays
10581          backgroundElement.style.transitionDelay = '0ms';
10582          iconElement && (iconElement.style.transitionDelay = '.3s');
10583
10584          // Apply a transition delay to actions
10585          angular.forEach(actions, function(action, index) {
10586            action.style.transitionDelay = (actions.length - index) * 25 + 'ms';
10587          });
10588        } else {
10589          // Turn off toolbar pointer events when closed
10590          toolbarElement.style.pointerEvents = 'none';
10591
10592          // Scale it back down to the trigger's size
10593          backgroundElement.style.transform = 'scale(1)';
10594
10595          // Reset the position
10596          backgroundElement.style.top = '0';
10597
10598          if (element.hasClass('md-right')) {
10599            backgroundElement.style.left = '0';
10600            backgroundElement.style.right = null;
10601          }
10602
10603          if (element.hasClass('md-left')) {
10604            backgroundElement.style.right = '0';
10605            backgroundElement.style.left = null;
10606          }
10607
10608          // Set the next open animation to have the proper delays
10609          backgroundElement.style.transitionDelay = '200ms';
10610          iconElement && (iconElement.style.transitionDelay = '0ms');
10611
10612          // Apply a transition delay to actions
10613          angular.forEach(actions, function(action, index) {
10614            action.style.transitionDelay = 200 + (index * 25) + 'ms';
10615          });
10616        }
10617      }
10618    }
10619
10620    return {
10621      addClass: function(element, className, done) {
10622        runAnimation(element, className, done);
10623        done();
10624      },
10625
10626      removeClass: function(element, className, done) {
10627        runAnimation(element, className, done);
10628        done();
10629      }
10630    }
10631  }
10632})();
10633
10634})();
10635(function(){
10636"use strict";
10637
10638/**
10639 * @ngdoc module
10640 * @name material.components.gridList
10641 */
10642GridListController.$inject = ["$mdUtil"];
10643GridLayoutFactory.$inject = ["$mdUtil"];
10644GridListDirective.$inject = ["$interpolate", "$mdConstant", "$mdGridLayout", "$mdMedia"];
10645GridTileDirective.$inject = ["$mdMedia"];
10646angular.module('material.components.gridList', ['material.core'])
10647       .directive('mdGridList', GridListDirective)
10648       .directive('mdGridTile', GridTileDirective)
10649       .directive('mdGridTileFooter', GridTileCaptionDirective)
10650       .directive('mdGridTileHeader', GridTileCaptionDirective)
10651       .factory('$mdGridLayout', GridLayoutFactory);
10652
10653/**
10654 * @ngdoc directive
10655 * @name mdGridList
10656 * @module material.components.gridList
10657 * @restrict E
10658 * @description
10659 * Grid lists are an alternative to standard list views. Grid lists are distinct
10660 * from grids used for layouts and other visual presentations.
10661 *
10662 * A grid list is best suited to presenting a homogenous data type, typically
10663 * images, and is optimized for visual comprehension and differentiating between
10664 * like data types.
10665 *
10666 * A grid list is a continuous element consisting of tessellated, regular
10667 * subdivisions called cells that contain tiles (`md-grid-tile`).
10668 *
10669 * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7OVlEaXZ5YmU1Xzg/components_grids_usage2.png"
10670 *    style="width: 300px; height: auto; margin-right: 16px;" alt="Concept of grid explained visually">
10671 * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7VGhsOE5idWlJWXM/components_grids_usage3.png"
10672 *    style="width: 300px; height: auto;" alt="Grid concepts legend">
10673 *
10674 * Cells are arrayed vertically and horizontally within the grid.
10675 *
10676 * Tiles hold content and can span one or more cells vertically or horizontally.
10677 *
10678 * ### Responsive Attributes
10679 *
10680 * The `md-grid-list` directive supports "responsive" attributes, which allow
10681 * different `md-cols`, `md-gutter` and `md-row-height` values depending on the
10682 * currently matching media query.
10683 *
10684 * In order to set a responsive attribute, first define the fallback value with
10685 * the standard attribute name, then add additional attributes with the
10686 * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
10687 * (ie. `md-cols-lg="8"`)
10688 *
10689 * @param {number} md-cols Number of columns in the grid.
10690 * @param {string} md-row-height One of
10691 * <ul>
10692 *   <li>CSS length - Fixed height rows (eg. `8px` or `1rem`)</li>
10693 *   <li>`{width}:{height}` - Ratio of width to height (eg.
10694 *   `md-row-height="16:9"`)</li>
10695 *   <li>`"fit"` - Height will be determined by subdividing the available
10696 *   height by the number of rows</li>
10697 * </ul>
10698 * @param {string=} md-gutter The amount of space between tiles in CSS units
10699 *     (default 1px)
10700 * @param {expression=} md-on-layout Expression to evaluate after layout. Event
10701 *     object is available as `$event`, and contains performance information.
10702 *
10703 * @usage
10704 * Basic:
10705 * <hljs lang="html">
10706 * <md-grid-list md-cols="5" md-gutter="1em" md-row-height="4:3">
10707 *   <md-grid-tile></md-grid-tile>
10708 * </md-grid-list>
10709 * </hljs>
10710 *
10711 * Fixed-height rows:
10712 * <hljs lang="html">
10713 * <md-grid-list md-cols="4" md-row-height="200px" ...>
10714 *   <md-grid-tile></md-grid-tile>
10715 * </md-grid-list>
10716 * </hljs>
10717 *
10718 * Fit rows:
10719 * <hljs lang="html">
10720 * <md-grid-list md-cols="4" md-row-height="fit" style="height: 400px;" ...>
10721 *   <md-grid-tile></md-grid-tile>
10722 * </md-grid-list>
10723 * </hljs>
10724 *
10725 * Using responsive attributes:
10726 * <hljs lang="html">
10727 * <md-grid-list
10728 *     md-cols-sm="2"
10729 *     md-cols-md="4"
10730 *     md-cols-lg="8"
10731 *     md-cols-gt-lg="12"
10732 *     ...>
10733 *   <md-grid-tile></md-grid-tile>
10734 * </md-grid-list>
10735 * </hljs>
10736 */
10737function GridListDirective($interpolate, $mdConstant, $mdGridLayout, $mdMedia) {
10738  return {
10739    restrict: 'E',
10740    controller: GridListController,
10741    scope: {
10742      mdOnLayout: '&'
10743    },
10744    link: postLink
10745  };
10746
10747  function postLink(scope, element, attrs, ctrl) {
10748    element.addClass('_md');     // private md component indicator for styling
10749    
10750    // Apply semantics
10751    element.attr('role', 'list');
10752
10753    // Provide the controller with a way to trigger layouts.
10754    ctrl.layoutDelegate = layoutDelegate;
10755
10756    var invalidateLayout = angular.bind(ctrl, ctrl.invalidateLayout),
10757        unwatchAttrs = watchMedia();
10758      scope.$on('$destroy', unwatchMedia);
10759
10760    /**
10761     * Watches for changes in media, invalidating layout as necessary.
10762     */
10763    function watchMedia() {
10764      for (var mediaName in $mdConstant.MEDIA) {
10765        $mdMedia(mediaName); // initialize
10766        $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
10767            .addListener(invalidateLayout);
10768      }
10769      return $mdMedia.watchResponsiveAttributes(
10770          ['md-cols', 'md-row-height', 'md-gutter'], attrs, layoutIfMediaMatch);
10771    }
10772
10773    function unwatchMedia() {
10774      ctrl.layoutDelegate = angular.noop;
10775
10776      unwatchAttrs();
10777      for (var mediaName in $mdConstant.MEDIA) {
10778        $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
10779            .removeListener(invalidateLayout);
10780      }
10781    }
10782
10783    /**
10784     * Performs grid layout if the provided mediaName matches the currently
10785     * active media type.
10786     */
10787    function layoutIfMediaMatch(mediaName) {
10788      if (mediaName == null) {
10789        // TODO(shyndman): It would be nice to only layout if we have
10790        // instances of attributes using this media type
10791        ctrl.invalidateLayout();
10792      } else if ($mdMedia(mediaName)) {
10793        ctrl.invalidateLayout();
10794      }
10795    }
10796
10797    var lastLayoutProps;
10798
10799    /**
10800     * Invokes the layout engine, and uses its results to lay out our
10801     * tile elements.
10802     *
10803     * @param {boolean} tilesInvalidated Whether tiles have been
10804     *    added/removed/moved since the last layout. This is to avoid situations
10805     *    where tiles are replaced with properties identical to their removed
10806     *    counterparts.
10807     */
10808    function layoutDelegate(tilesInvalidated) {
10809      var tiles = getTileElements();
10810      var props = {
10811        tileSpans: getTileSpans(tiles),
10812        colCount: getColumnCount(),
10813        rowMode: getRowMode(),
10814        rowHeight: getRowHeight(),
10815        gutter: getGutter()
10816      };
10817
10818      if (!tilesInvalidated && angular.equals(props, lastLayoutProps)) {
10819        return;
10820      }
10821
10822      var performance =
10823        $mdGridLayout(props.colCount, props.tileSpans, tiles)
10824          .map(function(tilePositions, rowCount) {
10825            return {
10826              grid: {
10827                element: element,
10828                style: getGridStyle(props.colCount, rowCount,
10829                    props.gutter, props.rowMode, props.rowHeight)
10830              },
10831              tiles: tilePositions.map(function(ps, i) {
10832                return {
10833                  element: angular.element(tiles[i]),
10834                  style: getTileStyle(ps.position, ps.spans,
10835                      props.colCount, rowCount,
10836                      props.gutter, props.rowMode, props.rowHeight)
10837                }
10838              })
10839            }
10840          })
10841          .reflow()
10842          .performance();
10843
10844      // Report layout
10845      scope.mdOnLayout({
10846        $event: {
10847          performance: performance
10848        }
10849      });
10850
10851      lastLayoutProps = props;
10852    }
10853
10854    // Use $interpolate to do some simple string interpolation as a convenience.
10855
10856    var startSymbol = $interpolate.startSymbol();
10857    var endSymbol = $interpolate.endSymbol();
10858
10859    // Returns an expression wrapped in the interpolator's start and end symbols.
10860    function expr(exprStr) {
10861      return startSymbol + exprStr + endSymbol;
10862    }
10863
10864    // The amount of space a single 1x1 tile would take up (either width or height), used as
10865    // a basis for other calculations. This consists of taking the base size percent (as would be
10866    // if evenly dividing the size between cells), and then subtracting the size of one gutter.
10867    // However, since there are no gutters on the edges, each tile only uses a fration
10868    // (gutterShare = numGutters / numCells) of the gutter size. (Imagine having one gutter per
10869    // tile, and then breaking up the extra gutter on the edge evenly among the cells).
10870    var UNIT = $interpolate(expr('share') + '% - (' + expr('gutter') + ' * ' + expr('gutterShare') + ')');
10871
10872    // The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value.
10873    // The position comes the size of a 1x1 tile plus gutter for each previous tile in the
10874    // row/column (offset).
10875    var POSITION  = $interpolate('calc((' + expr('unit') + ' + ' + expr('gutter') + ') * ' + expr('offset') + ')');
10876
10877    // The actual size of a tile, e.g., width or height, taking rowSpan or colSpan into account.
10878    // This is computed by multiplying the base unit by the rowSpan/colSpan, and then adding back
10879    // in the space that the gutter would normally have used (which was already accounted for in
10880    // the base unit calculation).
10881    var DIMENSION = $interpolate('calc((' + expr('unit') + ') * ' + expr('span') + ' + (' + expr('span') + ' - 1) * ' + expr('gutter') + ')');
10882
10883    /**
10884     * Gets the styles applied to a tile element described by the given parameters.
10885     * @param {{row: number, col: number}} position The row and column indices of the tile.
10886     * @param {{row: number, col: number}} spans The rowSpan and colSpan of the tile.
10887     * @param {number} colCount The number of columns.
10888     * @param {number} rowCount The number of rows.
10889     * @param {string} gutter The amount of space between tiles. This will be something like
10890     *     '5px' or '2em'.
10891     * @param {string} rowMode The row height mode. Can be one of:
10892     *     'fixed': all rows have a fixed size, given by rowHeight,
10893     *     'ratio': row height defined as a ratio to width, or
10894     *     'fit': fit to the grid-list element height, divinding evenly among rows.
10895     * @param {string|number} rowHeight The height of a row. This is only used for 'fixed' mode and
10896     *     for 'ratio' mode. For 'ratio' mode, this is the *ratio* of width-to-height (e.g., 0.75).
10897     * @returns {Object} Map of CSS properties to be applied to the style element. Will define
10898     *     values for top, left, width, height, marginTop, and paddingTop.
10899     */
10900    function getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) {
10901      // TODO(shyndman): There are style caching opportunities here.
10902
10903      // Percent of the available horizontal space that one column takes up.
10904      var hShare = (1 / colCount) * 100;
10905
10906      // Fraction of the gutter size that each column takes up.
10907      var hGutterShare = (colCount - 1) / colCount;
10908
10909      // Base horizontal size of a column.
10910      var hUnit = UNIT({share: hShare, gutterShare: hGutterShare, gutter: gutter});
10911
10912      // The width and horizontal position of each tile is always calculated the same way, but the
10913      // height and vertical position depends on the rowMode.
10914      var style = {
10915        left: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }),
10916        width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }),
10917        // resets
10918        paddingTop: '',
10919        marginTop: '',
10920        top: '',
10921        height: ''
10922      };
10923
10924      switch (rowMode) {
10925        case 'fixed':
10926          // In fixed mode, simply use the given rowHeight.
10927          style.top = POSITION({ unit: rowHeight, offset: position.row, gutter: gutter });
10928          style.height = DIMENSION({ unit: rowHeight, span: spans.row, gutter: gutter });
10929          break;
10930
10931        case 'ratio':
10932          // Percent of the available vertical space that one row takes up. Here, rowHeight holds
10933          // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333.
10934          var vShare = hShare / rowHeight;
10935
10936          // Base veritcal size of a row.
10937          var vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
10938
10939          // padidngTop and marginTop are used to maintain the given aspect ratio, as
10940          // a percentage-based value for these properties is applied to the *width* of the
10941          // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
10942          style.paddingTop = DIMENSION({ unit: vUnit, span: spans.row, gutter: gutter});
10943          style.marginTop = POSITION({ unit: vUnit, offset: position.row, gutter: gutter });
10944          break;
10945
10946        case 'fit':
10947          // Fraction of the gutter size that each column takes up.
10948          var vGutterShare = (rowCount - 1) / rowCount;
10949
10950          // Percent of the available vertical space that one row takes up.
10951          var vShare = (1 / rowCount) * 100;
10952
10953          // Base vertical size of a row.
10954          var vUnit = UNIT({share: vShare, gutterShare: vGutterShare, gutter: gutter});
10955
10956          style.top = POSITION({unit: vUnit, offset: position.row, gutter: gutter});
10957          style.height = DIMENSION({unit: vUnit, span: spans.row, gutter: gutter});
10958          break;
10959      }
10960
10961      return style;
10962    }
10963
10964    function getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) {
10965      var style = {};
10966
10967      switch(rowMode) {
10968        case 'fixed':
10969          style.height = DIMENSION({ unit: rowHeight, span: rowCount, gutter: gutter });
10970          style.paddingBottom = '';
10971          break;
10972
10973        case 'ratio':
10974          // rowHeight is width / height
10975          var hGutterShare = colCount === 1 ? 0 : (colCount - 1) / colCount,
10976              hShare = (1 / colCount) * 100,
10977              vShare = hShare * (1 / rowHeight),
10978              vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
10979
10980          style.height = '';
10981          style.paddingBottom = DIMENSION({ unit: vUnit, span: rowCount, gutter: gutter});
10982          break;
10983
10984        case 'fit':
10985          // noop, as the height is user set
10986          break;
10987      }
10988
10989      return style;
10990    }
10991
10992    function getTileElements() {
10993      return [].filter.call(element.children(), function(ele) {
10994        return ele.tagName == 'MD-GRID-TILE' && !ele.$$mdDestroyed;
10995      });
10996    }
10997
10998    /**
10999     * Gets an array of objects containing the rowspan and colspan for each tile.
11000     * @returns {Array<{row: number, col: number}>}
11001     */
11002    function getTileSpans(tileElements) {
11003      return [].map.call(tileElements, function(ele) {
11004        var ctrl = angular.element(ele).controller('mdGridTile');
11005        return {
11006          row: parseInt(
11007              $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-rowspan'), 10) || 1,
11008          col: parseInt(
11009              $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-colspan'), 10) || 1
11010        };
11011      });
11012    }
11013
11014    function getColumnCount() {
11015      var colCount = parseInt($mdMedia.getResponsiveAttribute(attrs, 'md-cols'), 10);
11016      if (isNaN(colCount)) {
11017        throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value';
11018      }
11019      return colCount;
11020    }
11021
11022    function getGutter() {
11023      return applyDefaultUnit($mdMedia.getResponsiveAttribute(attrs, 'md-gutter') || 1);
11024    }
11025
11026    function getRowHeight() {
11027      var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
11028      if (!rowHeight) {
11029        throw 'md-grid-list: md-row-height attribute was not found';
11030      }
11031
11032      switch (getRowMode()) {
11033        case 'fixed':
11034          return applyDefaultUnit(rowHeight);
11035        case 'ratio':
11036          var whRatio = rowHeight.split(':');
11037          return parseFloat(whRatio[0]) / parseFloat(whRatio[1]);
11038        case 'fit':
11039          return 0; // N/A
11040      }
11041    }
11042
11043    function getRowMode() {
11044      var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
11045      if (!rowHeight) {
11046        throw 'md-grid-list: md-row-height attribute was not found';
11047      }
11048
11049      if (rowHeight == 'fit') {
11050        return 'fit';
11051      } else if (rowHeight.indexOf(':') !== -1) {
11052        return 'ratio';
11053      } else {
11054        return 'fixed';
11055      }
11056    }
11057
11058    function applyDefaultUnit(val) {
11059      return /\D$/.test(val) ? val : val + 'px';
11060    }
11061  }
11062}
11063
11064/* @ngInject */
11065function GridListController($mdUtil) {
11066  this.layoutInvalidated = false;
11067  this.tilesInvalidated = false;
11068  this.$timeout_ = $mdUtil.nextTick;
11069  this.layoutDelegate = angular.noop;
11070}
11071
11072GridListController.prototype = {
11073  invalidateTiles: function() {
11074    this.tilesInvalidated = true;
11075    this.invalidateLayout();
11076  },
11077
11078  invalidateLayout: function() {
11079    if (this.layoutInvalidated) {
11080      return;
11081    }
11082    this.layoutInvalidated = true;
11083    this.$timeout_(angular.bind(this, this.layout));
11084  },
11085
11086  layout: function() {
11087    try {
11088      this.layoutDelegate(this.tilesInvalidated);
11089    } finally {
11090      this.layoutInvalidated = false;
11091      this.tilesInvalidated = false;
11092    }
11093  }
11094};
11095
11096
11097/* @ngInject */
11098function GridLayoutFactory($mdUtil) {
11099  var defaultAnimator = GridTileAnimator;
11100
11101  /**
11102   * Set the reflow animator callback
11103   */
11104  GridLayout.animateWith = function(customAnimator) {
11105    defaultAnimator = !angular.isFunction(customAnimator) ? GridTileAnimator : customAnimator;
11106  };
11107
11108  return GridLayout;
11109
11110  /**
11111   * Publish layout function
11112   */
11113  function GridLayout(colCount, tileSpans) {
11114      var self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime;
11115
11116      layoutTime = $mdUtil.time(function() {
11117        layoutInfo = calculateGridFor(colCount, tileSpans);
11118      });
11119
11120      return self = {
11121
11122        /**
11123         * An array of objects describing each tile's position in the grid.
11124         */
11125        layoutInfo: function() {
11126          return layoutInfo;
11127        },
11128
11129        /**
11130         * Maps grid positioning to an element and a set of styles using the
11131         * provided updateFn.
11132         */
11133        map: function(updateFn) {
11134          mapTime = $mdUtil.time(function() {
11135            var info = self.layoutInfo();
11136            gridStyles = updateFn(info.positioning, info.rowCount);
11137          });
11138          return self;
11139        },
11140
11141        /**
11142         * Default animator simply sets the element.css( <styles> ). An alternate
11143         * animator can be provided as an argument. The function has the following
11144         * signature:
11145         *
11146         *    function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>)
11147         */
11148        reflow: function(animatorFn) {
11149          reflowTime = $mdUtil.time(function() {
11150            var animator = animatorFn || defaultAnimator;
11151            animator(gridStyles.grid, gridStyles.tiles);
11152          });
11153          return self;
11154        },
11155
11156        /**
11157         * Timing for the most recent layout run.
11158         */
11159        performance: function() {
11160          return {
11161            tileCount: tileSpans.length,
11162            layoutTime: layoutTime,
11163            mapTime: mapTime,
11164            reflowTime: reflowTime,
11165            totalTime: layoutTime + mapTime + reflowTime
11166          };
11167        }
11168      };
11169    }
11170
11171  /**
11172   * Default Gridlist animator simple sets the css for each element;
11173   * NOTE: any transitions effects must be manually set in the CSS.
11174   * e.g.
11175   *
11176   *  md-grid-tile {
11177   *    transition: all 700ms ease-out 50ms;
11178   *  }
11179   *
11180   */
11181  function GridTileAnimator(grid, tiles) {
11182    grid.element.css(grid.style);
11183    tiles.forEach(function(t) {
11184      t.element.css(t.style);
11185    })
11186  }
11187
11188  /**
11189   * Calculates the positions of tiles.
11190   *
11191   * The algorithm works as follows:
11192   *    An Array<Number> with length colCount (spaceTracker) keeps track of
11193   *    available tiling positions, where elements of value 0 represents an
11194   *    empty position. Space for a tile is reserved by finding a sequence of
11195   *    0s with length <= than the tile's colspan. When such a space has been
11196   *    found, the occupied tile positions are incremented by the tile's
11197   *    rowspan value, as these positions have become unavailable for that
11198   *    many rows.
11199   *
11200   *    If the end of a row has been reached without finding space for the
11201   *    tile, spaceTracker's elements are each decremented by 1 to a minimum
11202   *    of 0. Rows are searched in this fashion until space is found.
11203   */
11204  function calculateGridFor(colCount, tileSpans) {
11205    var curCol = 0,
11206        curRow = 0,
11207        spaceTracker = newSpaceTracker();
11208
11209    return {
11210      positioning: tileSpans.map(function(spans, i) {
11211        return {
11212          spans: spans,
11213          position: reserveSpace(spans, i)
11214        };
11215      }),
11216      rowCount: curRow + Math.max.apply(Math, spaceTracker)
11217    };
11218
11219    function reserveSpace(spans, i) {
11220      if (spans.col > colCount) {
11221        throw 'md-grid-list: Tile at position ' + i + ' has a colspan ' +
11222            '(' + spans.col + ') that exceeds the column count ' +
11223            '(' + colCount + ')';
11224      }
11225
11226      var start = 0,
11227          end = 0;
11228
11229      // TODO(shyndman): This loop isn't strictly necessary if you can
11230      // determine the minimum number of rows before a space opens up. To do
11231      // this, recognize that you've iterated across an entire row looking for
11232      // space, and if so fast-forward by the minimum rowSpan count. Repeat
11233      // until the required space opens up.
11234      while (end - start < spans.col) {
11235        if (curCol >= colCount) {
11236          nextRow();
11237          continue;
11238        }
11239
11240        start = spaceTracker.indexOf(0, curCol);
11241        if (start === -1 || (end = findEnd(start + 1)) === -1) {
11242          start = end = 0;
11243          nextRow();
11244          continue;
11245        }
11246
11247        curCol = end + 1;
11248      }
11249
11250      adjustRow(start, spans.col, spans.row);
11251      curCol = start + spans.col;
11252
11253      return {
11254        col: start,
11255        row: curRow
11256      };
11257    }
11258
11259    function nextRow() {
11260      curCol = 0;
11261      curRow++;
11262      adjustRow(0, colCount, -1); // Decrement row spans by one
11263    }
11264
11265    function adjustRow(from, cols, by) {
11266      for (var i = from; i < from + cols; i++) {
11267        spaceTracker[i] = Math.max(spaceTracker[i] + by, 0);
11268      }
11269    }
11270
11271    function findEnd(start) {
11272      var i;
11273      for (i = start; i < spaceTracker.length; i++) {
11274        if (spaceTracker[i] !== 0) {
11275          return i;
11276        }
11277      }
11278
11279      if (i === spaceTracker.length) {
11280        return i;
11281      }
11282    }
11283
11284    function newSpaceTracker() {
11285      var tracker = [];
11286      for (var i = 0; i < colCount; i++) {
11287        tracker.push(0);
11288      }
11289      return tracker;
11290    }
11291  }
11292}
11293
11294/**
11295 * @ngdoc directive
11296 * @name mdGridTile
11297 * @module material.components.gridList
11298 * @restrict E
11299 * @description
11300 * Tiles contain the content of an `md-grid-list`. They span one or more grid
11301 * cells vertically or horizontally, and use `md-grid-tile-{footer,header}` to
11302 * display secondary content.
11303 *
11304 * ### Responsive Attributes
11305 *
11306 * The `md-grid-tile` directive supports "responsive" attributes, which allow
11307 * different `md-rowspan` and `md-colspan` values depending on the currently
11308 * matching media query.
11309 *
11310 * In order to set a responsive attribute, first define the fallback value with
11311 * the standard attribute name, then add additional attributes with the
11312 * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
11313 * (ie. `md-colspan-sm="4"`)
11314 *
11315 * @param {number=} md-colspan The number of columns to span (default 1). Cannot
11316 *    exceed the number of columns in the grid. Supports interpolation.
11317 * @param {number=} md-rowspan The number of rows to span (default 1). Supports
11318 *     interpolation.
11319 *
11320 * @usage
11321 * With header:
11322 * <hljs lang="html">
11323 * <md-grid-tile>
11324 *   <md-grid-tile-header>
11325 *     <h3>This is a header</h3>
11326 *   </md-grid-tile-header>
11327 * </md-grid-tile>
11328 * </hljs>
11329 *
11330 * With footer:
11331 * <hljs lang="html">
11332 * <md-grid-tile>
11333 *   <md-grid-tile-footer>
11334 *     <h3>This is a footer</h3>
11335 *   </md-grid-tile-footer>
11336 * </md-grid-tile>
11337 * </hljs>
11338 *
11339 * Spanning multiple rows/columns:
11340 * <hljs lang="html">
11341 * <md-grid-tile md-colspan="2" md-rowspan="3">
11342 * </md-grid-tile>
11343 * </hljs>
11344 *
11345 * Responsive attributes:
11346 * <hljs lang="html">
11347 * <md-grid-tile md-colspan="1" md-colspan-sm="3" md-colspan-md="5">
11348 * </md-grid-tile>
11349 * </hljs>
11350 */
11351function GridTileDirective($mdMedia) {
11352  return {
11353    restrict: 'E',
11354    require: '^mdGridList',
11355    template: '<figure ng-transclude></figure>',
11356    transclude: true,
11357    scope: {},
11358    // Simple controller that exposes attributes to the grid directive
11359    controller: ["$attrs", function($attrs) {
11360      this.$attrs = $attrs;
11361    }],
11362    link: postLink
11363  };
11364
11365  function postLink(scope, element, attrs, gridCtrl) {
11366    // Apply semantics
11367    element.attr('role', 'listitem');
11368
11369    // If our colspan or rowspan changes, trigger a layout
11370    var unwatchAttrs = $mdMedia.watchResponsiveAttributes(['md-colspan', 'md-rowspan'],
11371        attrs, angular.bind(gridCtrl, gridCtrl.invalidateLayout));
11372
11373    // Tile registration/deregistration
11374    gridCtrl.invalidateTiles();
11375    scope.$on('$destroy', function() {
11376      // Mark the tile as destroyed so it is no longer considered in layout,
11377      // even if the DOM element sticks around (like during a leave animation)
11378      element[0].$$mdDestroyed = true;
11379      unwatchAttrs();
11380      gridCtrl.invalidateLayout();
11381    });
11382
11383    if (angular.isDefined(scope.$parent.$index)) {
11384      scope.$watch(function() { return scope.$parent.$index; },
11385        function indexChanged(newIdx, oldIdx) {
11386          if (newIdx === oldIdx) {
11387            return;
11388          }
11389          gridCtrl.invalidateTiles();
11390        });
11391    }
11392  }
11393}
11394
11395
11396function GridTileCaptionDirective() {
11397  return {
11398    template: '<figcaption ng-transclude></figcaption>',
11399    transclude: true
11400  };
11401}
11402
11403})();
11404(function(){
11405"use strict";
11406
11407/**
11408 * @ngdoc module
11409 * @name material.components.icon
11410 * @description
11411 * Icon
11412 */
11413angular.module('material.components.icon', ['material.core']);
11414
11415})();
11416(function(){
11417"use strict";
11418
11419/**
11420 * @ngdoc module
11421 * @name material.components.input
11422 */
11423mdInputContainerDirective.$inject = ["$mdTheming", "$parse"];
11424inputTextareaDirective.$inject = ["$mdUtil", "$window", "$mdAria", "$timeout", "$mdGesture"];
11425mdMaxlengthDirective.$inject = ["$animate", "$mdUtil"];
11426placeholderDirective.$inject = ["$compile"];
11427ngMessageDirective.$inject = ["$mdUtil"];
11428mdSelectOnFocusDirective.$inject = ["$timeout"];
11429mdInputInvalidMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil"];
11430ngMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil"];
11431ngMessageAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil"];
11432angular.module('material.components.input', [
11433    'material.core'
11434  ])
11435  .directive('mdInputContainer', mdInputContainerDirective)
11436  .directive('label', labelDirective)
11437  .directive('input', inputTextareaDirective)
11438  .directive('textarea', inputTextareaDirective)
11439  .directive('mdMaxlength', mdMaxlengthDirective)
11440  .directive('placeholder', placeholderDirective)
11441  .directive('ngMessages', ngMessagesDirective)
11442  .directive('ngMessage', ngMessageDirective)
11443  .directive('ngMessageExp', ngMessageDirective)
11444  .directive('mdSelectOnFocus', mdSelectOnFocusDirective)
11445
11446  .animation('.md-input-invalid', mdInputInvalidMessagesAnimation)
11447  .animation('.md-input-messages-animation', ngMessagesAnimation)
11448  .animation('.md-input-message-animation', ngMessageAnimation)
11449
11450  // Register a service for each animation so that we can easily inject them into unit tests
11451  .service('mdInputInvalidAnimation', mdInputInvalidMessagesAnimation)
11452  .service('mdInputMessagesAnimation', ngMessagesAnimation)
11453  .service('mdInputMessageAnimation', ngMessageAnimation);
11454
11455/**
11456 * @ngdoc directive
11457 * @name mdInputContainer
11458 * @module material.components.input
11459 *
11460 * @restrict E
11461 *
11462 * @description
11463 * `<md-input-container>` is the parent of any input or textarea element.
11464 *
11465 * Input and textarea elements will not behave properly unless the md-input-container
11466 * parent is provided.
11467 *
11468 * A single `<md-input-container>` should contain only one `<input>` element, otherwise it will throw an error.
11469 *
11470 * <b>Exception:</b> Hidden inputs (`<input type="hidden" />`) are ignored and will not throw an error, so
11471 * you may combine these with other inputs.
11472 *
11473 * <b>Note:</b> When using `ngMessages` with your input element, make sure the message and container elements
11474 * are *block* elements, otherwise animations applied to the messages will not look as intended. Either use a `div` and
11475 * apply the `ng-message` and `ng-messages` classes respectively, or use the `md-block` class on your element.
11476 *
11477 * @param md-is-error {expression=} When the given expression evaluates to true, the input container
11478 *   will go into error state. Defaults to erroring if the input has been touched and is invalid.
11479 * @param md-no-float {boolean=} When present, `placeholder` attributes on the input will not be converted to floating
11480 *   labels.
11481 *
11482 * @usage
11483 * <hljs lang="html">
11484 *
11485 * <md-input-container>
11486 *   <label>Username</label>
11487 *   <input type="text" ng-model="user.name">
11488 * </md-input-container>
11489 *
11490 * <md-input-container>
11491 *   <label>Description</label>
11492 *   <textarea ng-model="user.description"></textarea>
11493 * </md-input-container>
11494 *
11495 * </hljs>
11496 *
11497 * <h3>When disabling floating labels</h3>
11498 * <hljs lang="html">
11499 *
11500 * <md-input-container md-no-float>
11501 *   <input type="text" placeholder="Non-Floating Label">
11502 * </md-input-container>
11503 *
11504 * </hljs>
11505 */
11506function mdInputContainerDirective($mdTheming, $parse) {
11507
11508  ContainerCtrl.$inject = ["$scope", "$element", "$attrs", "$animate"];
11509  var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT', 'MD-SELECT'];
11510
11511  var LEFT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
11512    return selectors.concat(['md-icon ~ ' + isel, '.md-icon ~ ' + isel]);
11513  }, []).join(",");
11514
11515  var RIGHT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
11516    return selectors.concat([isel + ' ~ md-icon', isel + ' ~ .md-icon']);
11517  }, []).join(",");
11518
11519  return {
11520    restrict: 'E',
11521    compile: compile,
11522    controller: ContainerCtrl
11523  };
11524
11525  function compile(tElement) {
11526    // Check for both a left & right icon
11527    var leftIcon = tElement[0].querySelector(LEFT_SELECTORS);
11528    var rightIcon = tElement[0].querySelector(RIGHT_SELECTORS);
11529
11530    if (leftIcon) { tElement.addClass('md-icon-left'); }
11531    if (rightIcon) { tElement.addClass('md-icon-right'); }
11532
11533    return function postLink(scope, element) {
11534      $mdTheming(element);
11535    };
11536  }
11537
11538  function ContainerCtrl($scope, $element, $attrs, $animate) {
11539    var self = this;
11540
11541    self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);
11542
11543    self.delegateClick = function() {
11544      self.input.focus();
11545    };
11546    self.element = $element;
11547    self.setFocused = function(isFocused) {
11548      $element.toggleClass('md-input-focused', !!isFocused);
11549    };
11550    self.setHasValue = function(hasValue) {
11551      $element.toggleClass('md-input-has-value', !!hasValue);
11552    };
11553    self.setHasPlaceholder = function(hasPlaceholder) {
11554      $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder);
11555    };
11556    self.setInvalid = function(isInvalid) {
11557      if (isInvalid) {
11558        $animate.addClass($element, 'md-input-invalid');
11559      } else {
11560        $animate.removeClass($element, 'md-input-invalid');
11561      }
11562    };
11563    $scope.$watch(function() {
11564      return self.label && self.input;
11565    }, function(hasLabelAndInput) {
11566      if (hasLabelAndInput && !self.label.attr('for')) {
11567        self.label.attr('for', self.input.attr('id'));
11568      }
11569    });
11570  }
11571}
11572
11573function labelDirective() {
11574  return {
11575    restrict: 'E',
11576    require: '^?mdInputContainer',
11577    link: function(scope, element, attr, containerCtrl) {
11578      if (!containerCtrl || attr.mdNoFloat || element.hasClass('md-container-ignore')) return;
11579
11580      containerCtrl.label = element;
11581      scope.$on('$destroy', function() {
11582        containerCtrl.label = null;
11583      });
11584    }
11585  };
11586}
11587
11588/**
11589 * @ngdoc directive
11590 * @name mdInput
11591 * @restrict E
11592 * @module material.components.input
11593 *
11594 * @description
11595 * You can use any `<input>` or `<textarea>` element as a child of an `<md-input-container>`. This
11596 * allows you to build complex forms for data entry.
11597 *
11598 * When the input is required and uses a floating label, then the label will automatically contain
11599 * an asterisk (`*`).<br/>
11600 * This behavior can be disabled by using the `md-no-asterisk` attribute.
11601 *
11602 * @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is
11603 *   specified, a character counter will be shown underneath the input.<br/><br/>
11604 *   The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
11605 *   want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
11606 *   or maxlength attributes.<br/><br/>
11607 *   **Note:** Only valid for text/string inputs (not numeric).
11608 *
11609 * @param {string=} aria-label Aria-label is required when no label is present.  A warning message
11610 *   will be logged in the console if not present.
11611 * @param {string=} placeholder An alternative approach to using aria-label when the label is not
11612 *   PRESENT. The placeholder text is copied to the aria-label attribute.
11613 * @param md-no-autogrow {boolean=} When present, textareas will not grow automatically.
11614 * @param md-no-asterisk {boolean=} When present, an asterisk will not be appended to the inputs floating label
11615 * @param md-no-resize {boolean=} Disables the textarea resize handle.
11616 * @param {number=} max-rows The maximum amount of rows for a textarea.
11617 * @param md-detect-hidden {boolean=} When present, textareas will be sized properly when they are
11618 *   revealed after being hidden. This is off by default for performance reasons because it
11619 *   guarantees a reflow every digest cycle.
11620 *
11621 * @usage
11622 * <hljs lang="html">
11623 * <md-input-container>
11624 *   <label>Color</label>
11625 *   <input type="text" ng-model="color" required md-maxlength="10">
11626 * </md-input-container>
11627 * </hljs>
11628 *
11629 * <h3>With Errors</h3>
11630 *
11631 * `md-input-container` also supports errors using the standard `ng-messages` directives and
11632 * animates the messages when they become visible using from the `ngEnter`/`ngLeave` events or
11633 * the `ngShow`/`ngHide` events.
11634 *
11635 * By default, the messages will be hidden until the input is in an error state. This is based off
11636 * of the `md-is-error` expression of the `md-input-container`. This gives the user a chance to
11637 * fill out the form before the errors become visible.
11638 *
11639 * <hljs lang="html">
11640 * <form name="colorForm">
11641 *   <md-input-container>
11642 *     <label>Favorite Color</label>
11643 *     <input name="favoriteColor" ng-model="favoriteColor" required>
11644 *     <div ng-messages="colorForm.favoriteColor.$error">
11645 *       <div ng-message="required">This is required!</div>
11646 *     </div>
11647 *   </md-input-container>
11648 * </form>
11649 * </hljs>
11650 *
11651 * We automatically disable this auto-hiding functionality if you provide any of the following
11652 * visibility directives on the `ng-messages` container:
11653 *
11654 *  - `ng-if`
11655 *  - `ng-show`/`ng-hide`
11656 *  - `ng-switch-when`/`ng-switch-default`
11657 *
11658 * You can also disable this functionality manually by adding the `md-auto-hide="false"` expression
11659 * to the `ng-messages` container. This may be helpful if you always want to see the error messages
11660 * or if you are building your own visibilty directive.
11661 *
11662 * _<b>Note:</b> The `md-auto-hide` attribute is a static string that is  only checked upon
11663 * initialization of the `ng-messages` directive to see if it equals the string `false`._
11664 *
11665 * <hljs lang="html">
11666 * <form name="userForm">
11667 *   <md-input-container>
11668 *     <label>Last Name</label>
11669 *     <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
11670 *     <div ng-messages="userForm.lastName.$error" ng-show="userForm.lastName.$dirty">
11671 *       <div ng-message="required">This is required!</div>
11672 *       <div ng-message="md-maxlength">That's too long!</div>
11673 *       <div ng-message="minlength">That's too short!</div>
11674 *     </div>
11675 *   </md-input-container>
11676 *   <md-input-container>
11677 *     <label>Biography</label>
11678 *     <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
11679 *     <div ng-messages="userForm.bio.$error" ng-show="userForm.bio.$dirty">
11680 *       <div ng-message="required">This is required!</div>
11681 *       <div ng-message="md-maxlength">That's too long!</div>
11682 *     </div>
11683 *   </md-input-container>
11684 *   <md-input-container>
11685 *     <input aria-label='title' ng-model='title'>
11686 *   </md-input-container>
11687 *   <md-input-container>
11688 *     <input placeholder='title' ng-model='title'>
11689 *   </md-input-container>
11690 * </form>
11691 * </hljs>
11692 *
11693 * <h3>Notes</h3>
11694 *
11695 * - Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
11696 * - Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
11697 *
11698 * The `md-input` and `md-input-container` directives use very specific positioning to achieve the
11699 * error animation effects. Therefore, it is *not* advised to use the Layout system inside of the
11700 * `<md-input-container>` tags. Instead, use relative or absolute positioning.
11701 *
11702 *
11703 * <h3>Textarea directive</h3>
11704 * The `textarea` element within a `md-input-container` has the following specific behavior:
11705 * - By default the `textarea` grows as the user types. This can be disabled via the `md-no-autogrow`
11706 * attribute.
11707 * - If a `textarea` has the `rows` attribute, it will treat the `rows` as the minimum height and will
11708 * continue growing as the user types. For example a textarea with `rows="3"` will be 3 lines of text
11709 * high initially. If no rows are specified, the directive defaults to 1.
11710 * - The textarea's height gets set on initialization, as well as while the user is typing. In certain situations
11711 * (e.g. while animating) the directive might have been initialized, before the element got it's final height. In
11712 * those cases, you can trigger a resize manually by broadcasting a `md-resize-textarea` event on the scope.
11713 * - If you wan't a `textarea` to stop growing at a certain point, you can specify the `max-rows` attribute.
11714 * - The textarea's bottom border acts as a handle which users can drag, in order to resize the element vertically.
11715 * Once the user has resized a `textarea`, the autogrowing functionality becomes disabled. If you don't want a
11716 * `textarea` to be resizeable by the user, you can add the `md-no-resize` attribute.
11717 */
11718
11719function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) {
11720  return {
11721    restrict: 'E',
11722    require: ['^?mdInputContainer', '?ngModel', '?^form'],
11723    link: postLink
11724  };
11725
11726  function postLink(scope, element, attr, ctrls) {
11727
11728    var containerCtrl = ctrls[0];
11729    var hasNgModel = !!ctrls[1];
11730    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
11731    var parentForm = ctrls[2];
11732    var isReadonly = angular.isDefined(attr.readonly);
11733    var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
11734    var tagName = element[0].tagName.toLowerCase();
11735
11736
11737    if (!containerCtrl) return;
11738    if (attr.type === 'hidden') {
11739      element.attr('aria-hidden', 'true');
11740      return;
11741    } else if (containerCtrl.input) {
11742      if (containerCtrl.input[0].contains(element[0])) {
11743        return;
11744      } else {
11745        throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
11746      }
11747    }
11748    containerCtrl.input = element;
11749
11750    setupAttributeWatchers();
11751
11752    // Add an error spacer div after our input to provide space for the char counter and any ng-messages
11753    var errorsSpacer = angular.element('<div class="md-errors-spacer">');
11754    element.after(errorsSpacer);
11755
11756    if (!containerCtrl.label) {
11757      $mdAria.expect(element, 'aria-label', attr.placeholder);
11758    }
11759
11760    element.addClass('md-input');
11761    if (!element.attr('id')) {
11762      element.attr('id', 'input_' + $mdUtil.nextUid());
11763    }
11764
11765    // This works around a Webkit issue where number inputs, placed in a flexbox, that have
11766    // a `min` and `max` will collapse to about 1/3 of their proper width. Please check #7349
11767    // for more info. Also note that we don't override the `step` if the user has specified it,
11768    // in order to prevent some unexpected behaviour.
11769    if (tagName === 'input' && attr.type === 'number' && attr.min && attr.max && !attr.step) {
11770      element.attr('step', 'any');
11771    } else if (tagName === 'textarea') {
11772      setupTextarea();
11773    }
11774
11775    // If the input doesn't have an ngModel, it may have a static value. For that case,
11776    // we have to do one initial check to determine if the container should be in the
11777    // "has a value" state.
11778    if (!hasNgModel) {
11779      inputCheckValue();
11780    }
11781
11782    var isErrorGetter = containerCtrl.isErrorGetter || function() {
11783      return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
11784    };
11785
11786    scope.$watch(isErrorGetter, containerCtrl.setInvalid);
11787
11788    // When the developer uses the ngValue directive for the input, we have to observe the attribute, because
11789    // Angular's ngValue directive is just setting the `value` attribute.
11790    if (attr.ngValue) {
11791      attr.$observe('value', inputCheckValue);
11792    }
11793
11794    ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
11795    ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);
11796
11797    element.on('input', inputCheckValue);
11798
11799    if (!isReadonly) {
11800      element
11801        .on('focus', function(ev) {
11802          $mdUtil.nextTick(function() {
11803            containerCtrl.setFocused(true);
11804          });
11805        })
11806        .on('blur', function(ev) {
11807          $mdUtil.nextTick(function() {
11808            containerCtrl.setFocused(false);
11809            inputCheckValue();
11810          });
11811        });
11812    }
11813
11814    scope.$on('$destroy', function() {
11815      containerCtrl.setFocused(false);
11816      containerCtrl.setHasValue(false);
11817      containerCtrl.input = null;
11818    });
11819
11820    /** Gets run through ngModel's pipeline and set the `has-value` class on the container. */
11821    function ngModelPipelineCheckValue(arg) {
11822      containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
11823      return arg;
11824    }
11825
11826    function setupAttributeWatchers() {
11827      if (containerCtrl.label) {
11828        attr.$observe('required', function (value) {
11829          // We don't need to parse the required value, it's always a boolean because of angular's
11830          // required directive.
11831          containerCtrl.label.toggleClass('md-required', value && !mdNoAsterisk);
11832        });
11833      }
11834    }
11835
11836    function inputCheckValue() {
11837      // An input's value counts if its length > 0,
11838      // or if the input's validity state says it has bad input (eg string in a number input)
11839      containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity || {}).badInput);
11840    }
11841
11842    function setupTextarea() {
11843      var isAutogrowing = !attr.hasOwnProperty('mdNoAutogrow');
11844
11845      attachResizeHandle();
11846
11847      if (!isAutogrowing) return;
11848
11849      // Can't check if height was or not explicity set,
11850      // so rows attribute will take precedence if present
11851      var minRows = attr.hasOwnProperty('rows') ? parseInt(attr.rows) : NaN;
11852      var maxRows = attr.hasOwnProperty('maxRows') ? parseInt(attr.maxRows) : NaN;
11853      var scopeResizeListener = scope.$on('md-resize-textarea', growTextarea);
11854      var lineHeight = null;
11855      var node = element[0];
11856
11857      // This timeout is necessary, because the browser needs a little bit
11858      // of time to calculate the `clientHeight` and `scrollHeight`.
11859      $timeout(function() {
11860        $mdUtil.nextTick(growTextarea);
11861      }, 10, false);
11862
11863      // We could leverage ngModel's $parsers here, however it
11864      // isn't reliable, because Angular trims the input by default,
11865      // which means that growTextarea won't fire when newlines and
11866      // spaces are added.
11867      element.on('input', growTextarea);
11868
11869      // We should still use the $formatters, because they fire when
11870      // the value was changed from outside the textarea.
11871      if (hasNgModel) {
11872        ngModelCtrl.$formatters.push(formattersListener);
11873      }
11874
11875      if (!minRows) {
11876        element.attr('rows', 1);
11877      }
11878
11879      angular.element($window).on('resize', growTextarea);
11880      scope.$on('$destroy', disableAutogrow);
11881
11882      function growTextarea() {
11883        // temporarily disables element's flex so its height 'runs free'
11884        element
11885          .attr('rows', 1)
11886          .css('height', 'auto')
11887          .addClass('md-no-flex');
11888
11889        var height = getHeight();
11890
11891        if (!lineHeight) {
11892          // offsetHeight includes padding which can throw off our value
11893          var originalPadding = element[0].style.padding || '';
11894          lineHeight = element.css('padding', 0).prop('offsetHeight');
11895          element[0].style.padding = originalPadding;
11896        }
11897
11898        if (minRows && lineHeight) {
11899          height = Math.max(height, lineHeight * minRows);
11900        }
11901
11902        if (maxRows && lineHeight) {
11903          var maxHeight = lineHeight * maxRows;
11904
11905          if (maxHeight < height) {
11906            element.attr('md-no-autogrow', '');
11907            height = maxHeight;
11908          } else {
11909            element.removeAttr('md-no-autogrow');
11910          }
11911        }
11912
11913        if (lineHeight) {
11914          element.attr('rows', Math.round(height / lineHeight));
11915        }
11916
11917        element
11918          .css('height', height + 'px')
11919          .removeClass('md-no-flex');
11920      }
11921
11922      function getHeight() {
11923        var offsetHeight = node.offsetHeight;
11924        var line = node.scrollHeight - offsetHeight;
11925        return offsetHeight + Math.max(line, 0);
11926      }
11927
11928      function formattersListener(value) {
11929        $mdUtil.nextTick(growTextarea);
11930        return value;
11931      }
11932
11933      function disableAutogrow() {
11934        if (!isAutogrowing) return;
11935
11936        isAutogrowing = false;
11937        angular.element($window).off('resize', growTextarea);
11938        scopeResizeListener && scopeResizeListener();
11939        element
11940          .attr('md-no-autogrow', '')
11941          .off('input', growTextarea);
11942
11943        if (hasNgModel) {
11944          var listenerIndex = ngModelCtrl.$formatters.indexOf(formattersListener);
11945
11946          if (listenerIndex > -1) {
11947            ngModelCtrl.$formatters.splice(listenerIndex, 1);
11948          }
11949        }
11950      }
11951
11952      function attachResizeHandle() {
11953        if (attr.hasOwnProperty('mdNoResize')) return;
11954
11955        var handle = angular.element('<div class="md-resize-handle"></div>');
11956        var isDragging = false;
11957        var dragStart = null;
11958        var startHeight = 0;
11959        var container = containerCtrl.element;
11960        var dragGestureHandler = $mdGesture.register(handle, 'drag', { horizontal: false });
11961
11962
11963        element.wrap('<div class="md-resize-wrapper">').after(handle);
11964        handle.on('mousedown', onMouseDown);
11965
11966        container
11967          .on('$md.dragstart', onDragStart)
11968          .on('$md.drag', onDrag)
11969          .on('$md.dragend', onDragEnd);
11970
11971        scope.$on('$destroy', function() {
11972          handle
11973            .off('mousedown', onMouseDown)
11974            .remove();
11975
11976          container
11977            .off('$md.dragstart', onDragStart)
11978            .off('$md.drag', onDrag)
11979            .off('$md.dragend', onDragEnd);
11980
11981          dragGestureHandler();
11982          handle = null;
11983          container = null;
11984          dragGestureHandler = null;
11985        });
11986
11987        function onMouseDown(ev) {
11988          ev.preventDefault();
11989          isDragging = true;
11990          dragStart = ev.clientY;
11991          startHeight = parseFloat(element.css('height')) || element.prop('offsetHeight');
11992        }
11993
11994        function onDragStart(ev) {
11995          if (!isDragging) return;
11996          ev.preventDefault();
11997          disableAutogrow();
11998          container.addClass('md-input-resized');
11999        }
12000
12001        function onDrag(ev) {
12002          if (!isDragging) return;
12003          element.css('height', startHeight + (ev.pointer.y - dragStart) - $mdUtil.scrollTop() + 'px');
12004        }
12005
12006        function onDragEnd(ev) {
12007          if (!isDragging) return;
12008          isDragging = false;
12009          container.removeClass('md-input-resized');
12010        }
12011      }
12012
12013      // Attach a watcher to detect when the textarea gets shown.
12014      if (attr.hasOwnProperty('mdDetectHidden')) {
12015
12016        var handleHiddenChange = function() {
12017          var wasHidden = false;
12018
12019          return function() {
12020            var isHidden = node.offsetHeight === 0;
12021
12022            if (isHidden === false && wasHidden === true) {
12023              growTextarea();
12024            }
12025
12026            wasHidden = isHidden;
12027          };
12028        }();
12029
12030        // Check every digest cycle whether the visibility of the textarea has changed.
12031        // Queue up to run after the digest cycle is complete.
12032        scope.$watch(function() {
12033          $mdUtil.nextTick(handleHiddenChange, false);
12034          return true;
12035        });
12036      }
12037    }
12038  }
12039}
12040
12041function mdMaxlengthDirective($animate, $mdUtil) {
12042  return {
12043    restrict: 'A',
12044    require: ['ngModel', '^mdInputContainer'],
12045    link: postLink
12046  };
12047
12048  function postLink(scope, element, attr, ctrls) {
12049    var maxlength;
12050    var ngModelCtrl = ctrls[0];
12051    var containerCtrl = ctrls[1];
12052    var charCountEl, errorsSpacer;
12053
12054    // Wait until the next tick to ensure that the input has setup the errors spacer where we will
12055    // append our counter
12056    $mdUtil.nextTick(function() {
12057      errorsSpacer = angular.element(containerCtrl.element[0].querySelector('.md-errors-spacer'));
12058      charCountEl = angular.element('<div class="md-char-counter">');
12059
12060      // Append our character counter inside the errors spacer
12061      errorsSpacer.append(charCountEl);
12062
12063      // Stop model from trimming. This makes it so whitespace
12064      // over the maxlength still counts as invalid.
12065      attr.$set('ngTrim', 'false');
12066
12067      ngModelCtrl.$formatters.push(renderCharCount);
12068      ngModelCtrl.$viewChangeListeners.push(renderCharCount);
12069      element.on('input keydown keyup', function() {
12070        renderCharCount(); //make sure it's called with no args
12071      });
12072
12073      scope.$watch(attr.mdMaxlength, function(value) {
12074        maxlength = value;
12075        if (angular.isNumber(value) && value > 0) {
12076          if (!charCountEl.parent().length) {
12077            $animate.enter(charCountEl, errorsSpacer);
12078          }
12079          renderCharCount();
12080        } else {
12081          $animate.leave(charCountEl);
12082        }
12083      });
12084
12085      ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
12086        if (!angular.isNumber(maxlength) || maxlength < 0) {
12087          return true;
12088        }
12089        return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
12090      };
12091    });
12092
12093    function renderCharCount(value) {
12094      // If we have not been appended to the body yet; do not render
12095      if (!charCountEl.parent) {
12096        return value;
12097      }
12098
12099      // Force the value into a string since it may be a number,
12100      // which does not have a length property.
12101      charCountEl.text(String(element.val() || value || '').length + ' / ' + maxlength);
12102      return value;
12103    }
12104  }
12105}
12106
12107function placeholderDirective($compile) {
12108  return {
12109    restrict: 'A',
12110    require: '^^?mdInputContainer',
12111    priority: 200,
12112    link: {
12113      // Note that we need to do this in the pre-link, as opposed to the post link, if we want to
12114      // support data bindings in the placeholder. This is necessary, because we have a case where
12115      // we transfer the placeholder value to the `<label>` and we remove it from the original `<input>`.
12116      // If we did this in the post-link, Angular would have set up the observers already and would be
12117      // re-adding the attribute, even though we removed it from the element.
12118      pre: preLink
12119    }
12120  };
12121
12122  function preLink(scope, element, attr, inputContainer) {
12123    // If there is no input container, just return
12124    if (!inputContainer) return;
12125
12126    var label = inputContainer.element.find('label');
12127    var noFloat = inputContainer.element.attr('md-no-float');
12128
12129    // If we have a label, or they specify the md-no-float attribute, just return
12130    if ((label && label.length) || noFloat === '' || scope.$eval(noFloat)) {
12131      // Add a placeholder class so we can target it in the CSS
12132      inputContainer.setHasPlaceholder(true);
12133      return;
12134    }
12135
12136    // md-select handles placeholders on it's own
12137    if (element[0].nodeName != 'MD-SELECT') {
12138      // Move the placeholder expression to the label
12139      var newLabel = angular.element('<label ng-click="delegateClick()" tabindex="-1">' + attr.placeholder + '</label>');
12140
12141      // Note that we unset it via `attr`, in order to get Angular
12142      // to remove any observers that it might have set up. Otherwise
12143      // the attribute will be added on the next digest.
12144      attr.$set('placeholder', null);
12145
12146      // We need to compile the label manually in case it has any bindings.
12147      // A gotcha here is that we first add the element to the DOM and we compile
12148      // it later. This is necessary, because if we compile the element beforehand,
12149      // it won't be able to find the `mdInputContainer` controller.
12150      inputContainer.element
12151        .addClass('md-icon-float')
12152        .prepend(newLabel);
12153
12154      $compile(newLabel)(scope);
12155    }
12156  }
12157}
12158
12159/**
12160 * @ngdoc directive
12161 * @name mdSelectOnFocus
12162 * @module material.components.input
12163 *
12164 * @restrict A
12165 *
12166 * @description
12167 * The `md-select-on-focus` directive allows you to automatically select the element's input text on focus.
12168 *
12169 * <h3>Notes</h3>
12170 * - The use of `md-select-on-focus` is restricted to `<input>` and `<textarea>` elements.
12171 *
12172 * @usage
12173 * <h3>Using with an Input</h3>
12174 * <hljs lang="html">
12175 *
12176 * <md-input-container>
12177 *   <label>Auto Select</label>
12178 *   <input type="text" md-select-on-focus>
12179 * </md-input-container>
12180 * </hljs>
12181 *
12182 * <h3>Using with a Textarea</h3>
12183 * <hljs lang="html">
12184 *
12185 * <md-input-container>
12186 *   <label>Auto Select</label>
12187 *   <textarea md-select-on-focus>This text will be selected on focus.</textarea>
12188 * </md-input-container>
12189 *
12190 * </hljs>
12191 */
12192function mdSelectOnFocusDirective($timeout) {
12193
12194  return {
12195    restrict: 'A',
12196    link: postLink
12197  };
12198
12199  function postLink(scope, element, attr) {
12200    if (element[0].nodeName !== 'INPUT' && element[0].nodeName !== "TEXTAREA") return;
12201
12202    var preventMouseUp = false;
12203
12204    element
12205      .on('focus', onFocus)
12206      .on('mouseup', onMouseUp);
12207
12208    scope.$on('$destroy', function() {
12209      element
12210        .off('focus', onFocus)
12211        .off('mouseup', onMouseUp);
12212    });
12213
12214    function onFocus() {
12215      preventMouseUp = true;
12216
12217      $timeout(function() {
12218        // Use HTMLInputElement#select to fix firefox select issues.
12219        // The debounce is here for Edge's sake, otherwise the selection doesn't work.
12220        element[0].select();
12221
12222        // This should be reset from inside the `focus`, because the event might
12223        // have originated from something different than a click, e.g. a keyboard event.
12224        preventMouseUp = false;
12225      }, 1, false);
12226    }
12227
12228    // Prevents the default action of the first `mouseup` after a focus.
12229    // This is necessary, because browsers fire a `mouseup` right after the element
12230    // has been focused. In some browsers (Firefox in particular) this can clear the
12231    // selection. There are examples of the problem in issue #7487.
12232    function onMouseUp(event) {
12233      if (preventMouseUp) {
12234        event.preventDefault();
12235      }
12236    }
12237  }
12238}
12239
12240var visibilityDirectives = ['ngIf', 'ngShow', 'ngHide', 'ngSwitchWhen', 'ngSwitchDefault'];
12241function ngMessagesDirective() {
12242  return {
12243    restrict: 'EA',
12244    link: postLink,
12245
12246    // This is optional because we don't want target *all* ngMessage instances, just those inside of
12247    // mdInputContainer.
12248    require: '^^?mdInputContainer'
12249  };
12250
12251  function postLink(scope, element, attrs, inputContainer) {
12252    // If we are not a child of an input container, don't do anything
12253    if (!inputContainer) return;
12254
12255    // Add our animation class
12256    element.toggleClass('md-input-messages-animation', true);
12257
12258    // Add our md-auto-hide class to automatically hide/show messages when container is invalid
12259    element.toggleClass('md-auto-hide', true);
12260
12261    // If we see some known visibility directives, remove the md-auto-hide class
12262    if (attrs.mdAutoHide == 'false' || hasVisibiltyDirective(attrs)) {
12263      element.toggleClass('md-auto-hide', false);
12264    }
12265  }
12266
12267  function hasVisibiltyDirective(attrs) {
12268    return visibilityDirectives.some(function(attr) {
12269      return attrs[attr];
12270    });
12271  }
12272}
12273
12274function ngMessageDirective($mdUtil) {
12275  return {
12276    restrict: 'EA',
12277    compile: compile,
12278    priority: 100
12279  };
12280
12281  function compile(tElement) {
12282    if (!isInsideInputContainer(tElement)) {
12283
12284      // When the current element is inside of a document fragment, then we need to check for an input-container
12285      // in the postLink, because the element will be later added to the DOM and is currently just in a temporary
12286      // fragment, which causes the input-container check to fail.
12287      if (isInsideFragment()) {
12288        return function (scope, element) {
12289          if (isInsideInputContainer(element)) {
12290            // Inside of the postLink function, a ngMessage directive will be a comment element, because it's
12291            // currently hidden. To access the shown element, we need to use the element from the compile function.
12292            initMessageElement(tElement);
12293          }
12294        };
12295      }
12296    } else {
12297      initMessageElement(tElement);
12298    }
12299
12300    function isInsideFragment() {
12301      var nextNode = tElement[0];
12302      while (nextNode = nextNode.parentNode) {
12303        if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
12304          return true;
12305        }
12306      }
12307      return false;
12308    }
12309
12310    function isInsideInputContainer(element) {
12311      return !!$mdUtil.getClosest(element, "md-input-container");
12312    }
12313
12314    function initMessageElement(element) {
12315      // Add our animation class
12316      element.toggleClass('md-input-message-animation', true);
12317    }
12318  }
12319}
12320
12321var $$AnimateRunner, $animateCss, $mdUtil;
12322
12323function mdInputInvalidMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil) {
12324  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil);
12325
12326  return {
12327    addClass: function(element, className, done) {
12328      showInputMessages(element, done);
12329    }
12330
12331    // NOTE: We do not need the removeClass method, because the message ng-leave animation will fire
12332  };
12333}
12334
12335function ngMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil) {
12336  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil);
12337
12338  return {
12339    enter: function(element, done) {
12340      showInputMessages(element, done);
12341    },
12342
12343    leave: function(element, done) {
12344      hideInputMessages(element, done);
12345    },
12346
12347    addClass: function(element, className, done) {
12348      if (className == "ng-hide") {
12349        hideInputMessages(element, done);
12350      } else {
12351        done();
12352      }
12353    },
12354
12355    removeClass: function(element, className, done) {
12356      if (className == "ng-hide") {
12357        showInputMessages(element, done);
12358      } else {
12359        done();
12360      }
12361    }
12362  }
12363}
12364
12365function ngMessageAnimation($$AnimateRunner, $animateCss, $mdUtil) {
12366  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil);
12367
12368  return {
12369    enter: function(element, done) {
12370      var animator = showMessage(element);
12371
12372      animator.start().done(done);
12373    },
12374
12375    leave: function(element, done) {
12376      var animator = hideMessage(element);
12377
12378      animator.start().done(done);
12379    }
12380  }
12381}
12382
12383function showInputMessages(element, done) {
12384  var animators = [], animator;
12385  var messages = getMessagesElement(element);
12386
12387  angular.forEach(messages.children(), function(child) {
12388    animator = showMessage(angular.element(child));
12389
12390    animators.push(animator.start());
12391  });
12392
12393  $$AnimateRunner.all(animators, done);
12394}
12395
12396function hideInputMessages(element, done) {
12397  var animators = [], animator;
12398  var messages = getMessagesElement(element);
12399
12400  angular.forEach(messages.children(), function(child) {
12401    animator = hideMessage(angular.element(child));
12402
12403    animators.push(animator.start());
12404  });
12405
12406  $$AnimateRunner.all(animators, done);
12407}
12408
12409function showMessage(element) {
12410  var height = parseInt(window.getComputedStyle(element[0]).height);
12411  var topMargin = parseInt(window.getComputedStyle(element[0]).marginTop);
12412
12413  var messages = getMessagesElement(element);
12414  var container = getInputElement(element);
12415
12416  // Check to see if the message is already visible so we can skip
12417  var alreadyVisible = (topMargin > -height);
12418
12419  // If we have the md-auto-hide class, the md-input-invalid animation will fire, so we can skip
12420  if (alreadyVisible || (messages.hasClass('md-auto-hide') && !container.hasClass('md-input-invalid'))) {
12421    return $animateCss(element, {});
12422  }
12423
12424  return $animateCss(element, {
12425    event: 'enter',
12426    structural: true,
12427    from: {"opacity": 0, "margin-top": -height + "px"},
12428    to: {"opacity": 1, "margin-top": "0"},
12429    duration: 0.3
12430  });
12431}
12432
12433function hideMessage(element) {
12434  var height = element[0].offsetHeight;
12435  var styles = window.getComputedStyle(element[0]);
12436
12437  // If we are already hidden, just return an empty animation
12438  if (styles.opacity == 0) {
12439    return $animateCss(element, {});
12440  }
12441
12442  // Otherwise, animate
12443  return $animateCss(element, {
12444    event: 'leave',
12445    structural: true,
12446    from: {"opacity": 1, "margin-top": 0},
12447    to: {"opacity": 0, "margin-top": -height + "px"},
12448    duration: 0.3
12449  });
12450}
12451
12452function getInputElement(element) {
12453  var inputContainer = element.controller('mdInputContainer');
12454
12455  return inputContainer.element;
12456}
12457
12458function getMessagesElement(element) {
12459  // If we are a ng-message element, we need to traverse up the DOM tree
12460  if (element.hasClass('md-input-message-animation')) {
12461    return angular.element($mdUtil.getClosest(element, function(node) {
12462      return node.classList.contains('md-input-messages-animation');
12463    }));
12464  }
12465
12466  // Otherwise, we can traverse down
12467  return angular.element(element[0].querySelector('.md-input-messages-animation'));
12468}
12469
12470function saveSharedServices(_$$AnimateRunner_, _$animateCss_, _$mdUtil_) {
12471  $$AnimateRunner = _$$AnimateRunner_;
12472  $animateCss = _$animateCss_;
12473  $mdUtil = _$mdUtil_;
12474}
12475
12476})();
12477(function(){
12478"use strict";
12479
12480/**
12481 * @ngdoc module
12482 * @name material.components.list
12483 * @description
12484 * List module
12485 */
12486MdListController.$inject = ["$scope", "$element", "$mdListInkRipple"];
12487mdListDirective.$inject = ["$mdTheming"];
12488mdListItemDirective.$inject = ["$mdAria", "$mdConstant", "$mdUtil", "$timeout"];
12489angular.module('material.components.list', [
12490  'material.core'
12491])
12492  .controller('MdListController', MdListController)
12493  .directive('mdList', mdListDirective)
12494  .directive('mdListItem', mdListItemDirective);
12495
12496/**
12497 * @ngdoc directive
12498 * @name mdList
12499 * @module material.components.list
12500 *
12501 * @restrict E
12502 *
12503 * @description
12504 * The `<md-list>` directive is a list container for 1..n `<md-list-item>` tags.
12505 *
12506 * @usage
12507 * <hljs lang="html">
12508 * <md-list>
12509 *   <md-list-item class="md-2-line" ng-repeat="item in todos">
12510 *     <md-checkbox ng-model="item.done"></md-checkbox>
12511 *     <div class="md-list-item-text">
12512 *       <h3>{{item.title}}</h3>
12513 *       <p>{{item.description}}</p>
12514 *     </div>
12515 *   </md-list-item>
12516 * </md-list>
12517 * </hljs>
12518 */
12519
12520function mdListDirective($mdTheming) {
12521  return {
12522    restrict: 'E',
12523    compile: function(tEl) {
12524      tEl[0].setAttribute('role', 'list');
12525      return $mdTheming;
12526    }
12527  };
12528}
12529/**
12530 * @ngdoc directive
12531 * @name mdListItem
12532 * @module material.components.list
12533 *
12534 * @restrict E
12535 *
12536 * @description
12537 * A `md-list-item` element can be used to represent some information in a row.<br/>
12538 *
12539 * @usage
12540 * ### Single Row Item
12541 * <hljs lang="html">
12542 *   <md-list-item>
12543 *     <span>Single Row Item</span>
12544 *   </md-list-item>
12545 * </hljs>
12546 *
12547 * ### Multiple Lines
12548 * By using the following markup, you will be able to have two lines inside of one `md-list-item`.
12549 *
12550 * <hljs lang="html">
12551 *   <md-list-item class="md-2-line">
12552 *     <div class="md-list-item-text" layout="column">
12553 *       <p>First Line</p>
12554 *       <p>Second Line</p>
12555 *     </div>
12556 *   </md-list-item>
12557 * </hljs>
12558 *
12559 * It is also possible to have three lines inside of one list item.
12560 *
12561 * <hljs lang="html">
12562 *   <md-list-item class="md-3-line">
12563 *     <div class="md-list-item-text" layout="column">
12564 *       <p>First Line</p>
12565 *       <p>Second Line</p>
12566 *       <p>Third Line</p>
12567 *     </div>
12568 *   </md-list-item>
12569 * </hljs>
12570 *
12571 * ### Secondary Items
12572 * Secondary items are elements which will be aligned at the end of the `md-list-item`.
12573 *
12574 * <hljs lang="html">
12575 *   <md-list-item>
12576 *     <span>Single Row Item</span>
12577 *     <md-button class="md-secondary">
12578 *       Secondary Button
12579 *     </md-button>
12580 *   </md-list-item>
12581 * </hljs>
12582 *
12583 * It also possible to have multiple secondary items inside of one `md-list-item`.
12584 *
12585 * <hljs lang="html">
12586 *   <md-list-item>
12587 *     <span>Single Row Item</span>
12588 *     <md-button class="md-secondary">First Button</md-button>
12589 *     <md-button class="md-secondary">Second Button</md-button>
12590 *   </md-list-item>
12591 * </hljs>
12592 *
12593 * ### Proxy Item
12594 * Proxies are elements, which will execute their specific action on click<br/>
12595 * Currently supported proxy items are
12596 * - `md-checkbox` (Toggle)
12597 * - `md-switch` (Toggle)
12598 * - `md-menu` (Open)
12599 *
12600 * This means, when using a supported proxy item inside of `md-list-item`, the list item will
12601 * become clickable and executes the associated action of the proxy element on click.
12602 *
12603 * <hljs lang="html">
12604 *   <md-list-item>
12605 *     <span>First Line</span>
12606 *     <md-checkbox class="md-secondary"></md-checkbox>
12607 *   </md-list-item>
12608 * </hljs>
12609 *
12610 * The `md-checkbox` element will be automatically detected as a proxy element and will toggle on click.
12611 *
12612 * <hljs lang="html">
12613 *   <md-list-item>
12614 *     <span>First Line</span>
12615 *     <md-switch class="md-secondary"></md-switch>
12616 *   </md-list-item>
12617 * </hljs>
12618 *
12619 * The recognized `md-switch` will toggle its state, when the user clicks on the `md-list-item`.
12620 *
12621 * It is also possible to have a `md-menu` inside of a `md-list-item`.
12622 * <hljs lang="html">
12623 *   <md-list-item>
12624 *     <p>Click anywhere to fire the secondary action</p>
12625 *     <md-menu class="md-secondary">
12626 *       <md-button class="md-icon-button">
12627 *         <md-icon md-svg-icon="communication:message"></md-icon>
12628 *       </md-button>
12629 *       <md-menu-content width="4">
12630 *         <md-menu-item>
12631 *           <md-button>
12632 *             Redial
12633 *           </md-button>
12634 *         </md-menu-item>
12635 *         <md-menu-item>
12636 *           <md-button>
12637 *             Check voicemail
12638 *           </md-button>
12639 *         </md-menu-item>
12640 *         <md-menu-divider></md-menu-divider>
12641 *         <md-menu-item>
12642 *           <md-button>
12643 *             Notifications
12644 *           </md-button>
12645 *         </md-menu-item>
12646 *       </md-menu-content>
12647 *     </md-menu>
12648 *   </md-list-item>
12649 * </hljs>
12650 *
12651 * The menu will automatically open, when the users clicks on the `md-list-item`.<br/>
12652 *
12653 * If the developer didn't specify any position mode on the menu, the `md-list-item` will automatically detect the
12654 * position mode and applies it to the `md-menu`.
12655 *
12656 * ### Avatars
12657 * Sometimes you may want to have some avatars inside of the `md-list-item `.<br/>
12658 * You are able to create a optimized icon for the list item, by applying the `.md-avatar` class on the `<img>` element.
12659 *
12660 * <hljs lang="html">
12661 *   <md-list-item>
12662 *     <img src="my-avatar.png" class="md-avatar">
12663 *     <span>Alan Turing</span>
12664 * </hljs>
12665 *
12666 * When using `<md-icon>` for an avater, you have to use the `.md-avatar-icon` class.
12667 * <hljs lang="html">
12668 *   <md-list-item>
12669 *     <md-icon class="md-avatar-icon" md-svg-icon="avatars:timothy"></md-icon>
12670 *     <span>Timothy Kopra</span>
12671 *   </md-list-item>
12672 * </hljs>
12673 *
12674 * In cases, you have a `md-list-item`, which doesn't have any avatar,
12675 * but you want to align it with the other avatar items, you have to use the `.md-offset` class.
12676 *
12677 * <hljs lang="html">
12678 *   <md-list-item class="md-offset">
12679 *     <span>Jon Doe</span>
12680 *   </md-list-item>
12681 * </hljs>
12682 *
12683 * ### DOM modification
12684 * The `md-list-item` component automatically detects if the list item should be clickable.
12685 *
12686 * ---
12687 * If the `md-list-item` is clickable, we wrap all content inside of a `<div>` and create
12688 * an overlaying button, which will will execute the given actions (like `ng-href`, `ng-click`)
12689 *
12690 * We create an overlaying button, instead of wrapping all content inside of the button,
12691 * because otherwise some elements may not be clickable inside of the button.
12692 *
12693 * ---
12694 * When using a secondary item inside of your list item, the `md-list-item` component will automatically create
12695 * a secondary container at the end of the `md-list-item`, which contains all secondary items.
12696 *
12697 * The secondary item container is not static, because otherwise the overflow will not work properly on the
12698 * list item.
12699 *
12700 */
12701function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
12702  var proxiedTypes = ['md-checkbox', 'md-switch', 'md-menu'];
12703  return {
12704    restrict: 'E',
12705    controller: 'MdListController',
12706    compile: function(tEl, tAttrs) {
12707
12708      // Check for proxy controls (no ng-click on parent, and a control inside)
12709      var secondaryItems = tEl[0].querySelectorAll('.md-secondary');
12710      var hasProxiedElement;
12711      var proxyElement;
12712      var itemContainer = tEl;
12713
12714      tEl[0].setAttribute('role', 'listitem');
12715
12716      if (tAttrs.ngClick || tAttrs.ngDblclick ||  tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref) {
12717        wrapIn('button');
12718      } else {
12719        for (var i = 0, type; type = proxiedTypes[i]; ++i) {
12720          if (proxyElement = tEl[0].querySelector(type)) {
12721            hasProxiedElement = true;
12722            break;
12723          }
12724        }
12725        if (hasProxiedElement) {
12726          wrapIn('div');
12727        } else if (!tEl[0].querySelector('md-button:not(.md-secondary):not(.md-exclude)')) {
12728          tEl.addClass('md-no-proxy');
12729        }
12730      }
12731
12732      wrapSecondaryItems();
12733      setupToggleAria();
12734
12735      if (hasProxiedElement && proxyElement.nodeName === "MD-MENU") {
12736        setupProxiedMenu();
12737      }
12738
12739      function setupToggleAria() {
12740        var toggleTypes = ['md-switch', 'md-checkbox'];
12741        var toggle;
12742
12743        for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) {
12744          if (toggle = tEl.find(toggleType)[0]) {
12745            if (!toggle.hasAttribute('aria-label')) {
12746              var p = tEl.find('p')[0];
12747              if (!p) return;
12748              toggle.setAttribute('aria-label', 'Toggle ' + p.textContent);
12749            }
12750          }
12751        }
12752      }
12753
12754      function setupProxiedMenu() {
12755        var menuEl = angular.element(proxyElement);
12756
12757        var isEndAligned = menuEl.parent().hasClass('md-secondary-container') ||
12758                           proxyElement.parentNode.firstElementChild !== proxyElement;
12759
12760        var xAxisPosition = 'left';
12761
12762        if (isEndAligned) {
12763          // When the proxy item is aligned at the end of the list, we have to set the origin to the end.
12764          xAxisPosition = 'right';
12765        }
12766        
12767        // Set the position mode / origin of the proxied menu.
12768        if (!menuEl.attr('md-position-mode')) {
12769          menuEl.attr('md-position-mode', xAxisPosition + ' target');
12770        }
12771
12772        // Apply menu open binding to menu button
12773        var menuOpenButton = menuEl.children().eq(0);
12774        if (!hasClickEvent(menuOpenButton[0])) {
12775          menuOpenButton.attr('ng-click', '$mdOpenMenu($event)');
12776        }
12777
12778        if (!menuOpenButton.attr('aria-label')) {
12779          menuOpenButton.attr('aria-label', 'Open List Menu');
12780        }
12781      }
12782
12783      function wrapIn(type) {
12784        if (type == 'div') {
12785          itemContainer = angular.element('<div class="md-no-style md-list-item-inner">');
12786          itemContainer.append(tEl.contents());
12787          tEl.addClass('md-proxy-focus');
12788        } else {
12789          // Element which holds the default list-item content.
12790          itemContainer = angular.element(
12791            '<div class="md-button md-no-style">'+
12792            '   <div class="md-list-item-inner"></div>'+
12793            '</div>'
12794          );
12795
12796          // Button which shows ripple and executes primary action.
12797          var buttonWrap = angular.element(
12798            '<md-button class="md-no-style"></md-button>'
12799          );
12800
12801          buttonWrap[0].setAttribute('aria-label', tEl[0].textContent);
12802
12803          copyAttributes(tEl[0], buttonWrap[0]);
12804
12805          // We allow developers to specify the `md-no-focus` class, to disable the focus style
12806          // on the button executor. Once more classes should be forwarded, we should probably make the
12807          // class forward more generic.
12808          if (tEl.hasClass('md-no-focus')) {
12809            buttonWrap.addClass('md-no-focus');
12810          }
12811
12812          // Append the button wrap before our list-item content, because it will overlay in relative.
12813          itemContainer.prepend(buttonWrap);
12814          itemContainer.children().eq(1).append(tEl.contents());
12815
12816          tEl.addClass('_md-button-wrap');
12817        }
12818
12819        tEl[0].setAttribute('tabindex', '-1');
12820        tEl.append(itemContainer);
12821      }
12822
12823      function wrapSecondaryItems() {
12824        var secondaryItemsWrapper = angular.element('<div class="md-secondary-container">');
12825
12826        angular.forEach(secondaryItems, function(secondaryItem) {
12827          wrapSecondaryItem(secondaryItem, secondaryItemsWrapper);
12828        });
12829
12830        itemContainer.append(secondaryItemsWrapper);
12831      }
12832
12833      function wrapSecondaryItem(secondaryItem, container) {
12834        // If the current secondary item is not a button, but contains a ng-click attribute,
12835        // the secondary item will be automatically wrapped inside of a button.
12836        if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) {
12837
12838          $mdAria.expect(secondaryItem, 'aria-label');
12839          var buttonWrapper = angular.element('<md-button class="md-secondary md-icon-button">');
12840
12841          // Copy the attributes from the secondary item to the generated button.
12842          // We also support some additional attributes from the secondary item,
12843          // because some developers may use a ngIf, ngHide, ngShow on their item.
12844          copyAttributes(secondaryItem, buttonWrapper[0], ['ng-if', 'ng-hide', 'ng-show']);
12845
12846          secondaryItem.setAttribute('tabindex', '-1');
12847          buttonWrapper.append(secondaryItem);
12848
12849          secondaryItem = buttonWrapper[0];
12850        }
12851
12852        if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) {
12853          // In this case we remove the secondary class, so we can identify it later, when we searching for the
12854          // proxy items.
12855          angular.element(secondaryItem).removeClass('md-secondary');
12856        }
12857
12858        tEl.addClass('md-with-secondary');
12859        container.append(secondaryItem);
12860      }
12861
12862      /**
12863       * Copies attributes from a source element to the destination element
12864       * By default the function will copy the most necessary attributes, supported
12865       * by the button executor for clickable list items.
12866       * @param source Element with the specified attributes
12867       * @param destination Element which will retrieve the attributes
12868       * @param extraAttrs Additional attributes, which will be copied over.
12869       */
12870      function copyAttributes(source, destination, extraAttrs) {
12871        var copiedAttrs = $mdUtil.prefixer([
12872          'ng-if', 'ng-click', 'ng-dblclick', 'aria-label', 'ng-disabled', 'ui-sref',
12873          'href', 'ng-href', 'target', 'ng-attr-ui-sref', 'ui-sref-opts'
12874        ]);
12875
12876        if (extraAttrs) {
12877          copiedAttrs = copiedAttrs.concat($mdUtil.prefixer(extraAttrs));
12878        }
12879
12880        angular.forEach(copiedAttrs, function(attr) {
12881          if (source.hasAttribute(attr)) {
12882            destination.setAttribute(attr, source.getAttribute(attr));
12883            source.removeAttribute(attr);
12884          }
12885        });
12886      }
12887
12888      function isProxiedElement(el) {
12889        return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1;
12890      }
12891
12892      function isButton(el) {
12893        var nodeName = el.nodeName.toUpperCase();
12894
12895        return nodeName == "MD-BUTTON" || nodeName == "BUTTON";
12896      }
12897
12898      function hasClickEvent (element) {
12899        var attr = element.attributes;
12900        for (var i = 0; i < attr.length; i++) {
12901          if (tAttrs.$normalize(attr[i].name) === 'ngClick') return true;
12902        }
12903        return false;
12904      }
12905
12906      return postLink;
12907
12908      function postLink($scope, $element, $attr, ctrl) {
12909        $element.addClass('_md');     // private md component indicator for styling
12910
12911        var proxies       = [],
12912            firstElement  = $element[0].firstElementChild,
12913            isButtonWrap  = $element.hasClass('_md-button-wrap'),
12914            clickChild    = isButtonWrap ? firstElement.firstElementChild : firstElement,
12915            hasClick      = clickChild && hasClickEvent(clickChild);
12916
12917        computeProxies();
12918        computeClickable();
12919
12920        if ($element.hasClass('md-proxy-focus') && proxies.length) {
12921          angular.forEach(proxies, function(proxy) {
12922            proxy = angular.element(proxy);
12923
12924            $scope.mouseActive = false;
12925            proxy.on('mousedown', function() {
12926              $scope.mouseActive = true;
12927              $timeout(function(){
12928                $scope.mouseActive = false;
12929              }, 100);
12930            })
12931            .on('focus', function() {
12932              if ($scope.mouseActive === false) { $element.addClass('md-focused'); }
12933              proxy.on('blur', function proxyOnBlur() {
12934                $element.removeClass('md-focused');
12935                proxy.off('blur', proxyOnBlur);
12936              });
12937            });
12938          });
12939        }
12940
12941
12942        function computeProxies() {
12943          if (firstElement && firstElement.children && !hasClick) {
12944
12945            angular.forEach(proxiedTypes, function(type) {
12946
12947              // All elements which are not capable for being used a proxy have the .md-secondary class
12948              // applied. These items had been sorted out in the secondary wrap function.
12949              angular.forEach(firstElement.querySelectorAll(type + ':not(.md-secondary)'), function(child) {
12950                proxies.push(child);
12951              });
12952            });
12953
12954          }
12955        }
12956
12957        function computeClickable() {
12958          if (proxies.length == 1 || hasClick) {
12959            $element.addClass('md-clickable');
12960
12961            if (!hasClick) {
12962              ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style')));
12963            }
12964          }
12965        }
12966
12967        function isEventFromControl(event) {
12968          var forbiddenControls = ['md-slider'];
12969
12970          // If there is no path property in the event, then we can assume that the event was not bubbled.
12971          if (!event.path) {
12972            return forbiddenControls.indexOf(event.target.tagName.toLowerCase()) !== -1;
12973          }
12974
12975          // We iterate the event path up and check for a possible component.
12976          // Our maximum index to search, is the list item root.
12977          var maxPath = event.path.indexOf($element.children()[0]);
12978
12979          for (var i = 0; i < maxPath; i++) {
12980            if (forbiddenControls.indexOf(event.path[i].tagName.toLowerCase()) !== -1) {
12981              return true;
12982            }
12983          }
12984        }
12985
12986        var clickChildKeypressListener = function(e) {
12987          if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) {
12988            var keyCode = e.which || e.keyCode;
12989            if (keyCode == $mdConstant.KEY_CODE.SPACE) {
12990              if (clickChild) {
12991                clickChild.click();
12992                e.preventDefault();
12993                e.stopPropagation();
12994              }
12995            }
12996          }
12997        };
12998
12999        if (!hasClick && !proxies.length) {
13000          clickChild && clickChild.addEventListener('keypress', clickChildKeypressListener);
13001        }
13002
13003        $element.off('click');
13004        $element.off('keypress');
13005
13006        if (proxies.length == 1 && clickChild) {
13007          $element.children().eq(0).on('click', function(e) {
13008            // When the event is coming from an control and it should not trigger the proxied element
13009            // then we are skipping.
13010            if (isEventFromControl(e)) return;
13011
13012            var parentButton = $mdUtil.getClosest(e.target, 'BUTTON');
13013            if (!parentButton && clickChild.contains(e.target)) {
13014              angular.forEach(proxies, function(proxy) {
13015                if (e.target !== proxy && !proxy.contains(e.target)) {
13016                  if (proxy.nodeName === 'MD-MENU') {
13017                    proxy = proxy.children[0];
13018                  }
13019                  angular.element(proxy).triggerHandler('click');
13020                }
13021              });
13022            }
13023          });
13024        }
13025
13026        $scope.$on('$destroy', function () {
13027          clickChild && clickChild.removeEventListener('keypress', clickChildKeypressListener);
13028        });
13029      }
13030    }
13031  };
13032}
13033
13034/*
13035 * @private
13036 * @ngdoc controller
13037 * @name MdListController
13038 * @module material.components.list
13039 *
13040 */
13041function MdListController($scope, $element, $mdListInkRipple) {
13042  var ctrl = this;
13043  ctrl.attachRipple = attachRipple;
13044
13045  function attachRipple (scope, element) {
13046    var options = {};
13047    $mdListInkRipple.attach(scope, element, options);
13048  }
13049}
13050
13051})();
13052(function(){
13053"use strict";
13054
13055/**
13056 * @ngdoc module
13057 * @name material.components.menu
13058 */
13059
13060angular.module('material.components.menu', [
13061  'material.core',
13062  'material.components.backdrop'
13063]);
13064
13065})();
13066(function(){
13067"use strict";
13068
13069/**
13070 * @ngdoc module
13071 * @name material.components.menu-bar
13072 */
13073
13074angular.module('material.components.menuBar', [
13075  'material.core',
13076  'material.components.icon',
13077  'material.components.menu'
13078]);
13079
13080})();
13081(function(){
13082"use strict";
13083
13084/**
13085 * @ngdoc module
13086 * @name material.components.navBar
13087 */
13088
13089
13090MdNavBarController.$inject = ["$element", "$scope", "$timeout", "$mdConstant"];
13091MdNavItem.$inject = ["$$rAF"];
13092MdNavItemController.$inject = ["$element"];
13093MdNavBar.$inject = ["$mdAria", "$mdTheming"];
13094angular.module('material.components.navBar', ['material.core'])
13095    .controller('MdNavBarController', MdNavBarController)
13096    .directive('mdNavBar', MdNavBar)
13097    .controller('MdNavItemController', MdNavItemController)
13098    .directive('mdNavItem', MdNavItem);
13099
13100
13101/*****************************************************************************
13102 *                            PUBLIC DOCUMENTATION                           *
13103 *****************************************************************************/
13104/**
13105 * @ngdoc directive
13106 * @name mdNavBar
13107 * @module material.components.navBar
13108 *
13109 * @restrict E
13110 *
13111 * @description
13112 * The `<md-nav-bar>` directive renders a list of material tabs that can be used
13113 * for top-level page navigation. Unlike `<md-tabs>`, it has no concept of a tab
13114 * body and no bar pagination.
13115 *
13116 * Because it deals with page navigation, certain routing concepts are built-in.
13117 * Route changes via via ng-href, ui-sref, or ng-click events are supported.
13118 * Alternatively, the user could simply watch currentNavItem for changes.
13119 *
13120 * Accessibility functionality is implemented as a site navigator with a
13121 * listbox, according to
13122 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style
13123 *
13124 * @param {string=} mdSelectedNavItem The name of the current tab; this must
13125 * match the name attribute of `<md-nav-item>`
13126 * @param {string=} navBarAriaLabel An aria-label for the nav-bar
13127 *
13128 * @usage
13129 * <hljs lang="html">
13130 *  <md-nav-bar md-selected-nav-item="currentNavItem">
13131 *    <md-nav-item md-nav-click="goto('page1')" name="page1">Page One</md-nav-item>
13132 *    <md-nav-item md-nav-sref="app.page2" name="page2">Page Two</md-nav-item>
13133 *    <md-nav-item md-nav-href="#page3" name="page3">Page Three</md-nav-item>
13134 *  </md-nav-bar>
13135 *</hljs>
13136 * <hljs lang="js">
13137 * (function() {
13138 *   ‘use strict’;
13139 *
13140 *    $rootScope.$on('$routeChangeSuccess', function(event, current) {
13141 *      $scope.currentLink = getCurrentLinkFromRoute(current);
13142 *    });
13143 * });
13144 * </hljs>
13145 */
13146
13147/*****************************************************************************
13148 *                            mdNavItem
13149 *****************************************************************************/
13150/**
13151 * @ngdoc directive
13152 * @name mdNavItem
13153 * @module material.components.navBar
13154 *
13155 * @restrict E
13156 *
13157 * @description
13158 * `<md-nav-item>` describes a page navigation link within the `<md-nav-bar>`
13159 * component. It renders an md-button as the actual link.
13160 *
13161 * Exactly one of the mdNavClick, mdNavHref, mdNavSref attributes are required to be
13162 * specified.
13163 *
13164 * @param {Function=} mdNavClick Function which will be called when the
13165 * link is clicked to change the page. Renders as an `ng-click`.
13166 * @param {string=} mdNavHref url to transition to when this link is clicked.
13167 * Renders as an `ng-href`.
13168 * @param {string=} mdNavSref Ui-router state to transition to when this link is
13169 * clicked. Renders as a `ui-sref`.
13170 * @param {string=} name The name of this link. Used by the nav bar to know
13171 * which link is currently selected.
13172 *
13173 * @usage
13174 * See `<md-nav-bar>` for usage.
13175 */
13176
13177
13178/*****************************************************************************
13179 *                                IMPLEMENTATION                             *
13180 *****************************************************************************/
13181
13182function MdNavBar($mdAria, $mdTheming) {
13183  return {
13184    restrict: 'E',
13185    transclude: true,
13186    controller: MdNavBarController,
13187    controllerAs: 'ctrl',
13188    bindToController: true,
13189    scope: {
13190      'mdSelectedNavItem': '=?',
13191      'navBarAriaLabel': '@?',
13192    },
13193    template:
13194      '<div class="md-nav-bar">' +
13195        '<nav role="navigation">' +
13196          '<ul class="_md-nav-bar-list" ng-transclude role="listbox"' +
13197            'tabindex="0"' +
13198            'ng-focus="ctrl.onFocus()"' +
13199            'ng-blur="ctrl.onBlur()"' +
13200            'ng-keydown="ctrl.onKeydown($event)"' +
13201            'aria-label="{{ctrl.navBarAriaLabel}}">' +
13202          '</ul>' +
13203        '</nav>' +
13204        '<md-nav-ink-bar></md-nav-ink-bar>' +
13205      '</div>',
13206    link: function(scope, element, attrs, ctrl) {
13207      $mdTheming(element);
13208      if (!ctrl.navBarAriaLabel) {
13209        $mdAria.expectAsync(element, 'aria-label', angular.noop);
13210      }
13211    },
13212  };
13213}
13214
13215/**
13216 * Controller for the nav-bar component.
13217 *
13218 * Accessibility functionality is implemented as a site navigator with a
13219 * listbox, according to
13220 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style
13221 * @param {!angular.JQLite} $element
13222 * @param {!angular.Scope} $scope
13223 * @param {!angular.Timeout} $timeout
13224 * @param {!Object} $mdConstant
13225 * @constructor
13226 * @final
13227 * @ngInject
13228 */
13229function MdNavBarController($element, $scope, $timeout, $mdConstant) {
13230  // Injected variables
13231  /** @private @const {!angular.Timeout} */
13232  this._$timeout = $timeout;
13233
13234  /** @private @const {!angular.Scope} */
13235  this._$scope = $scope;
13236
13237  /** @private @const {!Object} */
13238  this._$mdConstant = $mdConstant;
13239
13240  // Data-bound variables.
13241  /** @type {string} */
13242  this.mdSelectedNavItem;
13243
13244  /** @type {string} */
13245  this.navBarAriaLabel;
13246
13247  // State variables.
13248
13249  /** @type {?angular.JQLite} */
13250  this._navBarEl = $element[0];
13251
13252  /** @type {?angular.JQLite} */
13253  this._inkbar;
13254
13255  var self = this;
13256  // need to wait for transcluded content to be available
13257  var deregisterTabWatch = this._$scope.$watch(function() {
13258    return self._navBarEl.querySelectorAll('._md-nav-button').length;
13259  },
13260  function(newLength) {
13261    if (newLength > 0) {
13262      self._initTabs();
13263      deregisterTabWatch();
13264    }
13265  });
13266}
13267
13268
13269
13270/**
13271 * Initializes the tab components once they exist.
13272 * @private
13273 */
13274MdNavBarController.prototype._initTabs = function() {
13275  this._inkbar = angular.element(this._navBarEl.getElementsByTagName('md-nav-ink-bar')[0]);
13276
13277  var self = this;
13278  this._$timeout(function() {
13279    self._updateTabs(self.mdSelectedNavItem, undefined);
13280  });
13281
13282  this._$scope.$watch('ctrl.mdSelectedNavItem', function(newValue, oldValue) {
13283    // Wait a digest before update tabs for products doing
13284    // anything dynamic in the template.
13285    self._$timeout(function() {
13286      self._updateTabs(newValue, oldValue);
13287    });
13288  });
13289};
13290
13291/**
13292 * Set the current tab to be selected.
13293 * @param {string|undefined} newValue New current tab name.
13294 * @param {string|undefined} oldValue Previous tab name.
13295 * @private
13296 */
13297MdNavBarController.prototype._updateTabs = function(newValue, oldValue) {
13298  var self = this;
13299  var tabs = this._getTabs();
13300  var oldIndex = -1;
13301  var newIndex = -1;
13302  var newTab = this._getTabByName(newValue);
13303  var oldTab = this._getTabByName(oldValue);
13304
13305  if (oldTab) {
13306    oldTab.setSelected(false);
13307    oldIndex = tabs.indexOf(oldTab);
13308  }
13309
13310  if (newTab) {
13311    newTab.setSelected(true);
13312    newIndex = tabs.indexOf(newTab);
13313  }
13314
13315  this._$timeout(function() {
13316    self._updateInkBarStyles(newTab, newIndex, oldIndex);
13317  });
13318};
13319
13320/**
13321 * Repositions the ink bar to the selected tab.
13322 * @private
13323 */
13324MdNavBarController.prototype._updateInkBarStyles = function(tab, newIndex, oldIndex) {
13325  this._inkbar.toggleClass('_md-left', newIndex < oldIndex)
13326      .toggleClass('_md-right', newIndex > oldIndex);
13327
13328  this._inkbar.css({display: newIndex < 0 ? 'none' : ''});
13329
13330  if(tab){
13331    var tabEl = tab.getButtonEl();
13332    var left = tabEl.offsetLeft;
13333
13334    this._inkbar.css({left: left + 'px', width: tabEl.offsetWidth + 'px'});
13335  }
13336};
13337
13338/**
13339 * Returns an array of the current tabs.
13340 * @return {!Array<!NavItemController>}
13341 * @private
13342 */
13343MdNavBarController.prototype._getTabs = function() {
13344  var linkArray = Array.prototype.slice.call(
13345      this._navBarEl.querySelectorAll('.md-nav-item'));
13346  return linkArray.map(function(el) {
13347    return angular.element(el).controller('mdNavItem')
13348  });
13349};
13350
13351/**
13352 * Returns the tab with the specified name.
13353 * @param {string} name The name of the tab, found in its name attribute.
13354 * @return {!NavItemController|undefined}
13355 * @private
13356 */
13357MdNavBarController.prototype._getTabByName = function(name) {
13358  return this._findTab(function(tab) {
13359    return tab.getName() == name;
13360  });
13361};
13362
13363/**
13364 * Returns the selected tab.
13365 * @return {!NavItemController|undefined}
13366 * @private
13367 */
13368MdNavBarController.prototype._getSelectedTab = function() {
13369  return this._findTab(function(tab) {
13370    return tab.isSelected()
13371  });
13372};
13373
13374/**
13375 * Returns the focused tab.
13376 * @return {!NavItemController|undefined}
13377 */
13378MdNavBarController.prototype.getFocusedTab = function() {
13379  return this._findTab(function(tab) {
13380    return tab.hasFocus()
13381  });
13382};
13383
13384/**
13385 * Find a tab that matches the specified function.
13386 * @private
13387 */
13388MdNavBarController.prototype._findTab = function(fn) {
13389  var tabs = this._getTabs();
13390  for (var i = 0; i < tabs.length; i++) {
13391    if (fn(tabs[i])) {
13392      return tabs[i];
13393    }
13394  }
13395
13396  return null;
13397};
13398
13399/**
13400 * Direct focus to the selected tab when focus enters the nav bar.
13401 */
13402MdNavBarController.prototype.onFocus = function() {
13403  var tab = this._getSelectedTab();
13404  if (tab) {
13405    tab.setFocused(true);
13406  }
13407};
13408
13409/**
13410 * Clear tab focus when focus leaves the nav bar.
13411 */
13412MdNavBarController.prototype.onBlur = function() {
13413  var tab = this.getFocusedTab();
13414  if (tab) {
13415    tab.setFocused(false);
13416  }
13417};
13418
13419/**
13420 * Move focus from oldTab to newTab.
13421 * @param {!NavItemController} oldTab
13422 * @param {!NavItemController} newTab
13423 * @private
13424 */
13425MdNavBarController.prototype._moveFocus = function(oldTab, newTab) {
13426  oldTab.setFocused(false);
13427  newTab.setFocused(true);
13428};
13429
13430/**
13431 * Responds to keypress events.
13432 * @param {!Event} e
13433 */
13434MdNavBarController.prototype.onKeydown = function(e) {
13435  var keyCodes = this._$mdConstant.KEY_CODE;
13436  var tabs = this._getTabs();
13437  var focusedTab = this.getFocusedTab();
13438  if (!focusedTab) return;
13439
13440  var focusedTabIndex = tabs.indexOf(focusedTab);
13441
13442  // use arrow keys to navigate between tabs
13443  switch (e.keyCode) {
13444    case keyCodes.UP_ARROW:
13445    case keyCodes.LEFT_ARROW:
13446      if (focusedTabIndex > 0) {
13447        this._moveFocus(focusedTab, tabs[focusedTabIndex - 1]);
13448      }
13449      break;
13450    case keyCodes.DOWN_ARROW:
13451    case keyCodes.RIGHT_ARROW:
13452      if (focusedTabIndex < tabs.length - 1) {
13453        this._moveFocus(focusedTab, tabs[focusedTabIndex + 1]);
13454      }
13455      break;
13456    case keyCodes.SPACE:
13457    case keyCodes.ENTER:
13458      // timeout to avoid a "digest already in progress" console error
13459      this._$timeout(function() {
13460        focusedTab.getButtonEl().click();
13461      });
13462      break;
13463  }
13464};
13465
13466/**
13467 * @ngInject
13468 */
13469function MdNavItem($$rAF) {
13470  return {
13471    restrict: 'E',
13472    require: ['mdNavItem', '^mdNavBar'],
13473    controller: MdNavItemController,
13474    bindToController: true,
13475    controllerAs: 'ctrl',
13476    replace: true,
13477    transclude: true,
13478    template:
13479      '<li class="md-nav-item" role="option" aria-selected="{{ctrl.isSelected()}}">' +
13480        '<md-button ng-if="ctrl.mdNavSref" class="_md-nav-button md-accent"' +
13481          'ng-class="ctrl.getNgClassMap()"' +
13482          'tabindex="-1"' +
13483          'ui-sref="{{ctrl.mdNavSref}}">' +
13484          '<span ng-transclude class="_md-nav-button-text"></span>' +
13485        '</md-button>' +
13486        '<md-button ng-if="ctrl.mdNavHref" class="_md-nav-button md-accent"' +
13487          'ng-class="ctrl.getNgClassMap()"' +
13488          'tabindex="-1"' +
13489          'ng-href="{{ctrl.mdNavHref}}">' +
13490          '<span ng-transclude class="_md-nav-button-text"></span>' +
13491        '</md-button>' +
13492        '<md-button ng-if="ctrl.mdNavClick" class="_md-nav-button md-accent"' +
13493          'ng-class="ctrl.getNgClassMap()"' +
13494          'tabindex="-1"' +
13495          'ng-click="ctrl.mdNavClick()">' +
13496          '<span ng-transclude class="_md-nav-button-text"></span>' +
13497        '</md-button>' +
13498      '</li>',
13499    scope: {
13500      'mdNavClick': '&?',
13501      'mdNavHref': '@?',
13502      'mdNavSref': '@?',
13503      'name': '@',
13504    },
13505    link: function(scope, element, attrs, controllers) {
13506      var mdNavItem = controllers[0];
13507      var mdNavBar = controllers[1];
13508
13509      // When accessing the element's contents synchronously, they
13510      // may not be defined yet because of transclusion. There is a higher chance
13511      // that it will be accessible if we wait one frame.
13512      $$rAF(function() {
13513        if (!mdNavItem.name) {
13514          mdNavItem.name = angular.element(element[0].querySelector('._md-nav-button-text'))
13515            .text().trim();
13516        }
13517
13518        var navButton = angular.element(element[0].querySelector('._md-nav-button'));
13519        navButton.on('click', function() {
13520          mdNavBar.mdSelectedNavItem = mdNavItem.name;
13521          scope.$apply();
13522        });
13523      });
13524    }
13525  };
13526}
13527
13528/**
13529 * Controller for the nav-item component.
13530 * @param {!angular.JQLite} $element
13531 * @constructor
13532 * @final
13533 * @ngInject
13534 */
13535function MdNavItemController($element) {
13536
13537  /** @private @const {!angular.JQLite} */
13538  this._$element = $element;
13539
13540  // Data-bound variables
13541  /** @const {?Function} */
13542  this.mdNavClick;
13543  /** @const {?string} */
13544  this.mdNavHref;
13545  /** @const {?string} */
13546  this.name;
13547
13548  // State variables
13549  /** @private {boolean} */
13550  this._selected = false;
13551
13552  /** @private {boolean} */
13553  this._focused = false;
13554
13555  var hasNavClick = !!($element.attr('md-nav-click'));
13556  var hasNavHref = !!($element.attr('md-nav-href'));
13557  var hasNavSref = !!($element.attr('md-nav-sref'));
13558
13559  // Cannot specify more than one nav attribute
13560  if ((hasNavClick ? 1:0) + (hasNavHref ? 1:0) + (hasNavSref ? 1:0) > 1) {
13561    throw Error(
13562        'Must specify exactly one of md-nav-click, md-nav-href, ' +
13563        'md-nav-sref for nav-item directive');
13564  }
13565}
13566
13567/**
13568 * Returns a map of class names and values for use by ng-class.
13569 * @return {!Object<string,boolean>}
13570 */
13571MdNavItemController.prototype.getNgClassMap = function() {
13572  return {
13573    'md-active': this._selected,
13574    'md-primary': this._selected,
13575    'md-unselected': !this._selected,
13576    'md-focused': this._focused,
13577  };
13578};
13579
13580/**
13581 * Get the name attribute of the tab.
13582 * @return {string}
13583 */
13584MdNavItemController.prototype.getName = function() {
13585  return this.name;
13586};
13587
13588/**
13589 * Get the button element associated with the tab.
13590 * @return {!Element}
13591 */
13592MdNavItemController.prototype.getButtonEl = function() {
13593  return this._$element[0].querySelector('._md-nav-button');
13594};
13595
13596/**
13597 * Set the selected state of the tab.
13598 * @param {boolean} isSelected
13599 */
13600MdNavItemController.prototype.setSelected = function(isSelected) {
13601  this._selected = isSelected;
13602};
13603
13604/**
13605 * @return {boolean}
13606 */
13607MdNavItemController.prototype.isSelected = function() {
13608  return this._selected;
13609};
13610
13611/**
13612 * Set the focused state of the tab.
13613 * @param {boolean} isFocused
13614 */
13615MdNavItemController.prototype.setFocused = function(isFocused) {
13616  this._focused = isFocused;
13617};
13618
13619/**
13620 * @return {boolean}
13621 */
13622MdNavItemController.prototype.hasFocus = function() {
13623  return this._focused;
13624};
13625
13626})();
13627(function(){
13628"use strict";
13629
13630/**
13631 * @ngdoc module
13632 * @name material.components.panel
13633 */
13634MdPanelService.$inject = ["$rootElement", "$rootScope", "$injector", "$window"];
13635angular
13636    .module('material.components.panel', [
13637      'material.core',
13638      'material.components.backdrop'
13639    ])
13640    .service('$mdPanel', MdPanelService);
13641
13642
13643/*****************************************************************************
13644 *                            PUBLIC DOCUMENTATION                           *
13645 *****************************************************************************/
13646
13647/**
13648 * @ngdoc service
13649 * @name $mdPanel
13650 * @module material.components.panel
13651 *
13652 * @description
13653 * `$mdPanel` is a robust, low-level service for creating floating panels on
13654 * the screen. It can be used to implement tooltips, dialogs, pop-ups, etc.
13655 *
13656 * @usage
13657 * <hljs lang="js">
13658 * (function(angular, undefined) {
13659 *   ‘use strict’;
13660 *
13661 *   angular
13662 *       .module('demoApp', ['ngMaterial'])
13663 *       .controller('DemoDialogController', DialogController);
13664 *
13665 *   var panelRef;
13666 *
13667 *   function showPanel($event) {
13668 *     var panelPosition = $mdPanel.newPanelPosition()
13669 *         .absolute()
13670 *         .top('50%')
13671 *         .left('50%');
13672 *
13673 *     var panelAnimation = $mdPanel.newPanelAnimation()
13674 *         .targetEvent($event)
13675 *         .defaultAnimation('md-panel-animate-fly')
13676 *         .closeTo('.show-button');
13677 *
13678 *     var config = {
13679 *       attachTo: angular.element(document.body),
13680 *       controller: DialogController,
13681 *       controllerAs: 'ctrl',
13682 *       position: panelPosition,
13683 *       animation: panelAnimation,
13684 *       targetEvent: $event,
13685 *       templateUrl: 'dialog-template.html',
13686 *       clickOutsideToClose: true,
13687 *       escapeToClose: true,
13688 *       focusOnOpen: true
13689 *     }
13690 *
13691 *     $mdPanel.open(config)
13692 *         .then(function(result) {
13693 *           panelRef = result;
13694 *         });
13695 *   }
13696 *
13697 *   function DialogController(MdPanelRef, toppings) {
13698 *     var toppings;
13699 *
13700 *     function closeDialog() {
13701 *       MdPanelRef && MdPanelRef.close();
13702 *     }
13703 *   }
13704 * })(angular);
13705 * </hljs>
13706 */
13707
13708/**
13709 * @ngdoc method
13710 * @name $mdPanel#create
13711 * @description
13712 * Creates a panel with the specified options.
13713 *
13714 * @param config {!Object=} Specific configuration object that may contain the
13715 *     following properties:
13716 *
13717 *   - `id` - `{string=}`: An ID to track the panel by. When an ID is provided,
13718 *     the created panel is added to a tracked panels object. Any subsequent
13719 *     requests made to create a panel with that ID are ignored. This is useful
13720 *     in having the panel service not open multiple panels from the same user
13721 *     interaction when there is no backdrop and events are propagated. Defaults
13722 *     to an arbitrary string that is not tracked.
13723 *   - `template` - `{string=}`: HTML template to show in the panel. This
13724 *     **must** be trusted HTML with respect to Angular’s
13725 *     [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
13726 *   - `templateUrl` - `{string=}`: The URL that will be used as the content of
13727 *     the panel.
13728 *   - `controller` - `{(function|string)=}`: The controller to associate with
13729 *     the panel. The controller can inject a reference to the returned
13730 *     panelRef, which allows the panel to be closed, hidden, and shown. Any
13731 *     fields passed in through locals or resolve will be bound to the
13732 *     controller.
13733 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on
13734 *     the scope.
13735 *   - `bindToController` - `{boolean=}`: Binds locals to the controller
13736 *     instead of passing them in. Defaults to true, as this is a best
13737 *     practice.
13738 *   - `locals` - `{Object=}`: An object containing key/value pairs. The keys
13739 *     will be used as names of values to inject into the controller. For
13740 *     example, `locals: {three: 3}` would inject `three` into the controller,
13741 *     with the value 3.
13742 *   - `resolve` - `{Object=}`: Similar to locals, except it takes promises as
13743 *     values. The panel will not open until all of the promises resolve.
13744 *   - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to
13745 *     attach the panel to. Defaults to appending to the root element of the
13746 *     application.
13747 *   - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch
13748 *     events should be allowed to propagate 'go through' the container, aka the
13749 *     wrapper, of the panel. Defaults to false.
13750 *   - `panelClass` - `{string=}`: A css class to apply to the panel element.
13751 *     This class should define any borders, box-shadow, etc. for the panel.
13752 *   - `zIndex` - `{number=}`: The z-index to place the panel at.
13753 *     Defaults to 80.
13754 *   - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that
13755 *     specifies the alignment of the panel. For more information, see
13756 *     `MdPanelPosition`.
13757 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click
13758 *     outside the panel to close it. Defaults to false.
13759 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to
13760 *     close the panel. Defaults to false.
13761 *   - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the
13762 *     panel. If `trapFocus` is true, the user will not be able to interact
13763 *     with the rest of the page until the panel is dismissed. Defaults to
13764 *     false.
13765 *   - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on
13766 *     open. Only disable if focusing some other way, as focus management is
13767 *     required for panels to be accessible. Defaults to true.
13768 *   - `fullscreen` - `{boolean=}`: Whether the panel should be full screen.
13769 *     Applies the class `._md-panel-fullscreen` to the panel on open. Defaults
13770 *     to false.
13771 *   - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that
13772 *     specifies the animation of the panel. For more information, see
13773 *     `MdPanelAnimation`.
13774 *   - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop
13775 *     behind the panel. Defaults to false.
13776 *   - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the
13777 *     page behind the panel. Defaults to false.
13778 *   - `onDomAdded` - `{function=}`: Callback function used to announce when
13779 *     the panel is added to the DOM.
13780 *   - `onOpenComplete` - `{function=}`: Callback function used to announce
13781 *     when the open() action is finished.
13782 *   - `onRemoving` - `{function=}`: Callback function used to announce the
13783 *     close/hide() action is starting.
13784 *   - `onDomRemoved` - `{function=}`: Callback function used to announce when
13785 *     the panel is removed from the DOM.
13786 *   - `origin` - `{(string|!angular.JQLite|!Element)=}`: The element to focus
13787 *     on when the panel closes. This is commonly the element which triggered
13788 *     the opening of the panel. If you do not use `origin`, you need to control
13789 *     the focus manually.
13790 *
13791 * @returns {!MdPanelRef} panelRef
13792 */
13793
13794
13795/**
13796 * @ngdoc method
13797 * @name $mdPanel#open
13798 * @description
13799 * Calls the create method above, then opens the panel. This is a shortcut for
13800 * creating and then calling open manually. If custom methods need to be
13801 * called when the panel is added to the DOM or opened, do not use this method.
13802 * Instead create the panel, chain promises on the domAdded and openComplete
13803 * methods, and call open from the returned panelRef.
13804 *
13805 * @param {!Object=} config Specific configuration object that may contain
13806 *     the properties defined in `$mdPanel.create`.
13807 * @returns {!angular.$q.Promise<!MdPanelRef>} panelRef A promise that resolves
13808 *     to an instance of the panel.
13809 */
13810
13811
13812/**
13813 * @ngdoc method
13814 * @name $mdPanel#newPanelPosition
13815 * @description
13816 * Returns a new instance of the MdPanelPosition object. Use this to create
13817 * the position config object.
13818 *
13819 * @returns {!MdPanelPosition} panelPosition
13820 */
13821
13822
13823/**
13824 * @ngdoc method
13825 * @name $mdPanel#newPanelAnimation
13826 * @description
13827 * Returns a new instance of the MdPanelAnimation object. Use this to create
13828 * the animation config object.
13829 *
13830 * @returns {!MdPanelAnimation} panelAnimation
13831 */
13832
13833
13834/*****************************************************************************
13835 *                                 MdPanelRef                                *
13836 *****************************************************************************/
13837
13838
13839/**
13840 * @ngdoc type
13841 * @name MdPanelRef
13842 * @module material.components.panel
13843 * @description
13844 * A reference to a created panel. This reference contains a unique id for the
13845 * panel, along with the following properties:
13846 *
13847 *   - `id` - `{string}`: The unique id for the panel. This id is used to track
13848 *     when a panel was interacted with.
13849 *   - `config` - `{!Object=}`: The entire config object that was used in
13850 *     create.
13851 *   - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM.
13852 *     Visibility to the user does not factor into isAttached.
13853 *   - `panelContainer` - `{angular.JQLite}`: The wrapper element containing the
13854 *     panel. This property is added in order to have access to the `addClass`,
13855 *     `removeClass`, `toggleClass`, etc methods.
13856 *   - `panelEl` - `{angular.JQLite}`: The panel element. This property is added
13857 *     in order to have access to the `addClass`, `removeClass`, `toggleClass`,
13858 *     etc methods.
13859 */
13860
13861/**
13862 * @ngdoc method
13863 * @name MdPanelRef#open
13864 * @description
13865 * Attaches and shows the panel.
13866 *
13867 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
13868 *     opened.
13869 */
13870
13871/**
13872 * @ngdoc method
13873 * @name MdPanelRef#close
13874 * @description
13875 * Hides and detaches the panel. Note that this will **not** destroy the panel.
13876 * If you don't intend on using the panel again, call the {@link #destroy
13877 * destroy} method afterwards.
13878 *
13879 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
13880 *     closed.
13881 */
13882
13883/**
13884 * @ngdoc method
13885 * @name MdPanelRef#attach
13886 * @description
13887 * Create the panel elements and attach them to the DOM. The panel will be
13888 * hidden by default.
13889 *
13890 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
13891 *     attached.
13892 */
13893
13894/**
13895 * @ngdoc method
13896 * @name MdPanelRef#detach
13897 * @description
13898 * Removes the panel from the DOM. This will NOT hide the panel before removing
13899 * it.
13900 *
13901 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
13902 *     detached.
13903 */
13904
13905/**
13906 * @ngdoc method
13907 * @name MdPanelRef#show
13908 * @description
13909 * Shows the panel.
13910 *
13911 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
13912 *     shown and animations are completed.
13913 */
13914
13915/**
13916 * @ngdoc method
13917 * @name MdPanelRef#hide
13918 * @description
13919 * Hides the panel.
13920 *
13921 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
13922 *     hidden and animations are completed.
13923 */
13924
13925/**
13926 * @ngdoc method
13927 * @name MdPanelRef#destroy
13928 * @description
13929 * Destroys the panel. The panel cannot be opened again after this is called.
13930 */
13931
13932/**
13933 * @ngdoc method
13934 * @name MdPanelRef#addClass
13935 * @deprecated
13936 * This method is in the process of being deprecated in favor of using the panel
13937 * and container JQLite elements that are referenced in the MdPanelRef object.
13938 * Full deprecation is scheduled for material 1.2.
13939 * @description
13940 * Adds a class to the panel. DO NOT use this hide/show the panel.
13941 *
13942 * @param {string} newClass class to be added.
13943 * @param {boolean} toElement Whether or not to add the class to the panel
13944 *     element instead of the container.
13945 */
13946
13947/**
13948 * @ngdoc method
13949 * @name MdPanelRef#removeClass
13950 * @deprecated
13951 * This method is in the process of being deprecated in favor of using the panel
13952 * and container JQLite elements that are referenced in the MdPanelRef object.
13953 * Full deprecation is scheduled for material 1.2.
13954 * @description
13955 * Removes a class from the panel. DO NOT use this to hide/show the panel.
13956 *
13957 * @param {string} oldClass Class to be removed.
13958 * @param {boolean} fromElement Whether or not to remove the class from the
13959 *     panel element instead of the container.
13960 */
13961
13962/**
13963 * @ngdoc method
13964 * @name MdPanelRef#toggleClass
13965 * @deprecated
13966 * This method is in the process of being deprecated in favor of using the panel
13967 * and container JQLite elements that are referenced in the MdPanelRef object.
13968 * Full deprecation is scheduled for material 1.2.
13969 * @description
13970 * Toggles a class on the panel. DO NOT use this to hide/show the panel.
13971 *
13972 * @param {string} toggleClass Class to be toggled.
13973 * @param {boolean} onElement Whether or not to remove the class from the panel
13974 *     element instead of the container.
13975 */
13976
13977/**
13978 * @ngdoc method
13979 * @name MdPanelRef#updatePosition
13980 * @description
13981 * Updates the position configuration of a panel. Use this to update the
13982 * position of a panel that is open, without having to close and re-open the
13983 * panel.
13984 *
13985 * @param {!MdPanelPosition} position
13986 */
13987
13988
13989/*****************************************************************************
13990 *                               MdPanelPosition                            *
13991 *****************************************************************************/
13992
13993
13994/**
13995 * @ngdoc type
13996 * @name MdPanelPosition
13997 * @module material.components.panel
13998 * @description
13999 *
14000 * Object for configuring the position of the panel.
14001 *
14002 * @usage
14003 *
14004 * #### Centering the panel
14005 *
14006 * <hljs lang="js">
14007 * new MdPanelPosition().absolute().center();
14008 * </hljs>
14009 *
14010 * #### Overlapping the panel with an element
14011 *
14012 * <hljs lang="js">
14013 * new MdPanelPosition()
14014 *     .relativeTo(someElement)
14015 *     .addPanelPosition(
14016 *       $mdPanel.xPosition.ALIGN_START,
14017 *       $mdPanel.yPosition.ALIGN_TOPS
14018 *     );
14019 * </hljs>
14020 *
14021 * #### Aligning the panel with the bottom of an element
14022 *
14023 * <hljs lang="js">
14024 * new MdPanelPosition()
14025 *     .relativeTo(someElement)
14026 *     .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW);
14027 * </hljs>
14028 */
14029
14030/**
14031 * @ngdoc method
14032 * @name MdPanelPosition#absolute
14033 * @description
14034 * Positions the panel absolutely relative to the parent element. If the parent
14035 * is document.body, this is equivalent to positioning the panel absolutely
14036 * within the viewport.
14037 *
14038 * @returns {!MdPanelPosition}
14039 */
14040
14041/**
14042 * @ngdoc method
14043 * @name MdPanelPosition#relativeTo
14044 * @description
14045 * Positions the panel relative to a specific element.
14046 *
14047 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
14048 *     or angular element to position the panel with respect to.
14049 * @returns {!MdPanelPosition}
14050 */
14051
14052/**
14053 * @ngdoc method
14054 * @name MdPanelPosition#top
14055 * @description
14056 * Sets the value of `top` for the panel. Clears any previously set vertical
14057 * position.
14058 *
14059 * @param {string=} top Value of `top`. Defaults to '0'.
14060 * @returns {!MdPanelPosition}
14061 */
14062
14063/**
14064 * @ngdoc method
14065 * @name MdPanelPosition#bottom
14066 * @description
14067 * Sets the value of `bottom` for the panel. Clears any previously set vertical
14068 * position.
14069 *
14070 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
14071 * @returns {!MdPanelPosition}
14072 */
14073
14074/**
14075 * @ngdoc method
14076 * @name MdPanelPosition#start
14077 * @description
14078 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
14079 * `rtl`. Clears any previously set horizontal position.
14080 *
14081 * @param {string=} start Value of position. Defaults to '0'.
14082 * @returns {!MdPanelPosition}
14083 */
14084
14085/**
14086 * @ngdoc method
14087 * @name MdPanelPosition#end
14088 * @description
14089 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
14090 * Clears any previously set horizontal position.
14091 *
14092 * @param {string=} end Value of position. Defaults to '0'.
14093 * @returns {!MdPanelPosition}
14094 */
14095
14096/**
14097 * @ngdoc method
14098 * @name MdPanelPosition#left
14099 * @description
14100 * Sets the value of `left` for the panel. Clears any previously set
14101 * horizontal position.
14102 *
14103 * @param {string=} left Value of `left`. Defaults to '0'.
14104 * @returns {!MdPanelPosition}
14105 */
14106
14107/**
14108 * @ngdoc method
14109 * @name MdPanelPosition#right
14110 * @description
14111 * Sets the value of `right` for the panel. Clears any previously set
14112 * horizontal position.
14113 *
14114 * @param {string=} right Value of `right`. Defaults to '0'.
14115 * @returns {!MdPanelPosition}
14116 */
14117
14118/**
14119 * @ngdoc method
14120 * @name MdPanelPosition#centerHorizontally
14121 * @description
14122 * Centers the panel horizontally in the viewport. Clears any previously set
14123 * horizontal position.
14124 *
14125 * @returns {!MdPanelPosition}
14126 */
14127
14128/**
14129 * @ngdoc method
14130 * @name MdPanelPosition#centerVertically
14131 * @description
14132 * Centers the panel vertically in the viewport. Clears any previously set
14133 * vertical position.
14134 *
14135 * @returns {!MdPanelPosition}
14136 */
14137
14138/**
14139 * @ngdoc method
14140 * @name MdPanelPosition#center
14141 * @description
14142 * Centers the panel horizontally and vertically in the viewport. This is
14143 * equivalent to calling both `centerHorizontally` and `centerVertically`.
14144 * Clears any previously set horizontal and vertical positions.
14145 *
14146 * @returns {!MdPanelPosition}
14147 */
14148
14149/**
14150 * @ngdoc method
14151 * @name MdPanelPosition#addPanelPosition
14152 * @description
14153 * Sets the x and y position for the panel relative to another element. Can be
14154 * called multiple times to specify an ordered list of panel positions. The
14155 * first position which allows the panel to be completely on-screen will be
14156 * chosen; the last position will be chose whether it is on-screen or not.
14157 *
14158 * xPosition must be one of the following values available on
14159 * $mdPanel.xPosition:
14160 *
14161 * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END
14162 *
14163 *    *************
14164 *    *           *
14165 *    *   PANEL   *
14166 *    *           *
14167 *    *************
14168 *   A B    C    D E
14169 *
14170 * A: OFFSET_START (for LTR displays)
14171 * B: ALIGN_START (for LTR displays)
14172 * C: CENTER
14173 * D: ALIGN_END (for LTR displays)
14174 * E: OFFSET_END (for LTR displays)
14175 *
14176 * yPosition must be one of the following values available on
14177 * $mdPanel.yPosition:
14178 *
14179 * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW
14180 *
14181 *   F
14182 *   G *************
14183 *     *           *
14184 *   H *   PANEL   *
14185 *     *           *
14186 *   I *************
14187 *   J
14188 *
14189 * F: BELOW
14190 * G: ALIGN_TOPS
14191 * H: CENTER
14192 * I: ALIGN_BOTTOMS
14193 * J: ABOVE
14194 *
14195 * @param {string} xPosition
14196 * @param {string} yPosition
14197 * @returns {!MdPanelPosition}
14198 */
14199
14200/**
14201 * @ngdoc method
14202 * @name MdPanelPosition#withOffsetX
14203 * @description
14204 * Sets the value of the offset in the x-direction.
14205 *
14206 * @param {string} offsetX
14207 * @returns {!MdPanelPosition}
14208 */
14209
14210/**
14211 * @ngdoc method
14212 * @name MdPanelPosition#withOffsetY
14213 * @description
14214 * Sets the value of the offset in the y-direction.
14215 *
14216 * @param {string} offsetY
14217 * @returns {!MdPanelPosition}
14218 */
14219
14220
14221/*****************************************************************************
14222 *                               MdPanelAnimation                            *
14223 *****************************************************************************/
14224
14225
14226/**
14227 * @ngdoc object
14228 * @name MdPanelAnimation
14229 * @description
14230 * Animation configuration object. To use, create an MdPanelAnimation with the
14231 * desired properties, then pass the object as part of $mdPanel creation.
14232 *
14233 * Example:
14234 *
14235 * var panelAnimation = new MdPanelAnimation()
14236 *     .openFrom(myButtonEl)
14237 *     .closeTo('.my-button')
14238 *     .withAnimation($mdPanel.animation.SCALE);
14239 *
14240 * $mdPanel.create({
14241 *   animation: panelAnimation
14242 * });
14243 */
14244
14245/**
14246 * @ngdoc method
14247 * @name MdPanelAnimation#openFrom
14248 * @description
14249 * Specifies where to start the open animation. `openFrom` accepts a
14250 * click event object, query selector, DOM element, or a Rect object that
14251 * is used to determine the bounds. When passed a click event, the location
14252 * of the click will be used as the position to start the animation.
14253 *
14254 * @param {string|!Element|!Event|{top: number, left: number}}
14255 * @returns {!MdPanelAnimation}
14256 */
14257
14258/**
14259 * @ngdoc method
14260 * @name MdPanelAnimation#closeTo
14261 * @description
14262 * Specifies where to animate the panel close. `closeTo` accepts a
14263 * query selector, DOM element, or a Rect object that is used to determine
14264 * the bounds.
14265 *
14266 * @param {string|!Element|{top: number, left: number}}
14267 * @returns {!MdPanelAnimation}
14268 */
14269
14270/**
14271 * @ngdoc method
14272 * @name MdPanelAnimation#withAnimation
14273 * @description
14274 * Specifies the animation class.
14275 *
14276 * There are several default animations that can be used:
14277 * ($mdPanel.animation)
14278 *   SLIDE: The panel slides in and out from the specified
14279 *       elements. It will not fade in or out.
14280 *   SCALE: The panel scales in and out. Slide and fade are
14281 *       included in this animation.
14282 *   FADE: The panel fades in and out.
14283 *
14284 * Custom classes will by default fade in and out unless
14285 * "transition: opacity 1ms" is added to the to custom class.
14286 *
14287 * @param {string|{open: string, close: string}} cssClass
14288 * @returns {!MdPanelAnimation}
14289 */
14290
14291
14292/*****************************************************************************
14293 *                                IMPLEMENTATION                             *
14294 *****************************************************************************/
14295
14296
14297// Default z-index for the panel.
14298var defaultZIndex = 80;
14299var MD_PANEL_HIDDEN = '_md-panel-hidden';
14300
14301var FOCUS_TRAP_TEMPLATE = angular.element(
14302    '<div class="_md-panel-focus-trap" tabindex="0"></div>');
14303
14304
14305/**
14306 * A service that is used for controlling/displaying panels on the screen.
14307 * @param {!angular.JQLite} $rootElement
14308 * @param {!angular.Scope} $rootScope
14309 * @param {!angular.$injector} $injector
14310 * @param {!angular.$window} $window
14311 * @final @constructor @ngInject
14312 */
14313function MdPanelService($rootElement, $rootScope, $injector, $window) {
14314  /**
14315   * Default config options for the panel.
14316   * Anything angular related needs to be done later. Therefore
14317   *     scope: $rootScope.$new(true),
14318   *     attachTo: $rootElement,
14319   * are added later.
14320   * @private {!Object}
14321   */
14322  this._defaultConfigOptions = {
14323    bindToController: true,
14324    clickOutsideToClose: false,
14325    disableParentScroll: false,
14326    escapeToClose: false,
14327    focusOnOpen: true,
14328    fullscreen: false,
14329    hasBackdrop: false,
14330    propagateContainerEvents: false,
14331    transformTemplate: angular.bind(this, this._wrapTemplate),
14332    trapFocus: false,
14333    zIndex: defaultZIndex
14334  };
14335
14336  /** @private {!Object} */
14337  this._config = {};
14338
14339  /** @private @const */
14340  this._$rootElement = $rootElement;
14341
14342  /** @private @const */
14343  this._$rootScope = $rootScope;
14344
14345  /** @private @const */
14346  this._$injector = $injector;
14347
14348  /** @private @const */
14349  this._$window = $window;
14350
14351  /** @private {!Object<string, !MdPanelRef>} */
14352  this._trackedPanels = {};
14353
14354  /**
14355   * Default animations that can be used within the panel.
14356   * @type {enum}
14357   */
14358  this.animation = MdPanelAnimation.animation;
14359
14360  /**
14361   * Possible values of xPosition for positioning the panel relative to
14362   * another element.
14363   * @type {enum}
14364   */
14365  this.xPosition = MdPanelPosition.xPosition;
14366
14367  /**
14368   * Possible values of yPosition for positioning the panel relative to
14369   * another element.
14370   * @type {enum}
14371   */
14372  this.yPosition = MdPanelPosition.yPosition;
14373}
14374
14375
14376/**
14377 * Creates a panel with the specified options.
14378 * @param {!Object=} config Configuration object for the panel.
14379 * @returns {!MdPanelRef}
14380 */
14381MdPanelService.prototype.create = function(config) {
14382  config = config || {};
14383
14384  // If the passed-in config contains an ID and the ID is within _trackedPanels,
14385  // return the tracked panel.
14386  if (angular.isDefined(config.id) && this._trackedPanels[config.id]) {
14387    return this._trackedPanels[config.id];
14388  }
14389
14390  // If no ID is set within the passed-in config, then create an arbitrary ID.
14391  this._config = {
14392    id: config.id || 'panel_' + this._$injector.get('$mdUtil').nextUid(),
14393    scope: this._$rootScope.$new(true),
14394    attachTo: this._$rootElement
14395  };
14396  angular.extend(this._config, this._defaultConfigOptions, config);
14397
14398  var panelRef = new MdPanelRef(this._config, this._$injector);
14399  this._trackedPanels[config.id] = panelRef;
14400
14401  return panelRef;
14402};
14403
14404
14405/**
14406 * Creates and opens a panel with the specified options.
14407 * @param {!Object=} config Configuration object for the panel.
14408 * @returns {!angular.$q.Promise<!MdPanelRef>} The panel created from create.
14409 */
14410MdPanelService.prototype.open = function(config) {
14411  var panelRef = this.create(config);
14412  return panelRef.open().then(function() {
14413    return panelRef;
14414  });
14415};
14416
14417
14418/**
14419 * Returns a new instance of the MdPanelPosition. Use this to create the
14420 * positioning object.
14421 * @returns {!MdPanelPosition}
14422 */
14423MdPanelService.prototype.newPanelPosition = function() {
14424  return new MdPanelPosition(this._$injector);
14425};
14426
14427
14428/**
14429 * Returns a new instance of the MdPanelAnimation. Use this to create the
14430 * animation object.
14431 * @returns {!MdPanelAnimation}
14432 */
14433MdPanelService.prototype.newPanelAnimation = function() {
14434  return new MdPanelAnimation(this._$injector);
14435};
14436
14437
14438/**
14439 * Wraps the users template in two elements, md-panel-outer-wrapper, which
14440 * covers the entire attachTo element, and md-panel, which contains only the
14441 * template. This allows the panel control over positioning, animations,
14442 * and similar properties.
14443 * @param {string} origTemplate The original template.
14444 * @returns {string} The wrapped template.
14445 * @private
14446 */
14447MdPanelService.prototype._wrapTemplate = function(origTemplate) {
14448  var template = origTemplate || '';
14449
14450  // The panel should be initially rendered offscreen so we can calculate
14451  // height and width for positioning.
14452  return '' +
14453      '<div class="md-panel-outer-wrapper">' +
14454      '  <div class="md-panel" style="left: -9999px;">' + template + '</div>' +
14455      '</div>';
14456};
14457
14458
14459/*****************************************************************************
14460 *                                 MdPanelRef                                *
14461 *****************************************************************************/
14462
14463
14464/**
14465 * A reference to a created panel. This reference contains a unique id for the
14466 * panel, along with properties/functions used to control the panel.
14467 * @param {!Object} config
14468 * @param {!angular.$injector} $injector
14469 * @final @constructor
14470 */
14471function MdPanelRef(config, $injector) {
14472  // Injected variables.
14473  /** @private @const {!angular.$q} */
14474  this._$q = $injector.get('$q');
14475
14476  /** @private @const {!angular.$mdCompiler} */
14477  this._$mdCompiler = $injector.get('$mdCompiler');
14478
14479  /** @private @const {!angular.$mdConstant} */
14480  this._$mdConstant = $injector.get('$mdConstant');
14481
14482  /** @private @const {!angular.$mdUtil} */
14483  this._$mdUtil = $injector.get('$mdUtil');
14484
14485  /** @private @const {!angular.Scope} */
14486  this._$rootScope = $injector.get('$rootScope');
14487
14488  /** @private @const {!angular.$animate} */
14489  this._$animate = $injector.get('$animate');
14490
14491  /** @private @const {!MdPanelRef} */
14492  this._$mdPanel = $injector.get('$mdPanel');
14493
14494  /** @private @const {!angular.$log} */
14495  this._$log = $injector.get('$log');
14496
14497  /** @private @const {!angular.$window} */
14498  this._$window = $injector.get('$window');
14499
14500  /** @private @const {!Function} */
14501  this._$$rAF = $injector.get('$$rAF');
14502
14503  // Public variables.
14504  /**
14505   * Unique id for the panelRef.
14506   * @type {string}
14507   */
14508  this.id = config.id;
14509
14510  /** @type {!Object} */
14511  this.config = config;
14512
14513  /** @type {!angular.JQLite|undefined} */
14514  this.panelContainer;
14515
14516  /** @type {!angular.JQLite|undefined} */
14517  this.panelEl;
14518
14519  /**
14520   * Whether the panel is attached. This is synchronous. When attach is called,
14521   * isAttached is set to true. When detach is called, isAttached is set to
14522   * false.
14523   * @type {boolean}
14524   */
14525  this.isAttached = false;
14526
14527  // Private variables.
14528  /** @private {Array<function()>} */
14529  this._removeListeners = [];
14530
14531  /** @private {!angular.JQLite|undefined} */
14532  this._topFocusTrap;
14533
14534  /** @private {!angular.JQLite|undefined} */
14535  this._bottomFocusTrap;
14536
14537  /** @private {!$mdPanel|undefined} */
14538  this._backdropRef;
14539
14540  /** @private {Function?} */
14541  this._restoreScroll = null;
14542}
14543
14544
14545/**
14546 * Opens an already created and configured panel. If the panel is already
14547 * visible, does nothing.
14548 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
14549 *     the panel is opened and animations finish.
14550 */
14551MdPanelRef.prototype.open = function() {
14552  var self = this;
14553  return this._$q(function(resolve, reject) {
14554    var done = self._done(resolve, self);
14555    var show = self._simpleBind(self.show, self);
14556
14557    self.attach()
14558        .then(show)
14559        .then(done)
14560        .catch(reject);
14561  });
14562};
14563
14564
14565/**
14566 * Closes the panel.
14567 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
14568 *     the panel is closed and animations finish.
14569 */
14570MdPanelRef.prototype.close = function() {
14571  var self = this;
14572
14573  return this._$q(function(resolve, reject) {
14574    var done = self._done(resolve, self);
14575    var detach = self._simpleBind(self.detach, self);
14576
14577    self.hide()
14578        .then(detach)
14579        .then(done)
14580        .catch(reject);
14581  });
14582};
14583
14584
14585/**
14586 * Attaches the panel. The panel will be hidden afterwards.
14587 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
14588 *     the panel is attached.
14589 */
14590MdPanelRef.prototype.attach = function() {
14591  if (this.isAttached && this.panelEl) {
14592    return this._$q.when(this);
14593  }
14594
14595  var self = this;
14596  return this._$q(function(resolve, reject) {
14597    var done = self._done(resolve, self);
14598    var onDomAdded = self.config['onDomAdded'] || angular.noop;
14599    var addListeners = function(response) {
14600        self.isAttached = true;
14601        self._addEventListeners();
14602        return response;
14603    };
14604
14605    self._$q.all([
14606        self._createBackdrop(),
14607        self._createPanel()
14608            .then(addListeners)
14609            .catch(reject)
14610    ]).then(onDomAdded)
14611      .then(done)
14612      .catch(reject);
14613  });
14614};
14615
14616
14617/**
14618 * Only detaches the panel. Will NOT hide the panel first.
14619 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
14620 *     the panel is detached.
14621 */
14622MdPanelRef.prototype.detach = function() {
14623  if (!this.isAttached) {
14624    return this._$q.when(this);
14625  }
14626
14627  var self = this;
14628  var onDomRemoved = self.config['onDomRemoved'] || angular.noop;
14629
14630  var detachFn = function() {
14631    self._removeEventListeners();
14632
14633    // Remove the focus traps that we added earlier for keeping focus within
14634    // the panel.
14635    if (self._topFocusTrap && self._topFocusTrap.parentNode) {
14636      self._topFocusTrap.parentNode.removeChild(self._topFocusTrap);
14637    }
14638
14639    if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) {
14640      self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap);
14641    }
14642
14643    self.panelContainer.remove();
14644    self.isAttached = false;
14645    return self._$q.when(self);
14646  };
14647
14648  if (this._restoreScroll) {
14649    this._restoreScroll();
14650    this._restoreScroll = null;
14651  }
14652
14653  return this._$q(function(resolve, reject) {
14654    var done = self._done(resolve, self);
14655
14656    self._$q.all([
14657      detachFn(),
14658      self._backdropRef ? self._backdropRef.detach() : true
14659    ]).then(onDomRemoved)
14660      .then(done)
14661      .catch(reject);
14662  });
14663};
14664
14665
14666/**
14667 * Destroys the panel. The Panel cannot be opened again after this.
14668 */
14669MdPanelRef.prototype.destroy = function() {
14670  this.config.scope.$destroy();
14671  this.config.locals = null;
14672};
14673
14674
14675/**
14676 * Shows the panel.
14677 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
14678 *     the panel has shown and animations finish.
14679 */
14680MdPanelRef.prototype.show = function() {
14681  if (!this.panelContainer) {
14682    return this._$q(function(resolve, reject) {
14683      reject('Panel does not exist yet. Call open() or attach().');
14684    });
14685  }
14686
14687  if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
14688    return this._$q.when(this);
14689  }
14690
14691  var self = this;
14692  var animatePromise = function() {
14693    self.panelContainer.removeClass(MD_PANEL_HIDDEN);
14694    return self._animateOpen();
14695  };
14696
14697  return this._$q(function(resolve, reject) {
14698    var done = self._done(resolve, self);
14699    var onOpenComplete = self.config['onOpenComplete'] || angular.noop;
14700
14701    self._$q.all([
14702      self._backdropRef ? self._backdropRef.show() : self,
14703      animatePromise().then(function() { self._focusOnOpen(); }, reject)
14704    ]).then(onOpenComplete)
14705      .then(done)
14706      .catch(reject);
14707  });
14708};
14709
14710
14711/**
14712 * Hides the panel.
14713 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
14714 *     the panel has hidden and animations finish.
14715 */
14716MdPanelRef.prototype.hide = function() {
14717  if (!this.panelContainer) {
14718    return this._$q(function(resolve, reject) {
14719      reject('Panel does not exist yet. Call open() or attach().');
14720    });
14721  }
14722
14723  if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
14724    return this._$q.when(this);
14725  }
14726
14727  var self = this;
14728
14729  return this._$q(function(resolve, reject) {
14730    var done = self._done(resolve, self);
14731    var onRemoving = self.config['onRemoving'] || angular.noop;
14732
14733    var focusOnOrigin = function() {
14734      var origin = self.config['origin'];
14735      if (origin) {
14736        getElement(origin).focus();
14737      }
14738    };
14739
14740    var hidePanel = function() {
14741      self.panelContainer.addClass(MD_PANEL_HIDDEN);
14742    };
14743
14744    self._$q.all([
14745      self._backdropRef ? self._backdropRef.hide() : self,
14746      self._animateClose()
14747          .then(onRemoving)
14748          .then(hidePanel)
14749          .then(focusOnOrigin)
14750          .catch(reject)
14751    ]).then(done, reject);
14752  });
14753};
14754
14755
14756/**
14757 * Add a class to the panel. DO NOT use this to hide/show the panel.
14758 * @deprecated
14759 * This method is in the process of being deprecated in favor of using the panel
14760 * and container JQLite elements that are referenced in the MdPanelRef object.
14761 * Full deprecation is scheduled for material 1.2.
14762 *
14763 * @param {string} newClass Class to be added.
14764 * @param {boolean} toElement Whether or not to add the class to the panel
14765 *     element instead of the container.
14766 */
14767MdPanelRef.prototype.addClass = function(newClass, toElement) {
14768  this._$log.warn(
14769      'The addClass method is in the process of being deprecated. ' +
14770      'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
14771      'To achieve the same results, use the panelContainer or panelEl ' +
14772      'JQLite elements that are referenced in MdPanelRef.');
14773
14774  if (!this.panelContainer) {
14775    throw new Error('Panel does not exist yet. Call open() or attach().');
14776  }
14777
14778  if (!toElement && !this.panelContainer.hasClass(newClass)) {
14779    this.panelContainer.addClass(newClass);
14780  } else if (toElement && !this.panelEl.hasClass(newClass)) {
14781    this.panelEl.addClass(newClass);
14782  }
14783};
14784
14785
14786/**
14787 * Remove a class from the panel. DO NOT use this to hide/show the panel.
14788 * @deprecated
14789 * This method is in the process of being deprecated in favor of using the panel
14790 * and container JQLite elements that are referenced in the MdPanelRef object.
14791 * Full deprecation is scheduled for material 1.2.
14792 *
14793 * @param {string} oldClass Class to be removed.
14794 * @param {boolean} fromElement Whether or not to remove the class from the
14795 *     panel element instead of the container.
14796 */
14797MdPanelRef.prototype.removeClass = function(oldClass, fromElement) {
14798  this._$log.warn(
14799      'The removeClass method is in the process of being deprecated. ' +
14800      'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
14801      'To achieve the same results, use the panelContainer or panelEl ' +
14802      'JQLite elements that are referenced in MdPanelRef.');
14803
14804  if (!this.panelContainer) {
14805    throw new Error('Panel does not exist yet. Call open() or attach().');
14806  }
14807
14808  if (!fromElement && this.panelContainer.hasClass(oldClass)) {
14809    this.panelContainer.removeClass(oldClass);
14810  } else if (fromElement && this.panelEl.hasClass(oldClass)) {
14811    this.panelEl.removeClass(oldClass);
14812  }
14813};
14814
14815
14816/**
14817 * Toggle a class on the panel. DO NOT use this to hide/show the panel.
14818 * @deprecated
14819 * This method is in the process of being deprecated in favor of using the panel
14820 * and container JQLite elements that are referenced in the MdPanelRef object.
14821 * Full deprecation is scheduled for material 1.2.
14822 *
14823 * @param {string} toggleClass The class to toggle.
14824 * @param {boolean} onElement Whether or not to toggle the class on the panel
14825 *     element instead of the container.
14826 */
14827MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) {
14828  this._$log.warn(
14829      'The toggleClass method is in the process of being deprecated. ' +
14830      'Full deprecation is scheduled for the Angular Material 1.2 release. ' +
14831      'To achieve the same results, use the panelContainer or panelEl ' +
14832      'JQLite elements that are referenced in MdPanelRef.');
14833
14834  if (!this.panelContainer) {
14835    throw new Error('Panel does not exist yet. Call open() or attach().');
14836  }
14837
14838  if (!onElement) {
14839    this.panelContainer.toggleClass(toggleClass);
14840  } else {
14841    this.panelEl.toggleClass(toggleClass);
14842  }
14843};
14844
14845
14846/**
14847 * Creates a panel and adds it to the dom.
14848 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
14849 *     created.
14850 * @private
14851 */
14852MdPanelRef.prototype._createPanel = function() {
14853  var self = this;
14854
14855  return this._$q(function(resolve, reject) {
14856    if (!self.config.locals) {
14857      self.config.locals = {};
14858    }
14859
14860    self.config.locals.mdPanelRef = self;
14861    self._$mdCompiler.compile(self.config)
14862        .then(function(compileData) {
14863          self.panelContainer = compileData.link(self.config['scope']);
14864          getElement(self.config['attachTo']).append(self.panelContainer);
14865
14866          if (self.config['disableParentScroll']) {
14867            self._restoreScroll = self._$mdUtil.disableScrollAround(
14868              null,
14869              self.panelContainer,
14870              { disableScrollMask: true }
14871            );
14872          }
14873
14874          self.panelEl = angular.element(
14875              self.panelContainer[0].querySelector('.md-panel'));
14876
14877          // Add a custom CSS class to the panel element.
14878          if (self.config['panelClass']) {
14879            self.panelEl.addClass(self.config['panelClass']);
14880          }
14881
14882          // Handle click and touch events for the panel container.
14883          if (self.config['propagateContainerEvents']) {
14884            self.panelContainer.css('pointer-events', 'none');
14885          }
14886
14887          // Panel may be outside the $rootElement, tell ngAnimate to animate
14888          // regardless.
14889          if (self._$animate.pin) {
14890            self._$animate.pin(self.panelContainer,
14891                getElement(self.config['attachTo']));
14892          }
14893
14894          self._configureTrapFocus();
14895          self._addStyles().then(function() {
14896            resolve(self);
14897          }, reject);
14898        }, reject);
14899  });
14900};
14901
14902
14903/**
14904 * Adds the styles for the panel, such as positioning and z-index.
14905 * @returns {!angular.$q.Promise<!MdPanelRef>}
14906 * @private
14907 */
14908MdPanelRef.prototype._addStyles = function() {
14909  var self = this;
14910  return this._$q(function(resolve) {
14911    self.panelContainer.css('z-index', self.config['zIndex']);
14912    self.panelEl.css('z-index', self.config['zIndex'] + 1);
14913
14914    var hideAndResolve = function() {
14915      // Remove left: -9999px and add hidden class.
14916      self.panelEl.css('left', '');
14917      self.panelContainer.addClass(MD_PANEL_HIDDEN);
14918      resolve(self);
14919    };
14920
14921    if (self.config['fullscreen']) {
14922      self.panelEl.addClass('_md-panel-fullscreen');
14923      hideAndResolve();
14924      return; // Don't setup positioning.
14925    }
14926
14927    var positionConfig = self.config['position'];
14928    if (!positionConfig) {
14929      hideAndResolve();
14930      return; // Don't setup positioning.
14931    }
14932
14933    // Wait for angular to finish processing the template, then position it
14934    // correctly. This is necessary so that the panel will have a defined height
14935    // and width.
14936    self._$rootScope['$$postDigest'](function() {
14937      self._updatePosition(true);
14938      resolve(self);
14939    });
14940  });
14941};
14942
14943
14944/**
14945 * Updates the position configuration of a panel
14946 * @param {!MdPanelPosition} position
14947 */
14948MdPanelRef.prototype.updatePosition = function(position) {
14949  if (!this.panelContainer) {
14950    throw new Error('Panel does not exist yet. Call open() or attach().');
14951  }
14952
14953  this.config['position'] = position;
14954  this._updatePosition();
14955};
14956
14957
14958/**
14959 * Calculates and updates the position of the panel.
14960 * @param {boolean=} init
14961 * @private
14962 */
14963MdPanelRef.prototype._updatePosition = function(init) {
14964  var positionConfig = this.config['position'];
14965
14966  if (positionConfig) {
14967    positionConfig._setPanelPosition(this.panelEl);
14968
14969    // Hide the panel now that position is known.
14970    if (init) {
14971      this.panelContainer.addClass(MD_PANEL_HIDDEN);
14972    }
14973
14974    this.panelEl.css(
14975      MdPanelPosition.absPosition.TOP,
14976      positionConfig.getTop()
14977    );
14978    this.panelEl.css(
14979      MdPanelPosition.absPosition.BOTTOM,
14980      positionConfig.getBottom()
14981    );
14982    this.panelEl.css(
14983      MdPanelPosition.absPosition.LEFT,
14984      positionConfig.getLeft()
14985    );
14986    this.panelEl.css(
14987      MdPanelPosition.absPosition.RIGHT,
14988      positionConfig.getRight()
14989    );
14990
14991    // Use the vendor prefixed version of transform.
14992    var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
14993    this.panelEl.css(prefixedTransform, positionConfig.getTransform());
14994  }
14995};
14996
14997
14998/**
14999 * Focuses on the panel or the first focus target.
15000 * @private
15001 */
15002MdPanelRef.prototype._focusOnOpen = function() {
15003  if (this.config['focusOnOpen']) {
15004    // Wait for the template to finish rendering to guarantee md-autofocus has
15005    // finished adding the class md-autofocus, otherwise the focusable element
15006    // isn't available to focus.
15007    var self = this;
15008    this._$rootScope['$$postDigest'](function() {
15009      var target = self._$mdUtil.findFocusTarget(self.panelEl) ||
15010          self.panelEl;
15011      target.focus();
15012    });
15013  }
15014};
15015
15016
15017/**
15018 * Shows the backdrop.
15019 * @returns {!angular.$q.Promise} A promise that is resolved when the backdrop
15020 *     is created and attached.
15021 * @private
15022 */
15023MdPanelRef.prototype._createBackdrop = function() {
15024  if (this.config.hasBackdrop) {
15025    if (!this._backdropRef) {
15026      var backdropAnimation = this._$mdPanel.newPanelAnimation()
15027          .openFrom(this.config.attachTo)
15028          .withAnimation({
15029            open: '_md-opaque-enter',
15030            close: '_md-opaque-leave'
15031          });
15032      var backdropConfig = {
15033        animation: backdropAnimation,
15034        attachTo: this.config.attachTo,
15035        focusOnOpen: false,
15036        panelClass: '_md-panel-backdrop',
15037        zIndex: this.config.zIndex - 1
15038      };
15039      this._backdropRef = this._$mdPanel.create(backdropConfig);
15040    }
15041    if (!this._backdropRef.isAttached) {
15042      return this._backdropRef.attach();
15043    }
15044  }
15045};
15046
15047
15048/**
15049 * Listen for escape keys and outside clicks to auto close.
15050 * @private
15051 */
15052MdPanelRef.prototype._addEventListeners = function() {
15053  this._configureEscapeToClose();
15054  this._configureClickOutsideToClose();
15055  this._configureScrollListener();
15056};
15057
15058
15059/**
15060 * Remove event listeners added in _addEventListeners.
15061 * @private
15062 */
15063MdPanelRef.prototype._removeEventListeners = function() {
15064  this._removeListeners && this._removeListeners.forEach(function(removeFn) {
15065    removeFn();
15066  });
15067  this._removeListeners = [];
15068};
15069
15070
15071/**
15072 * Setup the escapeToClose event listeners.
15073 * @private
15074 */
15075MdPanelRef.prototype._configureEscapeToClose = function() {
15076  if (this.config['escapeToClose']) {
15077    var parentTarget = getElement(this.config['attachTo']);
15078    var self = this;
15079
15080    var keyHandlerFn = function(ev) {
15081      if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) {
15082        ev.stopPropagation();
15083        ev.preventDefault();
15084
15085        self.close();
15086      }
15087    };
15088
15089    // Add keydown listeners
15090    this.panelContainer.on('keydown', keyHandlerFn);
15091    parentTarget.on('keydown', keyHandlerFn);
15092
15093    // Queue remove listeners function
15094    this._removeListeners.push(function() {
15095      self.panelContainer.off('keydown', keyHandlerFn);
15096      parentTarget.off('keydown', keyHandlerFn);
15097    });
15098  }
15099};
15100
15101
15102/**
15103 * Setup the clickOutsideToClose event listeners.
15104 * @private
15105 */
15106MdPanelRef.prototype._configureClickOutsideToClose = function() {
15107  if (this.config['clickOutsideToClose']) {
15108    var target = this.panelContainer;
15109    var sourceElem;
15110
15111    // Keep track of the element on which the mouse originally went down
15112    // so that we can only close the backdrop when the 'click' started on it.
15113    // A simple 'click' handler does not work,
15114    // it sets the target object as the element the mouse went down on.
15115    var mousedownHandler = function(ev) {
15116      sourceElem = ev.target;
15117    };
15118
15119    // We check if our original element and the target is the backdrop
15120    // because if the original was the backdrop and the target was inside the
15121    // panel we don't want to panel to close.
15122    var self = this;
15123    var mouseupHandler = function(ev) {
15124      if (sourceElem === target[0] && ev.target === target[0]) {
15125        ev.stopPropagation();
15126        ev.preventDefault();
15127
15128        self.close();
15129      }
15130    };
15131
15132    // Add listeners
15133    target.on('mousedown', mousedownHandler);
15134    target.on('mouseup', mouseupHandler);
15135
15136    // Queue remove listeners function
15137    this._removeListeners.push(function() {
15138      target.off('mousedown', mousedownHandler);
15139      target.off('mouseup', mouseupHandler);
15140    });
15141  }
15142};
15143
15144
15145/**
15146 * Configures the listeners for updating the panel position on scroll.
15147 * @private
15148*/
15149MdPanelRef.prototype._configureScrollListener = function() {
15150  var updatePosition = angular.bind(this, this._updatePosition);
15151  var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition);
15152  var self = this;
15153
15154  var onScroll = function() {
15155    if (!self.config['disableParentScroll']) {
15156      debouncedUpdatePosition();
15157    }
15158  };
15159
15160  // Add listeners.
15161  this._$window.addEventListener('scroll', onScroll, true);
15162
15163  // Queue remove listeners function.
15164  this._removeListeners.push(function() {
15165    self._$window.removeEventListener('scroll', onScroll, true);
15166  });
15167};
15168
15169
15170/**
15171 * Setup the focus traps. These traps will wrap focus when tabbing past the
15172 * panel. When shift-tabbing, the focus will stick in place.
15173 * @private
15174 */
15175MdPanelRef.prototype._configureTrapFocus = function() {
15176  // Focus doesn't remain instead of the panel without this.
15177  this.panelEl.attr('tabIndex', '-1');
15178  if (this.config['trapFocus']) {
15179    var element = this.panelEl;
15180    // Set up elements before and after the panel to capture focus and
15181    // redirect back into the panel.
15182    this._topFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
15183    this._bottomFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
15184
15185    // When focus is about to move out of the panel, we want to intercept it
15186    // and redirect it back to the panel element.
15187    var focusHandler = function() {
15188      element.focus();
15189    };
15190    this._topFocusTrap.addEventListener('focus', focusHandler);
15191    this._bottomFocusTrap.addEventListener('focus', focusHandler);
15192
15193    // Queue remove listeners function
15194    this._removeListeners.push(this._simpleBind(function() {
15195      this._topFocusTrap.removeEventListener('focus', focusHandler);
15196      this._bottomFocusTrap.removeEventListener('focus', focusHandler);
15197    }, this));
15198
15199    // The top focus trap inserted immediately before the md-panel element (as
15200    // a sibling). The bottom focus trap inserted immediately after the
15201    // md-panel element (as a sibling).
15202    element[0].parentNode.insertBefore(this._topFocusTrap, element[0]);
15203    element.after(this._bottomFocusTrap);
15204  }
15205};
15206
15207
15208/**
15209 * Animate the panel opening.
15210 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
15211 *     animated open.
15212 * @private
15213 */
15214MdPanelRef.prototype._animateOpen = function() {
15215  this.panelContainer.addClass('md-panel-is-showing');
15216  var animationConfig = this.config['animation'];
15217  if (!animationConfig) {
15218    // Promise is in progress, return it.
15219    this.panelContainer.addClass('_md-panel-shown');
15220    return this._$q.when(this);
15221  }
15222
15223  var self = this;
15224  return this._$q(function(resolve) {
15225    var done = self._done(resolve, self);
15226    var warnAndOpen = function() {
15227      self._$log.warn(
15228          'MdPanel Animations failed. Showing panel without animating.');
15229      done();
15230    };
15231
15232    animationConfig.animateOpen(self.panelEl)
15233        .then(done, warnAndOpen);
15234  });
15235};
15236
15237
15238/**
15239 * Animate the panel closing.
15240 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
15241 *     animated closed.
15242 * @private
15243 */
15244MdPanelRef.prototype._animateClose = function() {
15245  var animationConfig = this.config['animation'];
15246  if (!animationConfig) {
15247    this.panelContainer.removeClass('md-panel-is-showing');
15248    this.panelContainer.removeClass('_md-panel-shown');
15249    return this._$q.when(this);
15250  }
15251
15252  var self = this;
15253  return this._$q(function(resolve) {
15254    var done = function() {
15255      self.panelContainer.removeClass('md-panel-is-showing');
15256      resolve(self);
15257    };
15258    var warnAndClose = function() {
15259      self._$log.warn(
15260          'MdPanel Animations failed. Hiding panel without animating.');
15261      done();
15262    };
15263
15264    animationConfig.animateClose(self.panelEl)
15265        .then(done, warnAndClose);
15266  });
15267};
15268
15269
15270/**
15271 * Faster, more basic than angular.bind
15272 * http://jsperf.com/angular-bind-vs-custom-vs-native
15273 * @param {function} callback
15274 * @param {!Object} self
15275 * @return {function} Callback function with a bound self.
15276 */
15277MdPanelRef.prototype._simpleBind = function(callback, self) {
15278  return function(value) {
15279    return callback.apply(self, value);
15280  };
15281};
15282
15283
15284/**
15285 * @param {function} callback
15286 * @param {!Object} self
15287 * @return {function} Callback function with a self param.
15288 */
15289MdPanelRef.prototype._done = function(callback, self) {
15290  return function() {
15291    callback(self);
15292  };
15293};
15294
15295
15296/*****************************************************************************
15297 *                               MdPanelPosition                             *
15298 *****************************************************************************/
15299
15300
15301/**
15302 * Position configuration object. To use, create an MdPanelPosition with the
15303 * desired properties, then pass the object as part of $mdPanel creation.
15304 *
15305 * Example:
15306 *
15307 * var panelPosition = new MdPanelPosition()
15308 *     .relativeTo(myButtonEl)
15309 *     .addPanelPosition(
15310 *       $mdPanel.xPosition.CENTER,
15311 *       $mdPanel.yPosition.ALIGN_TOPS
15312 *     );
15313 *
15314 * $mdPanel.create({
15315 *   position: panelPosition
15316 * });
15317 *
15318 * @param {!angular.$injector} $injector
15319 * @final @constructor
15320 */
15321function MdPanelPosition($injector) {
15322  /** @private @const {!angular.$window} */
15323  this._$window = $injector.get('$window');
15324
15325  /** @private {boolean} */
15326  this._isRTL = $injector.get('$mdUtil').bidi() === 'rtl';
15327
15328  /** @private {boolean} */
15329  this._absolute = false;
15330
15331  /** @private {!angular.JQLite} */
15332  this._relativeToEl;
15333
15334  /** @private {string} */
15335  this._top = '';
15336
15337  /** @private {string} */
15338  this._bottom = '';
15339
15340  /** @private {string} */
15341  this._left = '';
15342
15343  /** @private {string} */
15344  this._right = '';
15345
15346  /** @private {!Array<string>} */
15347  this._translateX = [];
15348
15349  /** @private {!Array<string>} */
15350  this._translateY = [];
15351
15352  /** @private {!Array<{x:string, y:string}>} */
15353  this._positions = [];
15354
15355  /** @private {?{x:string, y:string}} */
15356  this._actualPosition;
15357}
15358
15359
15360/**
15361 * Possible values of xPosition.
15362 * @enum {string}
15363 */
15364MdPanelPosition.xPosition = {
15365  CENTER: 'center',
15366  ALIGN_START: 'align-start',
15367  ALIGN_END: 'align-end',
15368  OFFSET_START: 'offset-start',
15369  OFFSET_END: 'offset-end'
15370};
15371
15372
15373/**
15374 * Possible values of yPosition.
15375 * @enum {string}
15376 */
15377MdPanelPosition.yPosition = {
15378  CENTER: 'center',
15379  ALIGN_TOPS: 'align-tops',
15380  ALIGN_BOTTOMS: 'align-bottoms',
15381  ABOVE: 'above',
15382  BELOW: 'below'
15383};
15384
15385
15386/**
15387 * Possible values of absolute position.
15388 * @enum {string}
15389 */
15390MdPanelPosition.absPosition = {
15391  TOP: 'top',
15392  RIGHT: 'right',
15393  BOTTOM: 'bottom',
15394  LEFT: 'left'
15395};
15396
15397
15398/**
15399 * Sets absolute positioning for the panel.
15400 * @return {!MdPanelPosition}
15401 */
15402MdPanelPosition.prototype.absolute = function() {
15403  this._absolute = true;
15404  return this;
15405};
15406
15407/**
15408 * Sets the value of a position for the panel. Clears any previously set
15409 * position.
15410 * @param {string} position Position to set
15411 * @param {string=} value Value of the position. Defaults to '0'.
15412 * @returns {!MdPanelPosition}
15413 * @private
15414 */
15415MdPanelPosition.prototype._setPosition = function(position, value) {
15416  if (position === MdPanelPosition.absPosition.RIGHT ||
15417      position === MdPanelPosition.absPosition.LEFT) {
15418    this._left = this._right = '';
15419  } else if (
15420      position === MdPanelPosition.absPosition.BOTTOM ||
15421      position === MdPanelPosition.absPosition.TOP) {
15422    this._top = this._bottom = '';
15423  } else {
15424    var positions = Object.keys(MdPanelPosition.absPosition).join()
15425        .toLowerCase();
15426
15427    throw new Error('Position must be one of ' + positions + '.');
15428  }
15429
15430  this['_' +  position] = angular.isString(value) ? value : '0';
15431
15432  return this;
15433};
15434
15435
15436/**
15437 * Sets the value of `top` for the panel. Clears any previously set vertical
15438 * position.
15439 * @param {string=} top Value of `top`. Defaults to '0'.
15440 * @returns {!MdPanelPosition}
15441 */
15442MdPanelPosition.prototype.top = function(top) {
15443  return this._setPosition(MdPanelPosition.absPosition.TOP, top);
15444};
15445
15446
15447/**
15448 * Sets the value of `bottom` for the panel. Clears any previously set vertical
15449 * position.
15450 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
15451 * @returns {!MdPanelPosition}
15452 */
15453MdPanelPosition.prototype.bottom = function(bottom) {
15454  return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom);
15455};
15456
15457
15458/**
15459 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
15460 * `rtl`. Clears any previously set horizontal position.
15461 * @param {string=} start Value of position. Defaults to '0'.
15462 * @returns {!MdPanelPosition}
15463 */
15464MdPanelPosition.prototype.start = function(start) {
15465  var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT;
15466  return this._setPosition(position, start);
15467};
15468
15469
15470/**
15471 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
15472 * Clears any previously set horizontal position.
15473 * @param {string=} end Value of position. Defaults to '0'.
15474 * @returns {!MdPanelPosition}
15475 */
15476MdPanelPosition.prototype.end = function(end) {
15477  var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT;
15478  return this._setPosition(position, end);
15479};
15480
15481
15482/**
15483 * Sets the value of `left` for the panel. Clears any previously set
15484 * horizontal position.
15485 * @param {string=} left Value of `left`. Defaults to '0'.
15486 * @returns {!MdPanelPosition}
15487 */
15488MdPanelPosition.prototype.left = function(left) {
15489  return this._setPosition(MdPanelPosition.absPosition.LEFT, left);
15490};
15491
15492
15493/**
15494 * Sets the value of `right` for the panel. Clears any previously set
15495 * horizontal position.
15496 * @param {string=} right Value of `right`. Defaults to '0'.
15497 * @returns {!MdPanelPosition}
15498*/
15499MdPanelPosition.prototype.right = function(right) {
15500  return this._setPosition(MdPanelPosition.absPosition.RIGHT, right);
15501};
15502
15503
15504/**
15505 * Centers the panel horizontally in the viewport. Clears any previously set
15506 * horizontal position.
15507 * @returns {!MdPanelPosition}
15508 */
15509MdPanelPosition.prototype.centerHorizontally = function() {
15510  this._left = '50%';
15511  this._right = '';
15512  this._translateX = ['-50%'];
15513  return this;
15514};
15515
15516
15517/**
15518 * Centers the panel vertically in the viewport. Clears any previously set
15519 * vertical position.
15520 * @returns {!MdPanelPosition}
15521 */
15522MdPanelPosition.prototype.centerVertically = function() {
15523  this._top = '50%';
15524  this._bottom = '';
15525  this._translateY = ['-50%'];
15526  return this;
15527};
15528
15529
15530/**
15531 * Centers the panel horizontally and vertically in the viewport. This is
15532 * equivalent to calling both `centerHorizontally` and `centerVertically`.
15533 * Clears any previously set horizontal and vertical positions.
15534 * @returns {!MdPanelPosition}
15535 */
15536MdPanelPosition.prototype.center = function() {
15537  return this.centerHorizontally().centerVertically();
15538};
15539
15540
15541/**
15542 * Sets element for relative positioning.
15543 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
15544 *     or angular element to set the panel relative to.
15545 * @returns {!MdPanelPosition}
15546 */
15547MdPanelPosition.prototype.relativeTo = function(element) {
15548  this._absolute = false;
15549  this._relativeToEl = getElement(element);
15550  return this;
15551};
15552
15553
15554/**
15555 * Sets the x and y positions for the panel relative to another element.
15556 * @param {string} xPosition must be one of the MdPanelPosition.xPosition
15557 *     values.
15558 * @param {string} yPosition must be one of the MdPanelPosition.yPosition
15559 *     values.
15560 * @returns {!MdPanelPosition}
15561 */
15562MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) {
15563  if (!this._relativeToEl) {
15564    throw new Error('addPanelPosition can only be used with relative ' +
15565        'positioning. Set relativeTo first.');
15566  }
15567
15568  this._validateXPosition(xPosition);
15569  this._validateYPosition(yPosition);
15570
15571  this._positions.push({
15572      x: xPosition,
15573      y: yPosition,
15574  });
15575  return this;
15576};
15577
15578
15579/**
15580 * Ensures that yPosition is a valid position name. Throw an exception if not.
15581 * @param {string} yPosition
15582 */
15583MdPanelPosition.prototype._validateYPosition = function(yPosition) {
15584  // empty is ok
15585  if (yPosition == null) {
15586      return;
15587  }
15588
15589  var positionKeys = Object.keys(MdPanelPosition.yPosition);
15590  var positionValues = [];
15591  for (var key, i = 0; key = positionKeys[i]; i++) {
15592    var position = MdPanelPosition.yPosition[key];
15593    positionValues.push(position);
15594
15595    if (position === yPosition) {
15596      return;
15597    }
15598  }
15599
15600  throw new Error('Panel y position only accepts the following values:\n' +
15601    positionValues.join(' | '));
15602};
15603
15604
15605/**
15606 * Ensures that xPosition is a valid position name. Throw an exception if not.
15607 * @param {string} xPosition
15608 */
15609MdPanelPosition.prototype._validateXPosition = function(xPosition) {
15610  // empty is ok
15611  if (xPosition == null) {
15612      return;
15613  }
15614
15615  var positionKeys = Object.keys(MdPanelPosition.xPosition);
15616  var positionValues = [];
15617  for (var key, i = 0; key = positionKeys[i]; i++) {
15618    var position = MdPanelPosition.xPosition[key];
15619    positionValues.push(position);
15620    if (position === xPosition) {
15621      return;
15622    }
15623  }
15624
15625  throw new Error('Panel x Position only accepts the following values:\n' +
15626      positionValues.join(' | '));
15627};
15628
15629
15630/**
15631 * Sets the value of the offset in the x-direction. This will add to any
15632 * previously set offsets.
15633 * @param {string} offsetX
15634 * @returns {!MdPanelPosition}
15635 */
15636MdPanelPosition.prototype.withOffsetX = function(offsetX) {
15637  this._translateX.push(offsetX);
15638  return this;
15639};
15640
15641
15642/**
15643 * Sets the value of the offset in the y-direction. This will add to any
15644 * previously set offsets.
15645 * @param {string} offsetY
15646 * @returns {!MdPanelPosition}
15647 */
15648MdPanelPosition.prototype.withOffsetY = function(offsetY) {
15649  this._translateY.push(offsetY);
15650  return this;
15651};
15652
15653
15654/**
15655 * Gets the value of `top` for the panel.
15656 * @returns {string}
15657 */
15658MdPanelPosition.prototype.getTop = function() {
15659  return this._top;
15660};
15661
15662
15663/**
15664 * Gets the value of `bottom` for the panel.
15665 * @returns {string}
15666 */
15667MdPanelPosition.prototype.getBottom = function() {
15668  return this._bottom;
15669};
15670
15671
15672/**
15673 * Gets the value of `left` for the panel.
15674 * @returns {string}
15675 */
15676MdPanelPosition.prototype.getLeft = function() {
15677  return this._left;
15678};
15679
15680
15681/**
15682 * Gets the value of `right` for the panel.
15683 * @returns {string}
15684 */
15685MdPanelPosition.prototype.getRight = function() {
15686  return this._right;
15687};
15688
15689
15690/**
15691 * Gets the value of `transform` for the panel.
15692 * @returns {string}
15693 */
15694MdPanelPosition.prototype.getTransform = function() {
15695  var translateX = this._reduceTranslateValues('translateX', this._translateX);
15696  var translateY = this._reduceTranslateValues('translateY', this._translateY);
15697
15698  // It's important to trim the result, because the browser will ignore the set
15699  // operation if the string contains only whitespace.
15700  return (translateX + ' ' + translateY).trim();
15701};
15702
15703/**
15704 * True if the panel is completely on-screen with this positioning; false
15705 * otherwise.
15706 * @param {!angular.JQLite} panelEl
15707 * @return {boolean}
15708 */
15709MdPanelPosition.prototype._isOnscreen = function(panelEl) {
15710  // this works because we always use fixed positioning for the panel,
15711  // which is relative to the viewport.
15712  // TODO(gmoothart): take into account _translateX and _translateY to the
15713  // extent feasible.
15714
15715  var left = parseInt(this.getLeft());
15716  var top = parseInt(this.getTop());
15717  var right = left + panelEl[0].offsetWidth;
15718  var bottom = top + panelEl[0].offsetHeight;
15719
15720  return (left >= 0) &&
15721    (top >= 0) &&
15722    (bottom <= this._$window.innerHeight) &&
15723    (right <= this._$window.innerWidth);
15724};
15725
15726
15727/**
15728 * Gets the first x/y position that can fit on-screen.
15729 * @returns {{x: string, y: string}}
15730 */
15731MdPanelPosition.prototype.getActualPosition = function() {
15732  return this._actualPosition;
15733};
15734
15735
15736/**
15737 * Reduces a list of translate values to a string that can be used within
15738 * transform.
15739 * @param {string} translateFn
15740 * @param {!Array<string>} values
15741 * @returns {string}
15742 * @private
15743 */
15744MdPanelPosition.prototype._reduceTranslateValues =
15745    function(translateFn, values) {
15746      return values.map(function(translation) {
15747        return translateFn + '(' + translation + ')';
15748      }).join(' ');
15749    };
15750
15751
15752/**
15753 * Sets the panel position based on the created panel element and best x/y
15754 * positioning.
15755 * @param {!angular.JQLite} panelEl
15756 * @private
15757 */
15758MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
15759  // Only calculate the position if necessary.
15760  if (this._absolute) {
15761    return;
15762  }
15763
15764  if (this._actualPosition) {
15765    this._calculatePanelPosition(panelEl, this._actualPosition);
15766    return;
15767  }
15768
15769  for (var i = 0; i < this._positions.length; i++) {
15770    this._actualPosition = this._positions[i];
15771    this._calculatePanelPosition(panelEl, this._actualPosition);
15772    if (this._isOnscreen(panelEl)) {
15773      break;
15774    }
15775  }
15776};
15777
15778
15779/**
15780 * Switches between 'start' and 'end'.
15781 * @param {string} position Horizontal position of the panel
15782 * @returns {string} Reversed position
15783 * @private
15784 */
15785MdPanelPosition.prototype._reverseXPosition = function(position) {
15786  if (position === MdPanelPosition.xPosition.CENTER) {
15787    return;
15788  }
15789
15790  var start = 'start';
15791  var end = 'end';
15792
15793  return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start);
15794};
15795
15796
15797/**
15798 * Handles horizontal positioning in rtl or ltr environments.
15799 * @param {string} position Horizontal position of the panel
15800 * @returns {string} The correct position according the page direction
15801 * @private
15802 */
15803MdPanelPosition.prototype._bidi = function(position) {
15804  return this._isRTL ? this._reverseXPosition(position) : position;
15805};
15806
15807
15808/**
15809 * Calculates the panel position based on the created panel element and the
15810 * provided positioning.
15811 * @param {!angular.JQLite} panelEl
15812 * @param {!{x:string, y:string}} position
15813 * @private
15814 */
15815MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) {
15816
15817  var panelBounds = panelEl[0].getBoundingClientRect();
15818  var panelWidth = panelBounds.width;
15819  var panelHeight = panelBounds.height;
15820
15821  var targetBounds = this._relativeToEl[0].getBoundingClientRect();
15822
15823  var targetLeft = targetBounds.left;
15824  var targetRight = targetBounds.right;
15825  var targetWidth = targetBounds.width;
15826
15827  switch (this._bidi(position.x)) {
15828    case MdPanelPosition.xPosition.OFFSET_START:
15829      this._left = targetLeft - panelWidth + 'px';
15830      break;
15831    case MdPanelPosition.xPosition.ALIGN_END:
15832      this._left = targetRight - panelWidth + 'px';
15833      break;
15834    case MdPanelPosition.xPosition.CENTER:
15835      var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth);
15836      this._left = left + 'px';
15837      break;
15838    case MdPanelPosition.xPosition.ALIGN_START:
15839      this._left = targetLeft + 'px';
15840      break;
15841    case MdPanelPosition.xPosition.OFFSET_END:
15842      this._left = targetRight + 'px';
15843      break;
15844  }
15845
15846  var targetTop = targetBounds.top;
15847  var targetBottom = targetBounds.bottom;
15848  var targetHeight = targetBounds.height;
15849
15850  switch (position.y) {
15851    case MdPanelPosition.yPosition.ABOVE:
15852      this._top = targetTop - panelHeight + 'px';
15853      break;
15854    case MdPanelPosition.yPosition.ALIGN_BOTTOMS:
15855      this._top = targetBottom - panelHeight + 'px';
15856      break;
15857    case MdPanelPosition.yPosition.CENTER:
15858      var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight);
15859      this._top = top + 'px';
15860      break;
15861    case MdPanelPosition.yPosition.ALIGN_TOPS:
15862      this._top = targetTop + 'px';
15863      break;
15864    case MdPanelPosition.yPosition.BELOW:
15865      this._top = targetBottom + 'px';
15866      break;
15867  }
15868};
15869
15870
15871/*****************************************************************************
15872 *                               MdPanelAnimation                            *
15873 *****************************************************************************/
15874
15875
15876/**
15877 * Animation configuration object. To use, create an MdPanelAnimation with the
15878 * desired properties, then pass the object as part of $mdPanel creation.
15879 *
15880 * Example:
15881 *
15882 * var panelAnimation = new MdPanelAnimation()
15883 *     .openFrom(myButtonEl)
15884 *     .closeTo('.my-button')
15885 *     .withAnimation($mdPanel.animation.SCALE);
15886 *
15887 * $mdPanel.create({
15888 *   animation: panelAnimation
15889 * });
15890 *
15891 * @param {!angular.$injector} $injector
15892 * @final @constructor
15893 */
15894function MdPanelAnimation($injector) {
15895  /** @private @const {!angular.$mdUtil} */
15896  this._$mdUtil = $injector.get('$mdUtil');
15897
15898  /**
15899   * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
15900   *     undefined}
15901   */
15902  this._openFrom;
15903
15904  /**
15905   * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
15906   *     undefined}
15907   */
15908  this._closeTo;
15909
15910  /** @private {string|{open: string, close: string}} */
15911  this._animationClass = '';
15912}
15913
15914
15915/**
15916 * Possible default animations.
15917 * @enum {string}
15918 */
15919MdPanelAnimation.animation = {
15920  SLIDE: 'md-panel-animate-slide',
15921  SCALE: 'md-panel-animate-scale',
15922  FADE: 'md-panel-animate-fade'
15923};
15924
15925
15926/**
15927 * Specifies where to start the open animation. `openFrom` accepts a
15928 * click event object, query selector, DOM element, or a Rect object that
15929 * is used to determine the bounds. When passed a click event, the location
15930 * of the click will be used as the position to start the animation.
15931 * @param {string|!Element|!Event|{top: number, left: number}} openFrom
15932 * @returns {!MdPanelAnimation}
15933 */
15934MdPanelAnimation.prototype.openFrom = function(openFrom) {
15935  // Check if 'openFrom' is an Event.
15936  openFrom = openFrom.target ? openFrom.target : openFrom;
15937
15938  this._openFrom = this._getPanelAnimationTarget(openFrom);
15939
15940  if (!this._closeTo) {
15941    this._closeTo = this._openFrom;
15942  }
15943  return this;
15944};
15945
15946
15947/**
15948 * Specifies where to animate the panel close. `closeTo` accepts a
15949 * query selector, DOM element, or a Rect object that is used to determine
15950 * the bounds.
15951 * @param {string|!Element|{top: number, left: number}} closeTo
15952 * @returns {!MdPanelAnimation}
15953 */
15954MdPanelAnimation.prototype.closeTo = function(closeTo) {
15955  this._closeTo = this._getPanelAnimationTarget(closeTo);
15956  return this;
15957};
15958
15959
15960/**
15961 * Returns the element and bounds for the animation target.
15962 * @param {string|!Element|{top: number, left: number}} location
15963 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
15964 * @private
15965 */
15966MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) {
15967  if (angular.isDefined(location.top) || angular.isDefined(location.left)) {
15968    return {
15969      element: undefined,
15970      bounds: {
15971        top: location.top || 0,
15972        left: location.left || 0
15973      }
15974    };
15975  } else {
15976    return this._getBoundingClientRect(getElement(location));
15977  }
15978};
15979
15980
15981/**
15982 * Specifies the animation class.
15983 *
15984 * There are several default animations that can be used:
15985 * (MdPanelAnimation.animation)
15986 *   SLIDE: The panel slides in and out from the specified
15987 *        elements.
15988 *   SCALE: The panel scales in and out.
15989 *   FADE: The panel fades in and out.
15990 *
15991 * @param {string|{open: string, close: string}} cssClass
15992 * @returns {!MdPanelAnimation}
15993 */
15994MdPanelAnimation.prototype.withAnimation = function(cssClass) {
15995  this._animationClass = cssClass;
15996  return this;
15997};
15998
15999
16000/**
16001 * Animate the panel open.
16002 * @param {!angular.JQLite} panelEl
16003 * @returns {!angular.$q.Promise} A promise that is resolved when the open
16004 *     animation is complete.
16005 */
16006MdPanelAnimation.prototype.animateOpen = function(panelEl) {
16007  var animator = this._$mdUtil.dom.animator;
16008
16009  this._fixBounds(panelEl);
16010  var animationOptions = {};
16011
16012  // Include the panel transformations when calculating the animations.
16013  var panelTransform = panelEl[0].style.transform || '';
16014
16015  var openFrom = animator.toTransformCss(panelTransform);
16016  var openTo = animator.toTransformCss(panelTransform);
16017
16018  switch (this._animationClass) {
16019    case MdPanelAnimation.animation.SLIDE:
16020      // Slide should start with opacity: 1.
16021      panelEl.css('opacity', '1');
16022
16023      animationOptions = {
16024        transitionInClass: '_md-panel-animate-enter'
16025      };
16026
16027      var openSlide = animator.calculateSlideToOrigin(
16028              panelEl, this._openFrom) || '';
16029      openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform);
16030      break;
16031
16032    case MdPanelAnimation.animation.SCALE:
16033      animationOptions = {
16034        transitionInClass: '_md-panel-animate-enter'
16035      };
16036
16037      var openScale = animator.calculateZoomToOrigin(
16038              panelEl, this._openFrom) || '';
16039      openFrom = animator.toTransformCss(openScale + ' ' + panelTransform);
16040      break;
16041
16042    case MdPanelAnimation.animation.FADE:
16043      animationOptions = {
16044        transitionInClass: '_md-panel-animate-enter'
16045      };
16046      break;
16047
16048    default:
16049      if (angular.isString(this._animationClass)) {
16050        animationOptions = {
16051          transitionInClass: this._animationClass
16052        };
16053      } else {
16054        animationOptions = {
16055          transitionInClass: this._animationClass['open'],
16056          transitionOutClass: this._animationClass['close'],
16057        };
16058      }
16059  }
16060
16061  return animator
16062      .translate3d(panelEl, openFrom, openTo, animationOptions);
16063};
16064
16065
16066/**
16067 * Animate the panel close.
16068 * @param {!angular.JQLite} panelEl
16069 * @returns {!angular.$q.Promise} A promise that resolves when the close
16070 *     animation is complete.
16071 */
16072MdPanelAnimation.prototype.animateClose = function(panelEl) {
16073  var animator = this._$mdUtil.dom.animator;
16074  var reverseAnimationOptions = {};
16075
16076  // Include the panel transformations when calculating the animations.
16077  var panelTransform = panelEl[0].style.transform || '';
16078
16079  var closeFrom = animator.toTransformCss(panelTransform);
16080  var closeTo = animator.toTransformCss(panelTransform);
16081
16082  switch (this._animationClass) {
16083    case MdPanelAnimation.animation.SLIDE:
16084      // Slide should start with opacity: 1.
16085      panelEl.css('opacity', '1');
16086      reverseAnimationOptions = {
16087        transitionInClass: '_md-panel-animate-leave'
16088      };
16089
16090      var closeSlide = animator.calculateSlideToOrigin(
16091              panelEl, this._closeTo) || '';
16092      closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform);
16093      break;
16094
16095    case MdPanelAnimation.animation.SCALE:
16096      reverseAnimationOptions = {
16097        transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave'
16098      };
16099
16100      var closeScale = animator.calculateZoomToOrigin(
16101              panelEl, this._closeTo) || '';
16102      closeTo = animator.toTransformCss(closeScale + ' ' + panelTransform);
16103      break;
16104
16105    case MdPanelAnimation.animation.FADE:
16106      reverseAnimationOptions = {
16107        transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave'
16108      };
16109      break;
16110
16111    default:
16112      if (angular.isString(this._animationClass)) {
16113        reverseAnimationOptions = {
16114          transitionOutClass: this._animationClass
16115        };
16116      } else {
16117        reverseAnimationOptions = {
16118          transitionInClass: this._animationClass['close'],
16119          transitionOutClass: this._animationClass['open']
16120        };
16121      }
16122  }
16123
16124  return animator
16125      .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions);
16126};
16127
16128
16129/**
16130 * Set the height and width to match the panel if not provided.
16131 * @param {!angular.JQLite} panelEl
16132 * @private
16133 */
16134MdPanelAnimation.prototype._fixBounds = function(panelEl) {
16135  var panelWidth = panelEl[0].offsetWidth;
16136  var panelHeight = panelEl[0].offsetHeight;
16137
16138  if (this._openFrom && this._openFrom.bounds.height == null) {
16139    this._openFrom.bounds.height = panelHeight;
16140  }
16141  if (this._openFrom && this._openFrom.bounds.width == null) {
16142    this._openFrom.bounds.width = panelWidth;
16143  }
16144  if (this._closeTo && this._closeTo.bounds.height == null) {
16145    this._closeTo.bounds.height = panelHeight;
16146  }
16147  if (this._closeTo && this._closeTo.bounds.width == null) {
16148    this._closeTo.bounds.width = panelWidth;
16149  }
16150};
16151
16152
16153/**
16154 * Identify the bounding RECT for the target element.
16155 * @param {!angular.JQLite} element
16156 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
16157 * @private
16158 */
16159MdPanelAnimation.prototype._getBoundingClientRect = function(element) {
16160  if (element instanceof angular.element) {
16161    return {
16162      element: element,
16163      bounds: element[0].getBoundingClientRect()
16164    };
16165  }
16166};
16167
16168
16169/*****************************************************************************
16170 *                                Util Methods                               *
16171 *****************************************************************************/
16172
16173/**
16174 * Returns the angular element associated with a css selector or element.
16175 * @param el {string|!angular.JQLite|!Element}
16176 * @returns {!angular.JQLite}
16177 */
16178function getElement(el) {
16179  var queryResult = angular.isString(el) ?
16180      document.querySelector(el) : el;
16181  return angular.element(queryResult);
16182}
16183
16184})();
16185(function(){
16186"use strict";
16187
16188/**
16189 * @ngdoc module
16190 * @name material.components.progressCircular
16191 * @description Module for a circular progressbar
16192 */
16193
16194angular.module('material.components.progressCircular', ['material.core']);
16195
16196})();
16197(function(){
16198"use strict";
16199
16200/**
16201 * @ngdoc module
16202 * @name material.components.progressLinear
16203 * @description Linear Progress module!
16204 */
16205MdProgressLinearDirective.$inject = ["$mdTheming", "$mdUtil", "$log"];
16206angular.module('material.components.progressLinear', [
16207  'material.core'
16208])
16209  .directive('mdProgressLinear', MdProgressLinearDirective);
16210
16211/**
16212 * @ngdoc directive
16213 * @name mdProgressLinear
16214 * @module material.components.progressLinear
16215 * @restrict E
16216 *
16217 * @description
16218 * The linear progress directive is used to make loading content
16219 * in your app as delightful and painless as possible by minimizing
16220 * the amount of visual change a user sees before they can view
16221 * and interact with content.
16222 *
16223 * Each operation should only be represented by one activity indicator
16224 * For example: one refresh operation should not display both a
16225 * refresh bar and an activity circle.
16226 *
16227 * For operations where the percentage of the operation completed
16228 * can be determined, use a determinate indicator. They give users
16229 * a quick sense of how long an operation will take.
16230 *
16231 * For operations where the user is asked to wait a moment while
16232 * something finishes up, and it’s not necessary to expose what's
16233 * happening behind the scenes and how long it will take, use an
16234 * indeterminate indicator.
16235 *
16236 * @param {string} md-mode Select from one of four modes: determinate, indeterminate, buffer or query.
16237 *
16238 * Note: if the `md-mode` value is set as undefined or specified as 1 of the four (4) valid modes, then `indeterminate`
16239 * will be auto-applied as the mode.
16240 *
16241 * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute. If `value=""` is also specified, however,
16242 * then `md-mode="determinate"` would be auto-injected instead.
16243 * @param {number=} value In determinate and buffer modes, this number represents the percentage of the primary progress bar. Default: 0
16244 * @param {number=} md-buffer-value In the buffer mode, this number represents the percentage of the secondary progress bar. Default: 0
16245 * @param {boolean=} ng-disabled Determines whether to disable the progress element.
16246 *
16247 * @usage
16248 * <hljs lang="html">
16249 * <md-progress-linear md-mode="determinate" value="..."></md-progress-linear>
16250 *
16251 * <md-progress-linear md-mode="determinate" ng-value="..."></md-progress-linear>
16252 *
16253 * <md-progress-linear md-mode="indeterminate"></md-progress-linear>
16254 *
16255 * <md-progress-linear md-mode="buffer" value="..." md-buffer-value="..."></md-progress-linear>
16256 *
16257 * <md-progress-linear md-mode="query"></md-progress-linear>
16258 * </hljs>
16259 */
16260function MdProgressLinearDirective($mdTheming, $mdUtil, $log) {
16261  var MODE_DETERMINATE = "determinate";
16262  var MODE_INDETERMINATE = "indeterminate";
16263  var MODE_BUFFER = "buffer";
16264  var MODE_QUERY = "query";
16265  var DISABLED_CLASS = "_md-progress-linear-disabled";
16266
16267  return {
16268    restrict: 'E',
16269    template: '<div class="md-container">' +
16270      '<div class="md-dashed"></div>' +
16271      '<div class="md-bar md-bar1"></div>' +
16272      '<div class="md-bar md-bar2"></div>' +
16273      '</div>',
16274    compile: compile
16275  };
16276
16277  function compile(tElement, tAttrs, transclude) {
16278    tElement.attr('aria-valuemin', 0);
16279    tElement.attr('aria-valuemax', 100);
16280    tElement.attr('role', 'progressbar');
16281
16282    return postLink;
16283  }
16284  function postLink(scope, element, attr) {
16285    $mdTheming(element);
16286
16287    var lastMode;
16288    var isDisabled = attr.hasOwnProperty('disabled');
16289    var toVendorCSS = $mdUtil.dom.animator.toCss;
16290    var bar1 = angular.element(element[0].querySelector('.md-bar1'));
16291    var bar2 = angular.element(element[0].querySelector('.md-bar2'));
16292    var container = angular.element(element[0].querySelector('.md-container'));
16293
16294    element
16295      .attr('md-mode', mode())
16296      .toggleClass(DISABLED_CLASS, isDisabled);
16297
16298    validateMode();
16299    watchAttributes();
16300
16301    /**
16302     * Watch the value, md-buffer-value, and md-mode attributes
16303     */
16304    function watchAttributes() {
16305      attr.$observe('value', function(value) {
16306        var percentValue = clamp(value);
16307        element.attr('aria-valuenow', percentValue);
16308
16309        if (mode() != MODE_QUERY) animateIndicator(bar2, percentValue);
16310      });
16311
16312      attr.$observe('mdBufferValue', function(value) {
16313        animateIndicator(bar1, clamp(value));
16314      });
16315
16316      attr.$observe('disabled', function(value) {
16317        if (value === true || value === false) {
16318          isDisabled = !!value;
16319        } else {
16320          isDisabled = angular.isDefined(value);
16321        }
16322
16323        element.toggleClass(DISABLED_CLASS, isDisabled);
16324        container.toggleClass(lastMode, !isDisabled);
16325      });
16326
16327      attr.$observe('mdMode', function(mode) {
16328        if (lastMode) container.removeClass( lastMode );
16329
16330        switch( mode ) {
16331          case MODE_QUERY:
16332          case MODE_BUFFER:
16333          case MODE_DETERMINATE:
16334          case MODE_INDETERMINATE:
16335            container.addClass( lastMode = "md-mode-" + mode );
16336            break;
16337          default:
16338            container.addClass( lastMode = "md-mode-" + MODE_INDETERMINATE );
16339            break;
16340        }
16341      });
16342    }
16343
16344    /**
16345     * Auto-defaults the mode to either `determinate` or `indeterminate` mode; if not specified
16346     */
16347    function validateMode() {
16348      if ( angular.isUndefined(attr.mdMode) ) {
16349        var hasValue = angular.isDefined(attr.value);
16350        var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE;
16351        var info = "Auto-adding the missing md-mode='{0}' to the ProgressLinear element";
16352
16353        //$log.debug( $mdUtil.supplant(info, [mode]) );
16354
16355        element.attr("md-mode", mode);
16356        attr.mdMode = mode;
16357      }
16358    }
16359
16360    /**
16361     * Is the md-mode a valid option?
16362     */
16363    function mode() {
16364      var value = (attr.mdMode || "").trim();
16365      if ( value ) {
16366        switch(value) {
16367          case MODE_DETERMINATE:
16368          case MODE_INDETERMINATE:
16369          case MODE_BUFFER:
16370          case MODE_QUERY:
16371            break;
16372          default:
16373            value = MODE_INDETERMINATE;
16374            break;
16375        }
16376      }
16377      return value;
16378    }
16379
16380    /**
16381     * Manually set CSS to animate the Determinate indicator based on the specified
16382     * percentage value (0-100).
16383     */
16384    function animateIndicator(target, value) {
16385      if ( isDisabled || !mode() ) return;
16386
16387      var to = $mdUtil.supplant("translateX({0}%) scale({1},1)", [ (value-100)/2, value/100 ]);
16388      var styles = toVendorCSS({ transform : to });
16389      angular.element(target).css( styles );
16390    }
16391  }
16392
16393  /**
16394   * Clamps the value to be between 0 and 100.
16395   * @param {number} value The value to clamp.
16396   * @returns {number}
16397   */
16398  function clamp(value) {
16399    return Math.max(0, Math.min(value || 0, 100));
16400  }
16401}
16402
16403
16404})();
16405(function(){
16406"use strict";
16407
16408/**
16409 * @ngdoc module
16410 * @name material.components.radioButton
16411 * @description radioButton module!
16412 */
16413mdRadioGroupDirective.$inject = ["$mdUtil", "$mdConstant", "$mdTheming", "$timeout"];
16414mdRadioButtonDirective.$inject = ["$mdAria", "$mdUtil", "$mdTheming"];
16415angular.module('material.components.radioButton', [
16416  'material.core'
16417])
16418  .directive('mdRadioGroup', mdRadioGroupDirective)
16419  .directive('mdRadioButton', mdRadioButtonDirective);
16420
16421/**
16422 * @ngdoc directive
16423 * @module material.components.radioButton
16424 * @name mdRadioGroup
16425 *
16426 * @restrict E
16427 *
16428 * @description
16429 * The `<md-radio-group>` directive identifies a grouping
16430 * container for the 1..n grouped radio buttons; specified using nested
16431 * `<md-radio-button>` tags.
16432 *
16433 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
16434 * the radio button is in the accent color by default. The primary color palette may be used with
16435 * the `md-primary` class.
16436 *
16437 * Note: `<md-radio-group>` and `<md-radio-button>` handle tabindex differently
16438 * than the native `<input type='radio'>` controls. Whereas the native controls
16439 * force the user to tab through all the radio buttons, `<md-radio-group>`
16440 * is focusable, and by default the `<md-radio-button>`s are not.
16441 *
16442 * @param {string} ng-model Assignable angular expression to data-bind to.
16443 * @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects.
16444 *
16445 * @usage
16446 * <hljs lang="html">
16447 * <md-radio-group ng-model="selected">
16448 *
16449 *   <md-radio-button
16450 *        ng-repeat="d in colorOptions"
16451 *        ng-value="d.value" aria-label="{{ d.label }}">
16452 *
16453 *          {{ d.label }}
16454 *
16455 *   </md-radio-button>
16456 *
16457 * </md-radio-group>
16458 * </hljs>
16459 *
16460 */
16461function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) {
16462  RadioGroupController.prototype = createRadioGroupControllerProto();
16463
16464  return {
16465    restrict: 'E',
16466    controller: ['$element', RadioGroupController],
16467    require: ['mdRadioGroup', '?ngModel'],
16468    link: { pre: linkRadioGroup }
16469  };
16470
16471  function linkRadioGroup(scope, element, attr, ctrls) {
16472    element.addClass('_md');     // private md component indicator for styling
16473    $mdTheming(element);
16474    
16475    var rgCtrl = ctrls[0];
16476    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
16477
16478    rgCtrl.init(ngModelCtrl);
16479
16480    scope.mouseActive = false;
16481
16482    element
16483      .attr({
16484        'role': 'radiogroup',
16485        'tabIndex': element.attr('tabindex') || '0'
16486      })
16487      .on('keydown', keydownListener)
16488      .on('mousedown', function(event) {
16489        scope.mouseActive = true;
16490        $timeout(function() {
16491          scope.mouseActive = false;
16492        }, 100);
16493      })
16494      .on('focus', function() {
16495        if(scope.mouseActive === false) {
16496          rgCtrl.$element.addClass('md-focused');
16497        }
16498      })
16499      .on('blur', function() {
16500        rgCtrl.$element.removeClass('md-focused');
16501      });
16502
16503    /**
16504     *
16505     */
16506    function setFocus() {
16507      if (!element.hasClass('md-focused')) { element.addClass('md-focused'); }
16508    }
16509
16510    /**
16511     *
16512     */
16513    function keydownListener(ev) {
16514      var keyCode = ev.which || ev.keyCode;
16515
16516      // Only listen to events that we originated ourselves
16517      // so that we don't trigger on things like arrow keys in
16518      // inputs.
16519
16520      if (keyCode != $mdConstant.KEY_CODE.ENTER &&
16521          ev.currentTarget != ev.target) {
16522        return;
16523      }
16524
16525      switch (keyCode) {
16526        case $mdConstant.KEY_CODE.LEFT_ARROW:
16527        case $mdConstant.KEY_CODE.UP_ARROW:
16528          ev.preventDefault();
16529          rgCtrl.selectPrevious();
16530          setFocus();
16531          break;
16532
16533        case $mdConstant.KEY_CODE.RIGHT_ARROW:
16534        case $mdConstant.KEY_CODE.DOWN_ARROW:
16535          ev.preventDefault();
16536          rgCtrl.selectNext();
16537          setFocus();
16538          break;
16539
16540        case $mdConstant.KEY_CODE.ENTER:
16541          var form = angular.element($mdUtil.getClosest(element[0], 'form'));
16542          if (form.length > 0) {
16543            form.triggerHandler('submit');
16544          }
16545          break;
16546      }
16547
16548    }
16549  }
16550
16551  function RadioGroupController($element) {
16552    this._radioButtonRenderFns = [];
16553    this.$element = $element;
16554  }
16555
16556  function createRadioGroupControllerProto() {
16557    return {
16558      init: function(ngModelCtrl) {
16559        this._ngModelCtrl = ngModelCtrl;
16560        this._ngModelCtrl.$render = angular.bind(this, this.render);
16561      },
16562      add: function(rbRender) {
16563        this._radioButtonRenderFns.push(rbRender);
16564      },
16565      remove: function(rbRender) {
16566        var index = this._radioButtonRenderFns.indexOf(rbRender);
16567        if (index !== -1) {
16568          this._radioButtonRenderFns.splice(index, 1);
16569        }
16570      },
16571      render: function() {
16572        this._radioButtonRenderFns.forEach(function(rbRender) {
16573          rbRender();
16574        });
16575      },
16576      setViewValue: function(value, eventType) {
16577        this._ngModelCtrl.$setViewValue(value, eventType);
16578        // update the other radio buttons as well
16579        this.render();
16580      },
16581      getViewValue: function() {
16582        return this._ngModelCtrl.$viewValue;
16583      },
16584      selectNext: function() {
16585        return changeSelectedButton(this.$element, 1);
16586      },
16587      selectPrevious: function() {
16588        return changeSelectedButton(this.$element, -1);
16589      },
16590      setActiveDescendant: function (radioId) {
16591        this.$element.attr('aria-activedescendant', radioId);
16592      },
16593      isDisabled: function() {
16594        return this.$element[0].hasAttribute('disabled');
16595      }
16596    };
16597  }
16598  /**
16599   * Change the radio group's selected button by a given increment.
16600   * If no button is selected, select the first button.
16601   */
16602  function changeSelectedButton(parent, increment) {
16603    // Coerce all child radio buttons into an array, then wrap then in an iterator
16604    var buttons = $mdUtil.iterator(parent[0].querySelectorAll('md-radio-button'), true);
16605
16606    if (buttons.count()) {
16607      var validate = function (button) {
16608        // If disabled, then NOT valid
16609        return !angular.element(button).attr("disabled");
16610      };
16611
16612      var selected = parent[0].querySelector('md-radio-button.md-checked');
16613      var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
16614
16615      // Activate radioButton's click listener (triggerHandler won't create a real click event)
16616      angular.element(target).triggerHandler('click');
16617
16618
16619    }
16620  }
16621
16622}
16623
16624/**
16625 * @ngdoc directive
16626 * @module material.components.radioButton
16627 * @name mdRadioButton
16628 *
16629 * @restrict E
16630 *
16631 * @description
16632 * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
16633 *
16634 * While similar to the `<input type="radio" ng-model="" value="">` directive,
16635 * the `<md-radio-button>` directive provides ink effects, ARIA support, and
16636 * supports use within named radio groups.
16637 *
16638 * @param {string} ngModel Assignable angular expression to data-bind to.
16639 * @param {string=} ngChange Angular expression to be executed when input changes due to user
16640 *    interaction with the input element.
16641 * @param {string} ngValue Angular expression which sets the value to which the expression should
16642 *    be set when selected.
16643 * @param {string} value The value to which the expression should be set when selected.
16644 * @param {string=} name Property name of the form under which the control is published.
16645 * @param {string=} aria-label Adds label to radio button for accessibility.
16646 * Defaults to radio button's text. If no text content is available, a warning will be logged.
16647 *
16648 * @usage
16649 * <hljs lang="html">
16650 *
16651 * <md-radio-button value="1" aria-label="Label 1">
16652 *   Label 1
16653 * </md-radio-button>
16654 *
16655 * <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
16656 *   Green
16657 * </md-radio-button>
16658 *
16659 * </hljs>
16660 *
16661 */
16662function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
16663
16664  var CHECKED_CSS = 'md-checked';
16665
16666  return {
16667    restrict: 'E',
16668    require: '^mdRadioGroup',
16669    transclude: true,
16670    template: '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
16671                '<div class="md-off"></div>' +
16672                '<div class="md-on"></div>' +
16673              '</div>' +
16674              '<div ng-transclude class="md-label"></div>',
16675    link: link
16676  };
16677
16678  function link(scope, element, attr, rgCtrl) {
16679    var lastChecked;
16680
16681    $mdTheming(element);
16682    configureAria(element, scope);
16683
16684    initialize();
16685
16686    /**
16687     *
16688     */
16689    function initialize() {
16690      if (!rgCtrl) {
16691        throw 'RadioButton: No RadioGroupController could be found.';
16692      }
16693
16694      rgCtrl.add(render);
16695      attr.$observe('value', render);
16696
16697      element
16698        .on('click', listener)
16699        .on('$destroy', function() {
16700          rgCtrl.remove(render);
16701        });
16702    }
16703
16704    /**
16705     *
16706     */
16707    function listener(ev) {
16708      if (element[0].hasAttribute('disabled') || rgCtrl.isDisabled()) return;
16709
16710      scope.$apply(function() {
16711        rgCtrl.setViewValue(attr.value, ev && ev.type);
16712      });
16713    }
16714
16715    /**
16716     *  Add or remove the `.md-checked` class from the RadioButton (and conditionally its parent).
16717     *  Update the `aria-activedescendant` attribute.
16718     */
16719    function render() {
16720      var checked = (rgCtrl.getViewValue() == attr.value);
16721      if (checked === lastChecked) {
16722        return;
16723      }
16724
16725      lastChecked = checked;
16726      element.attr('aria-checked', checked);
16727
16728      if (checked) {
16729        markParentAsChecked(true);
16730        element.addClass(CHECKED_CSS);
16731
16732        rgCtrl.setActiveDescendant(element.attr('id'));
16733
16734      } else {
16735        markParentAsChecked(false);
16736        element.removeClass(CHECKED_CSS);
16737      }
16738
16739      /**
16740       * If the radioButton is inside a div, then add class so highlighting will work...
16741       */
16742      function markParentAsChecked(addClass ) {
16743        if ( element.parent()[0].nodeName != "MD-RADIO-GROUP") {
16744          element.parent()[ !!addClass ? 'addClass' : 'removeClass'](CHECKED_CSS);
16745        }
16746
16747      }
16748    }
16749
16750    /**
16751     * Inject ARIA-specific attributes appropriate for each radio button
16752     */
16753    function configureAria( element, scope ){
16754      scope.ariaId = buildAriaID();
16755
16756      element.attr({
16757        'id' :  scope.ariaId,
16758        'role' : 'radio',
16759        'aria-checked' : 'false'
16760      });
16761
16762      $mdAria.expectWithText(element, 'aria-label');
16763
16764      /**
16765       * Build a unique ID for each radio button that will be used with aria-activedescendant.
16766       * Preserve existing ID if already specified.
16767       * @returns {*|string}
16768       */
16769      function buildAriaID() {
16770        return attr.id || ( 'radio' + "_" + $mdUtil.nextUid() );
16771      }
16772    }
16773  }
16774}
16775
16776})();
16777(function(){
16778"use strict";
16779
16780/**
16781 * @ngdoc module
16782 * @name material.components.select
16783 */
16784
16785/***************************************************
16786
16787 ### TODO - POST RC1 ###
16788 - [ ] Abstract placement logic in $mdSelect service to $mdMenu service
16789
16790 ***************************************************/
16791
16792SelectDirective.$inject = ["$mdSelect", "$mdUtil", "$mdConstant", "$mdTheming", "$mdAria", "$compile", "$parse"];
16793SelectMenuDirective.$inject = ["$parse", "$mdUtil", "$mdConstant", "$mdTheming"];
16794OptionDirective.$inject = ["$mdButtonInkRipple", "$mdUtil"];
16795SelectProvider.$inject = ["$$interimElementProvider"];
16796var SELECT_EDGE_MARGIN = 8;
16797var selectNextId = 0;
16798var CHECKBOX_SELECTION_INDICATOR =
16799  angular.element('<div class="md-container"><div class="md-icon"></div></div>');
16800
16801angular.module('material.components.select', [
16802    'material.core',
16803    'material.components.backdrop'
16804  ])
16805  .directive('mdSelect', SelectDirective)
16806  .directive('mdSelectMenu', SelectMenuDirective)
16807  .directive('mdOption', OptionDirective)
16808  .directive('mdOptgroup', OptgroupDirective)
16809  .directive('mdSelectHeader', SelectHeaderDirective)
16810  .provider('$mdSelect', SelectProvider);
16811
16812/**
16813 * @ngdoc directive
16814 * @name mdSelect
16815 * @restrict E
16816 * @module material.components.select
16817 *
16818 * @description Displays a select box, bound to an ng-model.
16819 *
16820 * When the select is required and uses a floating label, then the label will automatically contain
16821 * an asterisk (`*`). This behavior can be disabled by using the `md-no-asterisk` attribute.
16822 *
16823 * By default, the select will display with an underline to match other form elements. This can be
16824 * disabled by applying the `md-no-underline` CSS class.
16825 *
16826 * ### Option Params
16827 *
16828 * When applied, `md-option-empty` will mark the option as "empty" allowing the option to  clear the
16829 * select and put it back in it's default state. You may supply this attribute on any option you
16830 * wish, however, it is automatically applied to an option whose `value` or `ng-value` are not
16831 * defined.
16832 *
16833 * **Automatically Applied**
16834 *
16835 *  - `<md-option>`
16836 *  - `<md-option value>`
16837 *  - `<md-option value="">`
16838 *  - `<md-option ng-value>`
16839 *  - `<md-option ng-value="">`
16840 *
16841 * **NOT Automatically Applied**
16842 *
16843 *  - `<md-option ng-value="1">`
16844 *  - `<md-option ng-value="''">`
16845 *  - `<md-option ng-value="undefined">`
16846 *  - `<md-option value="undefined">` (this evaluates to the string `"undefined"`)
16847 *  - <code ng-non-bindable>&lt;md-option ng-value="{{someValueThatMightBeUndefined}}"&gt;</code>
16848 *
16849 * **Note:** A value of `undefined` ***is considered a valid value*** (and does not auto-apply this
16850 * attribute) since you may wish this to be your "Not Available" or "None" option.
16851 *
16852 * **Note:** Using the `value` attribute (as opposed to `ng-value`) always evaluates to a string, so
16853 * `value="null"` will require the test `ng-if="myValue != 'null'"` rather than `ng-if="!myValue"`.
16854 *
16855 * @param {expression} ng-model The model!
16856 * @param {boolean=} multiple Whether it's multiple.
16857 * @param {expression=} md-on-close Expression to be evaluated when the select is closed.
16858 * @param {expression=} md-on-open Expression to be evaluated when opening the select.
16859 * Will hide the select options and show a spinner until the evaluated promise resolves.
16860 * @param {expression=} md-selected-text Expression to be evaluated that will return a string
16861 * to be displayed as a placeholder in the select input box when it is closed.
16862 * @param {string=} placeholder Placeholder hint text.
16863 * @param md-no-asterisk {boolean=} When set to true, an asterisk will not be appended to the
16864 * floating label. **Note:** This attribute is only evaluated once; it is not watched.
16865 * @param {string=} aria-label Optional label for accessibility. Only necessary if no placeholder or
16866 * explicit label is present.
16867 * @param {string=} md-container-class Class list to get applied to the `.md-select-menu-container`
16868 * element (for custom styling).
16869 *
16870 * @usage
16871 * With a placeholder (label and aria-label are added dynamically)
16872 * <hljs lang="html">
16873 *   <md-input-container>
16874 *     <md-select
16875 *       ng-model="someModel"
16876 *       placeholder="Select a state">
16877 *       <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
16878 *     </md-select>
16879 *   </md-input-container>
16880 * </hljs>
16881 *
16882 * With an explicit label
16883 * <hljs lang="html">
16884 *   <md-input-container>
16885 *     <label>State</label>
16886 *     <md-select
16887 *       ng-model="someModel">
16888 *       <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
16889 *     </md-select>
16890 *   </md-input-container>
16891 * </hljs>
16892 *
16893 * With a select-header
16894 *
16895 * When a developer needs to put more than just a text label in the
16896 * md-select-menu, they should use the md-select-header.
16897 * The user can put custom HTML inside of the header and style it to their liking.
16898 * One common use case of this would be a sticky search bar.
16899 *
16900 * When using the md-select-header the labels that would previously be added to the
16901 * OptGroupDirective are ignored.
16902 *
16903 * <hljs lang="html">
16904 *   <md-input-container>
16905 *     <md-select ng-model="someModel">
16906 *       <md-select-header>
16907 *         <span> Neighborhoods - </span>
16908 *       </md-select-header>
16909 *       <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
16910 *     </md-select>
16911 *   </md-input-container>
16912 * </hljs>
16913 *
16914 * ## Selects and object equality
16915 * When using a `md-select` to pick from a list of objects, it is important to realize how javascript handles
16916 * equality. Consider the following example:
16917 * <hljs lang="js">
16918 * angular.controller('MyCtrl', function($scope) {
16919 *   $scope.users = [
16920 *     { id: 1, name: 'Bob' },
16921 *     { id: 2, name: 'Alice' },
16922 *     { id: 3, name: 'Steve' }
16923 *   ];
16924 *   $scope.selectedUser = { id: 1, name: 'Bob' };
16925 * });
16926 * </hljs>
16927 * <hljs lang="html">
16928 * <div ng-controller="MyCtrl">
16929 *   <md-select ng-model="selectedUser">
16930 *     <md-option ng-value="user" ng-repeat="user in users">{{ user.name }}</md-option>
16931 *   </md-select>
16932 * </div>
16933 * </hljs>
16934 *
16935 * At first one might expect that the select should be populated with "Bob" as the selected user. However,
16936 * this is not true. To determine whether something is selected,
16937 * `ngModelController` is looking at whether `$scope.selectedUser == (any user in $scope.users);`;
16938 *
16939 * Javascript's `==` operator does not check for deep equality (ie. that all properties
16940 * on the object are the same), but instead whether the objects are *the same object in memory*.
16941 * In this case, we have two instances of identical objects, but they exist in memory as unique
16942 * entities. Because of this, the select will have no value populated for a selected user.
16943 *
16944 * To get around this, `ngModelController` provides a `track by` option that allows us to specify a different
16945 * expression which will be used for the equality operator. As such, we can update our `html` to
16946 * make use of this by specifying the `ng-model-options="{trackBy: '$value.id'}"` on the `md-select`
16947 * element. This converts our equality expression to be
16948 * `$scope.selectedUser.id == (any id in $scope.users.map(function(u) { return u.id; }));`
16949 * which results in Bob being selected as desired.
16950 *
16951 * Working HTML:
16952 * <hljs lang="html">
16953 * <div ng-controller="MyCtrl">
16954 *   <md-select ng-model="selectedUser" ng-model-options="{trackBy: '$value.id'}">
16955 *     <md-option ng-value="user" ng-repeat="user in users">{{ user.name }}</md-option>
16956 *   </md-select>
16957 * </div>
16958 * </hljs>
16959 */
16960function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $compile, $parse) {
16961  var keyCodes = $mdConstant.KEY_CODE;
16962  var NAVIGATION_KEYS = [keyCodes.SPACE, keyCodes.ENTER, keyCodes.UP_ARROW, keyCodes.DOWN_ARROW];
16963
16964  return {
16965    restrict: 'E',
16966    require: ['^?mdInputContainer', 'mdSelect', 'ngModel', '?^form'],
16967    compile: compile,
16968    controller: function() {
16969    } // empty placeholder controller to be initialized in link
16970  };
16971
16972  function compile(element, attr) {
16973    // add the select value that will hold our placeholder or selected option value
16974    var valueEl = angular.element('<md-select-value><span></span></md-select-value>');
16975    valueEl.append('<span class="md-select-icon" aria-hidden="true"></span>');
16976    valueEl.addClass('md-select-value');
16977    if (!valueEl[0].hasAttribute('id')) {
16978      valueEl.attr('id', 'select_value_label_' + $mdUtil.nextUid());
16979    }
16980
16981    // There's got to be an md-content inside. If there's not one, let's add it.
16982    if (!element.find('md-content').length) {
16983      element.append(angular.element('<md-content>').append(element.contents()));
16984    }
16985
16986
16987    // Add progress spinner for md-options-loading
16988    if (attr.mdOnOpen) {
16989
16990      // Show progress indicator while loading async
16991      // Use ng-hide for `display:none` so the indicator does not interfere with the options list
16992      element
16993        .find('md-content')
16994        .prepend(angular.element(
16995          '<div>' +
16996          ' <md-progress-circular md-mode="indeterminate" ng-if="$$loadingAsyncDone === false" md-diameter="25px"></md-progress-circular>' +
16997          '</div>'
16998        ));
16999
17000      // Hide list [of item options] while loading async
17001      element
17002        .find('md-option')
17003        .attr('ng-show', '$$loadingAsyncDone');
17004    }
17005
17006    if (attr.name) {
17007      var autofillClone = angular.element('<select class="md-visually-hidden">');
17008      autofillClone.attr({
17009        'name': attr.name,
17010        'aria-hidden': 'true',
17011        'tabindex': '-1'
17012      });
17013      var opts = element.find('md-option');
17014      angular.forEach(opts, function(el) {
17015        var newEl = angular.element('<option>' + el.innerHTML + '</option>');
17016        if (el.hasAttribute('ng-value')) newEl.attr('ng-value', el.getAttribute('ng-value'));
17017        else if (el.hasAttribute('value')) newEl.attr('value', el.getAttribute('value'));
17018        autofillClone.append(newEl);
17019      });
17020
17021      // Adds an extra option that will hold the selected value for the
17022      // cases where the select is a part of a non-angular form. This can be done with a ng-model,
17023      // however if the `md-option` is being `ng-repeat`-ed, Angular seems to insert a similar
17024      // `option` node, but with a value of `? string: <value> ?` which would then get submitted.
17025      // This also goes around having to prepend a dot to the name attribute.
17026      autofillClone.append(
17027        '<option ng-value="' + attr.ngModel + '" selected></option>'
17028      );
17029
17030      element.parent().append(autofillClone);
17031    }
17032
17033    var isMultiple = $mdUtil.parseAttributeBoolean(attr.multiple);
17034
17035    // Use everything that's left inside element.contents() as the contents of the menu
17036    var multipleContent = isMultiple ? 'multiple' : '';
17037    var selectTemplate = '' +
17038      '<div class="md-select-menu-container" aria-hidden="true">' +
17039      '<md-select-menu {0}>{1}</md-select-menu>' +
17040      '</div>';
17041
17042    selectTemplate = $mdUtil.supplant(selectTemplate, [multipleContent, element.html()]);
17043    element.empty().append(valueEl);
17044    element.append(selectTemplate);
17045
17046    if(!attr.tabindex){
17047      attr.$set('tabindex', 0);
17048    }
17049
17050    return function postLink(scope, element, attr, ctrls) {
17051      var untouched = true;
17052      var isDisabled, ariaLabelBase;
17053
17054      var containerCtrl = ctrls[0];
17055      var mdSelectCtrl = ctrls[1];
17056      var ngModelCtrl = ctrls[2];
17057      var formCtrl = ctrls[3];
17058      // grab a reference to the select menu value label
17059      var valueEl = element.find('md-select-value');
17060      var isReadonly = angular.isDefined(attr.readonly);
17061      var disableAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
17062
17063      if (disableAsterisk) {
17064        element.addClass('md-no-asterisk');
17065      }
17066
17067      if (containerCtrl) {
17068        var isErrorGetter = containerCtrl.isErrorGetter || function() {
17069          return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (formCtrl && formCtrl.$submitted));
17070        };
17071
17072        if (containerCtrl.input) {
17073          // We ignore inputs that are in the md-select-header (one
17074          // case where this might be useful would be adding as searchbox)
17075          if (element.find('md-select-header').find('input')[0] !== containerCtrl.input[0]) {
17076            throw new Error("<md-input-container> can only have *one* child <input>, <textarea> or <select> element!");
17077          }
17078        }
17079
17080        containerCtrl.input = element;
17081        if (!containerCtrl.label) {
17082          $mdAria.expect(element, 'aria-label', element.attr('placeholder'));
17083        }
17084
17085        scope.$watch(isErrorGetter, containerCtrl.setInvalid);
17086      }
17087
17088      var selectContainer, selectScope, selectMenuCtrl;
17089
17090      findSelectContainer();
17091      $mdTheming(element);
17092
17093      if (formCtrl && angular.isDefined(attr.multiple)) {
17094        $mdUtil.nextTick(function() {
17095          var hasModelValue = ngModelCtrl.$modelValue || ngModelCtrl.$viewValue;
17096          if (hasModelValue) {
17097            formCtrl.$setPristine();
17098          }
17099        });
17100      }
17101
17102      var originalRender = ngModelCtrl.$render;
17103      ngModelCtrl.$render = function() {
17104        originalRender();
17105        syncLabelText();
17106        syncAriaLabel();
17107        inputCheckValue();
17108      };
17109
17110      attr.$observe('placeholder', ngModelCtrl.$render);
17111
17112      if (containerCtrl && containerCtrl.label) {
17113        attr.$observe('required', function (value) {
17114          // Toggle the md-required class on the input containers label, because the input container is automatically
17115          // applying the asterisk indicator on the label.
17116          containerCtrl.label.toggleClass('md-required', value && !disableAsterisk);
17117        });
17118      }
17119
17120      mdSelectCtrl.setLabelText = function(text) {
17121        mdSelectCtrl.setIsPlaceholder(!text);
17122
17123        if (attr.mdSelectedText) {
17124          text = $parse(attr.mdSelectedText)(scope);
17125        } else {
17126          // Use placeholder attribute, otherwise fallback to the md-input-container label
17127          var tmpPlaceholder = attr.placeholder ||
17128              (containerCtrl && containerCtrl.label ? containerCtrl.label.text() : '');
17129          text = text || tmpPlaceholder || '';
17130        }
17131
17132        var target = valueEl.children().eq(0);
17133        target.html(text);
17134      };
17135
17136      mdSelectCtrl.setIsPlaceholder = function(isPlaceholder) {
17137        if (isPlaceholder) {
17138          valueEl.addClass('md-select-placeholder');
17139          if (containerCtrl && containerCtrl.label) {
17140            containerCtrl.label.addClass('md-placeholder');
17141          }
17142        } else {
17143          valueEl.removeClass('md-select-placeholder');
17144          if (containerCtrl && containerCtrl.label) {
17145            containerCtrl.label.removeClass('md-placeholder');
17146          }
17147        }
17148      };
17149
17150      if (!isReadonly) {
17151        element
17152          .on('focus', function(ev) {
17153            // Always focus the container (if we have one) so floating labels and other styles are
17154            // applied properly
17155            containerCtrl && containerCtrl.setFocused(true);
17156          });
17157
17158        // Attach before ngModel's blur listener to stop propagation of blur event
17159        // to prevent from setting $touched.
17160        element.on('blur', function(event) {
17161          if (untouched) {
17162            untouched = false;
17163            if (selectScope._mdSelectIsOpen) {
17164              event.stopImmediatePropagation();
17165            }
17166          }
17167
17168          if (selectScope._mdSelectIsOpen) return;
17169          containerCtrl && containerCtrl.setFocused(false);
17170          inputCheckValue();
17171        });
17172      }
17173
17174      mdSelectCtrl.triggerClose = function() {
17175        $parse(attr.mdOnClose)(scope);
17176      };
17177
17178      scope.$$postDigest(function() {
17179        initAriaLabel();
17180        syncLabelText();
17181        syncAriaLabel();
17182      });
17183
17184      function initAriaLabel() {
17185        var labelText = element.attr('aria-label') || element.attr('placeholder');
17186        if (!labelText && containerCtrl && containerCtrl.label) {
17187          labelText = containerCtrl.label.text();
17188        }
17189        ariaLabelBase = labelText;
17190        $mdAria.expect(element, 'aria-label', labelText);
17191      }
17192
17193      scope.$watch(function() {
17194          return selectMenuCtrl.selectedLabels();
17195      }, syncLabelText);
17196
17197      function syncLabelText() {
17198        if (selectContainer) {
17199          selectMenuCtrl = selectMenuCtrl || selectContainer.find('md-select-menu').controller('mdSelectMenu');
17200          mdSelectCtrl.setLabelText(selectMenuCtrl.selectedLabels());
17201        }
17202      }
17203
17204      function syncAriaLabel() {
17205        if (!ariaLabelBase) return;
17206        var ariaLabels = selectMenuCtrl.selectedLabels({mode: 'aria'});
17207        element.attr('aria-label', ariaLabels.length ? ariaLabelBase + ': ' + ariaLabels : ariaLabelBase);
17208      }
17209
17210      var deregisterWatcher;
17211      attr.$observe('ngMultiple', function(val) {
17212        if (deregisterWatcher) deregisterWatcher();
17213        var parser = $parse(val);
17214        deregisterWatcher = scope.$watch(function() {
17215          return parser(scope);
17216        }, function(multiple, prevVal) {
17217          if (multiple === undefined && prevVal === undefined) return; // assume compiler did a good job
17218          if (multiple) {
17219            element.attr('multiple', 'multiple');
17220          } else {
17221            element.removeAttr('multiple');
17222          }
17223          element.attr('aria-multiselectable', multiple ? 'true' : 'false');
17224          if (selectContainer) {
17225            selectMenuCtrl.setMultiple(multiple);
17226            originalRender = ngModelCtrl.$render;
17227            ngModelCtrl.$render = function() {
17228              originalRender();
17229              syncLabelText();
17230              syncAriaLabel();
17231              inputCheckValue();
17232            };
17233            ngModelCtrl.$render();
17234          }
17235        });
17236      });
17237
17238      attr.$observe('disabled', function(disabled) {
17239        if (angular.isString(disabled)) {
17240          disabled = true;
17241        }
17242        // Prevent click event being registered twice
17243        if (isDisabled !== undefined && isDisabled === disabled) {
17244          return;
17245        }
17246        isDisabled = disabled;
17247        if (disabled) {
17248          element
17249            .attr({'aria-disabled': 'true'})
17250            .removeAttr('tabindex')
17251            .off('click', openSelect)
17252            .off('keydown', handleKeypress);
17253        } else {
17254          element
17255            .attr({'tabindex': attr.tabindex, 'aria-disabled': 'false'})
17256            .on('click', openSelect)
17257            .on('keydown', handleKeypress);
17258        }
17259      });
17260
17261      if (!attr.hasOwnProperty('disabled') && !attr.hasOwnProperty('ngDisabled')) {
17262        element.attr({'aria-disabled': 'false'});
17263        element.on('click', openSelect);
17264        element.on('keydown', handleKeypress);
17265      }
17266
17267      var ariaAttrs = {
17268        role: 'listbox',
17269        'aria-expanded': 'false',
17270        'aria-multiselectable': isMultiple && !attr.ngMultiple ? 'true' : 'false'
17271      };
17272
17273      if (!element[0].hasAttribute('id')) {
17274        ariaAttrs.id = 'select_' + $mdUtil.nextUid();
17275      }
17276
17277      var containerId = 'select_container_' + $mdUtil.nextUid();
17278      selectContainer.attr('id', containerId);
17279      ariaAttrs['aria-owns'] = containerId;
17280      element.attr(ariaAttrs);
17281
17282      scope.$on('$destroy', function() {
17283        $mdSelect
17284          .destroy()
17285          .finally(function() {
17286            if (containerCtrl) {
17287              containerCtrl.setFocused(false);
17288              containerCtrl.setHasValue(false);
17289              containerCtrl.input = null;
17290            }
17291            ngModelCtrl.$setTouched();
17292          });
17293      });
17294
17295
17296
17297      function inputCheckValue() {
17298        // The select counts as having a value if one or more options are selected,
17299        // or if the input's validity state says it has bad input (eg string in a number input)
17300        containerCtrl && containerCtrl.setHasValue(selectMenuCtrl.selectedLabels().length > 0 || (element[0].validity || {}).badInput);
17301      }
17302
17303      function findSelectContainer() {
17304        selectContainer = angular.element(
17305          element[0].querySelector('.md-select-menu-container')
17306        );
17307        selectScope = scope;
17308        if (attr.mdContainerClass) {
17309          var value = selectContainer[0].getAttribute('class') + ' ' + attr.mdContainerClass;
17310          selectContainer[0].setAttribute('class', value);
17311        }
17312        selectMenuCtrl = selectContainer.find('md-select-menu').controller('mdSelectMenu');
17313        selectMenuCtrl.init(ngModelCtrl, attr.ngModel);
17314        element.on('$destroy', function() {
17315          selectContainer.remove();
17316        });
17317      }
17318
17319      function handleKeypress(e) {
17320        if ($mdConstant.isNavigationKey(e)) {
17321          // prevent page scrolling on interaction
17322          e.preventDefault();
17323          openSelect(e);
17324        } else {
17325          if ($mdConstant.isInputKey(e) || $mdConstant.isNumPadKey(e)) {
17326            e.preventDefault();
17327
17328            var node = selectMenuCtrl.optNodeForKeyboardSearch(e);
17329            if (!node || node.hasAttribute('disabled')) return;
17330            var optionCtrl = angular.element(node).controller('mdOption');
17331            if (!selectMenuCtrl.isMultiple) {
17332              selectMenuCtrl.deselect(Object.keys(selectMenuCtrl.selected)[0]);
17333            }
17334            selectMenuCtrl.select(optionCtrl.hashKey, optionCtrl.value);
17335            selectMenuCtrl.refreshViewValue();
17336          }
17337        }
17338      }
17339
17340      function openSelect() {
17341        selectScope._mdSelectIsOpen = true;
17342        element.attr('aria-expanded', 'true');
17343
17344        $mdSelect.show({
17345          scope: selectScope,
17346          preserveScope: true,
17347          skipCompile: true,
17348          element: selectContainer,
17349          target: element[0],
17350          selectCtrl: mdSelectCtrl,
17351          preserveElement: true,
17352          hasBackdrop: true,
17353          loadingAsync: attr.mdOnOpen ? scope.$eval(attr.mdOnOpen) || true : false
17354        }).finally(function() {
17355          selectScope._mdSelectIsOpen = false;
17356          element.focus();
17357          element.attr('aria-expanded', 'false');
17358          ngModelCtrl.$setTouched();
17359        });
17360      }
17361
17362    };
17363  }
17364}
17365
17366function SelectMenuDirective($parse, $mdUtil, $mdConstant, $mdTheming) {
17367  // We want the scope to be set to 'false' so an isolated scope is not created
17368  // which would interfere with the md-select-header's access to the
17369  // parent scope.
17370  SelectMenuController.$inject = ["$scope", "$attrs", "$element"];
17371  return {
17372    restrict: 'E',
17373    require: ['mdSelectMenu'],
17374    scope: false,
17375    controller: SelectMenuController,
17376    link: {pre: preLink}
17377  };
17378
17379  // We use preLink instead of postLink to ensure that the select is initialized before
17380  // its child options run postLink.
17381  function preLink(scope, element, attr, ctrls) {
17382    var selectCtrl = ctrls[0];
17383
17384    element.addClass('_md');     // private md component indicator for styling
17385
17386    $mdTheming(element);
17387    element.on('click', clickListener);
17388    element.on('keypress', keyListener);
17389
17390    function keyListener(e) {
17391      if (e.keyCode == 13 || e.keyCode == 32) {
17392        clickListener(e);
17393      }
17394    }
17395
17396    function clickListener(ev) {
17397      var option = $mdUtil.getClosest(ev.target, 'md-option');
17398      var optionCtrl = option && angular.element(option).data('$mdOptionController');
17399      if (!option || !optionCtrl) return;
17400      if (option.hasAttribute('disabled')) {
17401        ev.stopImmediatePropagation();
17402        return false;
17403      }
17404
17405      var optionHashKey = selectCtrl.hashGetter(optionCtrl.value);
17406      var isSelected = angular.isDefined(selectCtrl.selected[optionHashKey]);
17407
17408      scope.$apply(function() {
17409        if (selectCtrl.isMultiple) {
17410          if (isSelected) {
17411            selectCtrl.deselect(optionHashKey);
17412          } else {
17413            selectCtrl.select(optionHashKey, optionCtrl.value);
17414          }
17415        } else {
17416          if (!isSelected) {
17417            selectCtrl.deselect(Object.keys(selectCtrl.selected)[0]);
17418            selectCtrl.select(optionHashKey, optionCtrl.value);
17419          }
17420        }
17421        selectCtrl.refreshViewValue();
17422      });
17423    }
17424  }
17425
17426  function SelectMenuController($scope, $attrs, $element) {
17427    var self = this;
17428    self.isMultiple = angular.isDefined($attrs.multiple);
17429    // selected is an object with keys matching all of the selected options' hashed values
17430    self.selected = {};
17431    // options is an object with keys matching every option's hash value,
17432    // and values matching every option's controller.
17433    self.options = {};
17434
17435    $scope.$watchCollection(function() {
17436      return self.options;
17437    }, function() {
17438      self.ngModel.$render();
17439    });
17440
17441    var deregisterCollectionWatch;
17442    var defaultIsEmpty;
17443    self.setMultiple = function(isMultiple) {
17444      var ngModel = self.ngModel;
17445      defaultIsEmpty = defaultIsEmpty || ngModel.$isEmpty;
17446
17447      self.isMultiple = isMultiple;
17448      if (deregisterCollectionWatch) deregisterCollectionWatch();
17449
17450      if (self.isMultiple) {
17451        ngModel.$validators['md-multiple'] = validateArray;
17452        ngModel.$render = renderMultiple;
17453
17454        // watchCollection on the model because by default ngModel only watches the model's
17455        // reference. This allowed the developer to also push and pop from their array.
17456        $scope.$watchCollection(self.modelBinding, function(value) {
17457          if (validateArray(value)) renderMultiple(value);
17458          self.ngModel.$setPristine();
17459        });
17460
17461        ngModel.$isEmpty = function(value) {
17462          return !value || value.length === 0;
17463        };
17464      } else {
17465        delete ngModel.$validators['md-multiple'];
17466        ngModel.$render = renderSingular;
17467      }
17468
17469      function validateArray(modelValue, viewValue) {
17470        // If a value is truthy but not an array, reject it.
17471        // If value is undefined/falsy, accept that it's an empty array.
17472        return angular.isArray(modelValue || viewValue || []);
17473      }
17474    };
17475
17476    var searchStr = '';
17477    var clearSearchTimeout, optNodes, optText;
17478    var CLEAR_SEARCH_AFTER = 300;
17479
17480    self.optNodeForKeyboardSearch = function(e) {
17481      clearSearchTimeout && clearTimeout(clearSearchTimeout);
17482      clearSearchTimeout = setTimeout(function() {
17483        clearSearchTimeout = undefined;
17484        searchStr = '';
17485        optText = undefined;
17486        optNodes = undefined;
17487      }, CLEAR_SEARCH_AFTER);
17488
17489      // Support 1-9 on numpad
17490      var keyCode = e.keyCode - ($mdConstant.isNumPadKey(e) ? 48 : 0);
17491
17492      searchStr += String.fromCharCode(keyCode);
17493      var search = new RegExp('^' + searchStr, 'i');
17494      if (!optNodes) {
17495        optNodes = $element.find('md-option');
17496        optText = new Array(optNodes.length);
17497        angular.forEach(optNodes, function(el, i) {
17498          optText[i] = el.textContent.trim();
17499        });
17500      }
17501      for (var i = 0; i < optText.length; ++i) {
17502        if (search.test(optText[i])) {
17503          return optNodes[i];
17504        }
17505      }
17506    };
17507
17508    self.init = function(ngModel, binding) {
17509      self.ngModel = ngModel;
17510      self.modelBinding = binding;
17511
17512      // Setup a more robust version of isEmpty to ensure value is a valid option
17513      self.ngModel.$isEmpty = function($viewValue) {
17514        // We have to transform the viewValue into the hashKey, because otherwise the
17515        // OptionCtrl may not exist. Developers may have specified a trackBy function.
17516        return !self.options[self.hashGetter($viewValue)];
17517      };
17518
17519      // Allow users to provide `ng-model="foo" ng-model-options="{trackBy: 'foo.id'}"` so
17520      // that we can properly compare objects set on the model to the available options
17521      if (ngModel.$options && ngModel.$options.trackBy) {
17522        var trackByLocals = {};
17523        var trackByParsed = $parse(ngModel.$options.trackBy);
17524        self.hashGetter = function(value, valueScope) {
17525          trackByLocals.$value = value;
17526          return trackByParsed(valueScope || $scope, trackByLocals);
17527        };
17528        // If the user doesn't provide a trackBy, we automatically generate an id for every
17529        // value passed in
17530      } else {
17531        self.hashGetter = function getHashValue(value) {
17532          if (angular.isObject(value)) {
17533            return 'object_' + (value.$$mdSelectId || (value.$$mdSelectId = ++selectNextId));
17534          }
17535          return value;
17536        };
17537      }
17538      self.setMultiple(self.isMultiple);
17539    };
17540
17541    self.selectedLabels = function(opts) {
17542      opts = opts || {};
17543      var mode = opts.mode || 'html';
17544      var selectedOptionEls = $mdUtil.nodesToArray($element[0].querySelectorAll('md-option[selected]'));
17545      if (selectedOptionEls.length) {
17546        var mapFn;
17547
17548        if (mode == 'html') {
17549          // Map the given element to its innerHTML string. If the element has a child ripple
17550          // container remove it from the HTML string, before returning the string.
17551          mapFn = function(el) {
17552            // If we do not have a `value` or `ng-value`, assume it is an empty option which clears the select
17553            if (el.hasAttribute('md-option-empty')) {
17554              return '';
17555            }
17556
17557            var html = el.innerHTML;
17558
17559            // Remove the ripple container from the selected option, copying it would cause a CSP violation.
17560            var rippleContainer = el.querySelector('.md-ripple-container');
17561            if (rippleContainer) {
17562              html = html.replace(rippleContainer.outerHTML, '');
17563            }
17564
17565            // Remove the checkbox container, because it will cause the label to wrap inside of the placeholder.
17566            // It should be not displayed inside of the label element.
17567            var checkboxContainer = el.querySelector('.md-container');
17568            if (checkboxContainer) {
17569              html = html.replace(checkboxContainer.outerHTML, '');
17570            }
17571
17572            return html;
17573          };
17574        } else if (mode == 'aria') {
17575          mapFn = function(el) { return el.hasAttribute('aria-label') ? el.getAttribute('aria-label') : el.textContent; };
17576        }
17577        return selectedOptionEls.map(mapFn).join(', ');
17578      } else {
17579        return '';
17580      }
17581    };
17582
17583    self.select = function(hashKey, hashedValue) {
17584      var option = self.options[hashKey];
17585      option && option.setSelected(true);
17586      self.selected[hashKey] = hashedValue;
17587    };
17588    self.deselect = function(hashKey) {
17589      var option = self.options[hashKey];
17590      option && option.setSelected(false);
17591      delete self.selected[hashKey];
17592    };
17593
17594    self.addOption = function(hashKey, optionCtrl) {
17595      if (angular.isDefined(self.options[hashKey])) {
17596        throw new Error('Duplicate md-option values are not allowed in a select. ' +
17597          'Duplicate value "' + optionCtrl.value + '" found.');
17598      }
17599
17600      self.options[hashKey] = optionCtrl;
17601
17602      // If this option's value was already in our ngModel, go ahead and select it.
17603      if (angular.isDefined(self.selected[hashKey])) {
17604        self.select(hashKey, optionCtrl.value);
17605
17606        // When the current $modelValue of the ngModel Controller is using the same hash as
17607        // the current option, which will be added, then we can be sure, that the validation
17608        // of the option has occurred before the option was added properly.
17609        // This means, that we have to manually trigger a new validation of the current option.
17610        if (angular.isDefined(self.ngModel.$modelValue) && self.hashGetter(self.ngModel.$modelValue) === hashKey) {
17611          self.ngModel.$validate();
17612        }
17613
17614        self.refreshViewValue();
17615      }
17616    };
17617    self.removeOption = function(hashKey) {
17618      delete self.options[hashKey];
17619      // Don't deselect an option when it's removed - the user's ngModel should be allowed
17620      // to have values that do not match a currently available option.
17621    };
17622
17623    self.refreshViewValue = function() {
17624      var values = [];
17625      var option;
17626      for (var hashKey in self.selected) {
17627        // If this hashKey has an associated option, push that option's value to the model.
17628        if ((option = self.options[hashKey])) {
17629          values.push(option.value);
17630        } else {
17631          // Otherwise, the given hashKey has no associated option, and we got it
17632          // from an ngModel value at an earlier time. Push the unhashed value of
17633          // this hashKey to the model.
17634          // This allows the developer to put a value in the model that doesn't yet have
17635          // an associated option.
17636          values.push(self.selected[hashKey]);
17637        }
17638      }
17639      var usingTrackBy = self.ngModel.$options && self.ngModel.$options.trackBy;
17640
17641      var newVal = self.isMultiple ? values : values[0];
17642      var prevVal = self.ngModel.$modelValue;
17643
17644      if (usingTrackBy ? !angular.equals(prevVal, newVal) : prevVal != newVal) {
17645        self.ngModel.$setViewValue(newVal);
17646        self.ngModel.$render();
17647      }
17648    };
17649
17650    function renderMultiple() {
17651      var newSelectedValues = self.ngModel.$modelValue || self.ngModel.$viewValue || [];
17652      if (!angular.isArray(newSelectedValues)) return;
17653
17654      var oldSelected = Object.keys(self.selected);
17655
17656      var newSelectedHashes = newSelectedValues.map(self.hashGetter);
17657      var deselected = oldSelected.filter(function(hash) {
17658        return newSelectedHashes.indexOf(hash) === -1;
17659      });
17660
17661      deselected.forEach(self.deselect);
17662      newSelectedHashes.forEach(function(hashKey, i) {
17663        self.select(hashKey, newSelectedValues[i]);
17664      });
17665    }
17666
17667    function renderSingular() {
17668      var value = self.ngModel.$viewValue || self.ngModel.$modelValue;
17669      Object.keys(self.selected).forEach(self.deselect);
17670      self.select(self.hashGetter(value), value);
17671    }
17672  }
17673
17674}
17675
17676function OptionDirective($mdButtonInkRipple, $mdUtil) {
17677
17678  OptionController.$inject = ["$element"];
17679  return {
17680    restrict: 'E',
17681    require: ['mdOption', '^^mdSelectMenu'],
17682    controller: OptionController,
17683    compile: compile
17684  };
17685
17686  function compile(element, attr) {
17687    // Manual transclusion to avoid the extra inner <span> that ng-transclude generates
17688    element.append(angular.element('<div class="md-text">').append(element.contents()));
17689
17690    element.attr('tabindex', attr.tabindex || '0');
17691
17692    if (!hasDefinedValue(attr)) {
17693      element.attr('md-option-empty', '');
17694    }
17695
17696    return postLink;
17697  }
17698
17699  function hasDefinedValue(attr) {
17700    var value = attr.value;
17701    var ngValue = attr.ngValue;
17702
17703    return value || ngValue;
17704  }
17705
17706  function postLink(scope, element, attr, ctrls) {
17707    var optionCtrl = ctrls[0];
17708    var selectCtrl = ctrls[1];
17709
17710    if (selectCtrl.isMultiple) {
17711      element.addClass('md-checkbox-enabled');
17712      element.prepend(CHECKBOX_SELECTION_INDICATOR.clone());
17713    }
17714
17715    if (angular.isDefined(attr.ngValue)) {
17716      scope.$watch(attr.ngValue, setOptionValue);
17717    } else if (angular.isDefined(attr.value)) {
17718      setOptionValue(attr.value);
17719    } else {
17720      scope.$watch(function() {
17721        return element.text().trim();
17722      }, setOptionValue);
17723    }
17724
17725    attr.$observe('disabled', function(disabled) {
17726      if (disabled) {
17727        element.attr('tabindex', '-1');
17728      } else {
17729        element.attr('tabindex', '0');
17730      }
17731    });
17732
17733    scope.$$postDigest(function() {
17734      attr.$observe('selected', function(selected) {
17735        if (!angular.isDefined(selected)) return;
17736        if (typeof selected == 'string') selected = true;
17737        if (selected) {
17738          if (!selectCtrl.isMultiple) {
17739            selectCtrl.deselect(Object.keys(selectCtrl.selected)[0]);
17740          }
17741          selectCtrl.select(optionCtrl.hashKey, optionCtrl.value);
17742        } else {
17743          selectCtrl.deselect(optionCtrl.hashKey);
17744        }
17745        selectCtrl.refreshViewValue();
17746      });
17747    });
17748
17749    $mdButtonInkRipple.attach(scope, element);
17750    configureAria();
17751
17752    function setOptionValue(newValue, oldValue, prevAttempt) {
17753      if (!selectCtrl.hashGetter) {
17754        if (!prevAttempt) {
17755          scope.$$postDigest(function() {
17756            setOptionValue(newValue, oldValue, true);
17757          });
17758        }
17759        return;
17760      }
17761      var oldHashKey = selectCtrl.hashGetter(oldValue, scope);
17762      var newHashKey = selectCtrl.hashGetter(newValue, scope);
17763
17764      optionCtrl.hashKey = newHashKey;
17765      optionCtrl.value = newValue;
17766
17767      selectCtrl.removeOption(oldHashKey, optionCtrl);
17768      selectCtrl.addOption(newHashKey, optionCtrl);
17769    }
17770
17771    scope.$on('$destroy', function() {
17772      selectCtrl.removeOption(optionCtrl.hashKey, optionCtrl);
17773    });
17774
17775    function configureAria() {
17776      var ariaAttrs = {
17777        'role': 'option',
17778        'aria-selected': 'false'
17779      };
17780
17781      if (!element[0].hasAttribute('id')) {
17782        ariaAttrs.id = 'select_option_' + $mdUtil.nextUid();
17783      }
17784      element.attr(ariaAttrs);
17785    }
17786  }
17787
17788  function OptionController($element) {
17789    this.selected = false;
17790    this.setSelected = function(isSelected) {
17791      if (isSelected && !this.selected) {
17792        $element.attr({
17793          'selected': 'selected',
17794          'aria-selected': 'true'
17795        });
17796      } else if (!isSelected && this.selected) {
17797        $element.removeAttr('selected');
17798        $element.attr('aria-selected', 'false');
17799      }
17800      this.selected = isSelected;
17801    };
17802  }
17803
17804}
17805
17806function OptgroupDirective() {
17807  return {
17808    restrict: 'E',
17809    compile: compile
17810  };
17811  function compile(el, attrs) {
17812    // If we have a select header element, we don't want to add the normal label
17813    // header.
17814    if (!hasSelectHeader()) {
17815      setupLabelElement();
17816    }
17817
17818    function hasSelectHeader() {
17819      return el.parent().find('md-select-header').length;
17820    }
17821
17822    function setupLabelElement() {
17823      var labelElement = el.find('label');
17824      if (!labelElement.length) {
17825        labelElement = angular.element('<label>');
17826        el.prepend(labelElement);
17827      }
17828      labelElement.addClass('md-container-ignore');
17829      if (attrs.label) labelElement.text(attrs.label);
17830    }
17831  }
17832}
17833
17834function SelectHeaderDirective() {
17835  return {
17836    restrict: 'E',
17837  };
17838}
17839
17840function SelectProvider($$interimElementProvider) {
17841  selectDefaultOptions.$inject = ["$mdSelect", "$mdConstant", "$mdUtil", "$window", "$q", "$$rAF", "$animateCss", "$animate", "$document"];
17842  return $$interimElementProvider('$mdSelect')
17843    .setDefaults({
17844      methods: ['target'],
17845      options: selectDefaultOptions
17846    });
17847
17848  /* @ngInject */
17849  function selectDefaultOptions($mdSelect, $mdConstant, $mdUtil, $window, $q, $$rAF, $animateCss, $animate, $document) {
17850    var ERROR_TARGET_EXPECTED = "$mdSelect.show() expected a target element in options.target but got '{0}'!";
17851    var animator = $mdUtil.dom.animator;
17852    var keyCodes = $mdConstant.KEY_CODE;
17853
17854    return {
17855      parent: 'body',
17856      themable: true,
17857      onShow: onShow,
17858      onRemove: onRemove,
17859      hasBackdrop: true,
17860      disableParentScroll: true
17861    };
17862
17863    /**
17864     * Interim-element onRemove logic....
17865     */
17866    function onRemove(scope, element, opts) {
17867      opts = opts || { };
17868      opts.cleanupInteraction();
17869      opts.cleanupResizing();
17870      opts.hideBackdrop();
17871
17872      // For navigation $destroy events, do a quick, non-animated removal,
17873      // but for normal closes (from clicks, etc) animate the removal
17874
17875      return  (opts.$destroy === true) ? cleanElement() : animateRemoval().then( cleanElement );
17876
17877      /**
17878       * For normal closes (eg clicks), animate the removal.
17879       * For forced closes (like $destroy events from navigation),
17880       * skip the animations
17881       */
17882      function animateRemoval() {
17883        return $animateCss(element, {addClass: 'md-leave'}).start();
17884      }
17885
17886      /**
17887       * Restore the element to a closed state
17888       */
17889      function cleanElement() {
17890
17891        element.removeClass('md-active');
17892        element.attr('aria-hidden', 'true');
17893        element[0].style.display = 'none';
17894
17895        announceClosed(opts);
17896
17897        if (!opts.$destroy && opts.restoreFocus) {
17898          opts.target.focus();
17899        }
17900      }
17901
17902    }
17903
17904    /**
17905     * Interim-element onShow logic....
17906     */
17907    function onShow(scope, element, opts) {
17908
17909      watchAsyncLoad();
17910      sanitizeAndConfigure(scope, opts);
17911
17912      opts.hideBackdrop = showBackdrop(scope, element, opts);
17913
17914      return showDropDown(scope, element, opts)
17915        .then(function(response) {
17916          element.attr('aria-hidden', 'false');
17917          opts.alreadyOpen = true;
17918          opts.cleanupInteraction = activateInteraction();
17919          opts.cleanupResizing = activateResizing();
17920
17921          return response;
17922        }, opts.hideBackdrop);
17923
17924      // ************************************
17925      // Closure Functions
17926      // ************************************
17927
17928      /**
17929       *  Attach the select DOM element(s) and animate to the correct positions
17930       *  and scalings...
17931       */
17932      function showDropDown(scope, element, opts) {
17933        opts.parent.append(element);
17934
17935        return $q(function(resolve, reject) {
17936
17937          try {
17938
17939            $animateCss(element, {removeClass: 'md-leave', duration: 0})
17940              .start()
17941              .then(positionAndFocusMenu)
17942              .then(resolve);
17943
17944          } catch (e) {
17945            reject(e);
17946          }
17947
17948        });
17949      }
17950
17951      /**
17952       * Initialize container and dropDown menu positions/scale, then animate
17953       * to show... and autoFocus.
17954       */
17955      function positionAndFocusMenu() {
17956        return $q(function(resolve) {
17957          if (opts.isRemoved) return $q.reject(false);
17958
17959          var info = calculateMenuPositions(scope, element, opts);
17960
17961          info.container.element.css(animator.toCss(info.container.styles));
17962          info.dropDown.element.css(animator.toCss(info.dropDown.styles));
17963
17964          $$rAF(function() {
17965            element.addClass('md-active');
17966            info.dropDown.element.css(animator.toCss({transform: ''}));
17967
17968            autoFocus(opts.focusedNode);
17969            resolve();
17970          });
17971
17972        });
17973      }
17974
17975      /**
17976       * Show modal backdrop element...
17977       */
17978      function showBackdrop(scope, element, options) {
17979
17980        // If we are not within a dialog...
17981        if (options.disableParentScroll && !$mdUtil.getClosest(options.target, 'MD-DIALOG')) {
17982          // !! DO this before creating the backdrop; since disableScrollAround()
17983          //    configures the scroll offset; which is used by mdBackDrop postLink()
17984          options.restoreScroll = $mdUtil.disableScrollAround(options.element, options.parent);
17985        } else {
17986          options.disableParentScroll = false;
17987        }
17988
17989        if (options.hasBackdrop) {
17990          // Override duration to immediately show invisible backdrop
17991          options.backdrop = $mdUtil.createBackdrop(scope, "md-select-backdrop md-click-catcher");
17992          $animate.enter(options.backdrop, $document[0].body, null, {duration: 0});
17993        }
17994
17995        /**
17996         * Hide modal backdrop element...
17997         */
17998        return function hideBackdrop() {
17999          if (options.backdrop) options.backdrop.remove();
18000          if (options.disableParentScroll) options.restoreScroll();
18001
18002          delete options.restoreScroll;
18003        };
18004      }
18005
18006      /**
18007       *
18008       */
18009      function autoFocus(focusedNode) {
18010        if (focusedNode && !focusedNode.hasAttribute('disabled')) {
18011          focusedNode.focus();
18012        }
18013      }
18014
18015      /**
18016       * Check for valid opts and set some sane defaults
18017       */
18018      function sanitizeAndConfigure(scope, options) {
18019        var selectEl = element.find('md-select-menu');
18020
18021        if (!options.target) {
18022          throw new Error($mdUtil.supplant(ERROR_TARGET_EXPECTED, [options.target]));
18023        }
18024
18025        angular.extend(options, {
18026          isRemoved: false,
18027          target: angular.element(options.target), //make sure it's not a naked dom node
18028          parent: angular.element(options.parent),
18029          selectEl: selectEl,
18030          contentEl: element.find('md-content'),
18031          optionNodes: selectEl[0].getElementsByTagName('md-option')
18032        });
18033      }
18034
18035      /**
18036       * Configure various resize listeners for screen changes
18037       */
18038      function activateResizing() {
18039        var debouncedOnResize = (function(scope, target, options) {
18040
18041          return function() {
18042            if (options.isRemoved) return;
18043
18044            var updates = calculateMenuPositions(scope, target, options);
18045            var container = updates.container;
18046            var dropDown = updates.dropDown;
18047
18048            container.element.css(animator.toCss(container.styles));
18049            dropDown.element.css(animator.toCss(dropDown.styles));
18050          };
18051
18052        })(scope, element, opts);
18053
18054        var window = angular.element($window);
18055        window.on('resize', debouncedOnResize);
18056        window.on('orientationchange', debouncedOnResize);
18057
18058        // Publish deactivation closure...
18059        return function deactivateResizing() {
18060
18061          // Disable resizing handlers
18062          window.off('resize', debouncedOnResize);
18063          window.off('orientationchange', debouncedOnResize);
18064        };
18065      }
18066
18067      /**
18068       *  If asynchronously loading, watch and update internal
18069       *  '$$loadingAsyncDone' flag
18070       */
18071      function watchAsyncLoad() {
18072        if (opts.loadingAsync && !opts.isRemoved) {
18073          scope.$$loadingAsyncDone = false;
18074
18075          $q.when(opts.loadingAsync)
18076            .then(function() {
18077              scope.$$loadingAsyncDone = true;
18078              delete opts.loadingAsync;
18079            }).then(function() {
18080              $$rAF(positionAndFocusMenu);
18081            });
18082        }
18083      }
18084
18085      /**
18086       *
18087       */
18088      function activateInteraction() {
18089        if (opts.isRemoved) return;
18090
18091        var dropDown = opts.selectEl;
18092        var selectCtrl = dropDown.controller('mdSelectMenu') || {};
18093
18094        element.addClass('md-clickable');
18095
18096        // Close on backdrop click
18097        opts.backdrop && opts.backdrop.on('click', onBackdropClick);
18098
18099        // Escape to close
18100        // Cycling of options, and closing on enter
18101        dropDown.on('keydown', onMenuKeyDown);
18102        dropDown.on('click', checkCloseMenu);
18103
18104        return function cleanupInteraction() {
18105          opts.backdrop && opts.backdrop.off('click', onBackdropClick);
18106          dropDown.off('keydown', onMenuKeyDown);
18107          dropDown.off('click', checkCloseMenu);
18108
18109          element.removeClass('md-clickable');
18110          opts.isRemoved = true;
18111        };
18112
18113        // ************************************
18114        // Closure Functions
18115        // ************************************
18116
18117        function onBackdropClick(e) {
18118          e.preventDefault();
18119          e.stopPropagation();
18120          opts.restoreFocus = false;
18121          $mdUtil.nextTick($mdSelect.hide, true);
18122        }
18123
18124        function onMenuKeyDown(ev) {
18125          ev.preventDefault();
18126          ev.stopPropagation();
18127
18128          switch (ev.keyCode) {
18129            case keyCodes.UP_ARROW:
18130              return focusPrevOption();
18131            case keyCodes.DOWN_ARROW:
18132              return focusNextOption();
18133            case keyCodes.SPACE:
18134            case keyCodes.ENTER:
18135              var option = $mdUtil.getClosest(ev.target, 'md-option');
18136              if (option) {
18137                dropDown.triggerHandler({
18138                  type: 'click',
18139                  target: option
18140                });
18141                ev.preventDefault();
18142              }
18143              checkCloseMenu(ev);
18144              break;
18145            case keyCodes.TAB:
18146            case keyCodes.ESCAPE:
18147              ev.stopPropagation();
18148              ev.preventDefault();
18149              opts.restoreFocus = true;
18150              $mdUtil.nextTick($mdSelect.hide, true);
18151              break;
18152            default:
18153              if ($mdConstant.isInputKey(ev) || $mdConstant.isNumPadKey(ev)) {
18154                var optNode = dropDown.controller('mdSelectMenu').optNodeForKeyboardSearch(ev);
18155                opts.focusedNode = optNode || opts.focusedNode;
18156                optNode && optNode.focus();
18157              }
18158          }
18159        }
18160
18161        function focusOption(direction) {
18162          var optionsArray = $mdUtil.nodesToArray(opts.optionNodes);
18163          var index = optionsArray.indexOf(opts.focusedNode);
18164
18165          var newOption;
18166
18167          do {
18168            if (index === -1) {
18169              // We lost the previously focused element, reset to first option
18170              index = 0;
18171            } else if (direction === 'next' && index < optionsArray.length - 1) {
18172              index++;
18173            } else if (direction === 'prev' && index > 0) {
18174              index--;
18175            }
18176            newOption = optionsArray[index];
18177            if (newOption.hasAttribute('disabled')) newOption = undefined;
18178          } while (!newOption && index < optionsArray.length - 1 && index > 0);
18179
18180          newOption && newOption.focus();
18181          opts.focusedNode = newOption;
18182        }
18183
18184        function focusNextOption() {
18185          focusOption('next');
18186        }
18187
18188        function focusPrevOption() {
18189          focusOption('prev');
18190        }
18191
18192        function checkCloseMenu(ev) {
18193          if (ev && ( ev.type == 'click') && (ev.currentTarget != dropDown[0])) return;
18194          if ( mouseOnScrollbar() ) return;
18195
18196          var option = $mdUtil.getClosest(ev.target, 'md-option');
18197          if (option && option.hasAttribute && !option.hasAttribute('disabled')) {
18198            ev.preventDefault();
18199            ev.stopPropagation();
18200            if (!selectCtrl.isMultiple) {
18201              opts.restoreFocus = true;
18202
18203              $mdUtil.nextTick(function () {
18204                $mdSelect.hide(selectCtrl.ngModel.$viewValue);
18205              }, true);
18206            }
18207          }
18208          /**
18209           * check if the mouseup event was on a scrollbar
18210           */
18211          function mouseOnScrollbar() {
18212            var clickOnScrollbar = false;
18213            if (ev && (ev.currentTarget.children.length > 0)) {
18214              var child = ev.currentTarget.children[0];
18215              var hasScrollbar = child.scrollHeight > child.clientHeight;
18216              if (hasScrollbar && child.children.length > 0) {
18217                var relPosX = ev.pageX - ev.currentTarget.getBoundingClientRect().left;
18218                if (relPosX > child.querySelector('md-option').offsetWidth)
18219                  clickOnScrollbar = true;
18220              }
18221            }
18222            return clickOnScrollbar;
18223          }
18224        }
18225      }
18226
18227    }
18228
18229    /**
18230     * To notify listeners that the Select menu has closed,
18231     * trigger the [optional] user-defined expression
18232     */
18233    function announceClosed(opts) {
18234      var mdSelect = opts.selectCtrl;
18235      if (mdSelect) {
18236        var menuController = opts.selectEl.controller('mdSelectMenu');
18237        mdSelect.setLabelText(menuController ? menuController.selectedLabels() : '');
18238        mdSelect.triggerClose();
18239      }
18240    }
18241
18242
18243    /**
18244     * Calculate the
18245     */
18246    function calculateMenuPositions(scope, element, opts) {
18247      var
18248        containerNode = element[0],
18249        targetNode = opts.target[0].children[0], // target the label
18250        parentNode = $document[0].body,
18251        selectNode = opts.selectEl[0],
18252        contentNode = opts.contentEl[0],
18253        parentRect = parentNode.getBoundingClientRect(),
18254        targetRect = targetNode.getBoundingClientRect(),
18255        shouldOpenAroundTarget = false,
18256        bounds = {
18257          left: parentRect.left + SELECT_EDGE_MARGIN,
18258          top: SELECT_EDGE_MARGIN,
18259          bottom: parentRect.height - SELECT_EDGE_MARGIN,
18260          right: parentRect.width - SELECT_EDGE_MARGIN - ($mdUtil.floatingScrollbars() ? 16 : 0)
18261        },
18262        spaceAvailable = {
18263          top: targetRect.top - bounds.top,
18264          left: targetRect.left - bounds.left,
18265          right: bounds.right - (targetRect.left + targetRect.width),
18266          bottom: bounds.bottom - (targetRect.top + targetRect.height)
18267        },
18268        maxWidth = parentRect.width - SELECT_EDGE_MARGIN * 2,
18269        selectedNode = selectNode.querySelector('md-option[selected]'),
18270        optionNodes = selectNode.getElementsByTagName('md-option'),
18271        optgroupNodes = selectNode.getElementsByTagName('md-optgroup'),
18272        isScrollable = calculateScrollable(element, contentNode),
18273        centeredNode;
18274
18275      var loading = isPromiseLike(opts.loadingAsync);
18276      if (!loading) {
18277        // If a selected node, center around that
18278        if (selectedNode) {
18279          centeredNode = selectedNode;
18280          // If there are option groups, center around the first option group
18281        } else if (optgroupNodes.length) {
18282          centeredNode = optgroupNodes[0];
18283          // Otherwise - if we are not loading async - center around the first optionNode
18284        } else if (optionNodes.length) {
18285          centeredNode = optionNodes[0];
18286          // In case there are no options, center on whatever's in there... (eg progress indicator)
18287        } else {
18288          centeredNode = contentNode.firstElementChild || contentNode;
18289        }
18290      } else {
18291        // If loading, center on progress indicator
18292        centeredNode = contentNode.firstElementChild || contentNode;
18293      }
18294
18295      if (contentNode.offsetWidth > maxWidth) {
18296        contentNode.style['max-width'] = maxWidth + 'px';
18297      } else {
18298        contentNode.style.maxWidth = null;
18299      }
18300      if (shouldOpenAroundTarget) {
18301        contentNode.style['min-width'] = targetRect.width + 'px';
18302      }
18303
18304      // Remove padding before we compute the position of the menu
18305      if (isScrollable) {
18306        selectNode.classList.add('md-overflow');
18307      }
18308
18309      var focusedNode = centeredNode;
18310      if ((focusedNode.tagName || '').toUpperCase() === 'MD-OPTGROUP') {
18311        focusedNode = optionNodes[0] || contentNode.firstElementChild || contentNode;
18312        centeredNode = focusedNode;
18313      }
18314      // Cache for autoFocus()
18315      opts.focusedNode = focusedNode;
18316
18317      // Get the selectMenuRect *after* max-width is possibly set above
18318      containerNode.style.display = 'block';
18319      var selectMenuRect = selectNode.getBoundingClientRect();
18320      var centeredRect = getOffsetRect(centeredNode);
18321
18322      if (centeredNode) {
18323        var centeredStyle = $window.getComputedStyle(centeredNode);
18324        centeredRect.paddingLeft = parseInt(centeredStyle.paddingLeft, 10) || 0;
18325        centeredRect.paddingRight = parseInt(centeredStyle.paddingRight, 10) || 0;
18326      }
18327
18328      if (isScrollable) {
18329        var scrollBuffer = contentNode.offsetHeight / 2;
18330        contentNode.scrollTop = centeredRect.top + centeredRect.height / 2 - scrollBuffer;
18331
18332        if (spaceAvailable.top < scrollBuffer) {
18333          contentNode.scrollTop = Math.min(
18334            centeredRect.top,
18335            contentNode.scrollTop + scrollBuffer - spaceAvailable.top
18336          );
18337        } else if (spaceAvailable.bottom < scrollBuffer) {
18338          contentNode.scrollTop = Math.max(
18339            centeredRect.top + centeredRect.height - selectMenuRect.height,
18340            contentNode.scrollTop - scrollBuffer + spaceAvailable.bottom
18341          );
18342        }
18343      }
18344
18345      var left, top, transformOrigin, minWidth, fontSize;
18346      if (shouldOpenAroundTarget) {
18347        left = targetRect.left;
18348        top = targetRect.top + targetRect.height;
18349        transformOrigin = '50% 0';
18350        if (top + selectMenuRect.height > bounds.bottom) {
18351          top = targetRect.top - selectMenuRect.height;
18352          transformOrigin = '50% 100%';
18353        }
18354      } else {
18355        left = (targetRect.left + centeredRect.left - centeredRect.paddingLeft) + 2;
18356        top = Math.floor(targetRect.top + targetRect.height / 2 - centeredRect.height / 2 -
18357            centeredRect.top + contentNode.scrollTop) + 2;
18358
18359        transformOrigin = (centeredRect.left + targetRect.width / 2) + 'px ' +
18360          (centeredRect.top + centeredRect.height / 2 - contentNode.scrollTop) + 'px 0px';
18361
18362        minWidth = Math.min(targetRect.width + centeredRect.paddingLeft + centeredRect.paddingRight, maxWidth);
18363
18364        fontSize = window.getComputedStyle(targetNode)['font-size'];
18365      }
18366
18367      // Keep left and top within the window
18368      var containerRect = containerNode.getBoundingClientRect();
18369      var scaleX = Math.round(100 * Math.min(targetRect.width / selectMenuRect.width, 1.0)) / 100;
18370      var scaleY = Math.round(100 * Math.min(targetRect.height / selectMenuRect.height, 1.0)) / 100;
18371
18372      return {
18373        container: {
18374          element: angular.element(containerNode),
18375          styles: {
18376            left: Math.floor(clamp(bounds.left, left, bounds.right - containerRect.width)),
18377            top: Math.floor(clamp(bounds.top, top, bounds.bottom - containerRect.height)),
18378            'min-width': minWidth,
18379            'font-size': fontSize
18380          }
18381        },
18382        dropDown: {
18383          element: angular.element(selectNode),
18384          styles: {
18385            transformOrigin: transformOrigin,
18386            transform: !opts.alreadyOpen ? $mdUtil.supplant('scale({0},{1})', [scaleX, scaleY]) : ""
18387          }
18388        }
18389      };
18390
18391    }
18392
18393  }
18394
18395  function isPromiseLike(obj) {
18396    return obj && angular.isFunction(obj.then);
18397  }
18398
18399  function clamp(min, n, max) {
18400    return Math.max(min, Math.min(n, max));
18401  }
18402
18403  function getOffsetRect(node) {
18404    return node ? {
18405      left: node.offsetLeft,
18406      top: node.offsetTop,
18407      width: node.offsetWidth,
18408      height: node.offsetHeight
18409    } : {left: 0, top: 0, width: 0, height: 0};
18410  }
18411
18412  function calculateScrollable(element, contentNode) {
18413    var isScrollable = false;
18414
18415    try {
18416      var oldDisplay = element[0].style.display;
18417
18418      // Set the element's display to block so that this calculation is correct
18419      element[0].style.display = 'block';
18420
18421      isScrollable = contentNode.scrollHeight > contentNode.offsetHeight;
18422
18423      // Reset it back afterwards
18424      element[0].style.display = oldDisplay;
18425    } finally {
18426      // Nothing to do
18427    }
18428    return isScrollable;
18429  }
18430}
18431
18432})();
18433(function(){
18434"use strict";
18435
18436/**
18437 * @ngdoc module
18438 * @name material.components.showHide
18439 */
18440
18441// Add additional handlers to ng-show and ng-hide that notify directives
18442// contained within that they should recompute their size.
18443// These run in addition to Angular's built-in ng-hide and ng-show directives.
18444angular.module('material.components.showHide', [
18445  'material.core'
18446])
18447  .directive('ngShow', createDirective('ngShow', true))
18448  .directive('ngHide', createDirective('ngHide', false));
18449
18450
18451function createDirective(name, targetValue) {
18452  return ['$mdUtil', '$window', function($mdUtil, $window) {
18453    return {
18454      restrict: 'A',
18455      multiElement: true,
18456      link: function($scope, $element, $attr) {
18457        var unregister = $scope.$on('$md-resize-enable', function() {
18458          unregister();
18459
18460          var node = $element[0];
18461          var cachedTransitionStyles = node.nodeType === $window.Node.ELEMENT_NODE ?
18462            $window.getComputedStyle(node) : {};
18463
18464          $scope.$watch($attr[name], function(value) {
18465            if (!!value === targetValue) {
18466              $mdUtil.nextTick(function() {
18467                $scope.$broadcast('$md-resize');
18468              });
18469
18470              var opts = {
18471                cachedTransitionStyles: cachedTransitionStyles
18472              };
18473
18474              $mdUtil.dom.animator.waitTransitionEnd($element, opts).then(function() {
18475                $scope.$broadcast('$md-resize');
18476              });
18477            }
18478          });
18479        });
18480      }
18481    };
18482  }];
18483}
18484
18485})();
18486(function(){
18487"use strict";
18488
18489/**
18490 * @ngdoc module
18491 * @name material.components.sidenav
18492 *
18493 * @description
18494 * A Sidenav QP component.
18495 */
18496SidenavService.$inject = ["$mdComponentRegistry", "$mdUtil", "$q", "$log"];
18497SidenavDirective.$inject = ["$mdMedia", "$mdUtil", "$mdConstant", "$mdTheming", "$animate", "$compile", "$parse", "$log", "$q", "$document"];
18498SidenavController.$inject = ["$scope", "$element", "$attrs", "$mdComponentRegistry", "$q"];
18499angular
18500  .module('material.components.sidenav', [
18501    'material.core',
18502    'material.components.backdrop'
18503  ])
18504  .factory('$mdSidenav', SidenavService )
18505  .directive('mdSidenav', SidenavDirective)
18506  .directive('mdSidenavFocus', SidenavFocusDirective)
18507  .controller('$mdSidenavController', SidenavController);
18508
18509
18510/**
18511 * @ngdoc service
18512 * @name $mdSidenav
18513 * @module material.components.sidenav
18514 *
18515 * @description
18516 * `$mdSidenav` makes it easy to interact with multiple sidenavs
18517 * in an app. When looking up a sidenav instance, you can either look
18518 * it up synchronously or wait for it to be initializied asynchronously.
18519 * This is done by passing the second argument to `$mdSidenav`.
18520 *
18521 * @usage
18522 * <hljs lang="js">
18523 * // Async lookup for sidenav instance; will resolve when the instance is available
18524 * $mdSidenav(componentId, true).then(function(instance) {
18525 *   $log.debug( componentId + "is now ready" );
18526 * });
18527 * // Sync lookup for sidenav instance; this will resolve immediately.
18528 * $mdSidenav(componentId).then(function(instance) {
18529 *   $log.debug( componentId + "is now ready" );
18530 * });
18531 * // Async toggle the given sidenav;
18532 * // when instance is known ready and lazy lookup is not needed.
18533 * $mdSidenav(componentId)
18534 *    .toggle()
18535 *    .then(function(){
18536 *      $log.debug('toggled');
18537 *    });
18538 * // Async open the given sidenav
18539 * $mdSidenav(componentId)
18540 *    .open()
18541 *    .then(function(){
18542 *      $log.debug('opened');
18543 *    });
18544 * // Async close the given sidenav
18545 * $mdSidenav(componentId)
18546 *    .close()
18547 *    .then(function(){
18548 *      $log.debug('closed');
18549 *    });
18550 * // Sync check to see if the specified sidenav is set to be open
18551 * $mdSidenav(componentId).isOpen();
18552 * // Sync check to whether given sidenav is locked open
18553 * // If this is true, the sidenav will be open regardless of close()
18554 * $mdSidenav(componentId).isLockedOpen();
18555 * // On close callback to handle close, backdrop click or escape key pressed
18556 * // Callback happens BEFORE the close action occurs.
18557 * $mdSidenav(componentId).onClose(function () {
18558 *   $log.debug('closing');
18559 * });
18560 * </hljs>
18561 */
18562function SidenavService($mdComponentRegistry, $mdUtil, $q, $log) {
18563  var errorMsg = "SideNav '{0}' is not available! Did you use md-component-id='{0}'?";
18564  var service = {
18565        find    : findInstance,     //  sync  - returns proxy API
18566        waitFor : waitForInstance   //  async - returns promise
18567      };
18568
18569  /**
18570   * Service API that supports three (3) usages:
18571   *   $mdSidenav().find("left")                       // sync (must already exist) or returns undefined
18572   *   $mdSidenav("left").toggle();                    // sync (must already exist) or returns reject promise;
18573   *   $mdSidenav("left",true).then( function(left){   // async returns instance when available
18574   *    left.toggle();
18575   *   });
18576   */
18577  return function(handle, enableWait) {
18578    if ( angular.isUndefined(handle) ) return service;
18579
18580    var shouldWait = enableWait === true;
18581    var instance = service.find(handle, shouldWait);
18582    return  !instance && shouldWait ? service.waitFor(handle) :
18583            !instance && angular.isUndefined(enableWait) ? addLegacyAPI(service, handle) : instance;
18584  };
18585
18586  /**
18587   * For failed instance/handle lookups, older-clients expect an response object with noops
18588   * that include `rejected promise APIs`
18589   */
18590  function addLegacyAPI(service, handle) {
18591      var falseFn  = function() { return false; };
18592      var rejectFn = function() {
18593            return $q.when($mdUtil.supplant(errorMsg, [handle || ""]));
18594          };
18595
18596      return angular.extend({
18597        isLockedOpen : falseFn,
18598        isOpen       : falseFn,
18599        toggle       : rejectFn,
18600        open         : rejectFn,
18601        close        : rejectFn,
18602        onClose      : angular.noop,
18603        then : function(callback) {
18604          return waitForInstance(handle)
18605            .then(callback || angular.noop);
18606        }
18607       }, service);
18608    }
18609    /**
18610     * Synchronously lookup the controller instance for the specified sidNav instance which has been
18611     * registered with the markup `md-component-id`
18612     */
18613    function findInstance(handle, shouldWait) {
18614      var instance = $mdComponentRegistry.get(handle);
18615
18616      if (!instance && !shouldWait) {
18617
18618        // Report missing instance
18619        $log.error( $mdUtil.supplant(errorMsg, [handle || ""]) );
18620
18621        // The component has not registered itself... most like NOT yet created
18622        // return null to indicate that the Sidenav is not in the DOM
18623        return undefined;
18624      }
18625      return instance;
18626    }
18627
18628    /**
18629     * Asynchronously wait for the component instantiation,
18630     * Deferred lookup of component instance using $component registry
18631     */
18632    function waitForInstance(handle) {
18633      return $mdComponentRegistry.when(handle).catch($log.error);
18634    }
18635}
18636/**
18637 * @ngdoc directive
18638 * @name mdSidenavFocus
18639 * @module material.components.sidenav
18640 *
18641 * @restrict A
18642 *
18643 * @description
18644 * `mdSidenavFocus` provides a way to specify the focused element when a sidenav opens.
18645 * This is completely optional, as the sidenav itself is focused by default.
18646 *
18647 * @usage
18648 * <hljs lang="html">
18649 * <md-sidenav>
18650 *   <form>
18651 *     <md-input-container>
18652 *       <label for="testInput">Label</label>
18653 *       <input id="testInput" type="text" md-sidenav-focus>
18654 *     </md-input-container>
18655 *   </form>
18656 * </md-sidenav>
18657 * </hljs>
18658 **/
18659function SidenavFocusDirective() {
18660  return {
18661    restrict: 'A',
18662    require: '^mdSidenav',
18663    link: function(scope, element, attr, sidenavCtrl) {
18664      // @see $mdUtil.findFocusTarget(...)
18665    }
18666  };
18667}
18668/**
18669 * @ngdoc directive
18670 * @name mdSidenav
18671 * @module material.components.sidenav
18672 * @restrict E
18673 *
18674 * @description
18675 *
18676 * A Sidenav component that can be opened and closed programatically.
18677 *
18678 * By default, upon opening it will slide out on top of the main content area.
18679 *
18680 * For keyboard and screen reader accessibility, focus is sent to the sidenav wrapper by default.
18681 * It can be overridden with the `md-autofocus` directive on the child element you want focused.
18682 *
18683 * @usage
18684 * <hljs lang="html">
18685 * <div layout="row" ng-controller="MyController">
18686 *   <md-sidenav md-component-id="left" class="md-sidenav-left">
18687 *     Left Nav!
18688 *   </md-sidenav>
18689 *
18690 *   <md-content>
18691 *     Center Content
18692 *     <md-button ng-click="openLeftMenu()">
18693 *       Open Left Menu
18694 *     </md-button>
18695 *   </md-content>
18696 *
18697 *   <md-sidenav md-component-id="right"
18698 *     md-is-locked-open="$mdMedia('min-width: 333px')"
18699 *     class="md-sidenav-right">
18700 *     <form>
18701 *       <md-input-container>
18702 *         <label for="testInput">Test input</label>
18703 *         <input id="testInput" type="text"
18704 *                ng-model="data" md-autofocus>
18705 *       </md-input-container>
18706 *     </form>
18707 *   </md-sidenav>
18708 * </div>
18709 * </hljs>
18710 *
18711 * <hljs lang="js">
18712 * var app = angular.module('myApp', ['ngMaterial']);
18713 * app.controller('MyController', function($scope, $mdSidenav) {
18714 *   $scope.openLeftMenu = function() {
18715 *     $mdSidenav('left').toggle();
18716 *   };
18717 * });
18718 * </hljs>
18719 *
18720 * @param {expression=} md-is-open A model bound to whether the sidenav is opened.
18721 * @param {boolean=} md-disable-backdrop When present in the markup, the sidenav will not show a backdrop.
18722 * @param {string=} md-component-id componentId to use with $mdSidenav service.
18723 * @param {expression=} md-is-locked-open When this expression evaluates to true,
18724 * the sidenav 'locks open': it falls into the content's flow instead
18725 * of appearing over it. This overrides the `md-is-open` attribute.
18726 * @param {string=} md-disable-scroll-target Selector, pointing to an element, whose scrolling will
18727 * be disabled when the sidenav is opened. By default this is the sidenav's direct parent.
18728 *
18729* The $mdMedia() service is exposed to the is-locked-open attribute, which
18730 * can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
18731 * Examples:
18732 *
18733 *   - `<md-sidenav md-is-locked-open="shouldLockOpen"></md-sidenav>`
18734 *   - `<md-sidenav md-is-locked-open="$mdMedia('min-width: 1000px')"></md-sidenav>`
18735 *   - `<md-sidenav md-is-locked-open="$mdMedia('sm')"></md-sidenav>` (locks open on small screens)
18736 */
18737function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $compile, $parse, $log, $q, $document) {
18738  return {
18739    restrict: 'E',
18740    scope: {
18741      isOpen: '=?mdIsOpen'
18742    },
18743    controller: '$mdSidenavController',
18744    compile: function(element) {
18745      element.addClass('md-closed');
18746      element.attr('tabIndex', '-1');
18747      return postLink;
18748    }
18749  };
18750
18751  /**
18752   * Directive Post Link function...
18753   */
18754  function postLink(scope, element, attr, sidenavCtrl) {
18755    var lastParentOverFlow;
18756    var backdrop;
18757    var disableScrollTarget = null;
18758    var triggeringElement = null;
18759    var previousContainerStyles;
18760    var promise = $q.when(true);
18761    var isLockedOpenParsed = $parse(attr.mdIsLockedOpen);
18762    var isLocked = function() {
18763      return isLockedOpenParsed(scope.$parent, {
18764        $media: function(arg) {
18765          $log.warn("$media is deprecated for is-locked-open. Use $mdMedia instead.");
18766          return $mdMedia(arg);
18767        },
18768        $mdMedia: $mdMedia
18769      });
18770    };
18771
18772    if (attr.mdDisableScrollTarget) {
18773      disableScrollTarget = $document[0].querySelector(attr.mdDisableScrollTarget);
18774
18775      if (disableScrollTarget) {
18776        disableScrollTarget = angular.element(disableScrollTarget);
18777      } else {
18778        $log.warn($mdUtil.supplant('mdSidenav: couldn\'t find element matching ' +
18779          'selector "{selector}". Falling back to parent.', { selector: attr.mdDisableScrollTarget }));
18780      }
18781    }
18782
18783    if (!disableScrollTarget) {
18784      disableScrollTarget = element.parent();
18785    }
18786
18787    // Only create the backdrop if the backdrop isn't disabled.
18788    if (!attr.hasOwnProperty('mdDisableBackdrop')) {
18789      backdrop = $mdUtil.createBackdrop(scope, "md-sidenav-backdrop md-opaque ng-enter");
18790    }
18791
18792    element.addClass('_md');     // private md component indicator for styling
18793    $mdTheming(element);
18794
18795    // The backdrop should inherit the sidenavs theme,
18796    // because the backdrop will take its parent theme by default.
18797    if ( backdrop ) $mdTheming.inherit(backdrop, element);
18798
18799    element.on('$destroy', function() {
18800      backdrop && backdrop.remove();
18801      sidenavCtrl.destroy();
18802    });
18803
18804    scope.$on('$destroy', function(){
18805      backdrop && backdrop.remove();
18806    });
18807
18808    scope.$watch(isLocked, updateIsLocked);
18809    scope.$watch('isOpen', updateIsOpen);
18810
18811
18812    // Publish special accessor for the Controller instance
18813    sidenavCtrl.$toggleOpen = toggleOpen;
18814
18815    /**
18816     * Toggle the DOM classes to indicate `locked`
18817     * @param isLocked
18818     */
18819    function updateIsLocked(isLocked, oldValue) {
18820      scope.isLockedOpen = isLocked;
18821      if (isLocked === oldValue) {
18822        element.toggleClass('md-locked-open', !!isLocked);
18823      } else {
18824        $animate[isLocked ? 'addClass' : 'removeClass'](element, 'md-locked-open');
18825      }
18826      if (backdrop) {
18827        backdrop.toggleClass('md-locked-open', !!isLocked);
18828      }
18829    }
18830
18831    /**
18832     * Toggle the SideNav view and attach/detach listeners
18833     * @param isOpen
18834     */
18835    function updateIsOpen(isOpen) {
18836      // Support deprecated md-sidenav-focus attribute as fallback
18837      var focusEl = $mdUtil.findFocusTarget(element) || $mdUtil.findFocusTarget(element,'[md-sidenav-focus]') || element;
18838      var parent = element.parent();
18839
18840      parent[isOpen ? 'on' : 'off']('keydown', onKeyDown);
18841      if (backdrop) backdrop[isOpen ? 'on' : 'off']('click', close);
18842
18843      var restorePositioning = updateContainerPositions(parent, isOpen);
18844
18845      if ( isOpen ) {
18846        // Capture upon opening..
18847        triggeringElement = $document[0].activeElement;
18848      }
18849
18850      disableParentScroll(isOpen);
18851
18852      return promise = $q.all([
18853        isOpen && backdrop ? $animate.enter(backdrop, parent) : backdrop ?
18854                             $animate.leave(backdrop) : $q.when(true),
18855        $animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed')
18856      ]).then(function() {
18857        // Perform focus when animations are ALL done...
18858        if (scope.isOpen) {
18859          focusEl && focusEl.focus();
18860        }
18861
18862        // Restores the positioning on the sidenav and backdrop.
18863        restorePositioning && restorePositioning();
18864      });
18865    }
18866
18867    function updateContainerPositions(parent, willOpen) {
18868      var drawerEl = element[0];
18869      var scrollTop = parent[0].scrollTop;
18870
18871      if (willOpen && scrollTop) {
18872        previousContainerStyles = {
18873          top: drawerEl.style.top,
18874          bottom: drawerEl.style.bottom,
18875          height: drawerEl.style.height
18876        };
18877
18878        // When the parent is scrolled down, then we want to be able to show the sidenav at the current scroll
18879        // position. We're moving the sidenav down to the correct scroll position and apply the height of the
18880        // parent, to increase the performance. Using 100% as height, will impact the performance heavily.
18881        var positionStyle = {
18882          top: scrollTop + 'px',
18883          bottom: 'auto',
18884          height: parent[0].clientHeight + 'px'
18885        };
18886
18887        // Apply the new position styles to the sidenav and backdrop.
18888        element.css(positionStyle);
18889        backdrop.css(positionStyle);
18890      }
18891
18892      // When the sidenav is closing and we have previous defined container styles,
18893      // then we return a restore function, which resets the sidenav and backdrop.
18894      if (!willOpen && previousContainerStyles) {
18895        return function() {
18896          drawerEl.style.top = previousContainerStyles.top;
18897          drawerEl.style.bottom = previousContainerStyles.bottom;
18898          drawerEl.style.height = previousContainerStyles.height;
18899
18900          backdrop[0].style.top = null;
18901          backdrop[0].style.bottom = null;
18902          backdrop[0].style.height = null;
18903
18904          previousContainerStyles = null;
18905        };
18906      }
18907    }
18908
18909    /**
18910     * Prevent parent scrolling (when the SideNav is open)
18911     */
18912    function disableParentScroll(disabled) {
18913      if ( disabled && !lastParentOverFlow ) {
18914        lastParentOverFlow = disableScrollTarget.css('overflow');
18915        disableScrollTarget.css('overflow', 'hidden');
18916      } else if (angular.isDefined(lastParentOverFlow)) {
18917        disableScrollTarget.css('overflow', lastParentOverFlow);
18918        lastParentOverFlow = undefined;
18919      }
18920    }
18921
18922    /**
18923     * Toggle the sideNav view and publish a promise to be resolved when
18924     * the view animation finishes.
18925     *
18926     * @param isOpen
18927     * @returns {*}
18928     */
18929    function toggleOpen( isOpen ) {
18930      if (scope.isOpen == isOpen ) {
18931
18932        return $q.when(true);
18933
18934      } else {
18935        if (scope.isOpen && sidenavCtrl.onCloseCb) sidenavCtrl.onCloseCb();
18936
18937        return $q(function(resolve){
18938          // Toggle value to force an async `updateIsOpen()` to run
18939          scope.isOpen = isOpen;
18940
18941          $mdUtil.nextTick(function() {
18942            // When the current `updateIsOpen()` animation finishes
18943            promise.then(function(result) {
18944
18945              if ( !scope.isOpen ) {
18946                // reset focus to originating element (if available) upon close
18947                triggeringElement && triggeringElement.focus();
18948                triggeringElement = null;
18949              }
18950
18951              resolve(result);
18952            });
18953          });
18954
18955        });
18956
18957      }
18958    }
18959
18960    /**
18961     * Auto-close sideNav when the `escape` key is pressed.
18962     * @param evt
18963     */
18964    function onKeyDown(ev) {
18965      var isEscape = (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE);
18966      return isEscape ? close(ev) : $q.when(true);
18967    }
18968
18969    /**
18970     * With backdrop `clicks` or `escape` key-press, immediately
18971     * apply the CSS close transition... Then notify the controller
18972     * to close() and perform its own actions.
18973     */
18974    function close(ev) {
18975      ev.preventDefault();
18976
18977      return sidenavCtrl.close();
18978    }
18979
18980  }
18981}
18982
18983/*
18984 * @private
18985 * @ngdoc controller
18986 * @name SidenavController
18987 * @module material.components.sidenav
18988 *
18989 */
18990function SidenavController($scope, $element, $attrs, $mdComponentRegistry, $q) {
18991
18992  var self = this;
18993
18994  // Use Default internal method until overridden by directive postLink
18995
18996  // Synchronous getters
18997  self.isOpen = function() { return !!$scope.isOpen; };
18998  self.isLockedOpen = function() { return !!$scope.isLockedOpen; };
18999
19000  // Synchronous setters
19001  self.onClose = function (callback) {
19002    self.onCloseCb = callback;
19003    return self;
19004  };
19005
19006  // Async actions
19007  self.open   = function() { return self.$toggleOpen( true );  };
19008  self.close  = function() { return self.$toggleOpen( false ); };
19009  self.toggle = function() { return self.$toggleOpen( !$scope.isOpen );  };
19010  self.$toggleOpen = function(value) { return $q.when($scope.isOpen = value); };
19011
19012  self.destroy = $mdComponentRegistry.register(self, $attrs.mdComponentId);
19013}
19014
19015})();
19016(function(){
19017"use strict";
19018
19019  /**
19020   * @ngdoc module
19021   * @name material.components.slider
19022   */
19023SliderDirective.$inject = ["$$rAF", "$window", "$mdAria", "$mdUtil", "$mdConstant", "$mdTheming", "$mdGesture", "$parse", "$log", "$timeout"];
19024  angular.module('material.components.slider', [
19025    'material.core'
19026  ])
19027  .directive('mdSlider', SliderDirective)
19028  .directive('mdSliderContainer', SliderContainerDirective);
19029
19030/**
19031 * @ngdoc directive
19032 * @name mdSliderContainer
19033 * @module material.components.slider
19034 * @restrict E
19035 * @description
19036 * The `<md-slider-container>` contains slider with two other elements.
19037 *
19038 *
19039 * @usage
19040 * <h4>Normal Mode</h4>
19041 * <hljs lang="html">
19042 * </hljs>
19043 */
19044function SliderContainerDirective() {
19045  return {
19046    controller: function () {},
19047    compile: function (elem) {
19048      var slider = elem.find('md-slider');
19049
19050      if (!slider) {
19051        return;
19052      }
19053
19054      var vertical = slider.attr('md-vertical');
19055
19056      if (vertical !== undefined) {
19057        elem.attr('md-vertical', '');
19058      }
19059
19060      if(!slider.attr('flex')) {
19061        slider.attr('flex', '');
19062      }
19063
19064      return function postLink(scope, element, attr, ctrl) {
19065        element.addClass('_md');     // private md component indicator for styling
19066
19067        // We have to manually stop the $watch on ngDisabled because it exists
19068        // on the parent scope, and won't be automatically destroyed when
19069        // the component is destroyed.
19070        function setDisable(value) {
19071          element.children().attr('disabled', value);
19072          element.find('input').attr('disabled', value);
19073        }
19074
19075        var stopDisabledWatch = angular.noop;
19076
19077        if (attr.disabled) {
19078          setDisable(true);
19079        }
19080        else if (attr.ngDisabled) {
19081          stopDisabledWatch = scope.$watch(attr.ngDisabled, function (value) {
19082            setDisable(value);
19083          });
19084        }
19085
19086        scope.$on('$destroy', function () {
19087          stopDisabledWatch();
19088        });
19089
19090        var initialMaxWidth;
19091
19092        ctrl.fitInputWidthToTextLength = function (length) {
19093          var input = element[0].querySelector('md-input-container');
19094
19095          if (input) {
19096            var computedStyle = getComputedStyle(input);
19097            var minWidth = parseInt(computedStyle.minWidth);
19098            var padding = parseInt(computedStyle.padding) * 2;
19099
19100            initialMaxWidth = initialMaxWidth || parseInt(computedStyle.maxWidth);
19101            var newMaxWidth = Math.max(initialMaxWidth, minWidth + padding + (minWidth / 2 * length));
19102
19103            input.style.maxWidth = newMaxWidth + 'px';
19104          }
19105        };
19106      };
19107    }
19108  };
19109}
19110
19111/**
19112 * @ngdoc directive
19113 * @name mdSlider
19114 * @module material.components.slider
19115 * @restrict E
19116 * @description
19117 * The `<md-slider>` component allows the user to choose from a range of
19118 * values.
19119 *
19120 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
19121 * the slider is in the accent color by default. The primary color palette may be used with
19122 * the `md-primary` class.
19123 *
19124 * It has two modes: 'normal' mode, where the user slides between a wide range
19125 * of values, and 'discrete' mode, where the user slides between only a few
19126 * select values.
19127 *
19128 * To enable discrete mode, add the `md-discrete` attribute to a slider,
19129 * and use the `step` attribute to change the distance between
19130 * values the user is allowed to pick.
19131 *
19132 * @usage
19133 * <h4>Normal Mode</h4>
19134 * <hljs lang="html">
19135 * <md-slider ng-model="myValue" min="5" max="500">
19136 * </md-slider>
19137 * </hljs>
19138 * <h4>Discrete Mode</h4>
19139 * <hljs lang="html">
19140 * <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
19141 * </md-slider>
19142 * </hljs>
19143 * <h4>Invert Mode</h4>
19144 * <hljs lang="html">
19145 * <md-slider md-invert ng-model="myValue" step="10" min="10" max="130">
19146 * </md-slider>
19147 * </hljs>
19148 *
19149 * @param {boolean=} md-discrete Whether to enable discrete mode.
19150 * @param {boolean=} md-invert Whether to enable invert mode.
19151 * @param {number=} step The distance between values the user is allowed to pick. Default 1.
19152 * @param {number=} min The minimum value the user is allowed to pick. Default 0.
19153 * @param {number=} max The maximum value the user is allowed to pick. Default 100.
19154 * @param {number=} round The amount of numbers after the decimal point, maximum is 6 to prevent scientific notation. Default 3.
19155 */
19156function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdTheming, $mdGesture, $parse, $log, $timeout) {
19157  return {
19158    scope: {},
19159    require: ['?ngModel', '?^mdSliderContainer'],
19160    template:
19161      '<div class="md-slider-wrapper">' +
19162        '<div class="md-slider-content">' +
19163          '<div class="md-track-container">' +
19164            '<div class="md-track"></div>' +
19165            '<div class="md-track md-track-fill"></div>' +
19166            '<div class="md-track-ticks"></div>' +
19167          '</div>' +
19168          '<div class="md-thumb-container">' +
19169            '<div class="md-thumb"></div>' +
19170            '<div class="md-focus-thumb"></div>' +
19171            '<div class="md-focus-ring"></div>' +
19172            '<div class="md-sign">' +
19173              '<span class="md-thumb-text"></span>' +
19174            '</div>' +
19175            '<div class="md-disabled-thumb"></div>' +
19176          '</div>' +
19177        '</div>' +
19178      '</div>',
19179    compile: compile
19180  };
19181
19182  // **********************************************************
19183  // Private Methods
19184  // **********************************************************
19185
19186  function compile (tElement, tAttrs) {
19187    var wrapper = angular.element(tElement[0].getElementsByClassName('md-slider-wrapper'));
19188
19189    var tabIndex = tAttrs.tabindex || 0;
19190    wrapper.attr('tabindex', tabIndex);
19191
19192    if (tAttrs.disabled || tAttrs.ngDisabled) wrapper.attr('tabindex', -1);
19193
19194    wrapper.attr('role', 'slider');
19195
19196    $mdAria.expect(tElement, 'aria-label');
19197
19198    return postLink;
19199  }
19200
19201  function postLink(scope, element, attr, ctrls) {
19202    $mdTheming(element);
19203    var ngModelCtrl = ctrls[0] || {
19204      // Mock ngModelController if it doesn't exist to give us
19205      // the minimum functionality needed
19206      $setViewValue: function(val) {
19207        this.$viewValue = val;
19208        this.$viewChangeListeners.forEach(function(cb) { cb(); });
19209      },
19210      $parsers: [],
19211      $formatters: [],
19212      $viewChangeListeners: []
19213    };
19214
19215    var containerCtrl = ctrls[1];
19216    var container = angular.element($mdUtil.getClosest(element, '_md-slider-container', true));
19217    var isDisabled = attr.ngDisabled ? angular.bind(null, $parse(attr.ngDisabled), scope.$parent) : function () {
19218          return element[0].hasAttribute('disabled');
19219        };
19220
19221    var thumb = angular.element(element[0].querySelector('.md-thumb'));
19222    var thumbText = angular.element(element[0].querySelector('.md-thumb-text'));
19223    var thumbContainer = thumb.parent();
19224    var trackContainer = angular.element(element[0].querySelector('.md-track-container'));
19225    var activeTrack = angular.element(element[0].querySelector('.md-track-fill'));
19226    var tickContainer = angular.element(element[0].querySelector('.md-track-ticks'));
19227    var wrapper = angular.element(element[0].getElementsByClassName('md-slider-wrapper'));
19228    var content = angular.element(element[0].getElementsByClassName('md-slider-content'));
19229    var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
19230
19231    // Default values, overridable by attrs
19232    var DEFAULT_ROUND = 3;
19233    var vertical = angular.isDefined(attr.mdVertical);
19234    var discrete = angular.isDefined(attr.mdDiscrete);
19235    var invert = angular.isDefined(attr.mdInvert);
19236    angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(0);
19237    angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(100);
19238    angular.isDefined(attr.step)? attr.$observe('step', updateStep) : updateStep(1);
19239    angular.isDefined(attr.round)? attr.$observe('round', updateRound) : updateRound(DEFAULT_ROUND);
19240
19241    // We have to manually stop the $watch on ngDisabled because it exists
19242    // on the parent scope, and won't be automatically destroyed when
19243    // the component is destroyed.
19244    var stopDisabledWatch = angular.noop;
19245    if (attr.ngDisabled) {
19246      stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
19247    }
19248
19249    $mdGesture.register(wrapper, 'drag', { horizontal: !vertical });
19250
19251    scope.mouseActive = false;
19252
19253    wrapper
19254      .on('keydown', keydownListener)
19255      .on('mousedown', mouseDownListener)
19256      .on('focus', focusListener)
19257      .on('blur', blurListener)
19258      .on('$md.pressdown', onPressDown)
19259      .on('$md.pressup', onPressUp)
19260      .on('$md.dragstart', onDragStart)
19261      .on('$md.drag', onDrag)
19262      .on('$md.dragend', onDragEnd);
19263
19264    // On resize, recalculate the slider's dimensions and re-render
19265    function updateAll() {
19266      refreshSliderDimensions();
19267      ngModelRender();
19268    }
19269    setTimeout(updateAll, 0);
19270
19271    var debouncedUpdateAll = $$rAF.throttle(updateAll);
19272    angular.element($window).on('resize', debouncedUpdateAll);
19273
19274    scope.$on('$destroy', function() {
19275      angular.element($window).off('resize', debouncedUpdateAll);
19276    });
19277
19278    ngModelCtrl.$render = ngModelRender;
19279    ngModelCtrl.$viewChangeListeners.push(ngModelRender);
19280    ngModelCtrl.$formatters.push(minMaxValidator);
19281    ngModelCtrl.$formatters.push(stepValidator);
19282
19283    /**
19284     * Attributes
19285     */
19286    var min;
19287    var max;
19288    var step;
19289    var round;
19290    function updateMin(value) {
19291      min = parseFloat(value);
19292      element.attr('aria-valuemin', value);
19293      updateAll();
19294    }
19295    function updateMax(value) {
19296      max = parseFloat(value);
19297      element.attr('aria-valuemax', value);
19298      updateAll();
19299    }
19300    function updateStep(value) {
19301      step = parseFloat(value);
19302    }
19303    function updateRound(value) {
19304      // Set max round digits to 6, after 6 the input uses scientific notation
19305      round = minMaxValidator(parseInt(value), 0, 6);
19306    }
19307    function updateAriaDisabled() {
19308      element.attr('aria-disabled', !!isDisabled());
19309    }
19310
19311    // Draw the ticks with canvas.
19312    // The alternative to drawing ticks with canvas is to draw one element for each tick,
19313    // which could quickly become a performance bottleneck.
19314    var tickCanvas, tickCtx;
19315    function redrawTicks() {
19316      if (!discrete || isDisabled()) return;
19317      if ( angular.isUndefined(step) )         return;
19318
19319      if ( step <= 0 ) {
19320        var msg = 'Slider step value must be greater than zero when in discrete mode';
19321        $log.error(msg);
19322        throw new Error(msg);
19323      }
19324
19325      var numSteps = Math.floor( (max - min) / step );
19326      if (!tickCanvas) {
19327        tickCanvas = angular.element('<canvas>').css('position', 'absolute');
19328        tickContainer.append(tickCanvas);
19329
19330        tickCtx = tickCanvas[0].getContext('2d');
19331      }
19332
19333      var dimensions = getSliderDimensions();
19334
19335      // If `dimensions` doesn't have height and width it might be the first attempt so we will refresh dimensions
19336      if (dimensions && !dimensions.height && !dimensions.width) {
19337        refreshSliderDimensions();
19338        dimensions = sliderDimensions;
19339      }
19340
19341      tickCanvas[0].width = dimensions.width;
19342      tickCanvas[0].height = dimensions.height;
19343
19344      var distance;
19345      for (var i = 0; i <= numSteps; i++) {
19346        var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
19347        tickCtx.fillStyle = trackTicksStyle.color || 'black';
19348
19349        distance = Math.floor((vertical ? dimensions.height : dimensions.width) * (i / numSteps));
19350
19351        tickCtx.fillRect(vertical ? 0 : distance - 1,
19352          vertical ? distance - 1 : 0,
19353          vertical ? dimensions.width : 2,
19354          vertical ? 2 : dimensions.height);
19355      }
19356    }
19357
19358    function clearTicks() {
19359      if(tickCanvas && tickCtx) {
19360        var dimensions = getSliderDimensions();
19361        tickCtx.clearRect(0, 0, dimensions.width, dimensions.height);
19362      }
19363    }
19364
19365    /**
19366     * Refreshing Dimensions
19367     */
19368    var sliderDimensions = {};
19369    refreshSliderDimensions();
19370    function refreshSliderDimensions() {
19371      sliderDimensions = trackContainer[0].getBoundingClientRect();
19372    }
19373    function getSliderDimensions() {
19374      throttledRefreshDimensions();
19375      return sliderDimensions;
19376    }
19377
19378    /**
19379     * left/right/up/down arrow listener
19380     */
19381    function keydownListener(ev) {
19382      if (isDisabled()) return;
19383
19384      var changeAmount;
19385      if (vertical ? ev.keyCode === $mdConstant.KEY_CODE.DOWN_ARROW : ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
19386        changeAmount = -step;
19387      } else if (vertical ? ev.keyCode === $mdConstant.KEY_CODE.UP_ARROW : ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
19388        changeAmount = step;
19389      }
19390      changeAmount = invert ? -changeAmount : changeAmount;
19391      if (changeAmount) {
19392        if (ev.metaKey || ev.ctrlKey || ev.altKey) {
19393          changeAmount *= 4;
19394        }
19395        ev.preventDefault();
19396        ev.stopPropagation();
19397        scope.$evalAsync(function() {
19398          setModelValue(ngModelCtrl.$viewValue + changeAmount);
19399        });
19400      }
19401    }
19402
19403    function mouseDownListener() {
19404      redrawTicks();
19405
19406      scope.mouseActive = true;
19407      wrapper.removeClass('md-focused');
19408
19409      $timeout(function() {
19410        scope.mouseActive = false;
19411      }, 100);
19412    }
19413
19414    function focusListener() {
19415      if (scope.mouseActive === false) {
19416        wrapper.addClass('md-focused');
19417      }
19418    }
19419
19420    function blurListener() {
19421      wrapper.removeClass('md-focused');
19422      element.removeClass('md-active');
19423      clearTicks();
19424    }
19425
19426    /**
19427     * ngModel setters and validators
19428     */
19429    function setModelValue(value) {
19430      ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
19431    }
19432    function ngModelRender() {
19433      if (isNaN(ngModelCtrl.$viewValue)) {
19434        ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
19435      }
19436
19437      ngModelCtrl.$viewValue = minMaxValidator(ngModelCtrl.$viewValue);
19438
19439      var percent = valueToPercent(ngModelCtrl.$viewValue);
19440      scope.modelValue = ngModelCtrl.$viewValue;
19441      element.attr('aria-valuenow', ngModelCtrl.$viewValue);
19442      setSliderPercent(percent);
19443      thumbText.text( ngModelCtrl.$viewValue );
19444    }
19445
19446    function minMaxValidator(value, minValue, maxValue) {
19447      if (angular.isNumber(value)) {
19448        minValue = angular.isNumber(minValue) ? minValue : min;
19449        maxValue = angular.isNumber(maxValue) ? maxValue : max;
19450
19451        return Math.max(minValue, Math.min(maxValue, value));
19452      }
19453    }
19454
19455    function stepValidator(value) {
19456      if (angular.isNumber(value)) {
19457        var formattedValue = (Math.round((value - min) / step) * step + min);
19458        formattedValue = (Math.round(formattedValue * Math.pow(10, round)) / Math.pow(10, round));
19459
19460        if (containerCtrl && containerCtrl.fitInputWidthToTextLength){
19461          $mdUtil.debounce(function () {
19462            containerCtrl.fitInputWidthToTextLength(formattedValue.toString().length);
19463          }, 100)();
19464        }
19465
19466        return formattedValue;
19467      }
19468    }
19469
19470    /**
19471     * @param percent 0-1
19472     */
19473    function setSliderPercent(percent) {
19474
19475      percent = clamp(percent);
19476
19477      var thumbPosition = (percent * 100) + '%';
19478      var activeTrackPercent = invert ? (1 - percent) * 100 + '%' : thumbPosition;
19479
19480      if (vertical) {
19481        thumbContainer.css('bottom', thumbPosition);
19482      }
19483      else {
19484        $mdUtil.bidiProperty(thumbContainer, 'left', 'right', thumbPosition);
19485      }
19486
19487      
19488      activeTrack.css(vertical ? 'height' : 'width', activeTrackPercent);
19489
19490      element.toggleClass((invert ? 'md-max' : 'md-min'), percent === 0);
19491      element.toggleClass((invert ? 'md-min' : 'md-max'), percent === 1);
19492    }
19493
19494    /**
19495     * Slide listeners
19496     */
19497    var isDragging = false;
19498
19499    function onPressDown(ev) {
19500      if (isDisabled()) return;
19501
19502      element.addClass('md-active');
19503      element[0].focus();
19504      refreshSliderDimensions();
19505
19506      var exactVal = percentToValue( positionToPercent( vertical ? ev.pointer.y : ev.pointer.x ));
19507      var closestVal = minMaxValidator( stepValidator(exactVal) );
19508      scope.$apply(function() {
19509        setModelValue( closestVal );
19510        setSliderPercent( valueToPercent(closestVal));
19511      });
19512    }
19513    function onPressUp(ev) {
19514      if (isDisabled()) return;
19515
19516      element.removeClass('md-dragging');
19517
19518      var exactVal = percentToValue( positionToPercent( vertical ? ev.pointer.y : ev.pointer.x ));
19519      var closestVal = minMaxValidator( stepValidator(exactVal) );
19520      scope.$apply(function() {
19521        setModelValue(closestVal);
19522        ngModelRender();
19523      });
19524    }
19525    function onDragStart(ev) {
19526      if (isDisabled()) return;
19527      isDragging = true;
19528
19529      ev.stopPropagation();
19530
19531      element.addClass('md-dragging');
19532      setSliderFromEvent(ev);
19533    }
19534    function onDrag(ev) {
19535      if (!isDragging) return;
19536      ev.stopPropagation();
19537      setSliderFromEvent(ev);
19538    }
19539    function onDragEnd(ev) {
19540      if (!isDragging) return;
19541      ev.stopPropagation();
19542      isDragging = false;
19543    }
19544
19545    function setSliderFromEvent(ev) {
19546      // While panning discrete, update only the
19547      // visual positioning but not the model value.
19548      if ( discrete ) adjustThumbPosition( vertical ? ev.pointer.y : ev.pointer.x );
19549      else            doSlide( vertical ? ev.pointer.y : ev.pointer.x );
19550    }
19551
19552    /**
19553     * Slide the UI by changing the model value
19554     * @param x
19555     */
19556    function doSlide( x ) {
19557      scope.$evalAsync( function() {
19558        setModelValue( percentToValue( positionToPercent(x) ));
19559      });
19560    }
19561
19562    /**
19563     * Slide the UI without changing the model (while dragging/panning)
19564     * @param x
19565     */
19566    function adjustThumbPosition( x ) {
19567      var exactVal = percentToValue( positionToPercent( x ));
19568      var closestVal = minMaxValidator( stepValidator(exactVal) );
19569      setSliderPercent( positionToPercent(x) );
19570      thumbText.text( closestVal );
19571    }
19572
19573    /**
19574    * Clamps the value to be between 0 and 1.
19575    * @param {number} value The value to clamp.
19576    * @returns {number}
19577    */
19578    function clamp(value) {
19579      return Math.max(0, Math.min(value || 0, 1));
19580    }
19581
19582    /**
19583     * Convert position on slider to percentage value of offset from beginning...
19584     * @param position
19585     * @returns {number}
19586     */
19587    function positionToPercent( position ) {
19588      var offset = vertical ? sliderDimensions.top : sliderDimensions.left;
19589      var size = vertical ? sliderDimensions.height : sliderDimensions.width;
19590      var calc = (position - offset) / size;
19591
19592      if (!vertical && $mdUtil.bidi() === 'rtl') {
19593        calc = 1 - calc;
19594      }
19595
19596      return Math.max(0, Math.min(1, vertical ? 1 - calc : calc));
19597    }
19598
19599    /**
19600     * Convert percentage offset on slide to equivalent model value
19601     * @param percent
19602     * @returns {*}
19603     */
19604    function percentToValue( percent ) {
19605      var adjustedPercent = invert ? (1 - percent) : percent;
19606      return (min + adjustedPercent * (max - min));
19607    }
19608
19609    function valueToPercent( val ) {
19610      var percent = (val - min) / (max - min);
19611      return invert ? (1 - percent) : percent;
19612    }
19613  }
19614}
19615
19616})();
19617(function(){
19618"use strict";
19619
19620/**
19621 * @ngdoc module
19622 * @name material.components.sticky
19623 * @description
19624 * Sticky effects for md
19625 *
19626 */
19627MdSticky.$inject = ["$mdConstant", "$$rAF", "$mdUtil", "$compile"];
19628angular
19629  .module('material.components.sticky', [
19630    'material.core',
19631    'material.components.content'
19632  ])
19633  .factory('$mdSticky', MdSticky);
19634
19635/**
19636 * @ngdoc service
19637 * @name $mdSticky
19638 * @module material.components.sticky
19639 *
19640 * @description
19641 * The `$mdSticky`service provides a mixin to make elements sticky.
19642 *
19643 * Whenever the current browser supports stickiness natively, the `$mdSticky` service will just
19644 * use the native browser stickiness.
19645 *
19646 * By default the `$mdSticky` service compiles the cloned element, when not specified through the `elementClone`
19647 * parameter, in the same scope as the actual element lives.
19648 *
19649 *
19650 * <h3>Notes</h3>
19651 * When using an element which is containing a compiled directive, which changed its DOM structure during compilation,
19652 * you should compile the clone yourself using the plain template.<br/><br/>
19653 * See the right usage below:
19654 * <hljs lang="js">
19655 *   angular.module('myModule')
19656 *     .directive('stickySelect', function($mdSticky, $compile) {
19657 *       var SELECT_TEMPLATE =
19658 *         '<md-select ng-model="selected">' +
19659 *           '<md-option>Option 1</md-option>' +
19660 *         '</md-select>';
19661 *
19662 *       return {
19663 *         restrict: 'E',
19664 *         replace: true,
19665 *         template: SELECT_TEMPLATE,
19666 *         link: function(scope,element) {
19667 *           $mdSticky(scope, element, $compile(SELECT_TEMPLATE)(scope));
19668 *         }
19669 *       };
19670 *     });
19671 * </hljs>
19672 *
19673 * @usage
19674 * <hljs lang="js">
19675 *   angular.module('myModule')
19676 *     .directive('stickyText', function($mdSticky, $compile) {
19677 *       return {
19678 *         restrict: 'E',
19679 *         template: '<span>Sticky Text</span>',
19680 *         link: function(scope,element) {
19681 *           $mdSticky(scope, element);
19682 *         }
19683 *       };
19684 *     });
19685 * </hljs>
19686 *
19687 * @returns A `$mdSticky` function that takes three arguments:
19688 *   - `scope`
19689 *   - `element`: The element that will be 'sticky'
19690 *   - `elementClone`: A clone of the element, that will be shown
19691 *     when the user starts scrolling past the original element.
19692 *     If not provided, it will use the result of `element.clone()` and compiles it in the given scope.
19693 */
19694function MdSticky($mdConstant, $$rAF, $mdUtil, $compile) {
19695
19696  var browserStickySupport = $mdUtil.checkStickySupport();
19697
19698  /**
19699   * Registers an element as sticky, used internally by directives to register themselves
19700   */
19701  return function registerStickyElement(scope, element, stickyClone) {
19702    var contentCtrl = element.controller('mdContent');
19703    if (!contentCtrl) return;
19704
19705    if (browserStickySupport) {
19706      element.css({
19707        position: browserStickySupport,
19708        top: 0,
19709        'z-index': 2
19710      });
19711    } else {
19712      var $$sticky = contentCtrl.$element.data('$$sticky');
19713      if (!$$sticky) {
19714        $$sticky = setupSticky(contentCtrl);
19715        contentCtrl.$element.data('$$sticky', $$sticky);
19716      }
19717
19718      // Compile our cloned element, when cloned in this service, into the given scope.
19719      var cloneElement = stickyClone || $compile(element.clone())(scope);
19720
19721      var deregister = $$sticky.add(element, cloneElement);
19722      scope.$on('$destroy', deregister);
19723    }
19724  };
19725
19726  function setupSticky(contentCtrl) {
19727    var contentEl = contentCtrl.$element;
19728
19729    // Refresh elements is very expensive, so we use the debounced
19730    // version when possible.
19731    var debouncedRefreshElements = $$rAF.throttle(refreshElements);
19732
19733    // setupAugmentedScrollEvents gives us `$scrollstart` and `$scroll`,
19734    // more reliable than `scroll` on android.
19735    setupAugmentedScrollEvents(contentEl);
19736    contentEl.on('$scrollstart', debouncedRefreshElements);
19737    contentEl.on('$scroll', onScroll);
19738
19739    var self;
19740    return self = {
19741      prev: null,
19742      current: null, //the currently stickied item
19743      next: null,
19744      items: [],
19745      add: add,
19746      refreshElements: refreshElements
19747    };
19748
19749    /***************
19750     * Public
19751     ***************/
19752    // Add an element and its sticky clone to this content's sticky collection
19753    function add(element, stickyClone) {
19754      stickyClone.addClass('md-sticky-clone');
19755
19756      var item = {
19757        element: element,
19758        clone: stickyClone
19759      };
19760      self.items.push(item);
19761
19762      $mdUtil.nextTick(function() {
19763        contentEl.prepend(item.clone);
19764      });
19765
19766      debouncedRefreshElements();
19767
19768      return function remove() {
19769        self.items.forEach(function(item, index) {
19770          if (item.element[0] === element[0]) {
19771            self.items.splice(index, 1);
19772            item.clone.remove();
19773          }
19774        });
19775        debouncedRefreshElements();
19776      };
19777    }
19778
19779    function refreshElements() {
19780      // Sort our collection of elements by their current position in the DOM.
19781      // We need to do this because our elements' order of being added may not
19782      // be the same as their order of display.
19783      self.items.forEach(refreshPosition);
19784      self.items = self.items.sort(function(a, b) {
19785        return a.top < b.top ? -1 : 1;
19786      });
19787
19788      // Find which item in the list should be active, 
19789      // based upon the content's current scroll position
19790      var item;
19791      var currentScrollTop = contentEl.prop('scrollTop');
19792      for (var i = self.items.length - 1; i >= 0; i--) {
19793        if (currentScrollTop > self.items[i].top) {
19794          item = self.items[i];
19795          break;
19796        }
19797      }
19798      setCurrentItem(item);
19799    }
19800
19801    /***************
19802     * Private
19803     ***************/
19804
19805    // Find the `top` of an item relative to the content element,
19806    // and also the height.
19807    function refreshPosition(item) {
19808      // Find the top of an item by adding to the offsetHeight until we reach the 
19809      // content element.
19810      var current = item.element[0];
19811      item.top = 0;
19812      item.left = 0;
19813      item.right = 0;
19814      while (current && current !== contentEl[0]) {
19815        item.top += current.offsetTop;
19816        item.left += current.offsetLeft;
19817        if ( current.offsetParent ){
19818          item.right += current.offsetParent.offsetWidth - current.offsetWidth - current.offsetLeft; //Compute offsetRight
19819        }
19820        current = current.offsetParent;
19821      }
19822      item.height = item.element.prop('offsetHeight');
19823
19824      var defaultVal = $mdUtil.floatingScrollbars() ? '0' : undefined;
19825      $mdUtil.bidi(item.clone, 'margin-left', item.left, defaultVal);
19826      $mdUtil.bidi(item.clone, 'margin-right', defaultVal, item.right);
19827    }
19828
19829    // As we scroll, push in and select the correct sticky element.
19830    function onScroll() {
19831      var scrollTop = contentEl.prop('scrollTop');
19832      var isScrollingDown = scrollTop > (onScroll.prevScrollTop || 0);
19833
19834      // Store the previous scroll so we know which direction we are scrolling
19835      onScroll.prevScrollTop = scrollTop;
19836
19837      //
19838      // AT TOP (not scrolling)
19839      //
19840      if (scrollTop === 0) {
19841        // If we're at the top, just clear the current item and return
19842        setCurrentItem(null);
19843        return;
19844      }
19845
19846      //
19847      // SCROLLING DOWN (going towards the next item)
19848      //
19849      if (isScrollingDown) {
19850
19851        // If we've scrolled down past the next item's position, sticky it and return
19852        if (self.next && self.next.top <= scrollTop) {
19853          setCurrentItem(self.next);
19854          return;
19855        }
19856
19857        // If the next item is close to the current one, push the current one up out of the way
19858        if (self.current && self.next && self.next.top - scrollTop <= self.next.height) {
19859          translate(self.current, scrollTop + (self.next.top - self.next.height - scrollTop));
19860          return;
19861        }
19862      }
19863
19864      //
19865      // SCROLLING UP (not at the top & not scrolling down; must be scrolling up)
19866      //
19867      if (!isScrollingDown) {
19868
19869        // If we've scrolled up past the previous item's position, sticky it and return
19870        if (self.current && self.prev && scrollTop < self.current.top) {
19871          setCurrentItem(self.prev);
19872          return;
19873        }
19874
19875        // If the next item is close to the current one, pull the current one down into view
19876        if (self.next && self.current && (scrollTop >= (self.next.top - self.current.height))) {
19877          translate(self.current, scrollTop + (self.next.top - scrollTop - self.current.height));
19878          return;
19879        }
19880      }
19881
19882      //
19883      // Otherwise, just move the current item to the proper place (scrolling up or down)
19884      //
19885      if (self.current) {
19886        translate(self.current, scrollTop);
19887      }
19888    }
19889
19890    function setCurrentItem(item) {
19891      if (self.current === item) return;
19892      // Deactivate currently active item
19893      if (self.current) {
19894        translate(self.current, null);
19895        setStickyState(self.current, null);
19896      }
19897
19898      // Activate new item if given
19899      if (item) {
19900        setStickyState(item, 'active');
19901      }
19902
19903      self.current = item;
19904      var index = self.items.indexOf(item);
19905      // If index === -1, index + 1 = 0. It works out.
19906      self.next = self.items[index + 1];
19907      self.prev = self.items[index - 1];
19908      setStickyState(self.next, 'next');
19909      setStickyState(self.prev, 'prev');
19910    }
19911
19912    function setStickyState(item, state) {
19913      if (!item || item.state === state) return;
19914      if (item.state) {
19915        item.clone.attr('sticky-prev-state', item.state);
19916        item.element.attr('sticky-prev-state', item.state);
19917      }
19918      item.clone.attr('sticky-state', state);
19919      item.element.attr('sticky-state', state);
19920      item.state = state;
19921    }
19922
19923    function translate(item, amount) {
19924      if (!item) return;
19925      if (amount === null || amount === undefined) {
19926        if (item.translateY) {
19927          item.translateY = null;
19928          item.clone.css($mdConstant.CSS.TRANSFORM, '');
19929        }
19930      } else {
19931        item.translateY = amount;
19932
19933        $mdUtil.bidi( item.clone, $mdConstant.CSS.TRANSFORM,
19934          'translate3d(' + item.left + 'px,' + amount + 'px,0)',
19935          'translateY(' + amount + 'px)'
19936        );
19937      }
19938    }
19939  }
19940
19941
19942  // Android 4.4 don't accurately give scroll events.
19943  // To fix this problem, we setup a fake scroll event. We say:
19944  // > If a scroll or touchmove event has happened in the last DELAY milliseconds, 
19945  //   then send a `$scroll` event every animationFrame.
19946  // Additionally, we add $scrollstart and $scrollend events.
19947  function setupAugmentedScrollEvents(element) {
19948    var SCROLL_END_DELAY = 200;
19949    var isScrolling;
19950    var lastScrollTime;
19951    element.on('scroll touchmove', function() {
19952      if (!isScrolling) {
19953        isScrolling = true;
19954        $$rAF.throttle(loopScrollEvent);
19955        element.triggerHandler('$scrollstart');
19956      }
19957      element.triggerHandler('$scroll');
19958      lastScrollTime = +$mdUtil.now();
19959    });
19960
19961    function loopScrollEvent() {
19962      if (+$mdUtil.now() - lastScrollTime > SCROLL_END_DELAY) {
19963        isScrolling = false;
19964        element.triggerHandler('$scrollend');
19965      } else {
19966        element.triggerHandler('$scroll');
19967        $$rAF.throttle(loopScrollEvent);
19968      }
19969    }
19970  }
19971
19972}
19973
19974})();
19975(function(){
19976"use strict";
19977
19978/**
19979 * @ngdoc module
19980 * @name material.components.subheader
19981 * @description
19982 * SubHeader module
19983 *
19984 *  Subheaders are special list tiles that delineate distinct sections of a
19985 *  list or grid list and are typically related to the current filtering or
19986 *  sorting criteria. Subheader tiles are either displayed inline with tiles or
19987 *  can be associated with content, for example, in an adjacent column.
19988 *
19989 *  Upon scrolling, subheaders remain pinned to the top of the screen and remain
19990 *  pinned until pushed on or off screen by the next subheader. @see [Material
19991 *  Design Specifications](https://www.google.com/design/spec/components/subheaders.html)
19992 *
19993 *  > To improve the visual grouping of content, use the system color for your subheaders.
19994 *
19995 */
19996MdSubheaderDirective.$inject = ["$mdSticky", "$compile", "$mdTheming", "$mdUtil"];
19997angular
19998  .module('material.components.subheader', [
19999    'material.core',
20000    'material.components.sticky'
20001  ])
20002  .directive('mdSubheader', MdSubheaderDirective);
20003
20004/**
20005 * @ngdoc directive
20006 * @name mdSubheader
20007 * @module material.components.subheader
20008 *
20009 * @restrict E
20010 *
20011 * @description
20012 * The `md-subheader` directive creates a sticky subheader for a section.
20013 *
20014 * Developers are able to disable the stickiness of the subheader by using the following markup
20015 *
20016 * <hljs lang="html">
20017 *   <md-subheader class="md-no-sticky">Not Sticky</md-subheader>
20018 * </hljs>
20019 *
20020 * ### Notes
20021 * - The `md-subheader` directive uses the <a ng-href="api/service/$mdSticky">$mdSticky</a> service
20022 * to make the subheader sticky.
20023 *
20024 * > Whenever the current browser doesn't support stickiness natively, the subheader
20025 * will be compiled twice to create a sticky clone of the subheader.
20026 *
20027 * @usage
20028 * <hljs lang="html">
20029 * <md-subheader>Online Friends</md-subheader>
20030 * </hljs>
20031 */
20032
20033function MdSubheaderDirective($mdSticky, $compile, $mdTheming, $mdUtil) {
20034  return {
20035    restrict: 'E',
20036    replace: true,
20037    transclude: true,
20038    template: (
20039    '<div class="md-subheader _md">' +
20040    '  <div class="md-subheader-inner">' +
20041    '    <div class="md-subheader-content"></div>' +
20042    '  </div>' +
20043    '</div>'
20044    ),
20045    link: function postLink(scope, element, attr, controllers, transclude) {
20046      $mdTheming(element);
20047      element.addClass('_md');
20048
20049      // Remove the ngRepeat attribute from the root element, because we don't want to compile
20050      // the ngRepeat for the sticky clone again.
20051      $mdUtil.prefixer().removeAttribute(element, 'ng-repeat');
20052
20053      var outerHTML = element[0].outerHTML;
20054
20055      function getContent(el) {
20056        return angular.element(el[0].querySelector('.md-subheader-content'));
20057      }
20058
20059      // Transclude the user-given contents of the subheader
20060      // the conventional way.
20061      transclude(scope, function(clone) {
20062        getContent(element).append(clone);
20063      });
20064
20065      // Create another clone, that uses the outer and inner contents
20066      // of the element, that will be 'stickied' as the user scrolls.
20067      if (!element.hasClass('md-no-sticky')) {
20068        transclude(scope, function(clone) {
20069          // If the user adds an ng-if or ng-repeat directly to the md-subheader element, the
20070          // compiled clone below will only be a comment tag (since they replace their elements with
20071          // a comment) which cannot be properly passed to the $mdSticky; so we wrap it in our own
20072          // DIV to ensure we have something $mdSticky can use
20073          var wrapper = $compile('<div class="md-subheader-wrapper">' + outerHTML + '</div>')(scope);
20074
20075          // Delay initialization until after any `ng-if`/`ng-repeat`/etc has finished before
20076          // attempting to create the clone
20077          $mdUtil.nextTick(function() {
20078            // Append our transcluded clone into the wrapper.
20079            // We don't have to recompile the element again, because the clone is already
20080            // compiled in it's transclusion scope. If we recompile the outerHTML of the new clone, we would lose
20081            // our ngIf's and other previous registered bindings / properties.
20082            getContent(wrapper).append(clone);
20083          });
20084
20085          // Make the element sticky and provide the stickyClone our self, to avoid recompilation of the subheader
20086          // element.
20087          $mdSticky(scope, element, wrapper);
20088        });
20089      }
20090    }
20091  };
20092}
20093
20094})();
20095(function(){
20096"use strict";
20097
20098/**
20099 * @ngdoc module
20100 * @name material.components.swipe
20101 * @description Swipe module!
20102 */
20103/**
20104 * @ngdoc directive
20105 * @module material.components.swipe
20106 * @name mdSwipeLeft
20107 *
20108 * @restrict A
20109 *
20110 * @description
20111 * The md-swipe-left directive allows you to specify custom behavior when an element is swiped
20112 * left.
20113 *
20114 * @usage
20115 * <hljs lang="html">
20116 * <div md-swipe-left="onSwipeLeft()">Swipe me left!</div>
20117 * </hljs>
20118 */
20119/**
20120 * @ngdoc directive
20121 * @module material.components.swipe
20122 * @name mdSwipeRight
20123 *
20124 * @restrict A
20125 *
20126 * @description
20127 * The md-swipe-right directive allows you to specify custom behavior when an element is swiped
20128 * right.
20129 *
20130 * @usage
20131 * <hljs lang="html">
20132 * <div md-swipe-right="onSwipeRight()">Swipe me right!</div>
20133 * </hljs>
20134 */
20135/**
20136 * @ngdoc directive
20137 * @module material.components.swipe
20138 * @name mdSwipeUp
20139 *
20140 * @restrict A
20141 *
20142 * @description
20143 * The md-swipe-up directive allows you to specify custom behavior when an element is swiped
20144 * up.
20145 *
20146 * @usage
20147 * <hljs lang="html">
20148 * <div md-swipe-up="onSwipeUp()">Swipe me up!</div>
20149 * </hljs>
20150 */
20151/**
20152 * @ngdoc directive
20153 * @module material.components.swipe
20154 * @name mdSwipeDown
20155 *
20156 * @restrict A
20157 *
20158 * @description
20159 * The md-swipe-down directive allows you to specify custom behavior when an element is swiped
20160 * down.
20161 *
20162 * @usage
20163 * <hljs lang="html">
20164 * <div md-swipe-down="onSwipDown()">Swipe me down!</div>
20165 * </hljs>
20166 */
20167
20168angular.module('material.components.swipe', ['material.core'])
20169    .directive('mdSwipeLeft', getDirective('SwipeLeft'))
20170    .directive('mdSwipeRight', getDirective('SwipeRight'))
20171    .directive('mdSwipeUp', getDirective('SwipeUp'))
20172    .directive('mdSwipeDown', getDirective('SwipeDown'));
20173
20174function getDirective(name) {
20175    DirectiveFactory.$inject = ["$parse"];
20176  var directiveName = 'md' + name;
20177  var eventName = '$md.' + name.toLowerCase();
20178
20179  return DirectiveFactory;
20180
20181  /* @ngInject */
20182  function DirectiveFactory($parse) {
20183      return { restrict: 'A', link: postLink };
20184      function postLink(scope, element, attr) {
20185        var fn = $parse(attr[directiveName]);
20186        element.on(eventName, function(ev) {
20187          scope.$applyAsync(function() { fn(scope, { $event: ev }); });
20188        });
20189      }
20190    }
20191}
20192
20193
20194
20195})();
20196(function(){
20197"use strict";
20198
20199/**
20200 * @ngdoc module
20201 * @name material.components.switch
20202 */
20203
20204MdSwitch.$inject = ["mdCheckboxDirective", "$mdUtil", "$mdConstant", "$parse", "$$rAF", "$mdGesture", "$timeout"];
20205angular.module('material.components.switch', [
20206  'material.core',
20207  'material.components.checkbox'
20208])
20209  .directive('mdSwitch', MdSwitch);
20210
20211/**
20212 * @ngdoc directive
20213 * @module material.components.switch
20214 * @name mdSwitch
20215 * @restrict E
20216 *
20217 * The switch directive is used very much like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
20218 *
20219 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
20220 * the switch is in the accent color by default. The primary color palette may be used with
20221 * the `md-primary` class.
20222 *
20223 * @param {string} ng-model Assignable angular expression to data-bind to.
20224 * @param {string=} name Property name of the form under which the control is published.
20225 * @param {expression=} ng-true-value The value to which the expression should be set when selected.
20226 * @param {expression=} ng-false-value The value to which the expression should be set when not selected.
20227 * @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element.
20228 * @param {expression=} ng-disabled En/Disable based on the expression.
20229 * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects.
20230 * @param {string=} aria-label Publish the button label used by screen-readers for accessibility. Defaults to the switch's text.
20231 *
20232 * @usage
20233 * <hljs lang="html">
20234 * <md-switch ng-model="isActive" aria-label="Finished?">
20235 *   Finished ?
20236 * </md-switch>
20237 *
20238 * <md-switch md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
20239 *   No Ink Effects
20240 * </md-switch>
20241 *
20242 * <md-switch ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
20243 *   Disabled
20244 * </md-switch>
20245 *
20246 * </hljs>
20247 */
20248function MdSwitch(mdCheckboxDirective, $mdUtil, $mdConstant, $parse, $$rAF, $mdGesture, $timeout) {
20249  var checkboxDirective = mdCheckboxDirective[0];
20250
20251  return {
20252    restrict: 'E',
20253    priority: 210, // Run before ngAria
20254    transclude: true,
20255    template:
20256      '<div class="md-container">' +
20257        '<div class="md-bar"></div>' +
20258        '<div class="md-thumb-container">' +
20259          '<div class="md-thumb" md-ink-ripple md-ink-ripple-checkbox></div>' +
20260        '</div>'+
20261      '</div>' +
20262      '<div ng-transclude class="md-label"></div>',
20263    require: '?ngModel',
20264    compile: mdSwitchCompile
20265  };
20266
20267  function mdSwitchCompile(element, attr) {
20268    var checkboxLink = checkboxDirective.compile(element, attr).post;
20269    // No transition on initial load.
20270    element.addClass('md-dragging');
20271
20272    return function (scope, element, attr, ngModel) {
20273      ngModel = ngModel || $mdUtil.fakeNgModel();
20274
20275      var disabledGetter = null;
20276      if (attr.disabled != null) {
20277        disabledGetter = function() { return true; };
20278      } else if (attr.ngDisabled) {
20279        disabledGetter = $parse(attr.ngDisabled);
20280      }
20281
20282      var thumbContainer = angular.element(element[0].querySelector('.md-thumb-container'));
20283      var switchContainer = angular.element(element[0].querySelector('.md-container'));
20284
20285      // no transition on initial load
20286      $$rAF(function() {
20287        element.removeClass('md-dragging');
20288      });
20289
20290      checkboxLink(scope, element, attr, ngModel);
20291
20292      if (disabledGetter) {
20293        scope.$watch(disabledGetter, function(isDisabled) {
20294          element.attr('tabindex', isDisabled ? -1 : 0);
20295        });
20296      }
20297
20298      // These events are triggered by setup drag
20299      $mdGesture.register(switchContainer, 'drag');
20300      switchContainer
20301        .on('$md.dragstart', onDragStart)
20302        .on('$md.drag', onDrag)
20303        .on('$md.dragend', onDragEnd);
20304
20305      var drag;
20306      function onDragStart(ev) {
20307        // Don't go if the switch is disabled.
20308        if (disabledGetter && disabledGetter(scope)) return;
20309        ev.stopPropagation();
20310
20311        element.addClass('md-dragging');
20312        drag = {width: thumbContainer.prop('offsetWidth')};
20313      }
20314
20315      function onDrag(ev) {
20316        if (!drag) return;
20317        ev.stopPropagation();
20318        ev.srcEvent && ev.srcEvent.preventDefault();
20319
20320        var percent = ev.pointer.distanceX / drag.width;
20321
20322        //if checked, start from right. else, start from left
20323        var translate = ngModel.$viewValue ?  1 + percent : percent;
20324        // Make sure the switch stays inside its bounds, 0-1%
20325        translate = Math.max(0, Math.min(1, translate));
20326
20327        thumbContainer.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + (100*translate) + '%,0,0)');
20328        drag.translate = translate;
20329      }
20330
20331      function onDragEnd(ev) {
20332        if (!drag) return;
20333        ev.stopPropagation();
20334
20335        element.removeClass('md-dragging');
20336        thumbContainer.css($mdConstant.CSS.TRANSFORM, '');
20337
20338        // We changed if there is no distance (this is a click a click),
20339        // or if the drag distance is >50% of the total.
20340        var isChanged = ngModel.$viewValue ? drag.translate < 0.5 : drag.translate > 0.5;
20341        if (isChanged) {
20342          applyModelValue(!ngModel.$viewValue);
20343        }
20344        drag = null;
20345
20346        // Wait for incoming mouse click
20347        scope.skipToggle = true;
20348        $timeout(function() {
20349          scope.skipToggle = false;
20350        }, 1);
20351      }
20352
20353      function applyModelValue(newValue) {
20354        scope.$apply(function() {
20355          ngModel.$setViewValue(newValue);
20356          ngModel.$render();
20357        });
20358      }
20359
20360    };
20361  }
20362
20363
20364}
20365
20366})();
20367(function(){
20368"use strict";
20369
20370/**
20371 * @ngdoc module
20372 * @name material.components.tabs
20373 * @description
20374 *
20375 *  Tabs, created with the `<md-tabs>` directive provide *tabbed* navigation with different styles.
20376 *  The Tabs component consists of clickable tabs that are aligned horizontally side-by-side.
20377 *
20378 *  Features include support for:
20379 *
20380 *  - static or dynamic tabs,
20381 *  - responsive designs,
20382 *  - accessibility support (ARIA),
20383 *  - tab pagination,
20384 *  - external or internal tab content,
20385 *  - focus indicators and arrow-key navigations,
20386 *  - programmatic lookup and access to tab controllers, and
20387 *  - dynamic transitions through different tab contents.
20388 *
20389 */
20390/*
20391 * @see js folder for tabs implementation
20392 */
20393angular.module('material.components.tabs', [
20394  'material.core',
20395  'material.components.icon'
20396]);
20397
20398})();
20399(function(){
20400"use strict";
20401
20402/**
20403  * @ngdoc module
20404  * @name material.components.toast
20405  * @description
20406  * Toast
20407  */
20408MdToastDirective.$inject = ["$mdToast"];
20409MdToastProvider.$inject = ["$$interimElementProvider"];
20410angular.module('material.components.toast', [
20411  'material.core',
20412  'material.components.button'
20413])
20414  .directive('mdToast', MdToastDirective)
20415  .provider('$mdToast', MdToastProvider);
20416
20417/* @ngInject */
20418function MdToastDirective($mdToast) {
20419  return {
20420    restrict: 'E',
20421    link: function postLink(scope, element) {
20422      element.addClass('_md');     // private md component indicator for styling
20423      
20424      // When navigation force destroys an interimElement, then
20425      // listen and $destroy() that interim instance...
20426      scope.$on('$destroy', function() {
20427        $mdToast.destroy();
20428      });
20429    }
20430  };
20431}
20432
20433/**
20434  * @ngdoc service
20435  * @name $mdToast
20436  * @module material.components.toast
20437  *
20438  * @description
20439  * `$mdToast` is a service to build a toast notification on any position
20440  * on the screen with an optional duration, and provides a simple promise API.
20441  *
20442  * The toast will be always positioned at the `bottom`, when the screen size is
20443  * between `600px` and `959px` (`sm` breakpoint)
20444  *
20445  * ## Restrictions on custom toasts
20446  * - The toast's template must have an outer `<md-toast>` element.
20447  * - For a toast action, use element with class `md-action`.
20448  * - Add the class `md-capsule` for curved corners.
20449  *
20450  * ### Custom Presets
20451  * Developers are also able to create their own preset, which can be easily used without repeating
20452  * their options each time.
20453  *
20454  * <hljs lang="js">
20455  *   $mdToastProvider.addPreset('testPreset', {
20456  *     options: function() {
20457  *       return {
20458  *         template:
20459  *           '<md-toast>' +
20460  *             '<div class="md-toast-content">' +
20461  *               'This is a custom preset' +
20462  *             '</div>' +
20463  *           '</md-toast>',
20464  *         controllerAs: 'toast',
20465  *         bindToController: true
20466  *       };
20467  *     }
20468  *   });
20469  * </hljs>
20470  *
20471  * After you created your preset at config phase, you can easily access it.
20472  *
20473  * <hljs lang="js">
20474  *   $mdToast.show(
20475  *     $mdToast.testPreset()
20476  *   );
20477  * </hljs>
20478  *
20479  * ## Parent container notes
20480  *
20481  * The toast is positioned using absolute positioning relative to its first non-static parent
20482  * container. Thus, if the requested parent container uses static positioning, we will temporarily
20483  * set its positioning to `relative` while the toast is visible and reset it when the toast is
20484  * hidden.
20485  *
20486  * Because of this, it is usually best to ensure that the parent container has a fixed height and
20487  * prevents scrolling by setting the `overflow: hidden;` style. Since the position is based off of
20488  * the parent's height, the toast may be mispositioned if you allow the parent to scroll.
20489  *
20490  * You can, however, have a scrollable element inside of the container; just make sure the
20491  * container itself does not scroll.
20492  *
20493  * <hljs lang="html">
20494  * <div layout-fill id="toast-container">
20495  *   <md-content>
20496  *     I can have lots of content and scroll!
20497  *   </md-content>
20498  * </div>
20499  * </hljs>
20500  *
20501  * @usage
20502  * <hljs lang="html">
20503  * <div ng-controller="MyController">
20504  *   <md-button ng-click="openToast()">
20505  *     Open a Toast!
20506  *   </md-button>
20507  * </div>
20508  * </hljs>
20509  *
20510  * <hljs lang="js">
20511  * var app = angular.module('app', ['ngMaterial']);
20512  * app.controller('MyController', function($scope, $mdToast) {
20513  *   $scope.openToast = function($event) {
20514  *     $mdToast.show($mdToast.simple().textContent('Hello!'));
20515  *     // Could also do $mdToast.showSimple('Hello');
20516  *   };
20517  * });
20518  * </hljs>
20519  */
20520
20521/**
20522 * @ngdoc method
20523 * @name $mdToast#showSimple
20524 * 
20525 * @param {string} message The message to display inside the toast
20526 * @description
20527 * Convenience method which builds and shows a simple toast.
20528 *
20529 * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
20530 * rejected with `$mdToast.cancel()`.
20531 *
20532 */
20533
20534 /**
20535  * @ngdoc method
20536  * @name $mdToast#simple
20537  *
20538  * @description
20539  * Builds a preconfigured toast.
20540  *
20541  * @returns {obj} a `$mdToastPreset` with the following chainable configuration methods.
20542  *
20543  * _**Note:** These configuration methods are provided in addition to the methods provided by
20544  * the `build()` and `show()` methods below._
20545  *
20546  * <table class="md-api-table methods">
20547  *    <thead>
20548  *      <tr>
20549  *        <th>Method</th>
20550  *        <th>Description</th>
20551  *      </tr>
20552  *    </thead>
20553  *    <tbody>
20554  *      <tr>
20555  *        <td>`.textContent(string)`</td>
20556  *        <td>Sets the toast content to the specified string</td>
20557  *      </tr>
20558  *      <tr>
20559  *        <td>`.action(string)`</td>
20560  *        <td>
20561  *          Adds an action button. <br/>
20562  *          If clicked, the promise (returned from `show()`)
20563  *          will resolve with the value `'ok'`; otherwise, it is resolved with `true` after a `hideDelay`
20564  *          timeout
20565  *        </td>
20566  *      </tr>
20567  *      <tr>
20568  *        <td>`.highlightAction(boolean)`</td>
20569  *        <td>
20570  *          Whether or not the action button will have an additional highlight class.<br/>
20571  *          By default the `accent` color will be applied to the action button.
20572  *        </td>
20573  *      </tr>
20574  *      <tr>
20575  *        <td>`.highlightClass(string)`</td>
20576  *        <td>
20577  *          If set, the given class will be applied to the highlighted action button.<br/>
20578  *          This allows you to specify the highlight color easily. Highlight classes are `md-primary`, `md-warn`
20579  *          and `md-accent`
20580  *        </td>
20581  *      </tr>
20582  *      <tr>
20583  *        <td>`.capsule(boolean)`</td>
20584  *        <td>Whether or not to add the `md-capsule` class to the toast to provide rounded corners</td>
20585  *      </tr>
20586  *      <tr>
20587  *        <td>`.theme(string)`</td>
20588  *        <td>Sets the theme on the toast to the requested theme. Default is `$mdThemingProvider`'s default.</td>
20589  *      </tr>
20590  *      <tr>
20591  *        <td>`.toastClass(string)`</td>
20592  *        <td>Sets a class on the toast element</td>
20593  *      </tr>
20594  *    </tbody>
20595  * </table>
20596  *
20597  */
20598
20599/**
20600  * @ngdoc method
20601  * @name $mdToast#updateTextContent
20602  *
20603  * @description
20604  * Updates the content of an existing toast. Useful for updating things like counts, etc.
20605  *
20606  */
20607
20608 /**
20609  * @ngdoc method
20610  * @name $mdToast#build
20611  *
20612  * @description
20613  * Creates a custom `$mdToastPreset` that you can configure.
20614  *
20615  * @returns {obj} a `$mdToastPreset` with the chainable configuration methods for shows' options (see below).
20616  */
20617
20618 /**
20619  * @ngdoc method
20620  * @name $mdToast#show
20621  *
20622  * @description Shows the toast.
20623  *
20624  * @param {object} optionsOrPreset Either provide an `$mdToastPreset` returned from `simple()`
20625  * and `build()`, or an options object with the following properties:
20626  *
20627  *   - `templateUrl` - `{string=}`: The url of an html template file that will
20628  *     be used as the content of the toast. Restrictions: the template must
20629  *     have an outer `md-toast` element.
20630  *   - `template` - `{string=}`: Same as templateUrl, except this is an actual
20631  *     template string.
20632  *   - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template content with a
20633  *     `<div class="md-toast-content">` if one is not provided. Defaults to true. Can be disabled if you provide a
20634  *     custom toast directive.
20635  *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
20636  *     This scope will be destroyed when the toast is removed unless `preserveScope` is set to true.
20637  *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
20638  *   - `hideDelay` - `{number=}`: How many milliseconds the toast should stay
20639  *     active before automatically closing.  Set to 0 or false to have the toast stay open until
20640  *     closed manually. Default: 3000.
20641  *   - `position` - `{string=}`: Sets the position of the toast. <br/>
20642  *     Available: any combination of `'bottom'`, `'left'`, `'top'`, `'right'`, `'end'` and `'start'`.
20643  *     The properties `'end'` and `'start'` are dynamic and can be used for RTL support.<br/>
20644  *     Default combination: `'bottom left'`.
20645  *   - `toastClass` - `{string=}`: A class to set on the toast element.
20646  *   - `controller` - `{string=}`: The controller to associate with this toast.
20647  *     The controller will be injected the local `$mdToast.hide( )`, which is a function
20648  *     used to hide the toast.
20649  *   - `locals` - `{string=}`: An object containing key/value pairs. The keys will
20650  *     be used as names of values to inject into the controller. For example,
20651  *     `locals: {three: 3}` would inject `three` into the controller with the value
20652  *     of 3.
20653  *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
20654  *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
20655  *     and the toast will not open until the promises resolve.
20656  *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
20657  *   - `parent` - `{element=}`: The element to append the toast to. Defaults to appending
20658  *     to the root element of the application.
20659  *
20660  * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
20661  * rejected with `$mdToast.cancel()`. `$mdToast.hide()` will resolve either with a Boolean
20662  * value == 'true' or the value passed as an argument to `$mdToast.hide()`.
20663  * And `$mdToast.cancel()` will resolve the promise with a Boolean value == 'false'
20664  */
20665
20666/**
20667  * @ngdoc method
20668  * @name $mdToast#hide
20669  *
20670  * @description
20671  * Hide an existing toast and resolve the promise returned from `$mdToast.show()`.
20672  *
20673  * @param {*=} response An argument for the resolved promise.
20674  *
20675  * @returns {promise} a promise that is called when the existing element is removed from the DOM.
20676  * The promise is resolved with either a Boolean value == 'true' or the value passed as the
20677  * argument to `.hide()`.
20678  *
20679  */
20680
20681/**
20682  * @ngdoc method
20683  * @name $mdToast#cancel
20684  *
20685  * @description
20686  * `DEPRECATED` - The promise returned from opening a toast is used only to notify about the closing of the toast.
20687  * As such, there isn't any reason to also allow that promise to be rejected,
20688  * since it's not clear what the difference between resolve and reject would be.
20689  *
20690  * Hide the existing toast and reject the promise returned from
20691  * `$mdToast.show()`.
20692  *
20693  * @param {*=} response An argument for the rejected promise.
20694  *
20695  * @returns {promise} a promise that is called when the existing element is removed from the DOM
20696  * The promise is resolved with a Boolean value == 'false'.
20697  *
20698  */
20699
20700function MdToastProvider($$interimElementProvider) {
20701  // Differentiate promise resolves: hide timeout (value == true) and hide action clicks (value == ok).
20702  toastDefaultOptions.$inject = ["$animate", "$mdToast", "$mdUtil", "$mdMedia"];
20703  var ACTION_RESOLVE = 'ok';
20704
20705  var activeToastContent;
20706  var $mdToast = $$interimElementProvider('$mdToast')
20707    .setDefaults({
20708      methods: ['position', 'hideDelay', 'capsule', 'parent', 'position', 'toastClass'],
20709      options: toastDefaultOptions
20710    })
20711    .addPreset('simple', {
20712      argOption: 'textContent',
20713      methods: ['textContent', 'content', 'action', 'highlightAction', 'highlightClass', 'theme', 'parent' ],
20714      options: /* @ngInject */ ["$mdToast", "$mdTheming", function($mdToast, $mdTheming) {
20715        return {
20716          template:
20717            '<md-toast md-theme="{{ toast.theme }}" ng-class="{\'md-capsule\': toast.capsule}">' +
20718            '  <div class="md-toast-content">' +
20719            '    <span class="md-toast-text" role="alert" aria-relevant="all" aria-atomic="true">' +
20720            '      {{ toast.content }}' +
20721            '    </span>' +
20722            '    <md-button class="md-action" ng-if="toast.action" ng-click="toast.resolve()" ' +
20723            '        ng-class="highlightClasses">' +
20724            '      {{ toast.action }}' +
20725            '    </md-button>' +
20726            '  </div>' +
20727            '</md-toast>',
20728          controller: /* @ngInject */ ["$scope", function mdToastCtrl($scope) {
20729            var self = this;
20730
20731            if (self.highlightAction) {
20732              $scope.highlightClasses = [
20733                'md-highlight',
20734                self.highlightClass
20735              ]
20736            }
20737
20738            $scope.$watch(function() { return activeToastContent; }, function() {
20739              self.content = activeToastContent;
20740            });
20741
20742            this.resolve = function() {
20743              $mdToast.hide( ACTION_RESOLVE );
20744            };
20745          }],
20746          theme: $mdTheming.defaultTheme(),
20747          controllerAs: 'toast',
20748          bindToController: true
20749        };
20750      }]
20751    })
20752    .addMethod('updateTextContent', updateTextContent)
20753    .addMethod('updateContent', updateTextContent);
20754
20755    function updateTextContent(newContent) {
20756      activeToastContent = newContent;
20757    }
20758
20759    return $mdToast;
20760
20761  /* @ngInject */
20762  function toastDefaultOptions($animate, $mdToast, $mdUtil, $mdMedia) {
20763    var SWIPE_EVENTS = '$md.swipeleft $md.swiperight $md.swipeup $md.swipedown';
20764    return {
20765      onShow: onShow,
20766      onRemove: onRemove,
20767      toastClass: '',
20768      position: 'bottom left',
20769      themable: true,
20770      hideDelay: 3000,
20771      autoWrap: true,
20772      transformTemplate: function(template, options) {
20773        var shouldAddWrapper = options.autoWrap && template && !/md-toast-content/g.test(template);
20774
20775        if (shouldAddWrapper) {
20776          // Root element of template will be <md-toast>. We need to wrap all of its content inside of
20777          // of <div class="md-toast-content">. All templates provided here should be static, developer-controlled
20778          // content (meaning we're not attempting to guard against XSS).
20779          var templateRoot = document.createElement('md-template');
20780          templateRoot.innerHTML = template;
20781
20782          // Iterate through all root children, to detect possible md-toast directives.
20783          for (var i = 0; i < templateRoot.children.length; i++) {
20784            if (templateRoot.children[i].nodeName === 'MD-TOAST') {
20785              var wrapper = angular.element('<div class="md-toast-content">');
20786
20787              // Wrap the children of the `md-toast` directive in jqLite, to be able to append multiple
20788              // nodes with the same execution.
20789              wrapper.append(angular.element(templateRoot.children[i].childNodes));
20790
20791              // Append the new wrapped element to the `md-toast` directive.
20792              templateRoot.children[i].appendChild(wrapper[0]);
20793            }
20794          }
20795
20796          // We have to return the innerHTMl, because we do not want to have the `md-template` element to be
20797          // the root element of our interimElement.
20798          return templateRoot.innerHTML;
20799        }
20800
20801        return template || '';
20802      }
20803    };
20804
20805    function onShow(scope, element, options) {
20806      activeToastContent = options.textContent || options.content; // support deprecated #content method
20807
20808      var isSmScreen = !$mdMedia('gt-sm');
20809
20810      element = $mdUtil.extractElementByName(element, 'md-toast', true);
20811      options.element = element;
20812
20813      options.onSwipe = function(ev, gesture) {
20814        //Add the relevant swipe class to the element so it can animate correctly
20815        var swipe = ev.type.replace('$md.','');
20816        var direction = swipe.replace('swipe', '');
20817
20818        // If the swipe direction is down/up but the toast came from top/bottom don't fade away
20819        // Unless the screen is small, then the toast always on bottom
20820        if ((direction === 'down' && options.position.indexOf('top') != -1 && !isSmScreen) ||
20821            (direction === 'up' && (options.position.indexOf('bottom') != -1 || isSmScreen))) {
20822          return;
20823        }
20824
20825        if ((direction === 'left' || direction === 'right') && isSmScreen) {
20826          return;
20827        }
20828
20829        element.addClass('md-' + swipe);
20830        $mdUtil.nextTick($mdToast.cancel);
20831      };
20832      options.openClass = toastOpenClass(options.position);
20833
20834      element.addClass(options.toastClass);
20835
20836      // 'top left' -> 'md-top md-left'
20837      options.parent.addClass(options.openClass);
20838
20839      // static is the default position
20840      if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {
20841        options.parent.css('position', 'relative');
20842      }
20843
20844      element.on(SWIPE_EVENTS, options.onSwipe);
20845      element.addClass(isSmScreen ? 'md-bottom' : options.position.split(' ').map(function(pos) {
20846        return 'md-' + pos;
20847      }).join(' '));
20848
20849      if (options.parent) options.parent.addClass('md-toast-animating');
20850      return $animate.enter(element, options.parent).then(function() {
20851        if (options.parent) options.parent.removeClass('md-toast-animating');
20852      });
20853    }
20854
20855    function onRemove(scope, element, options) {
20856      element.off(SWIPE_EVENTS, options.onSwipe);
20857      if (options.parent) options.parent.addClass('md-toast-animating');
20858      if (options.openClass) options.parent.removeClass(options.openClass);
20859
20860      return ((options.$destroy == true) ? element.remove() : $animate.leave(element))
20861        .then(function () {
20862          if (options.parent) options.parent.removeClass('md-toast-animating');
20863          if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {
20864            options.parent.css('position', '');
20865          }
20866        });
20867    }
20868
20869    function toastOpenClass(position) {
20870      // For mobile, always open full-width on bottom
20871      if (!$mdMedia('gt-xs')) {
20872        return 'md-toast-open-bottom';
20873      }
20874
20875      return 'md-toast-open-' +
20876        (position.indexOf('top') > -1 ? 'top' : 'bottom');
20877    }
20878  }
20879
20880}
20881
20882})();
20883(function(){
20884"use strict";
20885
20886/**
20887 * @ngdoc module
20888 * @name material.components.toolbar
20889 */
20890mdToolbarDirective.$inject = ["$$rAF", "$mdConstant", "$mdUtil", "$mdTheming", "$animate"];
20891angular.module('material.components.toolbar', [
20892  'material.core',
20893  'material.components.content'
20894])
20895  .directive('mdToolbar', mdToolbarDirective);
20896
20897/**
20898 * @ngdoc directive
20899 * @name mdToolbar
20900 * @module material.components.toolbar
20901 * @restrict E
20902 * @description
20903 * `md-toolbar` is used to place a toolbar in your app.
20904 *
20905 * Toolbars are usually used above a content area to display the title of the
20906 * current page, and show relevant action buttons for that page.
20907 *
20908 * You can change the height of the toolbar by adding either the
20909 * `md-medium-tall` or `md-tall` class to the toolbar.
20910 *
20911 * @usage
20912 * <hljs lang="html">
20913 * <div layout="column" layout-fill>
20914 *   <md-toolbar>
20915 *
20916 *     <div class="md-toolbar-tools">
20917 *       <span>My App's Title</span>
20918 *
20919 *       <!-- fill up the space between left and right area -->
20920 *       <span flex></span>
20921 *
20922 *       <md-button>
20923 *         Right Bar Button
20924 *       </md-button>
20925 *     </div>
20926 *
20927 *   </md-toolbar>
20928 *   <md-content>
20929 *     Hello!
20930 *   </md-content>
20931 * </div>
20932 * </hljs>
20933 *
20934 * @param {boolean=} md-scroll-shrink Whether the header should shrink away as
20935 * the user scrolls down, and reveal itself as the user scrolls up.
20936 *
20937 * _**Note (1):** for scrollShrink to work, the toolbar must be a sibling of a
20938 * `md-content` element, placed before it. See the scroll shrink demo._
20939 *
20940 * _**Note (2):** The `md-scroll-shrink` attribute is only parsed on component
20941 * initialization, it does not watch for scope changes._
20942 *
20943 *
20944 * @param {number=} md-shrink-speed-factor How much to change the speed of the toolbar's
20945 * shrinking by. For example, if 0.25 is given then the toolbar will shrink
20946 * at one fourth the rate at which the user scrolls down. Default 0.5.
20947 */
20948
20949function mdToolbarDirective($$rAF, $mdConstant, $mdUtil, $mdTheming, $animate) {
20950  var translateY = angular.bind(null, $mdUtil.supplant, 'translate3d(0,{0}px,0)');
20951
20952  return {
20953    template: '',
20954    restrict: 'E',
20955
20956    link: function(scope, element, attr) {
20957
20958      element.addClass('_md');     // private md component indicator for styling
20959      $mdTheming(element);
20960
20961      $mdUtil.nextTick(function () {
20962        element.addClass('_md-toolbar-transitions');     // adding toolbar transitions after digest
20963      }, false);
20964
20965      if (angular.isDefined(attr.mdScrollShrink)) {
20966        setupScrollShrink();
20967      }
20968
20969      function setupScrollShrink() {
20970
20971        var toolbarHeight;
20972        var contentElement;
20973        var disableScrollShrink = angular.noop;
20974
20975        // Current "y" position of scroll
20976        // Store the last scroll top position
20977        var y = 0;
20978        var prevScrollTop = 0;
20979        var shrinkSpeedFactor = attr.mdShrinkSpeedFactor || 0.5;
20980
20981        var debouncedContentScroll = $$rAF.throttle(onContentScroll);
20982        var debouncedUpdateHeight = $mdUtil.debounce(updateToolbarHeight, 5 * 1000);
20983
20984        // Wait for $mdContentLoaded event from mdContent directive.
20985        // If the mdContent element is a sibling of our toolbar, hook it up
20986        // to scroll events.
20987
20988        scope.$on('$mdContentLoaded', onMdContentLoad);
20989
20990        // If the toolbar is used inside an ng-if statement, we may miss the
20991        // $mdContentLoaded event, so we attempt to fake it if we have a
20992        // md-content close enough.
20993
20994        attr.$observe('mdScrollShrink', onChangeScrollShrink);
20995
20996        // If the toolbar has ngShow or ngHide we need to update height immediately as it changed
20997        // and not wait for $mdUtil.debounce to happen
20998
20999        if (attr.ngShow) { scope.$watch(attr.ngShow, updateToolbarHeight); }
21000        if (attr.ngHide) { scope.$watch(attr.ngHide, updateToolbarHeight); }
21001
21002        // If the scope is destroyed (which could happen with ng-if), make sure
21003        // to disable scroll shrinking again
21004
21005        scope.$on('$destroy', disableScrollShrink);
21006
21007        /**
21008         *
21009         */
21010        function onChangeScrollShrink(shrinkWithScroll) {
21011          var closestContent = element.parent().find('md-content');
21012
21013          // If we have a content element, fake the call; this might still fail
21014          // if the content element isn't a sibling of the toolbar
21015
21016          if (!contentElement && closestContent.length) {
21017            onMdContentLoad(null, closestContent);
21018          }
21019
21020          // Evaluate the expression
21021          shrinkWithScroll = scope.$eval(shrinkWithScroll);
21022
21023          // Disable only if the attribute's expression evaluates to false
21024          if (shrinkWithScroll === false) {
21025            disableScrollShrink();
21026          } else {
21027            disableScrollShrink = enableScrollShrink();
21028          }
21029        }
21030
21031        /**
21032         *
21033         */
21034        function onMdContentLoad($event, newContentEl) {
21035          // Toolbar and content must be siblings
21036          if (newContentEl && element.parent()[0] === newContentEl.parent()[0]) {
21037            // unhook old content event listener if exists
21038            if (contentElement) {
21039              contentElement.off('scroll', debouncedContentScroll);
21040            }
21041
21042            contentElement = newContentEl;
21043            disableScrollShrink = enableScrollShrink();
21044          }
21045        }
21046
21047        /**
21048         *
21049         */
21050        function onContentScroll(e) {
21051          var scrollTop = e ? e.target.scrollTop : prevScrollTop;
21052
21053          debouncedUpdateHeight();
21054
21055          y = Math.min(
21056            toolbarHeight / shrinkSpeedFactor,
21057            Math.max(0, y + scrollTop - prevScrollTop)
21058          );
21059
21060          element.css($mdConstant.CSS.TRANSFORM, translateY([-y * shrinkSpeedFactor]));
21061          contentElement.css($mdConstant.CSS.TRANSFORM, translateY([(toolbarHeight - y) * shrinkSpeedFactor]));
21062
21063          prevScrollTop = scrollTop;
21064
21065          $mdUtil.nextTick(function() {
21066            var hasWhiteFrame = element.hasClass('md-whiteframe-z1');
21067
21068            if (hasWhiteFrame && !y) {
21069              $animate.removeClass(element, 'md-whiteframe-z1');
21070            } else if (!hasWhiteFrame && y) {
21071              $animate.addClass(element, 'md-whiteframe-z1');
21072            }
21073          });
21074
21075        }
21076
21077        /**
21078         *
21079         */
21080        function enableScrollShrink() {
21081          if (!contentElement)     return angular.noop;           // no md-content
21082
21083          contentElement.on('scroll', debouncedContentScroll);
21084          contentElement.attr('scroll-shrink', 'true');
21085
21086          $mdUtil.nextTick(updateToolbarHeight, false);
21087
21088          return function disableScrollShrink() {
21089            contentElement.off('scroll', debouncedContentScroll);
21090            contentElement.attr('scroll-shrink', 'false');
21091
21092            updateToolbarHeight();
21093          };
21094        }
21095
21096        /**
21097         *
21098         */
21099        function updateToolbarHeight() {
21100          toolbarHeight = element.prop('offsetHeight');
21101          // Add a negative margin-top the size of the toolbar to the content el.
21102          // The content will start transformed down the toolbarHeight amount,
21103          // so everything looks normal.
21104          //
21105          // As the user scrolls down, the content will be transformed up slowly
21106          // to put the content underneath where the toolbar was.
21107          var margin = (-toolbarHeight * shrinkSpeedFactor) + 'px';
21108
21109          contentElement.css({
21110            "margin-top": margin,
21111            "margin-bottom": margin
21112          });
21113
21114          onContentScroll();
21115        }
21116
21117      }
21118
21119    }
21120  };
21121
21122}
21123
21124})();
21125(function(){
21126"use strict";
21127
21128/**
21129 * @ngdoc module
21130 * @name material.components.tooltip
21131 */
21132MdTooltipDirective.$inject = ["$timeout", "$window", "$$rAF", "$document", "$mdUtil", "$mdTheming", "$rootElement", "$animate", "$q", "$interpolate"];
21133angular
21134    .module('material.components.tooltip', [ 'material.core' ])
21135    .directive('mdTooltip', MdTooltipDirective);
21136
21137/**
21138 * @ngdoc directive
21139 * @name mdTooltip
21140 * @module material.components.tooltip
21141 * @description
21142 * Tooltips are used to describe elements that are interactive and primarily graphical (not textual).
21143 *
21144 * Place a `<md-tooltip>` as a child of the element it describes.
21145 *
21146 * A tooltip will activate when the user focuses, hovers over, or touches the parent.
21147 *
21148 * @usage
21149 * <hljs lang="html">
21150 * <md-button class="md-fab md-accent" aria-label="Play">
21151 *   <md-tooltip>
21152 *     Play Music
21153 *   </md-tooltip>
21154 *   <md-icon icon="img/icons/ic_play_arrow_24px.svg"></md-icon>
21155 * </md-button>
21156 * </hljs>
21157 *
21158 * @param {expression=} md-visible Boolean bound to whether the tooltip is currently visible.
21159 * @param {number=} md-delay How many milliseconds to wait to show the tooltip after the user focuses, hovers, or touches the
21160 * parent. Defaults to 0ms on non-touch devices and 75ms on touch.
21161 * @param {boolean=} md-autohide If present or provided with a boolean value, the tooltip will hide on mouse leave, regardless of focus
21162 * @param {string=} md-direction Which direction would you like the tooltip to go?  Supports left, right, top, and bottom.  Defaults to bottom.
21163 */
21164function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdTheming, $rootElement,
21165                            $animate, $q, $interpolate) {
21166
21167  var ENTER_EVENTS = 'focus touchstart mouseenter';
21168  var LEAVE_EVENTS = 'blur touchcancel mouseleave';
21169  var SHOW_CLASS = 'md-show';
21170  var TOOLTIP_SHOW_DELAY = 0;
21171  var TOOLTIP_WINDOW_EDGE_SPACE = 8;
21172
21173  return {
21174    restrict: 'E',
21175    transclude: true,
21176    priority: 210, // Before ngAria
21177    template: '<div class="md-content _md" ng-transclude></div>',
21178    scope: {
21179      delay: '=?mdDelay',
21180      visible: '=?mdVisible',
21181      autohide: '=?mdAutohide',
21182      direction: '@?mdDirection'    // only expect raw or interpolated string value; not expression
21183    },
21184    compile: function(tElement, tAttr) {
21185      if (!tAttr.mdDirection) {
21186        tAttr.$set('mdDirection', 'bottom');
21187      }
21188
21189      return postLink;
21190    }
21191  };
21192
21193  function postLink(scope, element, attr) {
21194
21195    $mdTheming(element);
21196
21197    var parent        = $mdUtil.getParentWithPointerEvents(element),
21198        content       = angular.element(element[0].getElementsByClassName('md-content')[0]),
21199        tooltipParent = angular.element(document.body),
21200        showTimeout   = null,
21201        debouncedOnResize = $$rAF.throttle(function () { updatePosition(); });
21202
21203    if ($animate.pin) $animate.pin(element, parent);
21204
21205    // Initialize element
21206
21207    setDefaults();
21208    manipulateElement();
21209    bindEvents();
21210
21211    // Default origin transform point is 'center top'
21212    // positionTooltip() is always relative to center top
21213    updateContentOrigin();
21214
21215    configureWatchers();
21216    addAriaLabel();
21217
21218
21219    function setDefaults () {
21220      scope.delay = scope.delay || TOOLTIP_SHOW_DELAY;
21221    }
21222
21223    function updateContentOrigin() {
21224      var origin = 'center top';
21225      switch (scope.direction) {
21226        case 'left'  : origin =  'right center';  break;
21227        case 'right' : origin =  'left center';   break;
21228        case 'top'   : origin =  'center bottom'; break;
21229        case 'bottom': origin =  'center top';    break;
21230      }
21231      content.css('transform-origin', origin);
21232    }
21233
21234    function onVisibleChanged (isVisible) {
21235      if (isVisible) showTooltip();
21236      else hideTooltip();
21237    }
21238
21239    function configureWatchers () {
21240      if (element[0] && 'MutationObserver' in $window) {
21241        var attributeObserver = new MutationObserver(function(mutations) {
21242          mutations
21243            .forEach(function (mutation) {
21244              if (mutation.attributeName === 'md-visible') {
21245                if (!scope.visibleWatcher)
21246                  scope.visibleWatcher = scope.$watch('visible', onVisibleChanged );
21247              }
21248              if (mutation.attributeName === 'md-direction') {
21249                updatePosition(scope.direction);
21250              }
21251            });
21252        });
21253
21254        attributeObserver.observe(element[0], { attributes: true });
21255
21256        // build watcher only if mdVisible is being used
21257        if (attr.hasOwnProperty('mdVisible')) {
21258          scope.visibleWatcher = scope.$watch('visible', onVisibleChanged );
21259        }
21260      } else { // MutationObserver not supported
21261        scope.visibleWatcher = scope.$watch('visible', onVisibleChanged );
21262        scope.$watch('direction', updatePosition );
21263      }
21264
21265      var onElementDestroy = function() {
21266        scope.$destroy();
21267      };
21268
21269      // Clean up if the element or parent was removed via jqLite's .remove.
21270      // A couple of notes:
21271      // - In these cases the scope might not have been destroyed, which is why we
21272      // destroy it manually. An example of this can be having `md-visible="false"` and
21273      // adding tooltips while they're invisible. If `md-visible` becomes true, at some
21274      // point, you'd usually get a lot of inputs.
21275      // - We use `.one`, not `.on`, because this only needs to fire once. If we were
21276      // using `.on`, it would get thrown into an infinite loop.
21277      // - This kicks off the scope's `$destroy` event which finishes the cleanup.
21278      element.one('$destroy', onElementDestroy);
21279      parent.one('$destroy', onElementDestroy);
21280      scope.$on('$destroy', function() {
21281        setVisible(false);
21282        element.remove();
21283        attributeObserver && attributeObserver.disconnect();
21284      });
21285
21286      // Updates the aria-label when the element text changes. This watch
21287      // doesn't need to be set up if the element doesn't have any data
21288      // bindings.
21289      if (element.text().indexOf($interpolate.startSymbol()) > -1) {
21290        scope.$watch(function() {
21291          return element.text().trim();
21292        }, addAriaLabel);
21293      }
21294    }
21295
21296    function addAriaLabel (override) {
21297      if ((override || !parent.attr('aria-label')) && !parent.text().trim()) {
21298        var rawText = override || element.text().trim();
21299        var interpolatedText = $interpolate(rawText)(parent.scope());
21300        parent.attr('aria-label', interpolatedText);
21301      }
21302    }
21303
21304    function manipulateElement () {
21305      element.detach();
21306      element.attr('role', 'tooltip');
21307    }
21308
21309    function bindEvents () {
21310      var mouseActive = false;
21311
21312      // add an mutationObserver when there is support for it
21313      // and the need for it in the form of viable host(parent[0])
21314      if (parent[0] && 'MutationObserver' in $window) {
21315        // use an mutationObserver to tackle #2602
21316        var attributeObserver = new MutationObserver(function(mutations) {
21317          if (mutations.some(function (mutation) {
21318              return (mutation.attributeName === 'disabled' && parent[0].disabled);
21319            })) {
21320              $mdUtil.nextTick(function() {
21321                setVisible(false);
21322              });
21323          }
21324        });
21325
21326        attributeObserver.observe(parent[0], { attributes: true});
21327      }
21328
21329      // Store whether the element was focused when the window loses focus.
21330      var windowBlurHandler = function() {
21331        elementFocusedOnWindowBlur = document.activeElement === parent[0];
21332      };
21333      var elementFocusedOnWindowBlur = false;
21334
21335      function windowScrollHandler() {
21336        setVisible(false);
21337      }
21338
21339      angular.element($window)
21340        .on('blur', windowBlurHandler)
21341        .on('resize', debouncedOnResize);
21342
21343      document.addEventListener('scroll', windowScrollHandler, true);
21344      scope.$on('$destroy', function() {
21345        angular.element($window)
21346          .off('blur', windowBlurHandler)
21347          .off('resize', debouncedOnResize);
21348
21349        parent
21350          .off(ENTER_EVENTS, enterHandler)
21351          .off(LEAVE_EVENTS, leaveHandler)
21352          .off('mousedown', mousedownHandler);
21353
21354        // Trigger the handler in case any the tooltip was still visible.
21355        leaveHandler();
21356        document.removeEventListener('scroll', windowScrollHandler, true);
21357        attributeObserver && attributeObserver.disconnect();
21358      });
21359
21360      var enterHandler = function(e) {
21361        // Prevent the tooltip from showing when the window is receiving focus.
21362        if (e.type === 'focus' && elementFocusedOnWindowBlur) {
21363          elementFocusedOnWindowBlur = false;
21364        } else if (!scope.visible) {
21365          parent.on(LEAVE_EVENTS, leaveHandler);
21366          setVisible(true);
21367
21368          // If the user is on a touch device, we should bind the tap away after
21369          // the `touched` in order to prevent the tooltip being removed immediately.
21370          if (e.type === 'touchstart') {
21371            parent.one('touchend', function() {
21372              $mdUtil.nextTick(function() {
21373                $document.one('touchend', leaveHandler);
21374              }, false);
21375            });
21376          }
21377        }
21378      };
21379      var leaveHandler = function () {
21380        var autohide = scope.hasOwnProperty('autohide') ? scope.autohide : attr.hasOwnProperty('mdAutohide');
21381
21382        if (autohide || mouseActive || $document[0].activeElement !== parent[0]) {
21383          // When a show timeout is currently in progress, then we have to cancel it.
21384          // Otherwise the tooltip will remain showing without focus or hover.
21385          if (showTimeout) {
21386            $timeout.cancel(showTimeout);
21387            setVisible.queued = false;
21388            showTimeout = null;
21389          }
21390
21391          parent.off(LEAVE_EVENTS, leaveHandler);
21392          parent.triggerHandler('blur');
21393          setVisible(false);
21394        }
21395        mouseActive = false;
21396      };
21397      var mousedownHandler = function() {
21398        mouseActive = true;
21399      };
21400
21401      // to avoid `synthetic clicks` we listen to mousedown instead of `click`
21402      parent.on('mousedown', mousedownHandler);
21403      parent.on(ENTER_EVENTS, enterHandler);
21404    }
21405
21406    function setVisible (value) {
21407      // break if passed value is already in queue or there is no queue and passed value is current in the scope
21408      if (setVisible.queued && setVisible.value === !!value || !setVisible.queued && scope.visible === !!value) return;
21409      setVisible.value = !!value;
21410
21411      if (!setVisible.queued) {
21412        if (value) {
21413          setVisible.queued = true;
21414          showTimeout = $timeout(function() {
21415            scope.visible = setVisible.value;
21416            setVisible.queued = false;
21417            showTimeout = null;
21418
21419            if (!scope.visibleWatcher) {
21420              onVisibleChanged(scope.visible);
21421            }
21422          }, scope.delay);
21423        } else {
21424          $mdUtil.nextTick(function() {
21425            scope.visible = false;
21426            if (!scope.visibleWatcher)
21427              onVisibleChanged(false);
21428          });
21429        }
21430      }
21431    }
21432
21433    function showTooltip() {
21434      //  Do not show the tooltip if the text is empty.
21435      if (!element[0].textContent.trim()) return;
21436
21437      // Insert the element and position at top left, so we can get the position
21438      // and check if we should display it
21439      element.css({top: 0, left: 0});
21440      tooltipParent.append(element);
21441
21442      // Check if we should display it or not.
21443      // This handles hide-* and show-* along with any user defined css
21444      if ( $mdUtil.hasComputedStyle(element, 'display', 'none')) {
21445        scope.visible = false;
21446        element.detach();
21447        return;
21448      }
21449
21450      updatePosition();
21451
21452      $animate.addClass(content, SHOW_CLASS).then(function() {
21453        element.addClass(SHOW_CLASS);
21454      });
21455    }
21456
21457    function hideTooltip() {
21458      $animate.removeClass(content, SHOW_CLASS).then(function(){
21459        element.removeClass(SHOW_CLASS);
21460        if (!scope.visible) element.detach();
21461      });
21462    }
21463
21464    function updatePosition() {
21465      if ( !scope.visible ) return;
21466
21467      updateContentOrigin();
21468      positionTooltip();
21469    }
21470
21471    function positionTooltip() {
21472      var tipRect = $mdUtil.offsetRect(element, tooltipParent);
21473      var parentRect = $mdUtil.offsetRect(parent, tooltipParent);
21474      var newPosition = getPosition(scope.direction);
21475      var offsetParent = element.prop('offsetParent');
21476
21477      // If the user provided a direction, just nudge the tooltip onto the screen
21478      // Otherwise, recalculate based on 'top' since default is 'bottom'
21479      if (scope.direction) {
21480        newPosition = fitInParent(newPosition);
21481      } else if (offsetParent && newPosition.top > offsetParent.scrollHeight - tipRect.height - TOOLTIP_WINDOW_EDGE_SPACE) {
21482        newPosition = fitInParent(getPosition('top'));
21483      }
21484
21485      element.css({
21486        left: newPosition.left + 'px',
21487        top: newPosition.top + 'px'
21488      });
21489
21490      function fitInParent (pos) {
21491        var newPosition = { left: pos.left, top: pos.top };
21492        newPosition.left = Math.min( newPosition.left, tooltipParent.prop('scrollWidth') - tipRect.width - TOOLTIP_WINDOW_EDGE_SPACE );
21493        newPosition.left = Math.max( newPosition.left, TOOLTIP_WINDOW_EDGE_SPACE );
21494        newPosition.top  = Math.min( newPosition.top,  tooltipParent.prop('scrollHeight') - tipRect.height - TOOLTIP_WINDOW_EDGE_SPACE );
21495        newPosition.top  = Math.max( newPosition.top,  TOOLTIP_WINDOW_EDGE_SPACE );
21496        return newPosition;
21497      }
21498
21499      function getPosition (dir) {
21500        return dir === 'left'
21501          ? { left: parentRect.left - tipRect.width - TOOLTIP_WINDOW_EDGE_SPACE,
21502              top: parentRect.top + parentRect.height / 2 - tipRect.height / 2 }
21503          : dir === 'right'
21504          ? { left: parentRect.left + parentRect.width + TOOLTIP_WINDOW_EDGE_SPACE,
21505              top: parentRect.top + parentRect.height / 2 - tipRect.height / 2 }
21506          : dir === 'top'
21507          ? { left: parentRect.left + parentRect.width / 2 - tipRect.width / 2,
21508              top: parentRect.top - tipRect.height - TOOLTIP_WINDOW_EDGE_SPACE }
21509          : { left: parentRect.left + parentRect.width / 2 - tipRect.width / 2,
21510              top: parentRect.top + parentRect.height + TOOLTIP_WINDOW_EDGE_SPACE };
21511      }
21512    }
21513
21514  }
21515
21516}
21517
21518})();
21519(function(){
21520"use strict";
21521
21522/**
21523 * @ngdoc module
21524 * @name material.components.virtualRepeat
21525 */
21526VirtualRepeatContainerController.$inject = ["$$rAF", "$mdUtil", "$parse", "$rootScope", "$window", "$scope", "$element", "$attrs"];
21527VirtualRepeatController.$inject = ["$scope", "$element", "$attrs", "$browser", "$document", "$rootScope", "$$rAF", "$mdUtil"];
21528VirtualRepeatDirective.$inject = ["$parse"];
21529angular.module('material.components.virtualRepeat', [
21530  'material.core',
21531  'material.components.showHide'
21532])
21533.directive('mdVirtualRepeatContainer', VirtualRepeatContainerDirective)
21534.directive('mdVirtualRepeat', VirtualRepeatDirective);
21535
21536
21537/**
21538 * @ngdoc directive
21539 * @name mdVirtualRepeatContainer
21540 * @module material.components.virtualRepeat
21541 * @restrict E
21542 * @description
21543 * `md-virtual-repeat-container` provides the scroll container for md-virtual-repeat.
21544 *
21545 * VirtualRepeat is a limited substitute for ng-repeat that renders only
21546 * enough DOM nodes to fill the container and recycling them as the user scrolls.
21547 *
21548 * Once an element is not visible anymore, the VirtualRepeat recycles it and will reuse it for
21549 * another visible item by replacing the previous dataset with the new one.
21550 *
21551 * ### Common Issues
21552 *
21553 * > When having one-time bindings inside of the view template, the VirtualRepeat will not properly
21554 * > update the bindings for new items, since the view will be recycled.
21555 *
21556 * ### Notes
21557 *
21558 * > The VirtualRepeat is a similar implementation to the Android
21559 * [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)
21560 *
21561 * <!-- This comment forces a break between blockquotes //-->
21562 *
21563 * > Please also review the <a ng-href="api/directive/mdVirtualRepeat">VirtualRepeat</a>
21564 * documentation for more information.
21565 *
21566 *
21567 * @usage
21568 * <hljs lang="html">
21569 *
21570 * <md-virtual-repeat-container md-top-index="topIndex">
21571 *   <div md-virtual-repeat="i in items" md-item-size="20">Hello {{i}}!</div>
21572 * </md-virtual-repeat-container>
21573 * </hljs>
21574 *
21575 * @param {number=} md-top-index Binds the index of the item that is at the top of the scroll
21576 *     container to $scope. It can both read and set the scroll position.
21577 * @param {boolean=} md-orient-horizontal Whether the container should scroll horizontally
21578 *     (defaults to orientation and scrolling vertically).
21579 * @param {boolean=} md-auto-shrink When present, the container will shrink to fit
21580 *     the number of items when that number is less than its original size.
21581 * @param {number=} md-auto-shrink-min Minimum number of items that md-auto-shrink
21582 *     will shrink to (default: 0).
21583 */
21584function VirtualRepeatContainerDirective() {
21585  return {
21586    controller: VirtualRepeatContainerController,
21587    template: virtualRepeatContainerTemplate,
21588    compile: function virtualRepeatContainerCompile($element, $attrs) {
21589      $element
21590          .addClass('md-virtual-repeat-container')
21591          .addClass($attrs.hasOwnProperty('mdOrientHorizontal')
21592              ? 'md-orient-horizontal'
21593              : 'md-orient-vertical');
21594    }
21595  };
21596}
21597
21598
21599function virtualRepeatContainerTemplate($element) {
21600  return '<div class="md-virtual-repeat-scroller">' +
21601    '<div class="md-virtual-repeat-sizer"></div>' +
21602    '<div class="md-virtual-repeat-offsetter">' +
21603      $element[0].innerHTML +
21604    '</div></div>';
21605}
21606
21607/**
21608 * Maximum size, in pixels, that can be explicitly set to an element. The actual value varies
21609 * between browsers, but IE11 has the very lowest size at a mere 1,533,917px. Ideally we could
21610 * *compute* this value, but Firefox always reports an element to have a size of zero if it
21611 * goes over the max, meaning that we'd have to binary search for the value.
21612 * @const {number}
21613 */
21614var MAX_ELEMENT_SIZE = 1533917;
21615
21616/**
21617 * Number of additional elements to render above and below the visible area inside
21618 * of the virtual repeat container. A higher number results in less flicker when scrolling
21619 * very quickly in Safari, but comes with a higher rendering and dirty-checking cost.
21620 * @const {number}
21621 */
21622var NUM_EXTRA = 3;
21623
21624/** @ngInject */
21625function VirtualRepeatContainerController(
21626    $$rAF, $mdUtil, $parse, $rootScope, $window, $scope, $element, $attrs) {
21627  this.$rootScope = $rootScope;
21628  this.$scope = $scope;
21629  this.$element = $element;
21630  this.$attrs = $attrs;
21631
21632  /** @type {number} The width or height of the container */
21633  this.size = 0;
21634  /** @type {number} The scroll width or height of the scroller */
21635  this.scrollSize = 0;
21636  /** @type {number} The scrollLeft or scrollTop of the scroller */
21637  this.scrollOffset = 0;
21638  /** @type {boolean} Whether the scroller is oriented horizontally */
21639  this.horizontal = this.$attrs.hasOwnProperty('mdOrientHorizontal');
21640  /** @type {!VirtualRepeatController} The repeater inside of this container */
21641  this.repeater = null;
21642  /** @type {boolean} Whether auto-shrink is enabled */
21643  this.autoShrink = this.$attrs.hasOwnProperty('mdAutoShrink');
21644  /** @type {number} Minimum number of items to auto-shrink to */
21645  this.autoShrinkMin = parseInt(this.$attrs.mdAutoShrinkMin, 10) || 0;
21646  /** @type {?number} Original container size when shrank */
21647  this.originalSize = null;
21648  /** @type {number} Amount to offset the total scroll size by. */
21649  this.offsetSize = parseInt(this.$attrs.mdOffsetSize, 10) || 0;
21650  /** @type {?string} height or width element style on the container prior to auto-shrinking. */
21651  this.oldElementSize = null;
21652
21653  if (this.$attrs.mdTopIndex) {
21654    /** @type {function(angular.Scope): number} Binds to topIndex on Angular scope */
21655    this.bindTopIndex = $parse(this.$attrs.mdTopIndex);
21656    /** @type {number} The index of the item that is at the top of the scroll container */
21657    this.topIndex = this.bindTopIndex(this.$scope);
21658
21659    if (!angular.isDefined(this.topIndex)) {
21660      this.topIndex = 0;
21661      this.bindTopIndex.assign(this.$scope, 0);
21662    }
21663
21664    this.$scope.$watch(this.bindTopIndex, angular.bind(this, function(newIndex) {
21665      if (newIndex !== this.topIndex) {
21666        this.scrollToIndex(newIndex);
21667      }
21668    }));
21669  } else {
21670    this.topIndex = 0;
21671  }
21672
21673  this.scroller = $element[0].querySelector('.md-virtual-repeat-scroller');
21674  this.sizer = this.scroller.querySelector('.md-virtual-repeat-sizer');
21675  this.offsetter = this.scroller.querySelector('.md-virtual-repeat-offsetter');
21676
21677  // After the dom stablizes, measure the initial size of the container and
21678  // make a best effort at re-measuring as it changes.
21679  var boundUpdateSize = angular.bind(this, this.updateSize);
21680
21681  $$rAF(angular.bind(this, function() {
21682    boundUpdateSize();
21683
21684    var debouncedUpdateSize = $mdUtil.debounce(boundUpdateSize, 10, null, false);
21685    var jWindow = angular.element($window);
21686
21687    // Make one more attempt to get the size if it is 0.
21688    // This is not by any means a perfect approach, but there's really no
21689    // silver bullet here.
21690    if (!this.size) {
21691      debouncedUpdateSize();
21692    }
21693
21694    jWindow.on('resize', debouncedUpdateSize);
21695    $scope.$on('$destroy', function() {
21696      jWindow.off('resize', debouncedUpdateSize);
21697    });
21698
21699    $scope.$emit('$md-resize-enable');
21700    $scope.$on('$md-resize', boundUpdateSize);
21701  }));
21702}
21703
21704
21705/** Called by the md-virtual-repeat inside of the container at startup. */
21706VirtualRepeatContainerController.prototype.register = function(repeaterCtrl) {
21707  this.repeater = repeaterCtrl;
21708
21709  angular.element(this.scroller)
21710      .on('scroll wheel touchmove touchend', angular.bind(this, this.handleScroll_));
21711};
21712
21713
21714/** @return {boolean} Whether the container is configured for horizontal scrolling. */
21715VirtualRepeatContainerController.prototype.isHorizontal = function() {
21716  return this.horizontal;
21717};
21718
21719
21720/** @return {number} The size (width or height) of the container. */
21721VirtualRepeatContainerController.prototype.getSize = function() {
21722  return this.size;
21723};
21724
21725
21726/**
21727 * Resizes the container.
21728 * @private
21729 * @param {number} size The new size to set.
21730 */
21731VirtualRepeatContainerController.prototype.setSize_ = function(size) {
21732  var dimension = this.getDimensionName_();
21733
21734  this.size = size;
21735  this.$element[0].style[dimension] = size + 'px';
21736};
21737
21738
21739VirtualRepeatContainerController.prototype.unsetSize_ = function() {
21740  this.$element[0].style[this.getDimensionName_()] = this.oldElementSize;
21741  this.oldElementSize = null;
21742};
21743
21744
21745/** Instructs the container to re-measure its size. */
21746VirtualRepeatContainerController.prototype.updateSize = function() {
21747  // If the original size is already determined, we can skip the update.
21748  if (this.originalSize) return;
21749
21750  this.size = this.isHorizontal()
21751      ? this.$element[0].clientWidth
21752      : this.$element[0].clientHeight;
21753
21754  // Recheck the scroll position after updating the size. This resolves
21755  // problems that can result if the scroll position was measured while the
21756  // element was display: none or detached from the document.
21757  this.handleScroll_();
21758
21759  this.repeater && this.repeater.containerUpdated();
21760};
21761
21762
21763/** @return {number} The container's scrollHeight or scrollWidth. */
21764VirtualRepeatContainerController.prototype.getScrollSize = function() {
21765  return this.scrollSize;
21766};
21767
21768
21769VirtualRepeatContainerController.prototype.getDimensionName_ = function() {
21770  return this.isHorizontal() ? 'width' : 'height';
21771};
21772
21773
21774/**
21775 * Sets the scroller element to the specified size.
21776 * @private
21777 * @param {number} size The new size.
21778 */
21779VirtualRepeatContainerController.prototype.sizeScroller_ = function(size) {
21780  var dimension =  this.getDimensionName_();
21781  var crossDimension = this.isHorizontal() ? 'height' : 'width';
21782
21783  // Clear any existing dimensions.
21784  this.sizer.innerHTML = '';
21785
21786  // If the size falls within the browser's maximum explicit size for a single element, we can
21787  // set the size and be done. Otherwise, we have to create children that add up the the desired
21788  // size.
21789  if (size < MAX_ELEMENT_SIZE) {
21790    this.sizer.style[dimension] = size + 'px';
21791  } else {
21792    this.sizer.style[dimension] = 'auto';
21793    this.sizer.style[crossDimension] = 'auto';
21794
21795    // Divide the total size we have to render into N max-size pieces.
21796    var numChildren = Math.floor(size / MAX_ELEMENT_SIZE);
21797
21798    // Element template to clone for each max-size piece.
21799    var sizerChild = document.createElement('div');
21800    sizerChild.style[dimension] = MAX_ELEMENT_SIZE + 'px';
21801    sizerChild.style[crossDimension] = '1px';
21802
21803    for (var i = 0; i < numChildren; i++) {
21804      this.sizer.appendChild(sizerChild.cloneNode(false));
21805    }
21806
21807    // Re-use the element template for the remainder.
21808    sizerChild.style[dimension] = (size - (numChildren * MAX_ELEMENT_SIZE)) + 'px';
21809    this.sizer.appendChild(sizerChild);
21810  }
21811};
21812
21813
21814/**
21815 * If auto-shrinking is enabled, shrinks or unshrinks as appropriate.
21816 * @private
21817 * @param {number} size The new size.
21818 */
21819VirtualRepeatContainerController.prototype.autoShrink_ = function(size) {
21820  var shrinkSize = Math.max(size, this.autoShrinkMin * this.repeater.getItemSize());
21821
21822  if (this.autoShrink && shrinkSize !== this.size) {
21823    if (this.oldElementSize === null) {
21824      this.oldElementSize = this.$element[0].style[this.getDimensionName_()];
21825    }
21826
21827    var currentSize = this.originalSize || this.size;
21828
21829    if (!currentSize || shrinkSize < currentSize) {
21830      if (!this.originalSize) {
21831        this.originalSize = this.size;
21832      }
21833
21834      // Now we update the containers size, because shrinking is enabled.
21835      this.setSize_(shrinkSize);
21836    } else if (this.originalSize !== null) {
21837      // Set the size back to our initial size.
21838      this.unsetSize_();
21839
21840      var _originalSize = this.originalSize;
21841      this.originalSize = null;
21842
21843      // We determine the repeaters size again, if the original size was zero.
21844      // The originalSize needs to be null, to be able to determine the size.
21845      if (!_originalSize) this.updateSize();
21846
21847      // Apply the original size or the determined size back to the container, because
21848      // it has been overwritten before, in the shrink block.
21849      this.setSize_(_originalSize || this.size);
21850    }
21851
21852    this.repeater.containerUpdated();
21853  }
21854};
21855
21856
21857/**
21858 * Sets the scrollHeight or scrollWidth. Called by the repeater based on
21859 * its item count and item size.
21860 * @param {number} itemsSize The total size of the items.
21861 */
21862VirtualRepeatContainerController.prototype.setScrollSize = function(itemsSize) {
21863  var size = itemsSize + this.offsetSize;
21864  if (this.scrollSize === size) return;
21865
21866  this.sizeScroller_(size);
21867  this.autoShrink_(size);
21868  this.scrollSize = size;
21869};
21870
21871
21872/** @return {number} The container's current scroll offset. */
21873VirtualRepeatContainerController.prototype.getScrollOffset = function() {
21874  return this.scrollOffset;
21875};
21876
21877/**
21878 * Scrolls to a given scrollTop position.
21879 * @param {number} position
21880 */
21881VirtualRepeatContainerController.prototype.scrollTo = function(position) {
21882  this.scroller[this.isHorizontal() ? 'scrollLeft' : 'scrollTop'] = position;
21883  this.handleScroll_();
21884};
21885
21886/**
21887 * Scrolls the item with the given index to the top of the scroll container.
21888 * @param {number} index
21889 */
21890VirtualRepeatContainerController.prototype.scrollToIndex = function(index) {
21891  var itemSize = this.repeater.getItemSize();
21892  var itemsLength = this.repeater.itemsLength;
21893  if(index > itemsLength) {
21894    index = itemsLength - 1;
21895  }
21896  this.scrollTo(itemSize * index);
21897};
21898
21899VirtualRepeatContainerController.prototype.resetScroll = function() {
21900  this.scrollTo(0);
21901};
21902
21903
21904VirtualRepeatContainerController.prototype.handleScroll_ = function() {
21905  var doc = angular.element(document)[0];
21906  var ltr = doc.dir != 'rtl' && doc.body.dir != 'rtl';
21907  if(!ltr && !this.maxSize) {
21908    this.scroller.scrollLeft = this.scrollSize;
21909    this.maxSize = this.scroller.scrollLeft;
21910  }
21911  var offset = this.isHorizontal() ?
21912      (ltr?this.scroller.scrollLeft : this.maxSize - this.scroller.scrollLeft)
21913      : this.scroller.scrollTop;
21914  if (offset === this.scrollOffset || offset > this.scrollSize - this.size) return;
21915
21916  var itemSize = this.repeater.getItemSize();
21917  if (!itemSize) return;
21918
21919  var numItems = Math.max(0, Math.floor(offset / itemSize) - NUM_EXTRA);
21920
21921  var transform = (this.isHorizontal() ? 'translateX(' : 'translateY(') +
21922      (!this.isHorizontal() || ltr ? (numItems * itemSize) : - (numItems * itemSize))  + 'px)';
21923
21924  this.scrollOffset = offset;
21925  this.offsetter.style.webkitTransform = transform;
21926  this.offsetter.style.transform = transform;
21927
21928  if (this.bindTopIndex) {
21929    var topIndex = Math.floor(offset / itemSize);
21930    if (topIndex !== this.topIndex && topIndex < this.repeater.getItemCount()) {
21931      this.topIndex = topIndex;
21932      this.bindTopIndex.assign(this.$scope, topIndex);
21933      if (!this.$rootScope.$$phase) this.$scope.$digest();
21934    }
21935  }
21936
21937  this.repeater.containerUpdated();
21938};
21939
21940
21941/**
21942 * @ngdoc directive
21943 * @name mdVirtualRepeat
21944 * @module material.components.virtualRepeat
21945 * @restrict A
21946 * @priority 1000
21947 * @description
21948 * `md-virtual-repeat` specifies an element to repeat using virtual scrolling.
21949 *
21950 * Virtual repeat is a limited substitute for ng-repeat that renders only
21951 * enough dom nodes to fill the container and recycling them as the user scrolls.
21952 * Arrays, but not objects are supported for iteration.
21953 * Track by, as alias, and (key, value) syntax are not supported.
21954 *
21955 * > <b>Note:</b> Please also review the
21956 *   <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeatContainer</a> documentation
21957 *   for more information.
21958 *
21959 * @usage
21960 * <hljs lang="html">
21961 * <md-virtual-repeat-container>
21962 *   <div md-virtual-repeat="i in items">Hello {{i}}!</div>
21963 * </md-virtual-repeat-container>
21964 *
21965 * <md-virtual-repeat-container md-orient-horizontal>
21966 *   <div md-virtual-repeat="i in items" md-item-size="20">Hello {{i}}!</div>
21967 * </md-virtual-repeat-container>
21968 * </hljs>
21969 *
21970 * @param {number=} md-item-size The height or width of the repeated elements (which must be
21971 *   identical for each element). Optional. Will attempt to read the size from the dom if missing,
21972 *   but still assumes that all repeated nodes have same height or width.
21973 * @param {string=} md-extra-name Evaluates to an additional name to which the current iterated item
21974 *   can be assigned on the repeated scope (needed for use in `md-autocomplete`).
21975 * @param {boolean=} md-on-demand When present, treats the md-virtual-repeat argument as an object
21976 *   that can fetch rows rather than an array.
21977 *
21978 *   **NOTE:** This object must implement the following interface with two (2) methods:
21979 *
21980 *   - `getItemAtIndex: function(index) [object]` The item at that index or null if it is not yet
21981 *     loaded (it should start downloading the item in that case).
21982 *   - `getLength: function() [number]` The data length to which the repeater container
21983 *     should be sized. Ideally, when the count is known, this method should return it.
21984 *     Otherwise, return a higher number than the currently loaded items to produce an
21985 *     infinite-scroll behavior.
21986 */
21987function VirtualRepeatDirective($parse) {
21988  return {
21989    controller: VirtualRepeatController,
21990    priority: 1000,
21991    require: ['mdVirtualRepeat', '^^mdVirtualRepeatContainer'],
21992    restrict: 'A',
21993    terminal: true,
21994    transclude: 'element',
21995    compile: function VirtualRepeatCompile($element, $attrs) {
21996      var expression = $attrs.mdVirtualRepeat;
21997      var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/);
21998      var repeatName = match[1];
21999      var repeatListExpression = $parse(match[2]);
22000      var extraName = $attrs.mdExtraName && $parse($attrs.mdExtraName);
22001
22002      return function VirtualRepeatLink($scope, $element, $attrs, ctrl, $transclude) {
22003        ctrl[0].link_(ctrl[1], $transclude, repeatName, repeatListExpression, extraName);
22004      };
22005    }
22006  };
22007}
22008
22009
22010/** @ngInject */
22011function VirtualRepeatController($scope, $element, $attrs, $browser, $document, $rootScope,
22012    $$rAF, $mdUtil) {
22013  this.$scope = $scope;
22014  this.$element = $element;
22015  this.$attrs = $attrs;
22016  this.$browser = $browser;
22017  this.$document = $document;
22018  this.$rootScope = $rootScope;
22019  this.$$rAF = $$rAF;
22020
22021  /** @type {boolean} Whether we are in on-demand mode. */
22022  this.onDemand = $mdUtil.parseAttributeBoolean($attrs.mdOnDemand);
22023  /** @type {!Function} Backup reference to $browser.$$checkUrlChange */
22024  this.browserCheckUrlChange = $browser.$$checkUrlChange;
22025  /** @type {number} Most recent starting repeat index (based on scroll offset) */
22026  this.newStartIndex = 0;
22027  /** @type {number} Most recent ending repeat index (based on scroll offset) */
22028  this.newEndIndex = 0;
22029  /** @type {number} Most recent end visible index (based on scroll offset) */
22030  this.newVisibleEnd = 0;
22031  /** @type {number} Previous starting repeat index (based on scroll offset) */
22032  this.startIndex = 0;
22033  /** @type {number} Previous ending repeat index (based on scroll offset) */
22034  this.endIndex = 0;
22035  // TODO: measure width/height of first element from dom if not provided.
22036  // getComputedStyle?
22037  /** @type {?number} Height/width of repeated elements. */
22038  this.itemSize = $scope.$eval($attrs.mdItemSize) || null;
22039
22040  /** @type {boolean} Whether this is the first time that items are rendered. */
22041  this.isFirstRender = true;
22042
22043  /**
22044   * @private {boolean} Whether the items in the list are already being updated. Used to prevent
22045   *     nested calls to virtualRepeatUpdate_.
22046   */
22047  this.isVirtualRepeatUpdating_ = false;
22048
22049  /** @type {number} Most recently seen length of items. */
22050  this.itemsLength = 0;
22051
22052  /**
22053   * @type {!Function} Unwatch callback for item size (when md-items-size is
22054   *     not specified), or angular.noop otherwise.
22055   */
22056  this.unwatchItemSize_ = angular.noop;
22057
22058  /**
22059   * Presently rendered blocks by repeat index.
22060   * @type {Object<number, !VirtualRepeatController.Block}
22061   */
22062  this.blocks = {};
22063  /** @type {Array<!VirtualRepeatController.Block>} A pool of presently unused blocks. */
22064  this.pooledBlocks = [];
22065
22066  $scope.$on('$destroy', angular.bind(this, this.cleanupBlocks_));
22067}
22068
22069
22070/**
22071 * An object representing a repeated item.
22072 * @typedef {{element: !jqLite, new: boolean, scope: !angular.Scope}}
22073 */
22074VirtualRepeatController.Block;
22075
22076
22077/**
22078 * Called at startup by the md-virtual-repeat postLink function.
22079 * @param {!VirtualRepeatContainerController} container The container's controller.
22080 * @param {!Function} transclude The repeated element's bound transclude function.
22081 * @param {string} repeatName The left hand side of the repeat expression, indicating
22082 *     the name for each item in the array.
22083 * @param {!Function} repeatListExpression A compiled expression based on the right hand side
22084 *     of the repeat expression. Points to the array to repeat over.
22085 * @param {string|undefined} extraName The optional extra repeatName.
22086 */
22087VirtualRepeatController.prototype.link_ =
22088    function(container, transclude, repeatName, repeatListExpression, extraName) {
22089  this.container = container;
22090  this.transclude = transclude;
22091  this.repeatName = repeatName;
22092  this.rawRepeatListExpression = repeatListExpression;
22093  this.extraName = extraName;
22094  this.sized = false;
22095
22096  this.repeatListExpression = angular.bind(this, this.repeatListExpression_);
22097
22098  this.container.register(this);
22099};
22100
22101
22102/** @private Cleans up unused blocks. */
22103VirtualRepeatController.prototype.cleanupBlocks_ = function() {
22104  angular.forEach(this.pooledBlocks, function cleanupBlock(block) {
22105    block.element.remove();
22106  });
22107};
22108
22109
22110/** @private Attempts to set itemSize by measuring a repeated element in the dom */
22111VirtualRepeatController.prototype.readItemSize_ = function() {
22112  if (this.itemSize) {
22113    // itemSize was successfully read in a different asynchronous call.
22114    return;
22115  }
22116
22117  this.items = this.repeatListExpression(this.$scope);
22118  this.parentNode = this.$element[0].parentNode;
22119  var block = this.getBlock_(0);
22120  if (!block.element[0].parentNode) {
22121    this.parentNode.appendChild(block.element[0]);
22122  }
22123
22124  this.itemSize = block.element[0][
22125      this.container.isHorizontal() ? 'offsetWidth' : 'offsetHeight'] || null;
22126
22127  this.blocks[0] = block;
22128  this.poolBlock_(0);
22129
22130  if (this.itemSize) {
22131    this.containerUpdated();
22132  }
22133};
22134
22135
22136/**
22137 * Returns the user-specified repeat list, transforming it into an array-like
22138 * object in the case of infinite scroll/dynamic load mode.
22139 * @param {!angular.Scope} The scope.
22140 * @return {!Array|!Object} An array or array-like object for iteration.
22141 */
22142VirtualRepeatController.prototype.repeatListExpression_ = function(scope) {
22143  var repeatList = this.rawRepeatListExpression(scope);
22144
22145  if (this.onDemand && repeatList) {
22146    var virtualList = new VirtualRepeatModelArrayLike(repeatList);
22147    virtualList.$$includeIndexes(this.newStartIndex, this.newVisibleEnd);
22148    return virtualList;
22149  } else {
22150    return repeatList;
22151  }
22152};
22153
22154
22155/**
22156 * Called by the container. Informs us that the containers scroll or size has
22157 * changed.
22158 */
22159VirtualRepeatController.prototype.containerUpdated = function() {
22160  // If itemSize is unknown, attempt to measure it.
22161  if (!this.itemSize) {
22162    // Make sure to clean up watchers if we can (see #8178)
22163    if(this.unwatchItemSize_ && this.unwatchItemSize_ !== angular.noop){
22164      this.unwatchItemSize_();
22165    }
22166    this.unwatchItemSize_ = this.$scope.$watchCollection(
22167        this.repeatListExpression,
22168        angular.bind(this, function(items) {
22169          if (items && items.length) {
22170            this.readItemSize_();
22171          }
22172        }));
22173    if (!this.$rootScope.$$phase) this.$scope.$digest();
22174
22175    return;
22176  } else if (!this.sized) {
22177    this.items = this.repeatListExpression(this.$scope);
22178  }
22179
22180  if (!this.sized) {
22181    this.unwatchItemSize_();
22182    this.sized = true;
22183    this.$scope.$watchCollection(this.repeatListExpression,
22184        angular.bind(this, function(items, oldItems) {
22185          if (!this.isVirtualRepeatUpdating_) {
22186            this.virtualRepeatUpdate_(items, oldItems);
22187          }
22188        }));
22189  }
22190
22191  this.updateIndexes_();
22192
22193  if (this.newStartIndex !== this.startIndex ||
22194      this.newEndIndex !== this.endIndex ||
22195      this.container.getScrollOffset() > this.container.getScrollSize()) {
22196    if (this.items instanceof VirtualRepeatModelArrayLike) {
22197      this.items.$$includeIndexes(this.newStartIndex, this.newEndIndex);
22198    }
22199    this.virtualRepeatUpdate_(this.items, this.items);
22200  }
22201};
22202
22203
22204/**
22205 * Called by the container. Returns the size of a single repeated item.
22206 * @return {?number} Size of a repeated item.
22207 */
22208VirtualRepeatController.prototype.getItemSize = function() {
22209  return this.itemSize;
22210};
22211
22212
22213/**
22214 * Called by the container. Returns the size of a single repeated item.
22215 * @return {?number} Size of a repeated item.
22216 */
22217VirtualRepeatController.prototype.getItemCount = function() {
22218  return this.itemsLength;
22219};
22220
22221
22222/**
22223 * Updates the order and visible offset of repeated blocks in response to scrolling
22224 * or items updates.
22225 * @private
22226 */
22227VirtualRepeatController.prototype.virtualRepeatUpdate_ = function(items, oldItems) {
22228  this.isVirtualRepeatUpdating_ = true;
22229
22230  var itemsLength = items && items.length || 0;
22231  var lengthChanged = false;
22232
22233  // If the number of items shrank, keep the scroll position.
22234  if (this.items && itemsLength < this.items.length && this.container.getScrollOffset() !== 0) {
22235    this.items = items;
22236    var previousScrollOffset = this.container.getScrollOffset();
22237    this.container.resetScroll();
22238    this.container.scrollTo(previousScrollOffset);
22239  }
22240
22241  if (itemsLength !== this.itemsLength) {
22242    lengthChanged = true;
22243    this.itemsLength = itemsLength;
22244  }
22245
22246  this.items = items;
22247  if (items !== oldItems || lengthChanged) {
22248    this.updateIndexes_();
22249  }
22250
22251  this.parentNode = this.$element[0].parentNode;
22252
22253  if (lengthChanged) {
22254    this.container.setScrollSize(itemsLength * this.itemSize);
22255  }
22256
22257  if (this.isFirstRender) {
22258    this.isFirstRender = false;
22259    var startIndex = this.$attrs.mdStartIndex ?
22260      this.$scope.$eval(this.$attrs.mdStartIndex) :
22261      this.container.topIndex;
22262    this.container.scrollToIndex(startIndex);
22263  }
22264
22265  // Detach and pool any blocks that are no longer in the viewport.
22266  Object.keys(this.blocks).forEach(function(blockIndex) {
22267    var index = parseInt(blockIndex, 10);
22268    if (index < this.newStartIndex || index >= this.newEndIndex) {
22269      this.poolBlock_(index);
22270    }
22271  }, this);
22272
22273  // Add needed blocks.
22274  // For performance reasons, temporarily block browser url checks as we digest
22275  // the restored block scopes ($$checkUrlChange reads window.location to
22276  // check for changes and trigger route change, etc, which we don't need when
22277  // trying to scroll at 60fps).
22278  this.$browser.$$checkUrlChange = angular.noop;
22279
22280  var i, block,
22281      newStartBlocks = [],
22282      newEndBlocks = [];
22283
22284  // Collect blocks at the top.
22285  for (i = this.newStartIndex; i < this.newEndIndex && this.blocks[i] == null; i++) {
22286    block = this.getBlock_(i);
22287    this.updateBlock_(block, i);
22288    newStartBlocks.push(block);
22289  }
22290
22291  // Update blocks that are already rendered.
22292  for (; this.blocks[i] != null; i++) {
22293    this.updateBlock_(this.blocks[i], i);
22294  }
22295  var maxIndex = i - 1;
22296
22297  // Collect blocks at the end.
22298  for (; i < this.newEndIndex; i++) {
22299    block = this.getBlock_(i);
22300    this.updateBlock_(block, i);
22301    newEndBlocks.push(block);
22302  }
22303
22304  // Attach collected blocks to the document.
22305  if (newStartBlocks.length) {
22306    this.parentNode.insertBefore(
22307        this.domFragmentFromBlocks_(newStartBlocks),
22308        this.$element[0].nextSibling);
22309  }
22310  if (newEndBlocks.length) {
22311    this.parentNode.insertBefore(
22312        this.domFragmentFromBlocks_(newEndBlocks),
22313        this.blocks[maxIndex] && this.blocks[maxIndex].element[0].nextSibling);
22314  }
22315
22316  // Restore $$checkUrlChange.
22317  this.$browser.$$checkUrlChange = this.browserCheckUrlChange;
22318
22319  this.startIndex = this.newStartIndex;
22320  this.endIndex = this.newEndIndex;
22321
22322  this.isVirtualRepeatUpdating_ = false;
22323};
22324
22325
22326/**
22327 * @param {number} index Where the block is to be in the repeated list.
22328 * @return {!VirtualRepeatController.Block} A new or pooled block to place at the specified index.
22329 * @private
22330 */
22331VirtualRepeatController.prototype.getBlock_ = function(index) {
22332  if (this.pooledBlocks.length) {
22333    return this.pooledBlocks.pop();
22334  }
22335
22336  var block;
22337  this.transclude(angular.bind(this, function(clone, scope) {
22338    block = {
22339      element: clone,
22340      new: true,
22341      scope: scope
22342    };
22343
22344    this.updateScope_(scope, index);
22345    this.parentNode.appendChild(clone[0]);
22346  }));
22347
22348  return block;
22349};
22350
22351
22352/**
22353 * Updates and if not in a digest cycle, digests the specified block's scope to the data
22354 * at the specified index.
22355 * @param {!VirtualRepeatController.Block} block The block whose scope should be updated.
22356 * @param {number} index The index to set.
22357 * @private
22358 */
22359VirtualRepeatController.prototype.updateBlock_ = function(block, index) {
22360  this.blocks[index] = block;
22361
22362  if (!block.new &&
22363      (block.scope.$index === index && block.scope[this.repeatName] === this.items[index])) {
22364    return;
22365  }
22366  block.new = false;
22367
22368  // Update and digest the block's scope.
22369  this.updateScope_(block.scope, index);
22370
22371  // Perform digest before reattaching the block.
22372  // Any resulting synchronous dom mutations should be much faster as a result.
22373  // This might break some directives, but I'm going to try it for now.
22374  if (!this.$rootScope.$$phase) {
22375    block.scope.$digest();
22376  }
22377};
22378
22379
22380/**
22381 * Updates scope to the data at the specified index.
22382 * @param {!angular.Scope} scope The scope which should be updated.
22383 * @param {number} index The index to set.
22384 * @private
22385 */
22386VirtualRepeatController.prototype.updateScope_ = function(scope, index) {
22387  scope.$index = index;
22388  scope[this.repeatName] = this.items && this.items[index];
22389  if (this.extraName) scope[this.extraName(this.$scope)] = this.items[index];
22390};
22391
22392
22393/**
22394 * Pools the block at the specified index (Pulls its element out of the dom and stores it).
22395 * @param {number} index The index at which the block to pool is stored.
22396 * @private
22397 */
22398VirtualRepeatController.prototype.poolBlock_ = function(index) {
22399  this.pooledBlocks.push(this.blocks[index]);
22400  this.parentNode.removeChild(this.blocks[index].element[0]);
22401  delete this.blocks[index];
22402};
22403
22404
22405/**
22406 * Produces a dom fragment containing the elements from the list of blocks.
22407 * @param {!Array<!VirtualRepeatController.Block>} blocks The blocks whose elements
22408 *     should be added to the document fragment.
22409 * @return {DocumentFragment}
22410 * @private
22411 */
22412VirtualRepeatController.prototype.domFragmentFromBlocks_ = function(blocks) {
22413  var fragment = this.$document[0].createDocumentFragment();
22414  blocks.forEach(function(block) {
22415    fragment.appendChild(block.element[0]);
22416  });
22417  return fragment;
22418};
22419
22420
22421/**
22422 * Updates start and end indexes based on length of repeated items and container size.
22423 * @private
22424 */
22425VirtualRepeatController.prototype.updateIndexes_ = function() {
22426  var itemsLength = this.items ? this.items.length : 0;
22427  var containerLength = Math.ceil(this.container.getSize() / this.itemSize);
22428
22429  this.newStartIndex = Math.max(0, Math.min(
22430      itemsLength - containerLength,
22431      Math.floor(this.container.getScrollOffset() / this.itemSize)));
22432  this.newVisibleEnd = this.newStartIndex + containerLength + NUM_EXTRA;
22433  this.newEndIndex = Math.min(itemsLength, this.newVisibleEnd);
22434  this.newStartIndex = Math.max(0, this.newStartIndex - NUM_EXTRA);
22435};
22436
22437/**
22438 * This VirtualRepeatModelArrayLike class enforces the interface requirements
22439 * for infinite scrolling within a mdVirtualRepeatContainer. An object with this
22440 * interface must implement the following interface with two (2) methods:
22441 *
22442 * getItemAtIndex: function(index) -> item at that index or null if it is not yet
22443 *     loaded (It should start downloading the item in that case).
22444 *
22445 * getLength: function() -> number The data legnth to which the repeater container
22446 *     should be sized. Ideally, when the count is known, this method should return it.
22447 *     Otherwise, return a higher number than the currently loaded items to produce an
22448 *     infinite-scroll behavior.
22449 *
22450 * @usage
22451 * <hljs lang="html">
22452 *  <md-virtual-repeat-container md-orient-horizontal>
22453 *    <div md-virtual-repeat="i in items" md-on-demand>
22454 *      Hello {{i}}!
22455 *    </div>
22456 *  </md-virtual-repeat-container>
22457 * </hljs>
22458 *
22459 */
22460function VirtualRepeatModelArrayLike(model) {
22461  if (!angular.isFunction(model.getItemAtIndex) ||
22462      !angular.isFunction(model.getLength)) {
22463    throw Error('When md-on-demand is enabled, the Object passed to md-virtual-repeat must implement ' +
22464        'functions getItemAtIndex() and getLength() ');
22465  }
22466
22467  this.model = model;
22468}
22469
22470
22471VirtualRepeatModelArrayLike.prototype.$$includeIndexes = function(start, end) {
22472  for (var i = start; i < end; i++) {
22473    if (!this.hasOwnProperty(i)) {
22474      this[i] = this.model.getItemAtIndex(i);
22475    }
22476  }
22477  this.length = this.model.getLength();
22478};
22479
22480
22481function abstractMethod() {
22482  throw Error('Non-overridden abstract method called.');
22483}
22484
22485})();
22486(function(){
22487"use strict";
22488
22489/**
22490 * @ngdoc module
22491 * @name material.components.whiteframe
22492 */
22493MdWhiteframeDirective.$inject = ["$log"];
22494angular
22495  .module('material.components.whiteframe', ['material.core'])
22496  .directive('mdWhiteframe', MdWhiteframeDirective);
22497
22498/**
22499 * @ngdoc directive
22500 * @module material.components.whiteframe
22501 * @name mdWhiteframe
22502 *
22503 * @description
22504 * The md-whiteframe directive allows you to apply an elevation shadow to an element.
22505 *
22506 * The attribute values needs to be a number between 1 and 24 or -1.
22507 * When set to -1 no style is applied.
22508 *
22509 * ### Notes
22510 * - If there is no value specified it defaults to 4dp.
22511 * - If the value is not valid it defaults to 4dp.
22512
22513 * @usage
22514 * <hljs lang="html">
22515 * <div md-whiteframe="3">
22516 *   <span>Elevation of 3dp</span>
22517 * </div>
22518 * </hljs>
22519 *
22520 * <hljs lang="html">
22521 * <div md-whiteframe="-1">
22522 *   <span>No elevation shadow applied</span>
22523 * </div>
22524 * </hljs>
22525 *
22526 * <hljs lang="html">
22527 * <div ng-init="elevation = 5" md-whiteframe="{{elevation}}">
22528 *   <span>Elevation of 5dp with an interpolated value</span>
22529 * </div>
22530 * </hljs>
22531 */
22532function MdWhiteframeDirective($log) {
22533  var DISABLE_DP = -1;
22534  var MIN_DP = 1;
22535  var MAX_DP = 24;
22536  var DEFAULT_DP = 4;
22537
22538  return {
22539    link: postLink
22540  };
22541
22542  function postLink(scope, element, attr) {
22543    var oldClass = '';
22544
22545    attr.$observe('mdWhiteframe', function(elevation) {
22546      elevation = parseInt(elevation, 10) || DEFAULT_DP;
22547
22548      if (elevation != DISABLE_DP && (elevation > MAX_DP || elevation < MIN_DP)) {
22549        $log.warn('md-whiteframe attribute value is invalid. It should be a number between ' + MIN_DP + ' and ' + MAX_DP, element[0]);
22550        elevation = DEFAULT_DP;
22551      }
22552
22553      var newClass = elevation == DISABLE_DP ? '' : 'md-whiteframe-' + elevation + 'dp';
22554      attr.$updateClass(newClass, oldClass);
22555      oldClass = newClass;
22556    });
22557  }
22558}
22559
22560
22561})();
22562(function(){
22563"use strict";
22564
22565
22566MdAutocompleteCtrl.$inject = ["$scope", "$element", "$mdUtil", "$mdConstant", "$mdTheming", "$window", "$animate", "$rootElement", "$attrs", "$q", "$log"];angular
22567    .module('material.components.autocomplete')
22568    .controller('MdAutocompleteCtrl', MdAutocompleteCtrl);
22569
22570var ITEM_HEIGHT   = 41,
22571    MAX_HEIGHT    = 5.5 * ITEM_HEIGHT,
22572    MENU_PADDING  = 8,
22573    INPUT_PADDING = 2; // Padding provided by `md-input-container`
22574
22575function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, $window,
22576                             $animate, $rootElement, $attrs, $q, $log) {
22577
22578  // Internal Variables.
22579  var ctrl                 = this,
22580      itemParts            = $scope.itemsExpr.split(/ in /i),
22581      itemExpr             = itemParts[ 1 ],
22582      elements             = null,
22583      cache                = {},
22584      noBlur               = false,
22585      selectedItemWatchers = [],
22586      hasFocus             = false,
22587      lastCount            = 0,
22588      fetchesInProgress    = 0,
22589      enableWrapScroll     = null,
22590      inputModelCtrl       = null;
22591
22592  // Public Exported Variables with handlers
22593  defineProperty('hidden', handleHiddenChange, true);
22594
22595  // Public Exported Variables
22596  ctrl.scope      = $scope;
22597  ctrl.parent     = $scope.$parent;
22598  ctrl.itemName   = itemParts[ 0 ];
22599  ctrl.matches    = [];
22600  ctrl.loading    = false;
22601  ctrl.hidden     = true;
22602  ctrl.index      = null;
22603  ctrl.messages   = [];
22604  ctrl.id         = $mdUtil.nextUid();
22605  ctrl.isDisabled = null;
22606  ctrl.isRequired = null;
22607  ctrl.isReadonly = null;
22608  ctrl.hasNotFound = false;
22609
22610  // Public Exported Methods
22611  ctrl.keydown                       = keydown;
22612  ctrl.blur                          = blur;
22613  ctrl.focus                         = focus;
22614  ctrl.clear                         = clearValue;
22615  ctrl.select                        = select;
22616  ctrl.listEnter                     = onListEnter;
22617  ctrl.listLeave                     = onListLeave;
22618  ctrl.mouseUp                       = onMouseup;
22619  ctrl.getCurrentDisplayValue        = getCurrentDisplayValue;
22620  ctrl.registerSelectedItemWatcher   = registerSelectedItemWatcher;
22621  ctrl.unregisterSelectedItemWatcher = unregisterSelectedItemWatcher;
22622  ctrl.notFoundVisible               = notFoundVisible;
22623  ctrl.loadingIsVisible              = loadingIsVisible;
22624  ctrl.positionDropdown              = positionDropdown;
22625
22626  return init();
22627
22628  //-- initialization methods
22629
22630  /**
22631   * Initialize the controller, setup watchers, gather elements
22632   */
22633  function init () {
22634    $mdUtil.initOptionalProperties($scope, $attrs, { searchText: '', selectedItem: null });
22635    $mdTheming($element);
22636    configureWatchers();
22637    $mdUtil.nextTick(function () {
22638
22639      gatherElements();
22640      moveDropdown();
22641
22642      // Forward all focus events to the input element when autofocus is enabled
22643      if ($scope.autofocus) {
22644        $element.on('focus', focusInputElement);
22645      }
22646    });
22647  }
22648
22649  function updateModelValidators() {
22650    if (!$scope.requireMatch || !inputModelCtrl) return;
22651
22652    inputModelCtrl.$setValidity('md-require-match', !!$scope.selectedItem);
22653  }
22654
22655  /**
22656   * Calculates the dropdown's position and applies the new styles to the menu element
22657   * @returns {*}
22658   */
22659  function positionDropdown () {
22660    if (!elements) return $mdUtil.nextTick(positionDropdown, false, $scope);
22661    var hrect  = elements.wrap.getBoundingClientRect(),
22662        vrect  = elements.snap.getBoundingClientRect(),
22663        root   = elements.root.getBoundingClientRect(),
22664        top    = vrect.bottom - root.top,
22665        bot    = root.bottom - vrect.top,
22666        left   = hrect.left - root.left,
22667        width  = hrect.width,
22668        offset = getVerticalOffset(),
22669        styles;
22670    // Adjust the width to account for the padding provided by `md-input-container`
22671    if ($attrs.mdFloatingLabel) {
22672      left += INPUT_PADDING;
22673      width -= INPUT_PADDING * 2;
22674    }
22675    styles = {
22676      left:     left + 'px',
22677      minWidth: width + 'px',
22678      maxWidth: Math.max(hrect.right - root.left, root.right - hrect.left) - MENU_PADDING + 'px'
22679    };
22680    if (top > bot && root.height - hrect.bottom - MENU_PADDING < MAX_HEIGHT) {
22681      styles.top       = 'auto';
22682      styles.bottom    = bot + 'px';
22683      styles.maxHeight = Math.min(MAX_HEIGHT, hrect.top - root.top - MENU_PADDING) + 'px';
22684    } else {
22685      styles.top       = (top - offset) + 'px';
22686      styles.bottom    = 'auto';
22687      styles.maxHeight = Math.min(MAX_HEIGHT, root.bottom + $mdUtil.scrollTop() - hrect.bottom - MENU_PADDING) + 'px';
22688    }
22689
22690    elements.$.scrollContainer.css(styles);
22691    $mdUtil.nextTick(correctHorizontalAlignment, false);
22692
22693    /**
22694     * Calculates the vertical offset for floating label examples to account for ngMessages
22695     * @returns {number}
22696     */
22697    function getVerticalOffset () {
22698      var offset = 0;
22699      var inputContainer = $element.find('md-input-container');
22700      if (inputContainer.length) {
22701        var input = inputContainer.find('input');
22702        offset = inputContainer.prop('offsetHeight');
22703        offset -= input.prop('offsetTop');
22704        offset -= input.prop('offsetHeight');
22705        // add in the height left up top for the floating label text
22706        offset += inputContainer.prop('offsetTop');
22707      }
22708      return offset;
22709    }
22710
22711    /**
22712     * Makes sure that the menu doesn't go off of the screen on either side.
22713     */
22714    function correctHorizontalAlignment () {
22715      var dropdown = elements.scrollContainer.getBoundingClientRect(),
22716          styles   = {};
22717      if (dropdown.right > root.right - MENU_PADDING) {
22718        styles.left = (hrect.right - dropdown.width) + 'px';
22719      }
22720      elements.$.scrollContainer.css(styles);
22721    }
22722  }
22723
22724  /**
22725   * Moves the dropdown menu to the body tag in order to avoid z-index and overflow issues.
22726   */
22727  function moveDropdown () {
22728    if (!elements.$.root.length) return;
22729    $mdTheming(elements.$.scrollContainer);
22730    elements.$.scrollContainer.detach();
22731    elements.$.root.append(elements.$.scrollContainer);
22732    if ($animate.pin) $animate.pin(elements.$.scrollContainer, $rootElement);
22733  }
22734
22735  /**
22736   * Sends focus to the input element.
22737   */
22738  function focusInputElement () {
22739    elements.input.focus();
22740  }
22741
22742  /**
22743   * Sets up any watchers used by autocomplete
22744   */
22745  function configureWatchers () {
22746    var wait = parseInt($scope.delay, 10) || 0;
22747    $attrs.$observe('disabled', function (value) { ctrl.isDisabled = $mdUtil.parseAttributeBoolean(value, false); });
22748    $attrs.$observe('required', function (value) { ctrl.isRequired = $mdUtil.parseAttributeBoolean(value, false); });
22749    $attrs.$observe('readonly', function (value) { ctrl.isReadonly = $mdUtil.parseAttributeBoolean(value, false); });
22750    $scope.$watch('searchText', wait ? $mdUtil.debounce(handleSearchText, wait) : handleSearchText);
22751    $scope.$watch('selectedItem', selectedItemChange);
22752    angular.element($window).on('resize', positionDropdown);
22753    $scope.$on('$destroy', cleanup);
22754  }
22755
22756  /**
22757   * Removes any events or leftover elements created by this controller
22758   */
22759  function cleanup () {
22760    if (!ctrl.hidden) {
22761      $mdUtil.enableScrolling();
22762    }
22763
22764    angular.element($window).off('resize', positionDropdown);
22765    if ( elements ){
22766      var items = ['ul', 'scroller', 'scrollContainer', 'input'];
22767      angular.forEach(items, function(key){
22768        elements.$[key].remove();
22769      });
22770    }
22771  }
22772
22773  /**
22774   * Gathers all of the elements needed for this controller
22775   */
22776  function gatherElements () {
22777    elements = {
22778      main:  $element[0],
22779      scrollContainer: $element[0].querySelector('.md-virtual-repeat-container'),
22780      scroller: $element[0].querySelector('.md-virtual-repeat-scroller'),
22781      ul:    $element.find('ul')[0],
22782      input: $element.find('input')[0],
22783      wrap:  $element.find('md-autocomplete-wrap')[0],
22784      root:  document.body
22785    };
22786
22787    elements.li   = elements.ul.getElementsByTagName('li');
22788    elements.snap = getSnapTarget();
22789    elements.$    = getAngularElements(elements);
22790
22791    inputModelCtrl = elements.$.input.controller('ngModel');
22792  }
22793
22794  /**
22795   * Finds the element that the menu will base its position on
22796   * @returns {*}
22797   */
22798  function getSnapTarget () {
22799    for (var element = $element; element.length; element = element.parent()) {
22800      if (angular.isDefined(element.attr('md-autocomplete-snap'))) return element[ 0 ];
22801    }
22802    return elements.wrap;
22803  }
22804
22805  /**
22806   * Gathers angular-wrapped versions of each element
22807   * @param elements
22808   * @returns {{}}
22809   */
22810  function getAngularElements (elements) {
22811    var obj = {};
22812    for (var key in elements) {
22813      if (elements.hasOwnProperty(key)) obj[ key ] = angular.element(elements[ key ]);
22814    }
22815    return obj;
22816  }
22817
22818  //-- event/change handlers
22819
22820  /**
22821   * Handles changes to the `hidden` property.
22822   * @param hidden
22823   * @param oldHidden
22824   */
22825  function handleHiddenChange (hidden, oldHidden) {
22826    if (!hidden && oldHidden) {
22827      positionDropdown();
22828
22829      if (elements) {
22830        $mdUtil.disableScrollAround(elements.ul);
22831        enableWrapScroll = disableElementScrollEvents(angular.element(elements.wrap));
22832      }
22833    } else if (hidden && !oldHidden) {
22834      $mdUtil.enableScrolling();
22835
22836      if (enableWrapScroll) {
22837        enableWrapScroll();
22838        enableWrapScroll = null;
22839      }
22840    }
22841  }
22842
22843  /**
22844   * Disables scrolling for a specific element
22845   */
22846  function disableElementScrollEvents(element) {
22847
22848    function preventDefault(e) {
22849      e.preventDefault();
22850    }
22851
22852    element.on('wheel', preventDefault);
22853    element.on('touchmove', preventDefault);
22854
22855    return function() {
22856      element.off('wheel', preventDefault);
22857      element.off('touchmove', preventDefault);
22858    };
22859  }
22860
22861  /**
22862   * When the user mouses over the dropdown menu, ignore blur events.
22863   */
22864  function onListEnter () {
22865    noBlur = true;
22866  }
22867
22868  /**
22869   * When the user's mouse leaves the menu, blur events may hide the menu again.
22870   */
22871  function onListLeave () {
22872    if (!hasFocus && !ctrl.hidden) elements.input.focus();
22873    noBlur = false;
22874    ctrl.hidden = shouldHide();
22875  }
22876
22877  /**
22878   * When the mouse button is released, send focus back to the input field.
22879   */
22880  function onMouseup () {
22881    elements.input.focus();
22882  }
22883
22884  /**
22885   * Handles changes to the selected item.
22886   * @param selectedItem
22887   * @param previousSelectedItem
22888   */
22889  function selectedItemChange (selectedItem, previousSelectedItem) {
22890
22891    updateModelValidators();
22892
22893    if (selectedItem) {
22894      getDisplayValue(selectedItem).then(function (val) {
22895        $scope.searchText = val;
22896        handleSelectedItemChange(selectedItem, previousSelectedItem);
22897      });
22898    } else if (previousSelectedItem && $scope.searchText) {
22899      getDisplayValue(previousSelectedItem).then(function(displayValue) {
22900        // Clear the searchText, when the selectedItem is set to null.
22901        // Do not clear the searchText, when the searchText isn't matching with the previous
22902        // selected item.
22903        if (displayValue.toString().toLowerCase() === $scope.searchText.toLowerCase()) {
22904          $scope.searchText = '';
22905        }
22906      });
22907    }
22908
22909    if (selectedItem !== previousSelectedItem) announceItemChange();
22910  }
22911
22912  /**
22913   * Use the user-defined expression to announce changes each time a new item is selected
22914   */
22915  function announceItemChange () {
22916    angular.isFunction($scope.itemChange) && $scope.itemChange(getItemAsNameVal($scope.selectedItem));
22917  }
22918
22919  /**
22920   * Use the user-defined expression to announce changes each time the search text is changed
22921   */
22922  function announceTextChange () {
22923    angular.isFunction($scope.textChange) && $scope.textChange();
22924  }
22925
22926  /**
22927   * Calls any external watchers listening for the selected item.  Used in conjunction with
22928   * `registerSelectedItemWatcher`.
22929   * @param selectedItem
22930   * @param previousSelectedItem
22931   */
22932  function handleSelectedItemChange (selectedItem, previousSelectedItem) {
22933    selectedItemWatchers.forEach(function (watcher) { watcher(selectedItem, previousSelectedItem); });
22934  }
22935
22936  /**
22937   * Register a function to be called when the selected item changes.
22938   * @param cb
22939   */
22940  function registerSelectedItemWatcher (cb) {
22941    if (selectedItemWatchers.indexOf(cb) == -1) {
22942      selectedItemWatchers.push(cb);
22943    }
22944  }
22945
22946  /**
22947   * Unregister a function previously registered for selected item changes.
22948   * @param cb
22949   */
22950  function unregisterSelectedItemWatcher (cb) {
22951    var i = selectedItemWatchers.indexOf(cb);
22952    if (i != -1) {
22953      selectedItemWatchers.splice(i, 1);
22954    }
22955  }
22956
22957  /**
22958   * Handles changes to the searchText property.
22959   * @param searchText
22960   * @param previousSearchText
22961   */
22962  function handleSearchText (searchText, previousSearchText) {
22963    ctrl.index = getDefaultIndex();
22964
22965    // do nothing on init
22966    if (searchText === previousSearchText) return;
22967
22968    updateModelValidators();
22969
22970    getDisplayValue($scope.selectedItem).then(function (val) {
22971      // clear selected item if search text no longer matches it
22972      if (searchText !== val) {
22973        $scope.selectedItem = null;
22974
22975        // trigger change event if available
22976        if (searchText !== previousSearchText) announceTextChange();
22977
22978        // cancel results if search text is not long enough
22979        if (!isMinLengthMet()) {
22980          ctrl.matches = [];
22981          setLoading(false);
22982          updateMessages();
22983        } else {
22984          handleQuery();
22985        }
22986      }
22987    });
22988
22989  }
22990
22991  /**
22992   * Handles input blur event, determines if the dropdown should hide.
22993   */
22994  function blur($event) {
22995    hasFocus = false;
22996
22997    if (!noBlur) {
22998      ctrl.hidden = shouldHide();
22999      evalAttr('ngBlur', { $event: $event });
23000    }
23001  }
23002
23003  /**
23004   * Force blur on input element
23005   * @param forceBlur
23006   */
23007  function doBlur(forceBlur) {
23008    if (forceBlur) {
23009      noBlur = false;
23010      hasFocus = false;
23011    }
23012    elements.input.blur();
23013  }
23014
23015  /**
23016   * Handles input focus event, determines if the dropdown should show.
23017   */
23018  function focus($event) {
23019    hasFocus = true;
23020
23021    if (isSearchable() && isMinLengthMet()) {
23022      handleQuery();
23023    }
23024
23025    ctrl.hidden = shouldHide();
23026
23027    evalAttr('ngFocus', { $event: $event });
23028  }
23029
23030  /**
23031   * Handles keyboard input.
23032   * @param event
23033   */
23034  function keydown (event) {
23035    switch (event.keyCode) {
23036      case $mdConstant.KEY_CODE.DOWN_ARROW:
23037        if (ctrl.loading) return;
23038        event.stopPropagation();
23039        event.preventDefault();
23040        ctrl.index   = Math.min(ctrl.index + 1, ctrl.matches.length - 1);
23041        updateScroll();
23042        updateMessages();
23043        break;
23044      case $mdConstant.KEY_CODE.UP_ARROW:
23045        if (ctrl.loading) return;
23046        event.stopPropagation();
23047        event.preventDefault();
23048        ctrl.index   = ctrl.index < 0 ? ctrl.matches.length - 1 : Math.max(0, ctrl.index - 1);
23049        updateScroll();
23050        updateMessages();
23051        break;
23052      case $mdConstant.KEY_CODE.TAB:
23053        // If we hit tab, assume that we've left the list so it will close
23054        onListLeave();
23055
23056        if (ctrl.hidden || ctrl.loading || ctrl.index < 0 || ctrl.matches.length < 1) return;
23057        select(ctrl.index);
23058        break;
23059      case $mdConstant.KEY_CODE.ENTER:
23060        if (ctrl.hidden || ctrl.loading || ctrl.index < 0 || ctrl.matches.length < 1) return;
23061        if (hasSelection()) return;
23062        event.stopPropagation();
23063        event.preventDefault();
23064        select(ctrl.index);
23065        break;
23066      case $mdConstant.KEY_CODE.ESCAPE:
23067        event.preventDefault(); // Prevent browser from always clearing input
23068        if (!shouldProcessEscape()) return;
23069        event.stopPropagation();
23070
23071        clearSelectedItem();
23072        if ($scope.searchText && hasEscapeOption('clear')) {
23073          clearSearchText();
23074        }
23075
23076        // Manually hide (needed for mdNotFound support)
23077        ctrl.hidden = true;
23078
23079        if (hasEscapeOption('blur')) {
23080          // Force the component to blur if they hit escape
23081          doBlur(true);
23082        }
23083
23084        break;
23085      default:
23086    }
23087  }
23088
23089  //-- getters
23090
23091  /**
23092   * Returns the minimum length needed to display the dropdown.
23093   * @returns {*}
23094   */
23095  function getMinLength () {
23096    return angular.isNumber($scope.minLength) ? $scope.minLength : 1;
23097  }
23098
23099  /**
23100   * Returns the display value for an item.
23101   * @param item
23102   * @returns {*}
23103   */
23104  function getDisplayValue (item) {
23105    return $q.when(getItemText(item) || item).then(function(itemText) {
23106      if (itemText && !angular.isString(itemText)) {
23107        $log.warn('md-autocomplete: Could not resolve display value to a string. ' +
23108          'Please check the `md-item-text` attribute.');
23109      }
23110
23111      return itemText;
23112    });
23113
23114    /**
23115     * Getter function to invoke user-defined expression (in the directive)
23116     * to convert your object to a single string.
23117     */
23118    function getItemText (item) {
23119      return (item && $scope.itemText) ? $scope.itemText(getItemAsNameVal(item)) : null;
23120    }
23121  }
23122
23123  /**
23124   * Returns the locals object for compiling item templates.
23125   * @param item
23126   * @returns {{}}
23127   */
23128  function getItemAsNameVal (item) {
23129    if (!item) return undefined;
23130
23131    var locals = {};
23132    if (ctrl.itemName) locals[ ctrl.itemName ] = item;
23133
23134    return locals;
23135  }
23136
23137  /**
23138   * Returns the default index based on whether or not autoselect is enabled.
23139   * @returns {number}
23140   */
23141  function getDefaultIndex () {
23142    return $scope.autoselect ? 0 : -1;
23143  }
23144
23145  /**
23146   * Sets the loading parameter and updates the hidden state.
23147   * @param value {boolean} Whether or not the component is currently loading.
23148   */
23149  function setLoading(value) {
23150    if (ctrl.loading != value) {
23151      ctrl.loading = value;
23152    }
23153
23154    // Always refresh the hidden variable as something else might have changed
23155    ctrl.hidden = shouldHide();
23156  }
23157
23158  /**
23159   * Determines if the menu should be hidden.
23160   * @returns {boolean}
23161   */
23162  function shouldHide () {
23163    if (!isSearchable()) return true;    // Hide when not able to query
23164    else return !shouldShow();            // Hide when the dropdown is not able to show.
23165  }
23166
23167  /**
23168   * Determines whether the autocomplete is able to query within the current state.
23169   * @returns {boolean}
23170   */
23171  function isSearchable() {
23172    if (ctrl.loading && !hasMatches()) return false; // No query when query is in progress.
23173    else if (hasSelection()) return false;           // No query if there is already a selection
23174    else if (!hasFocus) return false;                // No query if the input does not have focus
23175    return true;
23176  }
23177
23178  /**
23179   * Determines if the escape keydown should be processed
23180   * @returns {boolean}
23181   */
23182  function shouldProcessEscape() {
23183    return hasEscapeOption('blur') || !ctrl.hidden || ctrl.loading || hasEscapeOption('clear') && $scope.searchText;
23184  }
23185
23186  /**
23187   * Determines if an escape option is set
23188   * @returns {boolean}
23189   */
23190  function hasEscapeOption(option) {
23191    return !$scope.escapeOptions || $scope.escapeOptions.toLowerCase().indexOf(option) !== -1;
23192  }
23193
23194  /**
23195   * Determines if the menu should be shown.
23196   * @returns {boolean}
23197   */
23198  function shouldShow() {
23199    return (isMinLengthMet() && hasMatches()) || notFoundVisible();
23200  }
23201
23202  /**
23203   * Returns true if the search text has matches.
23204   * @returns {boolean}
23205   */
23206  function hasMatches() {
23207    return ctrl.matches.length ? true : false;
23208  }
23209
23210  /**
23211   * Returns true if the autocomplete has a valid selection.
23212   * @returns {boolean}
23213   */
23214  function hasSelection() {
23215    return ctrl.scope.selectedItem ? true : false;
23216  }
23217
23218  /**
23219   * Returns true if the loading indicator is, or should be, visible.
23220   * @returns {boolean}
23221   */
23222  function loadingIsVisible() {
23223    return ctrl.loading && !hasSelection();
23224  }
23225
23226  /**
23227   * Returns the display value of the current item.
23228   * @returns {*}
23229   */
23230  function getCurrentDisplayValue () {
23231    return getDisplayValue(ctrl.matches[ ctrl.index ]);
23232  }
23233
23234  /**
23235   * Determines if the minimum length is met by the search text.
23236   * @returns {*}
23237   */
23238  function isMinLengthMet () {
23239    return ($scope.searchText || '').length >= getMinLength();
23240  }
23241
23242  //-- actions
23243
23244  /**
23245   * Defines a public property with a handler and a default value.
23246   * @param key
23247   * @param handler
23248   * @param value
23249   */
23250  function defineProperty (key, handler, value) {
23251    Object.defineProperty(ctrl, key, {
23252      get: function () { return value; },
23253      set: function (newValue) {
23254        var oldValue = value;
23255        value        = newValue;
23256        handler(newValue, oldValue);
23257      }
23258    });
23259  }
23260
23261  /**
23262   * Selects the item at the given index.
23263   * @param index
23264   */
23265  function select (index) {
23266    //-- force form to update state for validation
23267    $mdUtil.nextTick(function () {
23268      getDisplayValue(ctrl.matches[ index ]).then(function (val) {
23269        var ngModel = elements.$.input.controller('ngModel');
23270        ngModel.$setViewValue(val);
23271        ngModel.$render();
23272      }).finally(function () {
23273        $scope.selectedItem = ctrl.matches[ index ];
23274        setLoading(false);
23275      });
23276    }, false);
23277  }
23278
23279  /**
23280   * Clears the searchText value and selected item.
23281   */
23282  function clearValue () {
23283    clearSelectedItem();
23284    clearSearchText();
23285  }
23286
23287  /**
23288   * Clears the selected item
23289   */
23290  function clearSelectedItem () {
23291    // Reset our variables
23292    ctrl.index = 0;
23293    ctrl.matches = [];
23294  }
23295
23296  /**
23297   * Clears the searchText value
23298   */
23299  function clearSearchText () {
23300    // Set the loading to true so we don't see flashes of content.
23301    // The flashing will only occur when an async request is running.
23302    // So the loading process will stop when the results had been retrieved.
23303    setLoading(true);
23304
23305    $scope.searchText = '';
23306
23307    // Normally, triggering the change / input event is unnecessary, because the browser detects it properly.
23308    // But some browsers are not detecting it properly, which means that we have to trigger the event.
23309    // Using the `input` is not working properly, because for example IE11 is not supporting the `input` event.
23310    // The `change` event is a good alternative and is supported by all supported browsers.
23311    var eventObj = document.createEvent('CustomEvent');
23312    eventObj.initCustomEvent('change', true, true, { value: '' });
23313    elements.input.dispatchEvent(eventObj);
23314
23315    // For some reason, firing the above event resets the value of $scope.searchText if
23316    // $scope.searchText has a space character at the end, so we blank it one more time and then
23317    // focus.
23318    elements.input.blur();
23319    $scope.searchText = '';
23320    elements.input.focus();
23321  }
23322
23323  /**
23324   * Fetches the results for the provided search text.
23325   * @param searchText
23326   */
23327  function fetchResults (searchText) {
23328    var items = $scope.$parent.$eval(itemExpr),
23329        term  = searchText.toLowerCase(),
23330        isList = angular.isArray(items),
23331        isPromise = !!items.then; // Every promise should contain a `then` property
23332
23333    if (isList) onResultsRetrieved(items);
23334    else if (isPromise) handleAsyncResults(items);
23335
23336    function handleAsyncResults(items) {
23337      if ( !items ) return;
23338
23339      items = $q.when(items);
23340      fetchesInProgress++;
23341      setLoading(true);
23342
23343      $mdUtil.nextTick(function () {
23344          items
23345            .then(onResultsRetrieved)
23346            .finally(function(){
23347              if (--fetchesInProgress === 0) {
23348                setLoading(false);
23349              }
23350            });
23351      },true, $scope);
23352    }
23353
23354    function onResultsRetrieved(matches) {
23355      cache[term] = matches;
23356
23357      // Just cache the results if the request is now outdated.
23358      // The request becomes outdated, when the new searchText has changed during the result fetching.
23359      if ((searchText || '') !== ($scope.searchText || '')) {
23360        return;
23361      }
23362
23363      handleResults(matches);
23364    }
23365  }
23366
23367  /**
23368   * Updates the ARIA messages
23369   */
23370  function updateMessages () {
23371    getCurrentDisplayValue().then(function (msg) {
23372      ctrl.messages = [ getCountMessage(), msg ];
23373    });
23374  }
23375
23376  /**
23377   * Returns the ARIA message for how many results match the current query.
23378   * @returns {*}
23379   */
23380  function getCountMessage () {
23381    if (lastCount === ctrl.matches.length) return '';
23382    lastCount = ctrl.matches.length;
23383    switch (ctrl.matches.length) {
23384      case 0:
23385        return 'There are no matches available.';
23386      case 1:
23387        return 'There is 1 match available.';
23388      default:
23389        return 'There are ' + ctrl.matches.length + ' matches available.';
23390    }
23391  }
23392
23393  /**
23394   * Makes sure that the focused element is within view.
23395   */
23396  function updateScroll () {
23397    if (!elements.li[0]) return;
23398    var height = elements.li[0].offsetHeight,
23399        top = height * ctrl.index,
23400        bot = top + height,
23401        hgt = elements.scroller.clientHeight,
23402        scrollTop = elements.scroller.scrollTop;
23403    if (top < scrollTop) {
23404      scrollTo(top);
23405    } else if (bot > scrollTop + hgt) {
23406      scrollTo(bot - hgt);
23407    }
23408  }
23409
23410  function isPromiseFetching() {
23411    return fetchesInProgress !== 0;
23412  }
23413
23414  function scrollTo (offset) {
23415    elements.$.scrollContainer.controller('mdVirtualRepeatContainer').scrollTo(offset);
23416  }
23417
23418  function notFoundVisible () {
23419    var textLength = (ctrl.scope.searchText || '').length;
23420
23421    return ctrl.hasNotFound && !hasMatches() && (!ctrl.loading || isPromiseFetching()) && textLength >= getMinLength() && (hasFocus || noBlur) && !hasSelection();
23422  }
23423
23424  /**
23425   * Starts the query to gather the results for the current searchText.  Attempts to return cached
23426   * results first, then forwards the process to `fetchResults` if necessary.
23427   */
23428  function handleQuery () {
23429    var searchText = $scope.searchText || '';
23430    var term = searchText.toLowerCase();
23431
23432    // If caching is enabled and the current searchText is stored in the cache
23433    if (!$scope.noCache && cache[term]) {
23434      // The results should be handled as same as a normal un-cached request does.
23435      handleResults(cache[term]);
23436    } else {
23437      fetchResults(searchText);
23438    }
23439
23440    ctrl.hidden = shouldHide();
23441  }
23442
23443  /**
23444   * Handles the retrieved results by showing them in the autocompletes dropdown.
23445   * @param results Retrieved results
23446   */
23447  function handleResults(results) {
23448    ctrl.matches = results;
23449    ctrl.hidden  = shouldHide();
23450
23451    // If loading is in progress, then we'll end the progress. This is needed for example,
23452    // when the `clear` button was clicked, because there we always show the loading process, to prevent flashing.
23453    if (ctrl.loading) setLoading(false);
23454
23455    if ($scope.selectOnMatch) selectItemOnMatch();
23456
23457    updateMessages();
23458    positionDropdown();
23459  }
23460
23461  /**
23462   * If there is only one matching item and the search text matches its display value exactly,
23463   * automatically select that item.  Note: This function is only called if the user uses the
23464   * `md-select-on-match` flag.
23465   */
23466  function selectItemOnMatch () {
23467    var searchText = $scope.searchText,
23468        matches    = ctrl.matches,
23469        item       = matches[ 0 ];
23470    if (matches.length === 1) getDisplayValue(item).then(function (displayValue) {
23471      var isMatching = searchText == displayValue;
23472      if ($scope.matchInsensitive && !isMatching) {
23473        isMatching = searchText.toLowerCase() == displayValue.toLowerCase();
23474      }
23475
23476      if (isMatching) select(0);
23477    });
23478  }
23479
23480  /**
23481   * Evaluates an attribute expression against the parent scope.
23482   * @param {String} attr Name of the attribute to be evaluated.
23483   * @param {Object?} locals Properties to be injected into the evaluation context.
23484   */
23485 function evalAttr(attr, locals) {
23486    if ($attrs[attr]) {
23487      $scope.$parent.$eval($attrs[attr], locals || {});
23488    }
23489  }
23490
23491}
23492
23493})();
23494(function(){
23495"use strict";
23496
23497
23498MdAutocomplete.$inject = ["$$mdSvgRegistry"];angular
23499    .module('material.components.autocomplete')
23500    .directive('mdAutocomplete', MdAutocomplete);
23501
23502/**
23503 * @ngdoc directive
23504 * @name mdAutocomplete
23505 * @module material.components.autocomplete
23506 *
23507 * @description
23508 * `<md-autocomplete>` is a special input component with a drop-down of all possible matches to a
23509 *     custom query. This component allows you to provide real-time suggestions as the user types
23510 *     in the input area.
23511 *
23512 * To start, you will need to specify the required parameters and provide a template for your
23513 *     results. The content inside `md-autocomplete` will be treated as a template.
23514 *
23515 * In more complex cases, you may want to include other content such as a message to display when
23516 *     no matches were found.  You can do this by wrapping your template in `md-item-template` and
23517 *     adding a tag for `md-not-found`.  An example of this is shown below.
23518 *
23519 * To reset the displayed value you must clear both values for `md-search-text` and `md-selected-item`.
23520 *
23521 * ### Validation
23522 *
23523 * You can use `ng-messages` to include validation the same way that you would normally validate;
23524 *     however, if you want to replicate a standard input with a floating label, you will have to
23525 *     do the following:
23526 *
23527 * - Make sure that your template is wrapped in `md-item-template`
23528 * - Add your `ng-messages` code inside of `md-autocomplete`
23529 * - Add your validation properties to `md-autocomplete` (ie. `required`)
23530 * - Add a `name` to `md-autocomplete` (to be used on the generated `input`)
23531 *
23532 * There is an example below of how this should look.
23533 *
23534 * ### Notes
23535 * The `md-autocomplete` uses the the <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeat</a>
23536 * directive for displaying the results inside of the dropdown.<br/>
23537 * > When encountering issues regarding the item template please take a look at the
23538 *   <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeatContainer</a> documentation.
23539 *
23540 *
23541 * @param {expression} md-items An expression in the format of `item in items` to iterate over
23542 *     matches for your search.
23543 * @param {expression=} md-selected-item-change An expression to be run each time a new item is
23544 *     selected
23545 * @param {expression=} md-search-text-change An expression to be run each time the search text
23546 *     updates
23547 * @param {expression=} md-search-text A model to bind the search query text to
23548 * @param {object=} md-selected-item A model to bind the selected item to
23549 * @param {expression=} md-item-text An expression that will convert your object to a single string.
23550 * @param {string=} placeholder Placeholder text that will be forwarded to the input.
23551 * @param {boolean=} md-no-cache Disables the internal caching that happens in autocomplete
23552 * @param {boolean=} ng-disabled Determines whether or not to disable the input field
23553 * @param {boolean=} md-require-match When set to true, the autocomplete will add a validator,
23554 *     which will evaluate to false, when no item is currently selected.
23555 * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will
23556 *     make suggestions
23557 * @param {number=} md-delay Specifies the amount of time (in milliseconds) to wait before looking
23558 *     for results
23559 * @param {boolean=} md-autofocus If true, the autocomplete will be automatically focused when a `$mdDialog`,
23560 *     `$mdBottomsheet` or `$mdSidenav`, which contains the autocomplete, is opening. <br/><br/>
23561 *     Also the autocomplete will immediately focus the input element.
23562 * @param {boolean=} md-no-asterisk When present, asterisk will not be appended to the floating label
23563 * @param {boolean=} md-autoselect If set to true, the first item will be automatically selected
23564 *     in the dropdown upon open.
23565 * @param {string=} md-menu-class This will be applied to the dropdown menu for styling
23566 * @param {string=} md-floating-label This will add a floating label to autocomplete and wrap it in
23567 *     `md-input-container`
23568 * @param {string=} md-input-name The name attribute given to the input element to be used with
23569 *     FormController
23570 * @param {string=} md-select-on-focus When present the inputs text will be automatically selected
23571 *     on focus.
23572 * @param {string=} md-input-id An ID to be added to the input element
23573 * @param {number=} md-input-minlength The minimum length for the input's value for validation
23574 * @param {number=} md-input-maxlength The maximum length for the input's value for validation
23575 * @param {boolean=} md-select-on-match When set, autocomplete will automatically select exact
23576 *     the item if the search text is an exact match. <br/><br/>
23577 *     Exact match means that there is only one match showing up.
23578 * @param {boolean=} md-match-case-insensitive When set and using `md-select-on-match`, autocomplete
23579 *     will select on case-insensitive match
23580 * @param {string=} md-escape-options Override escape key logic. Default is `blur clear`.<br/>
23581 *     Options: `blur | clear`, `none`
23582 *
23583 * @usage
23584 * ### Basic Example
23585 * <hljs lang="html">
23586 *   <md-autocomplete
23587 *       md-selected-item="selectedItem"
23588 *       md-search-text="searchText"
23589 *       md-items="item in getMatches(searchText)"
23590 *       md-item-text="item.display">
23591 *     <span md-highlight-text="searchText">{{item.display}}</span>
23592 *   </md-autocomplete>
23593 * </hljs>
23594 *
23595 * ### Example with "not found" message
23596 * <hljs lang="html">
23597 * <md-autocomplete
23598 *     md-selected-item="selectedItem"
23599 *     md-search-text="searchText"
23600 *     md-items="item in getMatches(searchText)"
23601 *     md-item-text="item.display">
23602 *   <md-item-template>
23603 *     <span md-highlight-text="searchText">{{item.display}}</span>
23604 *   </md-item-template>
23605 *   <md-not-found>
23606 *     No matches found.
23607 *   </md-not-found>
23608 * </md-autocomplete>
23609 * </hljs>
23610 *
23611 * In this example, our code utilizes `md-item-template` and `md-not-found` to specify the
23612 *     different parts that make up our component.
23613 *
23614 * ### Example with validation
23615 * <hljs lang="html">
23616 * <form name="autocompleteForm">
23617 *   <md-autocomplete
23618 *       required
23619 *       md-input-name="autocomplete"
23620 *       md-selected-item="selectedItem"
23621 *       md-search-text="searchText"
23622 *       md-items="item in getMatches(searchText)"
23623 *       md-item-text="item.display">
23624 *     <md-item-template>
23625 *       <span md-highlight-text="searchText">{{item.display}}</span>
23626 *     </md-item-template>
23627 *     <div ng-messages="autocompleteForm.autocomplete.$error">
23628 *       <div ng-message="required">This field is required</div>
23629 *     </div>
23630 *   </md-autocomplete>
23631 * </form>
23632 * </hljs>
23633 *
23634 * In this example, our code utilizes `md-item-template` and `ng-messages` to specify
23635 *     input validation for the field.
23636 */
23637
23638function MdAutocomplete ($$mdSvgRegistry) {
23639
23640  return {
23641    controller:   'MdAutocompleteCtrl',
23642    controllerAs: '$mdAutocompleteCtrl',
23643    scope:        {
23644      inputName:        '@mdInputName',
23645      inputMinlength:   '@mdInputMinlength',
23646      inputMaxlength:   '@mdInputMaxlength',
23647      searchText:       '=?mdSearchText',
23648      selectedItem:     '=?mdSelectedItem',
23649      itemsExpr:        '@mdItems',
23650      itemText:         '&mdItemText',
23651      placeholder:      '@placeholder',
23652      noCache:          '=?mdNoCache',
23653      requireMatch:     '=?mdRequireMatch',
23654      selectOnMatch:    '=?mdSelectOnMatch',
23655      matchInsensitive: '=?mdMatchCaseInsensitive',
23656      itemChange:       '&?mdSelectedItemChange',
23657      textChange:       '&?mdSearchTextChange',
23658      minLength:        '=?mdMinLength',
23659      delay:            '=?mdDelay',
23660      autofocus:        '=?mdAutofocus',
23661      floatingLabel:    '@?mdFloatingLabel',
23662      autoselect:       '=?mdAutoselect',
23663      menuClass:        '@?mdMenuClass',
23664      inputId:          '@?mdInputId',
23665      escapeOptions:    '@?mdEscapeOptions'
23666    },
23667    link: function(scope, element, attrs, controller) {
23668      // Retrieve the state of using a md-not-found template by using our attribute, which will
23669      // be added to the element in the template function.
23670      controller.hasNotFound = !!element.attr('md-has-not-found');
23671    },
23672    template:     function (element, attr) {
23673      var noItemsTemplate = getNoItemsTemplate(),
23674          itemTemplate    = getItemTemplate(),
23675          leftover        = element.html(),
23676          tabindex        = attr.tabindex;
23677
23678      // Set our attribute for the link function above which runs later.
23679      // We will set an attribute, because otherwise the stored variables will be trashed when
23680      // removing the element is hidden while retrieving the template. For example when using ngIf.
23681      if (noItemsTemplate) element.attr('md-has-not-found', true);
23682
23683      // Always set our tabindex of the autocomplete directive to -1, because our input
23684      // will hold the actual tabindex.
23685      element.attr('tabindex', '-1');
23686
23687      return '\
23688        <md-autocomplete-wrap\
23689            ng-class="{ \'md-whiteframe-z1\': !floatingLabel, \'md-menu-showing\': !$mdAutocompleteCtrl.hidden }">\
23690          ' + getInputElement() + '\
23691          <md-progress-linear\
23692              class="' + (attr.mdFloatingLabel ? 'md-inline' : '') + '"\
23693              ng-if="$mdAutocompleteCtrl.loadingIsVisible()"\
23694              md-mode="indeterminate"></md-progress-linear>\
23695          <md-virtual-repeat-container\
23696              md-auto-shrink\
23697              md-auto-shrink-min="1"\
23698              ng-mouseenter="$mdAutocompleteCtrl.listEnter()"\
23699              ng-mouseleave="$mdAutocompleteCtrl.listLeave()"\
23700              ng-mouseup="$mdAutocompleteCtrl.mouseUp()"\
23701              ng-hide="$mdAutocompleteCtrl.hidden"\
23702              class="md-autocomplete-suggestions-container md-whiteframe-z1"\
23703              ng-class="{ \'md-not-found\': $mdAutocompleteCtrl.notFoundVisible() }"\
23704              role="presentation">\
23705            <ul class="md-autocomplete-suggestions"\
23706                ng-class="::menuClass"\
23707                id="ul-{{$mdAutocompleteCtrl.id}}">\
23708              <li md-virtual-repeat="item in $mdAutocompleteCtrl.matches"\
23709                  ng-class="{ selected: $index === $mdAutocompleteCtrl.index }"\
23710                  ng-click="$mdAutocompleteCtrl.select($index)"\
23711                  md-extra-name="$mdAutocompleteCtrl.itemName">\
23712                  ' + itemTemplate + '\
23713                  </li>' + noItemsTemplate + '\
23714            </ul>\
23715          </md-virtual-repeat-container>\
23716        </md-autocomplete-wrap>\
23717        <aria-status\
23718            class="md-visually-hidden"\
23719            role="status"\
23720            aria-live="assertive">\
23721          <p ng-repeat="message in $mdAutocompleteCtrl.messages track by $index" ng-if="message">{{message}}</p>\
23722        </aria-status>';
23723
23724      function getItemTemplate() {
23725        var templateTag = element.find('md-item-template').detach(),
23726            html = templateTag.length ? templateTag.html() : element.html();
23727        if (!templateTag.length) element.empty();
23728        return '<md-autocomplete-parent-scope md-autocomplete-replace>' + html + '</md-autocomplete-parent-scope>';
23729      }
23730
23731      function getNoItemsTemplate() {
23732        var templateTag = element.find('md-not-found').detach(),
23733            template = templateTag.length ? templateTag.html() : '';
23734        return template
23735            ? '<li ng-if="$mdAutocompleteCtrl.notFoundVisible()"\
23736                         md-autocomplete-parent-scope>' + template + '</li>'
23737            : '';
23738
23739      }
23740
23741      function getInputElement () {
23742        if (attr.mdFloatingLabel) {
23743          return '\
23744            <md-input-container ng-if="floatingLabel">\
23745              <label>{{floatingLabel}}</label>\
23746              <input type="search"\
23747                  ' + (tabindex != null ? 'tabindex="' + tabindex + '"' : '') + '\
23748                  id="{{ inputId || \'fl-input-\' + $mdAutocompleteCtrl.id }}"\
23749                  name="{{inputName}}"\
23750                  autocomplete="off"\
23751                  ng-required="$mdAutocompleteCtrl.isRequired"\
23752                  ng-readonly="$mdAutocompleteCtrl.isReadonly"\
23753                  ng-minlength="inputMinlength"\
23754                  ng-maxlength="inputMaxlength"\
23755                  ng-disabled="$mdAutocompleteCtrl.isDisabled"\
23756                  ng-model="$mdAutocompleteCtrl.scope.searchText"\
23757                  ng-model-options="{ allowInvalid: true }"\
23758                  ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
23759                  ng-blur="$mdAutocompleteCtrl.blur($event)"\
23760                  ng-focus="$mdAutocompleteCtrl.focus($event)"\
23761                  aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
23762                  ' + (attr.mdNoAsterisk != null ? 'md-no-asterisk="' + attr.mdNoAsterisk + '"' : '') + '\
23763                  ' + (attr.mdSelectOnFocus != null ? 'md-select-on-focus=""' : '') + '\
23764                  aria-label="{{floatingLabel}}"\
23765                  aria-autocomplete="list"\
23766                  role="combobox"\
23767                  aria-haspopup="true"\
23768                  aria-activedescendant=""\
23769                  aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
23770              <div md-autocomplete-parent-scope md-autocomplete-replace>' + leftover + '</div>\
23771            </md-input-container>';
23772        } else {
23773          return '\
23774            <input type="search"\
23775                ' + (tabindex != null ? 'tabindex="' + tabindex + '"' : '') + '\
23776                id="{{ inputId || \'input-\' + $mdAutocompleteCtrl.id }}"\
23777                name="{{inputName}}"\
23778                ng-if="!floatingLabel"\
23779                autocomplete="off"\
23780                ng-required="$mdAutocompleteCtrl.isRequired"\
23781                ng-disabled="$mdAutocompleteCtrl.isDisabled"\
23782                ng-readonly="$mdAutocompleteCtrl.isReadonly"\
23783                ng-model="$mdAutocompleteCtrl.scope.searchText"\
23784                ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
23785                ng-blur="$mdAutocompleteCtrl.blur($event)"\
23786                ng-focus="$mdAutocompleteCtrl.focus($event)"\
23787                placeholder="{{placeholder}}"\
23788                aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
23789                ' + (attr.mdSelectOnFocus != null ? 'md-select-on-focus=""' : '') + '\
23790                aria-label="{{placeholder}}"\
23791                aria-autocomplete="list"\
23792                role="combobox"\
23793                aria-haspopup="true"\
23794                aria-activedescendant=""\
23795                aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
23796            <button\
23797                type="button"\
23798                tabindex="-1"\
23799                ng-if="$mdAutocompleteCtrl.scope.searchText && !$mdAutocompleteCtrl.isDisabled"\
23800                ng-click="$mdAutocompleteCtrl.clear($event)">\
23801              <md-icon md-svg-src="' + $$mdSvgRegistry.mdClose + '"></md-icon>\
23802              <span class="md-visually-hidden">Clear</span>\
23803            </button>\
23804                ';
23805        }
23806      }
23807    }
23808  };
23809}
23810
23811})();
23812(function(){
23813"use strict";
23814
23815
23816MdAutocompleteItemScopeDirective.$inject = ["$compile", "$mdUtil"];angular
23817  .module('material.components.autocomplete')
23818  .directive('mdAutocompleteParentScope', MdAutocompleteItemScopeDirective);
23819
23820function MdAutocompleteItemScopeDirective($compile, $mdUtil) {
23821  return {
23822    restrict: 'AE',
23823    compile: compile,
23824    terminal: true,
23825    transclude: 'element'
23826  };
23827
23828  function compile(tElement, tAttr, transclude) {
23829    return function postLink(scope, element, attr) {
23830      var ctrl = scope.$mdAutocompleteCtrl;
23831      var newScope = ctrl.parent.$new();
23832      var itemName = ctrl.itemName;
23833
23834      // Watch for changes to our scope's variables and copy them to the new scope
23835      watchVariable('$index', '$index');
23836      watchVariable('item', itemName);
23837
23838      // Ensure that $digest calls on our scope trigger $digest on newScope.
23839      connectScopes();
23840
23841      // Link the element against newScope.
23842      transclude(newScope, function(clone) {
23843        element.after(clone);
23844      });
23845
23846      /**
23847       * Creates a watcher for variables that are copied from the parent scope
23848       * @param variable
23849       * @param alias
23850       */
23851      function watchVariable(variable, alias) {
23852        newScope[alias] = scope[variable];
23853
23854        scope.$watch(variable, function(value) {
23855          $mdUtil.nextTick(function() {
23856            newScope[alias] = value;
23857          });
23858        });
23859      }
23860
23861      /**
23862       * Creates watchers on scope and newScope that ensure that for any
23863       * $digest of scope, newScope is also $digested.
23864       */
23865      function connectScopes() {
23866        var scopeDigesting = false;
23867        var newScopeDigesting = false;
23868
23869        scope.$watch(function() {
23870          if (newScopeDigesting || scopeDigesting) {
23871            return;
23872          }
23873
23874          scopeDigesting = true;
23875          scope.$$postDigest(function() {
23876            if (!newScopeDigesting) {
23877              newScope.$digest();
23878            }
23879
23880            scopeDigesting = newScopeDigesting = false;
23881          });
23882        });
23883
23884        newScope.$watch(function() {
23885          newScopeDigesting = true;
23886        });
23887      }
23888    };
23889  }
23890}
23891})();
23892(function(){
23893"use strict";
23894
23895
23896MdHighlightCtrl.$inject = ["$scope", "$element", "$attrs"];angular
23897    .module('material.components.autocomplete')
23898    .controller('MdHighlightCtrl', MdHighlightCtrl);
23899
23900function MdHighlightCtrl ($scope, $element, $attrs) {
23901  this.$scope = $scope;
23902  this.$element = $element;
23903  this.$attrs = $attrs;
23904
23905  // Cache the Regex to avoid rebuilding each time.
23906  this.regex = null;
23907}
23908
23909MdHighlightCtrl.prototype.init = function(unsafeTermFn, unsafeContentFn) {
23910
23911  this.flags = this.$attrs.mdHighlightFlags || '';
23912
23913  this.unregisterFn = this.$scope.$watch(function($scope) {
23914    return {
23915      term: unsafeTermFn($scope),
23916      contentText: unsafeContentFn($scope)
23917    };
23918  }.bind(this), this.onRender.bind(this), true);
23919
23920  this.$element.on('$destroy', this.unregisterFn);
23921};
23922
23923/**
23924 * Triggered once a new change has been recognized and the highlighted
23925 * text needs to be updated.
23926 */
23927MdHighlightCtrl.prototype.onRender = function(state, prevState) {
23928
23929  var contentText = state.contentText;
23930
23931  /* Update the regex if it's outdated, because we don't want to rebuilt it constantly. */
23932  if (this.regex === null || state.term !== prevState.term) {
23933    this.regex = this.createRegex(state.term, this.flags);
23934  }
23935
23936  /* If a term is available apply the regex to the content */
23937  if (state.term) {
23938    this.applyRegex(contentText);
23939  } else {
23940    this.$element.text(contentText);
23941  }
23942
23943};
23944
23945/**
23946 * Decomposes the specified text into different tokens (whether match or not).
23947 * Breaking down the string guarantees proper XSS protection due to the native browser
23948 * escaping of unsafe text.
23949 */
23950MdHighlightCtrl.prototype.applyRegex = function(text) {
23951  var tokens = this.resolveTokens(text);
23952
23953  this.$element.empty();
23954
23955  tokens.forEach(function (token) {
23956
23957    if (token.isMatch) {
23958      var tokenEl = angular.element('<span class="highlight">').text(token.text);
23959
23960      this.$element.append(tokenEl);
23961    } else {
23962      this.$element.append(document.createTextNode(token));
23963    }
23964
23965  }.bind(this));
23966
23967};
23968
23969  /**
23970 * Decomposes the specified text into different tokens by running the regex against the text.
23971 */
23972MdHighlightCtrl.prototype.resolveTokens = function(string) {
23973  var tokens = [];
23974  var lastIndex = 0;
23975
23976  // Use replace here, because it supports global and single regular expressions at same time.
23977  string.replace(this.regex, function(match, index) {
23978    appendToken(lastIndex, index);
23979
23980    tokens.push({
23981      text: match,
23982      isMatch: true
23983    });
23984
23985    lastIndex = index + match.length;
23986  });
23987
23988  // Append the missing text as a token.
23989  appendToken(lastIndex);
23990
23991  return tokens;
23992
23993  function appendToken(from, to) {
23994    var targetText = string.slice(from, to);
23995    targetText && tokens.push(targetText);
23996  }
23997};
23998
23999/** Creates a regex for the specified text with the given flags. */
24000MdHighlightCtrl.prototype.createRegex = function(term, flags) {
24001  var startFlag = '', endFlag = '';
24002  var regexTerm = this.sanitizeRegex(term);
24003
24004  if (flags.indexOf('^') >= 0) startFlag = '^';
24005  if (flags.indexOf('$') >= 0) endFlag = '$';
24006
24007  return new RegExp(startFlag + regexTerm + endFlag, flags.replace(/[$\^]/g, ''));
24008};
24009
24010/** Sanitizes a regex by removing all common RegExp identifiers */
24011MdHighlightCtrl.prototype.sanitizeRegex = function(term) {
24012  return term && term.toString().replace(/[\\\^\$\*\+\?\.\(\)\|\{}\[\]]/g, '\\$&');
24013};
24014
24015})();
24016(function(){
24017"use strict";
24018
24019
24020MdHighlight.$inject = ["$interpolate", "$parse"];angular
24021    .module('material.components.autocomplete')
24022    .directive('mdHighlightText', MdHighlight);
24023
24024/**
24025 * @ngdoc directive
24026 * @name mdHighlightText
24027 * @module material.components.autocomplete
24028 *
24029 * @description
24030 * The `md-highlight-text` directive allows you to specify text that should be highlighted within
24031 *     an element.  Highlighted text will be wrapped in `<span class="highlight"></span>` which can
24032 *     be styled through CSS.  Please note that child elements may not be used with this directive.
24033 *
24034 * @param {string} md-highlight-text A model to be searched for
24035 * @param {string=} md-highlight-flags A list of flags (loosely based on JavaScript RexExp flags).
24036 * #### **Supported flags**:
24037 * - `g`: Find all matches within the provided text
24038 * - `i`: Ignore case when searching for matches
24039 * - `$`: Only match if the text ends with the search term
24040 * - `^`: Only match if the text begins with the search term
24041 *
24042 * @usage
24043 * <hljs lang="html">
24044 * <input placeholder="Enter a search term..." ng-model="searchTerm" type="text" />
24045 * <ul>
24046 *   <li ng-repeat="result in results" md-highlight-text="searchTerm">
24047 *     {{result.text}}
24048 *   </li>
24049 * </ul>
24050 * </hljs>
24051 */
24052
24053function MdHighlight ($interpolate, $parse) {
24054  return {
24055    terminal: true,
24056    controller: 'MdHighlightCtrl',
24057    compile: function mdHighlightCompile(tElement, tAttr) {
24058      var termExpr = $parse(tAttr.mdHighlightText);
24059      var unsafeContentExpr = $interpolate(tElement.html());
24060
24061      return function mdHighlightLink(scope, element, attr, ctrl) {
24062        ctrl.init(termExpr, unsafeContentExpr);
24063      };
24064    }
24065  };
24066}
24067
24068})();
24069(function(){
24070"use strict";
24071
24072
24073MdChipCtrl.$inject = ["$scope", "$element", "$mdConstant", "$timeout", "$mdUtil"];angular
24074  .module('material.components.chips')
24075  .controller('MdChipCtrl', MdChipCtrl);
24076
24077/**
24078 * Controller for the MdChip component. Responsible for handling keyboard
24079 * events and editting the chip if needed.
24080 *
24081 * @param $scope
24082 * @param $element
24083 * @param $mdConstant
24084 * @param $timeout
24085 * @param $mdUtil
24086 * @constructor
24087 */
24088function MdChipCtrl ($scope, $element, $mdConstant, $timeout, $mdUtil) {
24089  /**
24090   * @type {$scope}
24091   */
24092  this.$scope = $scope;
24093
24094  /**
24095   * @type {$element}
24096   */
24097  this.$element = $element;
24098
24099  /**
24100   * @type {$mdConstant}
24101   */
24102  this.$mdConstant = $mdConstant;
24103
24104  /**
24105   * @type {$timeout}
24106   */
24107  this.$timeout = $timeout;
24108
24109  /**
24110   * @type {$mdUtil}
24111   */
24112  this.$mdUtil = $mdUtil;
24113
24114  /**
24115   * @type {boolean}
24116   */
24117  this.isEditting = false;
24118
24119  /**
24120   * @type {MdChipsCtrl}
24121   */
24122  this.parentController = undefined;
24123
24124  /**
24125   * @type {boolean}
24126   */
24127  this.enableChipEdit = false;
24128}
24129
24130
24131/**
24132 * @param {MdChipsCtrl} controller
24133 */
24134MdChipCtrl.prototype.init = function(controller) {
24135  this.parentController = controller;
24136  this.enableChipEdit = this.parentController.enableChipEdit;
24137
24138  if (this.enableChipEdit) {
24139    this.$element.on('keydown', this.chipKeyDown.bind(this));
24140    this.$element.on('mousedown', this.chipMouseDown.bind(this));
24141    this.getChipContent().addClass('_md-chip-content-edit-is-enabled');
24142  }
24143};
24144
24145
24146/**
24147 * @return {Object}
24148 */
24149MdChipCtrl.prototype.getChipContent = function() {
24150  var chipContents = this.$element[0].getElementsByClassName('md-chip-content');
24151  return angular.element(chipContents[0]);
24152};
24153
24154
24155/**
24156 * @return {Object}
24157 */
24158MdChipCtrl.prototype.getContentElement = function() {
24159  return angular.element(this.getChipContent().children()[0]);
24160};
24161
24162
24163/**
24164 * @return {number}
24165 */
24166MdChipCtrl.prototype.getChipIndex = function() {
24167  return parseInt(this.$element.attr('index'));
24168};
24169
24170
24171/**
24172 * Presents an input element to edit the contents of the chip.
24173 */
24174MdChipCtrl.prototype.goOutOfEditMode = function() {
24175  if (!this.isEditting) return;
24176
24177  this.isEditting = false;
24178  this.$element.removeClass('_md-chip-editing');
24179  this.getChipContent()[0].contentEditable = 'false';
24180  var chipIndex = this.getChipIndex();
24181
24182  var content = this.getContentElement().text();
24183  if (content) {
24184    this.parentController.updateChipContents(
24185        chipIndex,
24186        this.getContentElement().text()
24187    );
24188
24189    this.$mdUtil.nextTick(function() {
24190      if (this.parentController.selectedChip === chipIndex) {
24191        this.parentController.focusChip(chipIndex);
24192      }
24193    }.bind(this));
24194  } else {
24195    this.parentController.removeChipAndFocusInput(chipIndex);
24196  }
24197};
24198
24199
24200/**
24201 * Given an HTML element. Selects contents of it.
24202 * @param node
24203 */
24204MdChipCtrl.prototype.selectNodeContents = function(node) {
24205  var range, selection;
24206  if (document.body.createTextRange) {
24207    range = document.body.createTextRange();
24208    range.moveToElementText(node);
24209    range.select();
24210  } else if (window.getSelection) {
24211    selection = window.getSelection();
24212    range = document.createRange();
24213    range.selectNodeContents(node);
24214    selection.removeAllRanges();
24215    selection.addRange(range);
24216  }
24217};
24218
24219
24220/**
24221 * Presents an input element to edit the contents of the chip.
24222 */
24223MdChipCtrl.prototype.goInEditMode = function() {
24224  this.isEditting = true;
24225  this.$element.addClass('_md-chip-editing');
24226  this.getChipContent()[0].contentEditable = 'true';
24227  this.getChipContent().on('blur', function() {
24228    this.goOutOfEditMode();
24229  }.bind(this));
24230
24231  this.selectNodeContents(this.getChipContent()[0]);
24232};
24233
24234
24235/**
24236 * Handles the keydown event on the chip element. If enable-chip-edit attribute is
24237 * set to true, space or enter keys can trigger going into edit mode. Enter can also
24238 * trigger submitting if the chip is already being edited.
24239 * @param event
24240 */
24241MdChipCtrl.prototype.chipKeyDown = function(event) {
24242  if (!this.isEditting &&
24243    (event.keyCode === this.$mdConstant.KEY_CODE.ENTER ||
24244    event.keyCode === this.$mdConstant.KEY_CODE.SPACE)) {
24245    event.preventDefault();
24246    this.goInEditMode();
24247  } else if (this.isEditting &&
24248    event.keyCode === this.$mdConstant.KEY_CODE.ENTER) {
24249    event.preventDefault();
24250    this.goOutOfEditMode();
24251  }
24252};
24253
24254
24255/**
24256 * Handles the double click event
24257 */
24258MdChipCtrl.prototype.chipMouseDown = function() {
24259  if(this.getChipIndex() == this.parentController.selectedChip &&
24260    this.enableChipEdit &&
24261    !this.isEditting) {
24262    this.goInEditMode();
24263  }
24264};
24265
24266})();
24267(function(){
24268"use strict";
24269
24270
24271MdChip.$inject = ["$mdTheming", "$mdUtil"];angular
24272    .module('material.components.chips')
24273    .directive('mdChip', MdChip);
24274
24275/**
24276 * @ngdoc directive
24277 * @name mdChip
24278 * @module material.components.chips
24279 *
24280 * @description
24281 * `<md-chip>` is a component used within `<md-chips>` and is responsible for rendering individual
24282 * chips.
24283 *
24284 *
24285 * @usage
24286 * <hljs lang="html">
24287 *   <md-chip>{{$chip}}</md-chip>
24288 * </hljs>
24289 *
24290 */
24291
24292// This hint text is hidden within a chip but used by screen readers to
24293// inform the user how they can interact with a chip.
24294var DELETE_HINT_TEMPLATE = '\
24295    <span ng-if="!$mdChipsCtrl.readonly" class="md-visually-hidden">\
24296      {{$mdChipsCtrl.deleteHint}}\
24297    </span>';
24298
24299/**
24300 * MDChip Directive Definition
24301 *
24302 * @param $mdTheming
24303 * @param $mdUtil
24304 * @ngInject
24305 */
24306function MdChip($mdTheming, $mdUtil) {
24307  var hintTemplate = $mdUtil.processTemplate(DELETE_HINT_TEMPLATE);
24308
24309  return {
24310    restrict: 'E',
24311    require: ['^?mdChips', 'mdChip'],
24312    compile:  compile,
24313    controller: 'MdChipCtrl'
24314  };
24315
24316  function compile(element, attr) {
24317    // Append the delete template
24318    element.append($mdUtil.processTemplate(hintTemplate));
24319
24320    return function postLink(scope, element, attr, ctrls) {
24321      var chipsController = ctrls.shift();
24322      var chipController  = ctrls.shift();
24323      $mdTheming(element);
24324
24325      if (chipsController) {
24326        chipController.init(chipsController);
24327
24328        angular
24329          .element(element[0]
24330          .querySelector('.md-chip-content'))
24331          .on('blur', function () {
24332            chipsController.resetSelectedChip();
24333            chipsController.$scope.$applyAsync();
24334          });
24335      }
24336    };
24337  }
24338}
24339
24340})();
24341(function(){
24342"use strict";
24343
24344
24345MdChipRemove.$inject = ["$timeout"];angular
24346    .module('material.components.chips')
24347    .directive('mdChipRemove', MdChipRemove);
24348
24349/**
24350 * @ngdoc directive
24351 * @name mdChipRemove
24352 * @restrict A
24353 * @module material.components.chips
24354 *
24355 * @description
24356 * Designates an element to be used as the delete button for a chip. <br/>
24357 * This element is passed as a child of the `md-chips` element.
24358 *
24359 * The designated button will be just appended to the chip and removes the given chip on click.<br/>
24360 * By default the button is not being styled by the `md-chips` component.
24361 *
24362 * @usage
24363 * <hljs lang="html">
24364 *   <md-chips>
24365 *     <button md-chip-remove="">
24366 *       <md-icon md-svg-icon="md-close"></md-icon>
24367 *     </button>
24368 *   </md-chips>
24369 * </hljs>
24370 */
24371
24372
24373/**
24374 * MdChipRemove Directive Definition.
24375 * 
24376 * @param $timeout
24377 * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}
24378 * @constructor
24379 */
24380function MdChipRemove ($timeout) {
24381  return {
24382    restrict: 'A',
24383    require: '^mdChips',
24384    scope: false,
24385    link: postLink
24386  };
24387
24388  function postLink(scope, element, attr, ctrl) {
24389    element.on('click', function(event) {
24390      scope.$apply(function() {
24391        ctrl.removeChip(scope.$$replacedScope.$index);
24392      });
24393    });
24394
24395    // Child elements aren't available until after a $timeout tick as they are hidden by an
24396    // `ng-if`. see http://goo.gl/zIWfuw
24397    $timeout(function() {
24398      element.attr({ tabindex: -1, 'aria-hidden': true });
24399      element.find('button').attr('tabindex', '-1');
24400    });
24401  }
24402}
24403
24404})();
24405(function(){
24406"use strict";
24407
24408
24409MdChipTransclude.$inject = ["$compile"];angular
24410    .module('material.components.chips')
24411    .directive('mdChipTransclude', MdChipTransclude);
24412
24413function MdChipTransclude ($compile) {
24414  return {
24415    restrict: 'EA',
24416    terminal: true,
24417    link: link,
24418    scope: false
24419  };
24420  function link (scope, element, attr) {
24421    var ctrl = scope.$parent.$mdChipsCtrl,
24422        newScope = ctrl.parent.$new(false, ctrl.parent);
24423    newScope.$$replacedScope = scope;
24424    newScope.$chip = scope.$chip;
24425    newScope.$index = scope.$index;
24426    newScope.$mdChipsCtrl = ctrl;
24427
24428    var newHtml = ctrl.$scope.$eval(attr.mdChipTransclude);
24429
24430    element.html(newHtml);
24431    $compile(element.contents())(newScope);
24432  }
24433}
24434
24435})();
24436(function(){
24437"use strict";
24438
24439
24440MdChipsCtrl.$inject = ["$scope", "$attrs", "$mdConstant", "$log", "$element", "$timeout", "$mdUtil"];angular
24441    .module('material.components.chips')
24442    .controller('MdChipsCtrl', MdChipsCtrl);
24443
24444/**
24445 * Controller for the MdChips component. Responsible for adding to and
24446 * removing from the list of chips, marking chips as selected, and binding to
24447 * the models of various input components.
24448 *
24449 * @param $scope
24450 * @param $attrs
24451 * @param $mdConstant
24452 * @param $log
24453 * @param $element
24454 * @param $timeout
24455 * @param $mdUtil
24456 * @constructor
24457 */
24458function MdChipsCtrl ($scope, $attrs, $mdConstant, $log, $element, $timeout, $mdUtil) {
24459  /** @type {$timeout} **/
24460  this.$timeout = $timeout;
24461
24462  /** @type {Object} */
24463  this.$mdConstant = $mdConstant;
24464
24465  /** @type {angular.$scope} */
24466  this.$scope = $scope;
24467
24468  /** @type {angular.$scope} */
24469  this.parent = $scope.$parent;
24470
24471  /** @type {$log} */
24472  this.$log = $log;
24473
24474  /** @type {$element} */
24475  this.$element = $element;
24476
24477  /** @type {angular.NgModelController} */
24478  this.ngModelCtrl = null;
24479
24480  /** @type {angular.NgModelController} */
24481  this.userInputNgModelCtrl = null;
24482
24483  /** @type {MdAutocompleteCtrl} */
24484  this.autocompleteCtrl = null;
24485
24486  /** @type {Element} */
24487  this.userInputElement = null;
24488
24489  /** @type {Array.<Object>} */
24490  this.items = [];
24491
24492  /** @type {number} */
24493  this.selectedChip = -1;
24494
24495  /** @type {string} */
24496  this.enableChipEdit = $mdUtil.parseAttributeBoolean($attrs.mdEnableChipEdit);
24497
24498  /** @type {string} */
24499  this.addOnBlur = $mdUtil.parseAttributeBoolean($attrs.mdAddOnBlur);
24500
24501  /**
24502   * Hidden hint text for how to delete a chip. Used to give context to screen readers.
24503   * @type {string}
24504   */
24505  this.deleteHint = 'Press delete to remove this chip.';
24506
24507  /**
24508   * Hidden label for the delete button. Used to give context to screen readers.
24509   * @type {string}
24510   */
24511  this.deleteButtonLabel = 'Remove';
24512
24513  /**
24514   * Model used by the input element.
24515   * @type {string}
24516   */
24517  this.chipBuffer = '';
24518
24519  /**
24520   * Whether to use the transformChip expression to transform the chip buffer
24521   * before appending it to the list.
24522   * @type {boolean}
24523   */
24524  this.useTransformChip = false;
24525
24526  /**
24527   * Whether to use the onAdd expression to notify of chip additions.
24528   * @type {boolean}
24529   */
24530  this.useOnAdd = false;
24531
24532  /**
24533   * Whether to use the onRemove expression to notify of chip removals.
24534   * @type {boolean}
24535   */
24536  this.useOnRemove = false;
24537
24538  /**
24539   * Whether to use the onSelect expression to notify the component's user
24540   * after selecting a chip from the list.
24541   * @type {boolean}
24542   */
24543}
24544
24545/**
24546 * Handles the keydown event on the input element: by default <enter> appends
24547 * the buffer to the chip list, while backspace removes the last chip in the
24548 * list if the current buffer is empty.
24549 * @param event
24550 */
24551MdChipsCtrl.prototype.inputKeydown = function(event) {
24552  var chipBuffer = this.getChipBuffer();
24553
24554  // If we have an autocomplete, and it handled the event, we have nothing to do
24555  if (this.autocompleteCtrl && event.isDefaultPrevented && event.isDefaultPrevented()) {
24556    return;
24557  }
24558
24559  if (event.keyCode === this.$mdConstant.KEY_CODE.BACKSPACE) {
24560    // Only select and focus the previous chip, if the current caret position of the
24561    // input element is at the beginning.
24562    if (this.getCursorPosition(event.target) !== 0) {
24563      return;
24564    }
24565
24566    event.preventDefault();
24567    event.stopPropagation();
24568
24569    if (this.items.length) {
24570      this.selectAndFocusChipSafe(this.items.length - 1);
24571    }
24572
24573    return;
24574  }
24575
24576  // By default <enter> appends the buffer to the chip list.
24577  if (!this.separatorKeys || this.separatorKeys.length < 1) {
24578    this.separatorKeys = [this.$mdConstant.KEY_CODE.ENTER];
24579  }
24580
24581  // Support additional separator key codes in an array of `md-separator-keys`.
24582  if (this.separatorKeys.indexOf(event.keyCode) !== -1) {
24583    if ((this.autocompleteCtrl && this.requireMatch) || !chipBuffer) return;
24584    event.preventDefault();
24585
24586    // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
24587    if (this.hasMaxChipsReached()) return;
24588
24589    this.appendChip(chipBuffer.trim());
24590    this.resetChipBuffer();
24591  }
24592};
24593
24594/**
24595 * Returns the cursor position of the specified input element.
24596 * @param element HTMLInputElement
24597 * @returns {Number} Cursor Position of the input.
24598 */
24599MdChipsCtrl.prototype.getCursorPosition = function(element) {
24600  /*
24601   * Figure out whether the current input for the chips buffer is valid for using
24602   * the selectionStart / end property to retrieve the cursor position.
24603   * Some browsers do not allow the use of those attributes, on different input types.
24604   */
24605  try {
24606    if (element.selectionStart === element.selectionEnd) {
24607      return element.selectionStart;
24608    }
24609  } catch (e) {
24610    if (!element.value) {
24611      return 0;
24612    }
24613  }
24614};
24615
24616
24617/**
24618 * Updates the content of the chip at given index
24619 * @param chipIndex
24620 * @param chipContents
24621 */
24622MdChipsCtrl.prototype.updateChipContents = function(chipIndex, chipContents){
24623  if(chipIndex >= 0 && chipIndex < this.items.length) {
24624    this.items[chipIndex] = chipContents;
24625    this.ngModelCtrl.$setDirty();
24626  }
24627};
24628
24629
24630/**
24631 * Returns true if a chip is currently being edited. False otherwise.
24632 * @return {boolean}
24633 */
24634MdChipsCtrl.prototype.isEditingChip = function() {
24635  return !!this.$element[0].getElementsByClassName('_md-chip-editing').length;
24636};
24637
24638
24639MdChipsCtrl.prototype.isRemovable = function() {
24640  // Return false if we have static chips
24641  if (!this.ngModelCtrl) {
24642    return false;
24643  }
24644
24645  return this.readonly ? this.removable :
24646         angular.isDefined(this.removable) ? this.removable : true;
24647};
24648
24649/**
24650 * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
24651 * keys switch which chips is active
24652 * @param event
24653 */
24654MdChipsCtrl.prototype.chipKeydown = function (event) {
24655  if (this.getChipBuffer()) return;
24656  if (this.isEditingChip()) return;
24657  
24658  switch (event.keyCode) {
24659    case this.$mdConstant.KEY_CODE.BACKSPACE:
24660    case this.$mdConstant.KEY_CODE.DELETE:
24661      if (this.selectedChip < 0) return;
24662      event.preventDefault();
24663      // Cancel the delete action only after the event cancel. Otherwise the page will go back.
24664      if (!this.isRemovable()) return;
24665      this.removeAndSelectAdjacentChip(this.selectedChip);
24666      break;
24667    case this.$mdConstant.KEY_CODE.LEFT_ARROW:
24668      event.preventDefault();
24669      if (this.selectedChip < 0) this.selectedChip = this.items.length;
24670      if (this.items.length) this.selectAndFocusChipSafe(this.selectedChip - 1);
24671      break;
24672    case this.$mdConstant.KEY_CODE.RIGHT_ARROW:
24673      event.preventDefault();
24674      this.selectAndFocusChipSafe(this.selectedChip + 1);
24675      break;
24676    case this.$mdConstant.KEY_CODE.ESCAPE:
24677    case this.$mdConstant.KEY_CODE.TAB:
24678      if (this.selectedChip < 0) return;
24679      event.preventDefault();
24680      this.onFocus();
24681      break;
24682  }
24683};
24684
24685/**
24686 * Get the input's placeholder - uses `placeholder` when list is empty and `secondary-placeholder`
24687 * when the list is non-empty. If `secondary-placeholder` is not provided, `placeholder` is used
24688 * always.
24689 */
24690MdChipsCtrl.prototype.getPlaceholder = function() {
24691  // Allow `secondary-placeholder` to be blank.
24692  var useSecondary = (this.items && this.items.length &&
24693      (this.secondaryPlaceholder == '' || this.secondaryPlaceholder));
24694  return useSecondary ? this.secondaryPlaceholder : this.placeholder;
24695};
24696
24697/**
24698 * Removes chip at {@code index} and selects the adjacent chip.
24699 * @param index
24700 */
24701MdChipsCtrl.prototype.removeAndSelectAdjacentChip = function(index) {
24702  var selIndex = this.getAdjacentChipIndex(index);
24703  this.removeChip(index);
24704  this.$timeout(angular.bind(this, function () {
24705      this.selectAndFocusChipSafe(selIndex);
24706  }));
24707};
24708
24709/**
24710 * Sets the selected chip index to -1.
24711 */
24712MdChipsCtrl.prototype.resetSelectedChip = function() {
24713  this.selectedChip = -1;
24714};
24715
24716/**
24717 * Gets the index of an adjacent chip to select after deletion. Adjacency is
24718 * determined as the next chip in the list, unless the target chip is the
24719 * last in the list, then it is the chip immediately preceding the target. If
24720 * there is only one item in the list, -1 is returned (select none).
24721 * The number returned is the index to select AFTER the target has been
24722 * removed.
24723 * If the current chip is not selected, then -1 is returned to select none.
24724 */
24725MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {
24726  var len = this.items.length - 1;
24727  return (len == 0) ? -1 :
24728      (index == len) ? index -1 : index;
24729};
24730
24731/**
24732 * Append the contents of the buffer to the chip list. This method will first
24733 * call out to the md-transform-chip method, if provided.
24734 *
24735 * @param newChip
24736 */
24737MdChipsCtrl.prototype.appendChip = function(newChip) {
24738  if (this.useTransformChip && this.transformChip) {
24739    var transformedChip = this.transformChip({'$chip': newChip});
24740
24741    // Check to make sure the chip is defined before assigning it, otherwise, we'll just assume
24742    // they want the string version.
24743    if (angular.isDefined(transformedChip)) {
24744      newChip = transformedChip;
24745    }
24746  }
24747
24748  // If items contains an identical object to newChip, do not append
24749  if (angular.isObject(newChip)){
24750    var identical = this.items.some(function(item){
24751      return angular.equals(newChip, item);
24752    });
24753    if (identical) return;
24754  }
24755
24756  // Check for a null (but not undefined), or existing chip and cancel appending
24757  if (newChip == null || this.items.indexOf(newChip) + 1) return;
24758
24759  // Append the new chip onto our list
24760  var index = this.items.push(newChip);
24761
24762  // Update model validation
24763  this.ngModelCtrl.$setDirty();
24764  this.validateModel();
24765
24766  // If they provide the md-on-add attribute, notify them of the chip addition
24767  if (this.useOnAdd && this.onAdd) {
24768    this.onAdd({ '$chip': newChip, '$index': index });
24769  }
24770};
24771
24772/**
24773 * Sets whether to use the md-transform-chip expression. This expression is
24774 * bound to scope and controller in {@code MdChipsDirective} as
24775 * {@code transformChip}. Due to the nature of directive scope bindings, the
24776 * controller cannot know on its own/from the scope whether an expression was
24777 * actually provided.
24778 */
24779MdChipsCtrl.prototype.useTransformChipExpression = function() {
24780  this.useTransformChip = true;
24781};
24782
24783/**
24784 * Sets whether to use the md-on-add expression. This expression is
24785 * bound to scope and controller in {@code MdChipsDirective} as
24786 * {@code onAdd}. Due to the nature of directive scope bindings, the
24787 * controller cannot know on its own/from the scope whether an expression was
24788 * actually provided.
24789 */
24790MdChipsCtrl.prototype.useOnAddExpression = function() {
24791  this.useOnAdd = true;
24792};
24793
24794/**
24795 * Sets whether to use the md-on-remove expression. This expression is
24796 * bound to scope and controller in {@code MdChipsDirective} as
24797 * {@code onRemove}. Due to the nature of directive scope bindings, the
24798 * controller cannot know on its own/from the scope whether an expression was
24799 * actually provided.
24800 */
24801MdChipsCtrl.prototype.useOnRemoveExpression = function() {
24802  this.useOnRemove = true;
24803};
24804
24805/*
24806 * Sets whether to use the md-on-select expression. This expression is
24807 * bound to scope and controller in {@code MdChipsDirective} as
24808 * {@code onSelect}. Due to the nature of directive scope bindings, the
24809 * controller cannot know on its own/from the scope whether an expression was
24810 * actually provided.
24811 */
24812MdChipsCtrl.prototype.useOnSelectExpression = function() {
24813  this.useOnSelect = true;
24814};
24815
24816/**
24817 * Gets the input buffer. The input buffer can be the model bound to the
24818 * default input item {@code this.chipBuffer}, the {@code selectedItem}
24819 * model of an {@code md-autocomplete}, or, through some magic, the model
24820 * bound to any inpput or text area element found within a
24821 * {@code md-input-container} element.
24822 * @return {Object|string}
24823 */
24824MdChipsCtrl.prototype.getChipBuffer = function() {
24825  return !this.userInputElement ? this.chipBuffer :
24826      this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :
24827          this.userInputElement[0].value;
24828};
24829
24830/**
24831 * Resets the input buffer for either the internal input or user provided input element.
24832 */
24833MdChipsCtrl.prototype.resetChipBuffer = function() {
24834  if (this.userInputElement) {
24835    if (this.userInputNgModelCtrl) {
24836      this.userInputNgModelCtrl.$setViewValue('');
24837      this.userInputNgModelCtrl.$render();
24838    } else {
24839      this.userInputElement[0].value = '';
24840    }
24841  } else {
24842    this.chipBuffer = '';
24843  }
24844};
24845
24846MdChipsCtrl.prototype.hasMaxChipsReached = function() {
24847  if (angular.isString(this.maxChips)) this.maxChips = parseInt(this.maxChips, 10) || 0;
24848
24849  return this.maxChips > 0 && this.items.length >= this.maxChips;
24850};
24851
24852/**
24853 * Updates the validity properties for the ngModel.
24854 */
24855MdChipsCtrl.prototype.validateModel = function() {
24856  this.ngModelCtrl.$setValidity('md-max-chips', !this.hasMaxChipsReached());
24857};
24858
24859/**
24860 * Removes the chip at the given index.
24861 * @param index
24862 */
24863MdChipsCtrl.prototype.removeChip = function(index) {
24864  var removed = this.items.splice(index, 1);
24865
24866  // Update model validation
24867  this.ngModelCtrl.$setDirty();
24868  this.validateModel();
24869
24870  if (removed && removed.length && this.useOnRemove && this.onRemove) {
24871    this.onRemove({ '$chip': removed[0], '$index': index });
24872  }
24873};
24874
24875MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {
24876  this.removeChip(index);
24877
24878  if (this.autocompleteCtrl) {
24879    // Always hide the autocomplete dropdown before focusing the autocomplete input.
24880    // Wait for the input to move horizontally, because the chip was removed.
24881    // This can lead to an incorrect dropdown position.
24882    this.autocompleteCtrl.hidden = true;
24883    this.$mdUtil.nextTick(this.onFocus.bind(this));
24884  } else {
24885    this.onFocus();
24886  }
24887
24888};
24889/**
24890 * Selects the chip at `index`,
24891 * @param index
24892 */
24893MdChipsCtrl.prototype.selectAndFocusChipSafe = function(index) {
24894  if (!this.items.length) {
24895    this.selectChip(-1);
24896    this.onFocus();
24897    return;
24898  }
24899  if (index === this.items.length) return this.onFocus();
24900  index = Math.max(index, 0);
24901  index = Math.min(index, this.items.length - 1);
24902  this.selectChip(index);
24903  this.focusChip(index);
24904};
24905
24906/**
24907 * Marks the chip at the given index as selected.
24908 * @param index
24909 */
24910MdChipsCtrl.prototype.selectChip = function(index) {
24911  if (index >= -1 && index <= this.items.length) {
24912    this.selectedChip = index;
24913
24914    // Fire the onSelect if provided
24915    if (this.useOnSelect && this.onSelect) {
24916      this.onSelect({'$chip': this.items[this.selectedChip] });
24917    }
24918  } else {
24919    this.$log.warn('Selected Chip index out of bounds; ignoring.');
24920  }
24921};
24922
24923/**
24924 * Selects the chip at `index` and gives it focus.
24925 * @param index
24926 */
24927MdChipsCtrl.prototype.selectAndFocusChip = function(index) {
24928  this.selectChip(index);
24929  if (index != -1) {
24930    this.focusChip(index);
24931  }
24932};
24933
24934/**
24935 * Call `focus()` on the chip at `index`
24936 */
24937MdChipsCtrl.prototype.focusChip = function(index) {
24938  this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content').focus();
24939};
24940
24941/**
24942 * Configures the required interactions with the ngModel Controller.
24943 * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.
24944 * @param ngModelCtrl
24945 */
24946MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {
24947  this.ngModelCtrl = ngModelCtrl;
24948
24949  var self = this;
24950  ngModelCtrl.$render = function() {
24951    // model is updated. do something.
24952    self.items = self.ngModelCtrl.$viewValue;
24953  };
24954};
24955
24956MdChipsCtrl.prototype.onFocus = function () {
24957  var input = this.$element[0].querySelector('input');
24958  input && input.focus();
24959  this.resetSelectedChip();
24960};
24961
24962MdChipsCtrl.prototype.onInputFocus = function () {
24963  this.inputHasFocus = true;
24964  this.resetSelectedChip();
24965};
24966
24967MdChipsCtrl.prototype.onInputBlur = function () {
24968  this.inputHasFocus = false;
24969
24970  var chipBuffer = this.getChipBuffer().trim();
24971
24972  // Update the custom chip validators.
24973  this.validateModel();
24974
24975  var isModelValid = this.ngModelCtrl.$valid;
24976
24977  if (this.userInputNgModelCtrl) {
24978    isModelValid &= this.userInputNgModelCtrl.$valid;
24979  }
24980
24981  // Only append the chip and reset the chip buffer if the chips and input ngModel is valid.
24982  if (this.addOnBlur && chipBuffer && isModelValid) {
24983    this.appendChip(chipBuffer);
24984    this.resetChipBuffer();
24985  }
24986};
24987
24988/**
24989 * Configure event bindings on a user-provided input element.
24990 * @param inputElement
24991 */
24992MdChipsCtrl.prototype.configureUserInput = function(inputElement) {
24993  this.userInputElement = inputElement;
24994
24995  // Find the NgModelCtrl for the input element
24996  var ngModelCtrl = inputElement.controller('ngModel');
24997  // `.controller` will look in the parent as well.
24998  if (ngModelCtrl != this.ngModelCtrl) {
24999    this.userInputNgModelCtrl = ngModelCtrl;
25000  }
25001
25002  var scope = this.$scope;
25003  var ctrl = this;
25004
25005  // Run all of the events using evalAsync because a focus may fire a blur in the same digest loop
25006  var scopeApplyFn = function(event, fn) {
25007    scope.$evalAsync(angular.bind(ctrl, fn, event));
25008  };
25009
25010  // Bind to keydown and focus events of input
25011  inputElement
25012      .attr({ tabindex: 0 })
25013      .on('keydown', function(event) { scopeApplyFn(event, ctrl.inputKeydown) })
25014      .on('focus', function(event) { scopeApplyFn(event, ctrl.onInputFocus) })
25015      .on('blur', function(event) { scopeApplyFn(event, ctrl.onInputBlur) })
25016};
25017
25018MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {
25019  if (ctrl) {
25020    this.autocompleteCtrl = ctrl;
25021
25022    ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {
25023      if (item) {
25024        // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
25025        if (this.hasMaxChipsReached()) return;
25026
25027        this.appendChip(item);
25028        this.resetChipBuffer();
25029      }
25030    }));
25031
25032    this.$element.find('input')
25033        .on('focus',angular.bind(this, this.onInputFocus) )
25034        .on('blur', angular.bind(this, this.onInputBlur) );
25035  }
25036};
25037
25038MdChipsCtrl.prototype.hasFocus = function () {
25039  return this.inputHasFocus || this.selectedChip >= 0;
25040};
25041
25042})();
25043(function(){
25044"use strict";
25045
25046  
25047  MdChips.$inject = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout", "$$mdSvgRegistry"];angular
25048      .module('material.components.chips')
25049      .directive('mdChips', MdChips);
25050
25051  /**
25052   * @ngdoc directive
25053   * @name mdChips
25054   * @module material.components.chips
25055   *
25056   * @description
25057   * `<md-chips>` is an input component for building lists of strings or objects. The list items are
25058   * displayed as 'chips'. This component can make use of an `<input>` element or an 
25059   * `<md-autocomplete>` element.
25060   *
25061   * ### Custom templates
25062   * A custom template may be provided to render the content of each chip. This is achieved by
25063   * specifying an `<md-chip-template>` element containing the custom content as a child of
25064   * `<md-chips>`.
25065   *
25066   * Note: Any attributes on
25067   * `<md-chip-template>` will be dropped as only the innerHTML is used for the chip template. The
25068   * variables `$chip` and `$index` are available in the scope of `<md-chip-template>`, representing
25069   * the chip object and its index in the list of chips, respectively.
25070   * To override the chip delete control, include an element (ideally a button) with the attribute
25071   * `md-chip-remove`. A click listener to remove the chip will be added automatically. The element
25072   * is also placed as a sibling to the chip content (on which there are also click listeners) to
25073   * avoid a nested ng-click situation.
25074   *
25075   * <h3> Pending Features </h3>
25076   * <ul style="padding-left:20px;">
25077   *
25078   *   <ul>Style
25079   *     <li>Colours for hover, press states (ripple?).</li>
25080   *   </ul>
25081   *
25082   *   <ul>Validation
25083   *     <li>allow a validation callback</li>
25084   *     <li>hilighting style for invalid chips</li>
25085   *   </ul>
25086   *
25087   *   <ul>Item mutation
25088   *     <li>Support `
25089   *       <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double
25090   *       click?
25091   *     </li>
25092   *   </ul>
25093   *
25094   *   <ul>Truncation and Disambiguation (?)
25095   *     <li>Truncate chip text where possible, but do not truncate entries such that two are
25096   *     indistinguishable.</li>
25097   *   </ul>
25098   *
25099   *   <ul>Drag and Drop
25100   *     <li>Drag and drop chips between related `<md-chips>` elements.
25101   *     </li>
25102   *   </ul>
25103   * </ul>
25104   *
25105   * <span style="font-size:.8em;text-align:center">
25106   *   Warning: This component is a WORK IN PROGRESS. If you use it now,
25107   *   it will probably break on you in the future.
25108   * </span>
25109   *
25110   * Sometimes developers want to limit the amount of possible chips.<br/>
25111   * You can specify the maximum amount of chips by using the following markup.
25112   *
25113   * <hljs lang="html">
25114   *   <md-chips
25115   *       ng-model="myItems"
25116   *       placeholder="Add an item"
25117   *       md-max-chips="5">
25118   *   </md-chips>
25119   * </hljs>
25120   *
25121   * In some cases, you have an autocomplete inside of the `md-chips`.<br/>
25122   * When the maximum amount of chips has been reached, you can also disable the autocomplete selection.<br/>
25123   * Here is an example markup.
25124   *
25125   * <hljs lang="html">
25126   *   <md-chips ng-model="myItems" md-max-chips="5">
25127   *     <md-autocomplete ng-hide="myItems.length > 5" ...></md-autocomplete>
25128   *   </md-chips>
25129   * </hljs>
25130   *
25131   * @param {string=|object=} ng-model A model to bind the list of items to
25132   * @param {string=} placeholder Placeholder text that will be forwarded to the input.
25133   * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
25134   *    displayed when there is at least one item in the list
25135   * @param {boolean=} md-removable Enables or disables the deletion of chips through the
25136   *    removal icon or the Delete/Backspace key. Defaults to true.
25137   * @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
25138   *    the input and delete buttons. If no `ng-model` is provided, the chips will automatically be
25139   *    marked as readonly.<br/><br/>
25140   *    When `md-removable` is not defined, the `md-remove` behavior will be overwritten and disabled.
25141   * @param {string=} md-enable-chip-edit Set this to "true" to enable editing of chip contents. The user can 
25142   *    go into edit mode with pressing "space", "enter", or double clicking on the chip. Chip edit is only
25143   *    supported for chips with basic template.
25144   * @param {number=} md-max-chips The maximum number of chips allowed to add through user input.
25145   *    <br/><br/>The validation property `md-max-chips` can be used when the max chips
25146   *    amount is reached.
25147   * @param {boolean=} md-add-on-blur When set to true, remaining text inside of the input will
25148   *    be converted into a new chip on blur.
25149   * @param {expression} md-transform-chip An expression of form `myFunction($chip)` that when called
25150   *    expects one of the following return values:
25151   *    - an object representing the `$chip` input string
25152   *    - `undefined` to simply add the `$chip` input string, or
25153   *    - `null` to prevent the chip from being appended
25154   * @param {expression=} md-on-add An expression which will be called when a chip has been
25155   *    added.
25156   * @param {expression=} md-on-remove An expression which will be called when a chip has been
25157   *    removed.
25158   * @param {expression=} md-on-select An expression which will be called when a chip is selected.
25159   * @param {boolean} md-require-match If true, and the chips template contains an autocomplete,
25160   *    only allow selection of pre-defined chips (i.e. you cannot add new ones).
25161   * @param {string=} delete-hint A string read by screen readers instructing users that pressing
25162   *    the delete key will remove the chip.
25163   * @param {string=} delete-button-label A label for the delete button. Also hidden and read by
25164   *    screen readers.
25165   * @param {expression=} md-separator-keys An array of key codes used to separate chips.
25166   *
25167   * @usage
25168   * <hljs lang="html">
25169   *   <md-chips
25170   *       ng-model="myItems"
25171   *       placeholder="Add an item"
25172   *       readonly="isReadOnly">
25173   *   </md-chips>
25174   * </hljs>
25175   *
25176   * <h3>Validation</h3>
25177   * When using [ngMessages](https://docs.angularjs.org/api/ngMessages), you can show errors based
25178   * on our custom validators.
25179   * <hljs lang="html">
25180   *   <form name="userForm">
25181   *     <md-chips
25182   *       name="fruits"
25183   *       ng-model="myItems"
25184   *       placeholder="Add an item"
25185   *       md-max-chips="5">
25186   *     </md-chips>
25187   *     <div ng-messages="userForm.fruits.$error" ng-if="userForm.$dirty">
25188   *       <div ng-message="md-max-chips">You reached the maximum amount of chips</div>
25189   *    </div>
25190   *   </form>
25191   * </hljs>
25192   *
25193   */
25194
25195
25196  var MD_CHIPS_TEMPLATE = '\
25197      <md-chips-wrap\
25198          ng-keydown="$mdChipsCtrl.chipKeydown($event)"\
25199          ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus(), \
25200                      \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly,\
25201                      \'md-removable\': $mdChipsCtrl.isRemovable() }"\
25202          class="md-chips">\
25203        <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
25204            index="{{$index}}"\
25205            ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index, \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly}">\
25206          <div class="md-chip-content"\
25207              tabindex="-1"\
25208              aria-hidden="true"\
25209              ng-click="!$mdChipsCtrl.readonly && $mdChipsCtrl.focusChip($index)"\
25210              ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)"\
25211              md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div>\
25212          <div ng-if="$mdChipsCtrl.isRemovable()"\
25213               class="md-chip-remove-container"\
25214               md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\
25215        </md-chip>\
25216        <div class="md-chip-input-container" ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl">\
25217          <div md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\
25218        </div>\
25219      </md-chips-wrap>';
25220
25221  var CHIP_INPUT_TEMPLATE = '\
25222        <input\
25223            class="md-input"\
25224            tabindex="0"\
25225            placeholder="{{$mdChipsCtrl.getPlaceholder()}}"\
25226            aria-label="{{$mdChipsCtrl.getPlaceholder()}}"\
25227            ng-model="$mdChipsCtrl.chipBuffer"\
25228            ng-focus="$mdChipsCtrl.onInputFocus()"\
25229            ng-blur="$mdChipsCtrl.onInputBlur()"\
25230            ng-keydown="$mdChipsCtrl.inputKeydown($event)">';
25231
25232  var CHIP_DEFAULT_TEMPLATE = '\
25233      <span>{{$chip}}</span>';
25234
25235  var CHIP_REMOVE_TEMPLATE = '\
25236      <button\
25237          class="md-chip-remove"\
25238          ng-if="$mdChipsCtrl.isRemovable()"\
25239          ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
25240          type="button"\
25241          aria-hidden="true"\
25242          tabindex="-1">\
25243        <md-icon md-svg-src="{{ $mdChipsCtrl.mdCloseIcon }}"></md-icon>\
25244        <span class="md-visually-hidden">\
25245          {{$mdChipsCtrl.deleteButtonLabel}}\
25246        </span>\
25247      </button>';
25248
25249  /**
25250   * MDChips Directive Definition
25251   */
25252  function MdChips ($mdTheming, $mdUtil, $compile, $log, $timeout, $$mdSvgRegistry) {
25253    // Run our templates through $mdUtil.processTemplate() to allow custom start/end symbols
25254    var templates = getTemplates();
25255
25256    return {
25257      template: function(element, attrs) {
25258        // Clone the element into an attribute. By prepending the attribute
25259        // name with '$', Angular won't write it into the DOM. The cloned
25260        // element propagates to the link function via the attrs argument,
25261        // where various contained-elements can be consumed.
25262        attrs['$mdUserTemplate'] = element.clone();
25263        return templates.chips;
25264      },
25265      require: ['mdChips'],
25266      restrict: 'E',
25267      controller: 'MdChipsCtrl',
25268      controllerAs: '$mdChipsCtrl',
25269      bindToController: true,
25270      compile: compile,
25271      scope: {
25272        readonly: '=readonly',
25273        removable: '=mdRemovable',
25274        placeholder: '@',
25275        secondaryPlaceholder: '@',
25276        maxChips: '@mdMaxChips',
25277        transformChip: '&mdTransformChip',
25278        onAppend: '&mdOnAppend',
25279        onAdd: '&mdOnAdd',
25280        onRemove: '&mdOnRemove',
25281        onSelect: '&mdOnSelect',
25282        deleteHint: '@',
25283        deleteButtonLabel: '@',
25284        separatorKeys: '=?mdSeparatorKeys',
25285        requireMatch: '=?mdRequireMatch'
25286      }
25287    };
25288
25289    /**
25290     * Builds the final template for `md-chips` and returns the postLink function.
25291     *
25292     * Building the template involves 3 key components:
25293     * static chips
25294     * chip template
25295     * input control
25296     *
25297     * If no `ng-model` is provided, only the static chip work needs to be done.
25298     *
25299     * If no user-passed `md-chip-template` exists, the default template is used. This resulting
25300     * template is appended to the chip content element.
25301     *
25302     * The remove button may be overridden by passing an element with an md-chip-remove attribute.
25303     *
25304     * If an `input` or `md-autocomplete` element is provided by the caller, it is set aside for
25305     * transclusion later. The transclusion happens in `postLink` as the parent scope is required.
25306     * If no user input is provided, a default one is appended to the input container node in the
25307     * template.
25308     *
25309     * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for
25310     * transclusion in the `postLink` function.
25311     *
25312     *
25313     * @param element
25314     * @param attr
25315     * @returns {Function}
25316     */
25317    function compile(element, attr) {
25318      // Grab the user template from attr and reset the attribute to null.
25319      var userTemplate = attr['$mdUserTemplate'];
25320      attr['$mdUserTemplate'] = null;
25321
25322      var chipTemplate = getTemplateByQuery('md-chips>md-chip-template');
25323
25324      var chipRemoveSelector = $mdUtil
25325        .prefixer()
25326        .buildList('md-chip-remove')
25327        .map(function(attr) {
25328          return 'md-chips>*[' + attr + ']';
25329        })
25330        .join(',');
25331
25332      // Set the chip remove, chip contents and chip input templates. The link function will put
25333      // them on the scope for transclusion later.
25334      var chipRemoveTemplate   = getTemplateByQuery(chipRemoveSelector) || templates.remove,
25335          chipContentsTemplate = chipTemplate || templates.default,
25336          chipInputTemplate    = getTemplateByQuery('md-chips>md-autocomplete')
25337              || getTemplateByQuery('md-chips>input')
25338              || templates.input,
25339          staticChips = userTemplate.find('md-chip');
25340
25341      // Warn of malformed template. See #2545
25342      if (userTemplate[0].querySelector('md-chip-template>*[md-chip-remove]')) {
25343        $log.warn('invalid placement of md-chip-remove within md-chip-template.');
25344      }
25345
25346      function getTemplateByQuery (query) {
25347        if (!attr.ngModel) return;
25348        var element = userTemplate[0].querySelector(query);
25349        return element && element.outerHTML;
25350      }
25351
25352      /**
25353       * Configures controller and transcludes.
25354       */
25355      return function postLink(scope, element, attrs, controllers) {
25356        $mdUtil.initOptionalProperties(scope, attr);
25357
25358        $mdTheming(element);
25359        var mdChipsCtrl = controllers[0];
25360        if(chipTemplate) {
25361          // Chip editing functionality assumes we are using the default chip template.
25362          mdChipsCtrl.enableChipEdit = false;
25363        }
25364
25365        mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;
25366        mdChipsCtrl.chipRemoveTemplate   = chipRemoveTemplate;
25367        mdChipsCtrl.chipInputTemplate    = chipInputTemplate;
25368
25369        mdChipsCtrl.mdCloseIcon = $$mdSvgRegistry.mdClose;
25370
25371        element
25372            .attr({ 'aria-hidden': true, tabindex: -1 })
25373            .on('focus', function () { mdChipsCtrl.onFocus(); });
25374
25375        if (attr.ngModel) {
25376          mdChipsCtrl.configureNgModel(element.controller('ngModel'));
25377
25378          // If an `md-transform-chip` attribute was set, tell the controller to use the expression
25379          // before appending chips.
25380          if (attrs.mdTransformChip) mdChipsCtrl.useTransformChipExpression();
25381
25382          // If an `md-on-append` attribute was set, tell the controller to use the expression
25383          // when appending chips.
25384          //
25385          // DEPRECATED: Will remove in official 1.0 release
25386          if (attrs.mdOnAppend) mdChipsCtrl.useOnAppendExpression();
25387
25388          // If an `md-on-add` attribute was set, tell the controller to use the expression
25389          // when adding chips.
25390          if (attrs.mdOnAdd) mdChipsCtrl.useOnAddExpression();
25391
25392          // If an `md-on-remove` attribute was set, tell the controller to use the expression
25393          // when removing chips.
25394          if (attrs.mdOnRemove) mdChipsCtrl.useOnRemoveExpression();
25395
25396          // If an `md-on-select` attribute was set, tell the controller to use the expression
25397          // when selecting chips.
25398          if (attrs.mdOnSelect) mdChipsCtrl.useOnSelectExpression();
25399
25400          // The md-autocomplete and input elements won't be compiled until after this directive
25401          // is complete (due to their nested nature). Wait a tick before looking for them to
25402          // configure the controller.
25403          if (chipInputTemplate != templates.input) {
25404            // The autocomplete will not appear until the readonly attribute is not true (i.e.
25405            // false or undefined), so we have to watch the readonly and then on the next tick
25406            // after the chip transclusion has run, we can configure the autocomplete and user
25407            // input.
25408            scope.$watch('$mdChipsCtrl.readonly', function(readonly) {
25409              if (!readonly) {
25410
25411                $mdUtil.nextTick(function(){
25412
25413                  if (chipInputTemplate.indexOf('<md-autocomplete') === 0) {
25414                    var autocompleteEl = element.find('md-autocomplete');
25415                    mdChipsCtrl.configureAutocomplete(autocompleteEl.controller('mdAutocomplete'));
25416                  }
25417
25418                  mdChipsCtrl.configureUserInput(element.find('input'));
25419                });
25420              }
25421            });
25422          }
25423
25424          // At the next tick, if we find an input, make sure it has the md-input class
25425          $mdUtil.nextTick(function() {
25426            var input = element.find('input');
25427
25428            input && input.toggleClass('md-input', true);
25429          });
25430        }
25431
25432        // Compile with the parent's scope and prepend any static chips to the wrapper.
25433        if (staticChips.length > 0) {
25434          var compiledStaticChips = $compile(staticChips.clone())(scope.$parent);
25435          $timeout(function() { element.find('md-chips-wrap').prepend(compiledStaticChips); });
25436        }
25437      };
25438    }
25439
25440    function getTemplates() {
25441      return {
25442        chips: $mdUtil.processTemplate(MD_CHIPS_TEMPLATE),
25443        input: $mdUtil.processTemplate(CHIP_INPUT_TEMPLATE),
25444        default: $mdUtil.processTemplate(CHIP_DEFAULT_TEMPLATE),
25445        remove: $mdUtil.processTemplate(CHIP_REMOVE_TEMPLATE)
25446      };
25447    }
25448  }
25449
25450})();
25451(function(){
25452"use strict";
25453
25454angular
25455    .module('material.components.chips')
25456    .controller('MdContactChipsCtrl', MdContactChipsCtrl);
25457
25458
25459
25460/**
25461 * Controller for the MdContactChips component
25462 * @constructor
25463 */
25464function MdContactChipsCtrl () {
25465  /** @type {Object} */
25466  this.selectedItem = null;
25467
25468  /** @type {string} */
25469  this.searchText = '';
25470}
25471
25472
25473MdContactChipsCtrl.prototype.queryContact = function(searchText) {
25474  var results = this.contactQuery({'$query': searchText});
25475  return this.filterSelected ?
25476      results.filter(angular.bind(this, this.filterSelectedContacts)) : results;
25477};
25478
25479
25480MdContactChipsCtrl.prototype.itemName = function(item) {
25481  return item[this.contactName];
25482};
25483
25484
25485MdContactChipsCtrl.prototype.filterSelectedContacts = function(contact) {
25486  return this.contacts.indexOf(contact) == -1;
25487};
25488
25489})();
25490(function(){
25491"use strict";
25492
25493
25494MdContactChips.$inject = ["$mdTheming", "$mdUtil"];angular
25495  .module('material.components.chips')
25496  .directive('mdContactChips', MdContactChips);
25497
25498/**
25499 * @ngdoc directive
25500 * @name mdContactChips
25501 * @module material.components.chips
25502 *
25503 * @description
25504 * `<md-contact-chips>` is an input component based on `md-chips` and makes use of an
25505 * `md-autocomplete` element. The component allows the caller to supply a query expression which
25506 * returns  a list of possible contacts. The user can select one of these and add it to the list of
25507 * chips.
25508 *
25509 * You may also use the `md-highlight-text` directive along with its parameters to control the
25510 * appearance of the matched text inside of the contacts' autocomplete popup.
25511 *
25512 * @param {string=|object=} ng-model A model to bind the list of items to
25513 * @param {string=} placeholder Placeholder text that will be forwarded to the input.
25514 * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
25515 *    displayed when there is at least on item in the list
25516 * @param {expression} md-contacts An expression expected to return contacts matching the search
25517 *    test, `$query`. If this expression involves a promise, a loading bar is displayed while
25518 *    waiting for it to resolve.
25519 * @param {string} md-contact-name The field name of the contact object representing the
25520 *    contact's name.
25521 * @param {string} md-contact-email The field name of the contact object representing the
25522 *    contact's email address.
25523 * @param {string} md-contact-image The field name of the contact object representing the
25524 *    contact's image.
25525 *
25526 *
25527 * @param {expression=} filter-selected Whether to filter selected contacts from the list of
25528 *    suggestions shown in the autocomplete. This attribute has been removed but may come back.
25529 *
25530 *
25531 *
25532 * @usage
25533 * <hljs lang="html">
25534 *   <md-contact-chips
25535 *       ng-model="ctrl.contacts"
25536 *       md-contacts="ctrl.querySearch($query)"
25537 *       md-contact-name="name"
25538 *       md-contact-image="image"
25539 *       md-contact-email="email"
25540 *       placeholder="To">
25541 *   </md-contact-chips>
25542 * </hljs>
25543 *
25544 */
25545
25546
25547var MD_CONTACT_CHIPS_TEMPLATE = '\
25548      <md-chips class="md-contact-chips"\
25549          ng-model="$mdContactChipsCtrl.contacts"\
25550          md-require-match="$mdContactChipsCtrl.requireMatch"\
25551          md-autocomplete-snap>\
25552          <md-autocomplete\
25553              md-menu-class="md-contact-chips-suggestions"\
25554              md-selected-item="$mdContactChipsCtrl.selectedItem"\
25555              md-search-text="$mdContactChipsCtrl.searchText"\
25556              md-items="item in $mdContactChipsCtrl.queryContact($mdContactChipsCtrl.searchText)"\
25557              md-item-text="$mdContactChipsCtrl.itemName(item)"\
25558              md-no-cache="true"\
25559              md-autoselect\
25560              placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\
25561                  $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\
25562            <div class="md-contact-suggestion">\
25563              <img \
25564                  ng-src="{{item[$mdContactChipsCtrl.contactImage]}}"\
25565                  alt="{{item[$mdContactChipsCtrl.contactName]}}"\
25566                  ng-if="item[$mdContactChipsCtrl.contactImage]" />\
25567              <span class="md-contact-name" md-highlight-text="$mdContactChipsCtrl.searchText"\
25568                    md-highlight-flags="{{$mdContactChipsCtrl.highlightFlags}}">\
25569                {{item[$mdContactChipsCtrl.contactName]}}\
25570              </span>\
25571              <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\
25572            </div>\
25573          </md-autocomplete>\
25574          <md-chip-template>\
25575            <div class="md-contact-avatar">\
25576              <img \
25577                  ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\
25578                  alt="{{$chip[$mdContactChipsCtrl.contactName]}}"\
25579                  ng-if="$chip[$mdContactChipsCtrl.contactImage]" />\
25580            </div>\
25581            <div class="md-contact-name">\
25582              {{$chip[$mdContactChipsCtrl.contactName]}}\
25583            </div>\
25584          </md-chip-template>\
25585      </md-chips>';
25586
25587
25588/**
25589 * MDContactChips Directive Definition
25590 *
25591 * @param $mdTheming
25592 * @returns {*}
25593 * @ngInject
25594 */
25595function MdContactChips($mdTheming, $mdUtil) {
25596  return {
25597    template: function(element, attrs) {
25598      return MD_CONTACT_CHIPS_TEMPLATE;
25599    },
25600    restrict: 'E',
25601    controller: 'MdContactChipsCtrl',
25602    controllerAs: '$mdContactChipsCtrl',
25603    bindToController: true,
25604    compile: compile,
25605    scope: {
25606      contactQuery: '&mdContacts',
25607      placeholder: '@',
25608      secondaryPlaceholder: '@',
25609      contactName: '@mdContactName',
25610      contactImage: '@mdContactImage',
25611      contactEmail: '@mdContactEmail',
25612      contacts: '=ngModel',
25613      requireMatch: '=?mdRequireMatch',
25614      highlightFlags: '@?mdHighlightFlags'
25615    }
25616  };
25617
25618  function compile(element, attr) {
25619    return function postLink(scope, element, attrs, controllers) {
25620
25621      $mdUtil.initOptionalProperties(scope, attr);
25622      $mdTheming(element);
25623
25624      element.attr('tabindex', '-1');
25625    };
25626  }
25627}
25628
25629})();
25630(function(){
25631"use strict";
25632
25633(function() {
25634  'use strict';
25635
25636  /**
25637   * @ngdoc directive
25638   * @name mdCalendar
25639   * @module material.components.datepicker
25640   *
25641   * @param {Date} ng-model The component's model. Should be a Date object.
25642   * @param {Date=} md-min-date Expression representing the minimum date.
25643   * @param {Date=} md-max-date Expression representing the maximum date.
25644   * @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not.
25645   *
25646   * @description
25647   * `<md-calendar>` is a component that renders a calendar that can be used to select a date.
25648   * It is a part of the `<md-datepicker` pane, however it can also be used on it's own.
25649   *
25650   * @usage
25651   *
25652   * <hljs lang="html">
25653   *   <md-calendar ng-model="birthday"></md-calendar>
25654   * </hljs>
25655   */
25656  CalendarCtrl.$inject = ["$element", "$scope", "$$mdDateUtil", "$mdUtil", "$mdConstant", "$mdTheming", "$$rAF", "$attrs", "$mdDateLocale"];
25657  angular.module('material.components.datepicker')
25658    .directive('mdCalendar', calendarDirective);
25659
25660  // POST RELEASE
25661  // TODO(jelbourn): Mac Cmd + left / right == Home / End
25662  // TODO(jelbourn): Refactor month element creation to use cloneNode (performance).
25663  // TODO(jelbourn): Define virtual scrolling constants (compactness) users can override.
25664  // TODO(jelbourn): Animated month transition on ng-model change (virtual-repeat)
25665  // TODO(jelbourn): Scroll snapping (virtual repeat)
25666  // TODO(jelbourn): Remove superfluous row from short months (virtual-repeat)
25667  // TODO(jelbourn): Month headers stick to top when scrolling.
25668  // TODO(jelbourn): Previous month opacity is lowered when partially scrolled out of view.
25669  // TODO(jelbourn): Support md-calendar standalone on a page (as a tabstop w/ aria-live
25670  //     announcement and key handling).
25671  // Read-only calendar (not just date-picker).
25672
25673  function calendarDirective() {
25674    return {
25675      template: function(tElement, tAttr) {
25676        // TODO(crisbeto): This is a workaround that allows the calendar to work, without
25677        // a datepicker, until issue #8585 gets resolved. It can safely be removed
25678        // afterwards. This ensures that the virtual repeater scrolls to the proper place on load by
25679        // deferring the execution until the next digest. It's necessary only if the calendar is used
25680        // without a datepicker, otherwise it's already wrapped in an ngIf.
25681        var extraAttrs = tAttr.hasOwnProperty('ngIf') ? '' : 'ng-if="calendarCtrl.isInitialized"';
25682        var template = '' +
25683          '<div ng-switch="calendarCtrl.currentView" ' + extraAttrs + '>' +
25684            '<md-calendar-year ng-switch-when="year"></md-calendar-year>' +
25685            '<md-calendar-month ng-switch-default></md-calendar-month>' +
25686          '</div>';
25687
25688        return template;
25689      },
25690      scope: {
25691        minDate: '=mdMinDate',
25692        maxDate: '=mdMaxDate',
25693        dateFilter: '=mdDateFilter',
25694        _currentView: '@mdCurrentView'
25695      },
25696      require: ['ngModel', 'mdCalendar'],
25697      controller: CalendarCtrl,
25698      controllerAs: 'calendarCtrl',
25699      bindToController: true,
25700      link: function(scope, element, attrs, controllers) {
25701        var ngModelCtrl = controllers[0];
25702        var mdCalendarCtrl = controllers[1];
25703        mdCalendarCtrl.configureNgModel(ngModelCtrl);
25704      }
25705    };
25706  }
25707
25708  /**
25709   * Occasionally the hideVerticalScrollbar method might read an element's
25710   * width as 0, because it hasn't been laid out yet. This value will be used
25711   * as a fallback, in order to prevent scenarios where the element's width
25712   * would otherwise have been set to 0. This value is the "usual" width of a
25713   * calendar within a floating calendar pane.
25714   */
25715  var FALLBACK_WIDTH = 340;
25716
25717  /** Next identifier for calendar instance. */
25718  var nextUniqueId = 0;
25719
25720  /**
25721   * Controller for the mdCalendar component.
25722   * @ngInject @constructor
25723   */
25724  function CalendarCtrl($element, $scope, $$mdDateUtil, $mdUtil,
25725    $mdConstant, $mdTheming, $$rAF, $attrs, $mdDateLocale) {
25726
25727    $mdTheming($element);
25728
25729    /** @final {!angular.JQLite} */
25730    this.$element = $element;
25731
25732    /** @final {!angular.Scope} */
25733    this.$scope = $scope;
25734
25735    /** @final */
25736    this.dateUtil = $$mdDateUtil;
25737
25738    /** @final */
25739    this.$mdUtil = $mdUtil;
25740
25741    /** @final */
25742    this.keyCode = $mdConstant.KEY_CODE;
25743
25744    /** @final */
25745    this.$$rAF = $$rAF;
25746
25747    /** @final {Date} */
25748    this.today = this.dateUtil.createDateAtMidnight();
25749
25750    /** @type {!angular.NgModelController} */
25751    this.ngModelCtrl = null;
25752
25753    /**
25754     * The currently visible calendar view. Note the prefix on the scope value,
25755     * which is necessary, because the datepicker seems to reset the real one value if the
25756     * calendar is open, but the value on the datepicker's scope is empty.
25757     * @type {String}
25758     */
25759    this.currentView = this._currentView || 'month';
25760
25761    /** @type {String} Class applied to the selected date cell. */
25762    this.SELECTED_DATE_CLASS = 'md-calendar-selected-date';
25763
25764    /** @type {String} Class applied to the cell for today. */
25765    this.TODAY_CLASS = 'md-calendar-date-today';
25766
25767    /** @type {String} Class applied to the focused cell. */
25768    this.FOCUSED_DATE_CLASS = 'md-focus';
25769
25770    /** @final {number} Unique ID for this calendar instance. */
25771    this.id = nextUniqueId++;
25772
25773    /**
25774     * The date that is currently focused or showing in the calendar. This will initially be set
25775     * to the ng-model value if set, otherwise to today. It will be updated as the user navigates
25776     * to other months. The cell corresponding to the displayDate does not necesarily always have
25777     * focus in the document (such as for cases when the user is scrolling the calendar).
25778     * @type {Date}
25779     */
25780    this.displayDate = null;
25781
25782    /**
25783     * The selected date. Keep track of this separately from the ng-model value so that we
25784     * can know, when the ng-model value changes, what the previous value was before it's updated
25785     * in the component's UI.
25786     *
25787     * @type {Date}
25788     */
25789    this.selectedDate = null;
25790
25791    /**
25792     * The first date that can be rendered by the calendar. The default is taken
25793     * from the mdDateLocale provider and is limited by the mdMinDate.
25794     * @type {Date}
25795     */
25796    this.firstRenderableDate = null;
25797
25798    /**
25799     * The last date that can be rendered by the calendar. The default comes
25800     * from the mdDateLocale provider and is limited by the maxDate.
25801     * @type {Date}
25802     */
25803    this.lastRenderableDate = null;
25804
25805    /**
25806     * Used to toggle initialize the root element in the next digest.
25807     * @type {Boolean}
25808     */
25809    this.isInitialized = false;
25810
25811    /**
25812     * Cache for the  width of the element without a scrollbar. Used to hide the scrollbar later on
25813     * and to avoid extra reflows when switching between views.
25814     * @type {Number}
25815     */
25816    this.width = 0;
25817
25818    /**
25819     * Caches the width of the scrollbar in order to be used when hiding it and to avoid extra reflows.
25820     * @type {Number}
25821     */
25822    this.scrollbarWidth = 0;
25823
25824    // Unless the user specifies so, the calendar should not be a tab stop.
25825    // This is necessary because ngAria might add a tabindex to anything with an ng-model
25826    // (based on whether or not the user has turned that particular feature on/off).
25827    if (!$attrs.tabindex) {
25828      $element.attr('tabindex', '-1');
25829    }
25830
25831    var boundKeyHandler = angular.bind(this, this.handleKeyEvent);
25832
25833    // Bind the keydown handler to the body, in order to handle cases where the focused
25834    // element gets removed from the DOM and stops propagating click events.
25835    angular.element(document.body).on('keydown', boundKeyHandler);
25836
25837    $scope.$on('$destroy', function() {
25838      angular.element(document.body).off('keydown', boundKeyHandler);
25839    });
25840
25841    if (this.minDate && this.minDate > $mdDateLocale.firstRenderableDate) {
25842      this.firstRenderableDate = this.minDate;
25843    } else {
25844      this.firstRenderableDate = $mdDateLocale.firstRenderableDate;
25845    }
25846
25847    if (this.maxDate && this.maxDate < $mdDateLocale.lastRenderableDate) {
25848      this.lastRenderableDate = this.maxDate;
25849    } else {
25850      this.lastRenderableDate = $mdDateLocale.lastRenderableDate;
25851    }
25852  }
25853
25854  /**
25855   * Sets up the controller's reference to ngModelController.
25856   * @param {!angular.NgModelController} ngModelCtrl
25857   */
25858  CalendarCtrl.prototype.configureNgModel = function(ngModelCtrl) {
25859    var self = this;
25860
25861    self.ngModelCtrl = ngModelCtrl;
25862
25863    self.$mdUtil.nextTick(function() {
25864      self.isInitialized = true;
25865    });
25866
25867    ngModelCtrl.$render = function() {
25868      var value = this.$viewValue;
25869
25870      // Notify the child scopes of any changes.
25871      self.$scope.$broadcast('md-calendar-parent-changed', value);
25872
25873      // Set up the selectedDate if it hasn't been already.
25874      if (!self.selectedDate) {
25875        self.selectedDate = value;
25876      }
25877
25878      // Also set up the displayDate.
25879      if (!self.displayDate) {
25880        self.displayDate = self.selectedDate || self.today;
25881      }
25882    };
25883  };
25884
25885  /**
25886   * Sets the ng-model value for the calendar and emits a change event.
25887   * @param {Date} date
25888   */
25889  CalendarCtrl.prototype.setNgModelValue = function(date) {
25890    var value = this.dateUtil.createDateAtMidnight(date);
25891    this.focus(value);
25892    this.$scope.$emit('md-calendar-change', value);
25893    this.ngModelCtrl.$setViewValue(value);
25894    this.ngModelCtrl.$render();
25895    return value;
25896  };
25897
25898  /**
25899   * Sets the current view that should be visible in the calendar
25900   * @param {string} newView View name to be set.
25901   * @param {number|Date} time Date object or a timestamp for the new display date.
25902   */
25903  CalendarCtrl.prototype.setCurrentView = function(newView, time) {
25904    var self = this;
25905
25906    self.$mdUtil.nextTick(function() {
25907      self.currentView = newView;
25908
25909      if (time) {
25910        self.displayDate = angular.isDate(time) ? time : new Date(time);
25911      }
25912    });
25913  };
25914
25915  /**
25916   * Focus the cell corresponding to the given date.
25917   * @param {Date} date The date to be focused.
25918   */
25919  CalendarCtrl.prototype.focus = function(date) {
25920    if (this.dateUtil.isValidDate(date)) {
25921      var previousFocus = this.$element[0].querySelector('.md-focus');
25922      if (previousFocus) {
25923        previousFocus.classList.remove(this.FOCUSED_DATE_CLASS);
25924      }
25925
25926      var cellId = this.getDateId(date, this.currentView);
25927      var cell = document.getElementById(cellId);
25928      if (cell) {
25929        cell.classList.add(this.FOCUSED_DATE_CLASS);
25930        cell.focus();
25931        this.displayDate = date;
25932      }
25933    } else {
25934      var rootElement = this.$element[0].querySelector('[ng-switch]');
25935
25936      if (rootElement) {
25937        rootElement.focus();
25938      }
25939    }
25940  };
25941
25942  /**
25943   * Normalizes the key event into an action name. The action will be broadcast
25944   * to the child controllers.
25945   * @param {KeyboardEvent} event
25946   * @returns {String} The action that should be taken, or null if the key
25947   * does not match a calendar shortcut.
25948   */
25949  CalendarCtrl.prototype.getActionFromKeyEvent = function(event) {
25950    var keyCode = this.keyCode;
25951
25952    switch (event.which) {
25953      case keyCode.ENTER: return 'select';
25954
25955      case keyCode.RIGHT_ARROW: return 'move-right';
25956      case keyCode.LEFT_ARROW: return 'move-left';
25957
25958      // TODO(crisbeto): Might want to reconsider using metaKey, because it maps
25959      // to the "Windows" key on PC, which opens the start menu or resizes the browser.
25960      case keyCode.DOWN_ARROW: return event.metaKey ? 'move-page-down' : 'move-row-down';
25961      case keyCode.UP_ARROW: return event.metaKey ? 'move-page-up' : 'move-row-up';
25962
25963      case keyCode.PAGE_DOWN: return 'move-page-down';
25964      case keyCode.PAGE_UP: return 'move-page-up';
25965
25966      case keyCode.HOME: return 'start';
25967      case keyCode.END: return 'end';
25968
25969      default: return null;
25970    }
25971  };
25972
25973  /**
25974   * Handles a key event in the calendar with the appropriate action. The action will either
25975   * be to select the focused date or to navigate to focus a new date.
25976   * @param {KeyboardEvent} event
25977   */
25978  CalendarCtrl.prototype.handleKeyEvent = function(event) {
25979    var self = this;
25980
25981    this.$scope.$apply(function() {
25982      // Capture escape and emit back up so that a wrapping component
25983      // (such as a date-picker) can decide to close.
25984      if (event.which == self.keyCode.ESCAPE || event.which == self.keyCode.TAB) {
25985        self.$scope.$emit('md-calendar-close');
25986
25987        if (event.which == self.keyCode.TAB) {
25988          event.preventDefault();
25989        }
25990
25991        return;
25992      }
25993
25994      // Broadcast the action that any child controllers should take.
25995      var action = self.getActionFromKeyEvent(event);
25996      if (action) {
25997        event.preventDefault();
25998        event.stopPropagation();
25999        self.$scope.$broadcast('md-calendar-parent-action', action);
26000      }
26001    });
26002  };
26003
26004  /**
26005   * Hides the vertical scrollbar on the calendar scroller of a child controller by
26006   * setting the width on the calendar scroller and the `overflow: hidden` wrapper
26007   * around the scroller, and then setting a padding-right on the scroller equal
26008   * to the width of the browser's scrollbar.
26009   *
26010   * This will cause a reflow.
26011   *
26012   * @param {object} childCtrl The child controller whose scrollbar should be hidden.
26013   */
26014  CalendarCtrl.prototype.hideVerticalScrollbar = function(childCtrl) {
26015    var self = this;
26016    var element = childCtrl.$element[0];
26017    var scrollMask = element.querySelector('.md-calendar-scroll-mask');
26018
26019    if (self.width > 0) {
26020      setWidth();
26021    } else {
26022      self.$$rAF(function() {
26023        var scroller = childCtrl.calendarScroller;
26024
26025        self.scrollbarWidth = scroller.offsetWidth - scroller.clientWidth;
26026        self.width = element.querySelector('table').offsetWidth;
26027        setWidth();
26028      });
26029    }
26030
26031    function setWidth() {
26032      var width = self.width || FALLBACK_WIDTH;
26033      var scrollbarWidth = self.scrollbarWidth;
26034      var scroller = childCtrl.calendarScroller;
26035
26036      scrollMask.style.width = width + 'px';
26037      scroller.style.width = (width + scrollbarWidth) + 'px';
26038      scroller.style.paddingRight = scrollbarWidth + 'px';
26039    }
26040  };
26041
26042  /**
26043   * Gets an identifier for a date unique to the calendar instance for internal
26044   * purposes. Not to be displayed.
26045   * @param {Date} date The date for which the id is being generated
26046   * @param {string} namespace Namespace for the id. (month, year etc.)
26047   * @returns {string}
26048   */
26049  CalendarCtrl.prototype.getDateId = function(date, namespace) {
26050    if (!namespace) {
26051      throw new Error('A namespace for the date id has to be specified.');
26052    }
26053
26054    return [
26055      'md',
26056      this.id,
26057      namespace,
26058      date.getFullYear(),
26059      date.getMonth(),
26060      date.getDate()
26061    ].join('-');
26062  };
26063
26064  /**
26065   * Util to trigger an extra digest on a parent scope, in order to to ensure that
26066   * any child virtual repeaters have updated. This is necessary, because the virtual
26067   * repeater doesn't update the $index the first time around since the content isn't
26068   * in place yet. The case, in which this is an issue, is when the repeater has less
26069   * than a page of content (e.g. a month or year view has a min or max date).
26070   */
26071  CalendarCtrl.prototype.updateVirtualRepeat = function() {
26072    var scope = this.$scope;
26073    var virtualRepeatResizeListener = scope.$on('$md-resize-enable', function() {
26074      if (!scope.$$phase) {
26075        scope.$apply();
26076      }
26077
26078      virtualRepeatResizeListener();
26079    });
26080  };
26081})();
26082
26083})();
26084(function(){
26085"use strict";
26086
26087(function() {
26088  'use strict';
26089
26090  CalendarMonthCtrl.$inject = ["$element", "$scope", "$animate", "$q", "$$mdDateUtil", "$mdDateLocale"];
26091  angular.module('material.components.datepicker')
26092    .directive('mdCalendarMonth', calendarDirective);
26093
26094  /**
26095   * Height of one calendar month tbody. This must be made known to the virtual-repeat and is
26096   * subsequently used for scrolling to specific months.
26097   */
26098  var TBODY_HEIGHT = 265;
26099
26100  /**
26101   * Height of a calendar month with a single row. This is needed to calculate the offset for
26102   * rendering an extra month in virtual-repeat that only contains one row.
26103   */
26104  var TBODY_SINGLE_ROW_HEIGHT = 45;
26105
26106  /** Private directive that represents a list of months inside the calendar. */
26107  function calendarDirective() {
26108    return {
26109      template:
26110        '<table aria-hidden="true" class="md-calendar-day-header"><thead></thead></table>' +
26111        '<div class="md-calendar-scroll-mask">' +
26112        '<md-virtual-repeat-container class="md-calendar-scroll-container" ' +
26113              'md-offset-size="' + (TBODY_SINGLE_ROW_HEIGHT - TBODY_HEIGHT) + '">' +
26114            '<table role="grid" tabindex="0" class="md-calendar" aria-readonly="true">' +
26115              '<tbody ' +
26116                  'md-calendar-month-body ' +
26117                  'role="rowgroup" ' +
26118                  'md-virtual-repeat="i in monthCtrl.items" ' +
26119                  'md-month-offset="$index" ' +
26120                  'class="md-calendar-month" ' +
26121                  'md-start-index="monthCtrl.getSelectedMonthIndex()" ' +
26122                  'md-item-size="' + TBODY_HEIGHT + '"></tbody>' +
26123            '</table>' +
26124          '</md-virtual-repeat-container>' +
26125        '</div>',
26126      require: ['^^mdCalendar', 'mdCalendarMonth'],
26127      controller: CalendarMonthCtrl,
26128      controllerAs: 'monthCtrl',
26129      bindToController: true,
26130      link: function(scope, element, attrs, controllers) {
26131        var calendarCtrl = controllers[0];
26132        var monthCtrl = controllers[1];
26133        monthCtrl.initialize(calendarCtrl);
26134      }
26135    };
26136  }
26137
26138  /**
26139   * Controller for the calendar month component.
26140   * @ngInject @constructor
26141   */
26142  function CalendarMonthCtrl($element, $scope, $animate, $q,
26143    $$mdDateUtil, $mdDateLocale) {
26144
26145    /** @final {!angular.JQLite} */
26146    this.$element = $element;
26147
26148    /** @final {!angular.Scope} */
26149    this.$scope = $scope;
26150
26151    /** @final {!angular.$animate} */
26152    this.$animate = $animate;
26153
26154    /** @final {!angular.$q} */
26155    this.$q = $q;
26156
26157    /** @final */
26158    this.dateUtil = $$mdDateUtil;
26159
26160    /** @final */
26161    this.dateLocale = $mdDateLocale;
26162
26163    /** @final {HTMLElement} */
26164    this.calendarScroller = $element[0].querySelector('.md-virtual-repeat-scroller');
26165
26166    /** @type {boolean} */
26167    this.isInitialized = false;
26168
26169    /** @type {boolean} */
26170    this.isMonthTransitionInProgress = false;
26171
26172    var self = this;
26173
26174    /**
26175     * Handles a click event on a date cell.
26176     * Created here so that every cell can use the same function instance.
26177     * @this {HTMLTableCellElement} The cell that was clicked.
26178     */
26179    this.cellClickHandler = function() {
26180      var timestamp = $$mdDateUtil.getTimestampFromNode(this);
26181      self.$scope.$apply(function() {
26182        self.calendarCtrl.setNgModelValue(timestamp);
26183      });
26184    };
26185
26186    /**
26187     * Handles click events on the month headers. Switches
26188     * the calendar to the year view.
26189     * @this {HTMLTableCellElement} The cell that was clicked.
26190     */
26191    this.headerClickHandler = function() {
26192      self.calendarCtrl.setCurrentView('year', $$mdDateUtil.getTimestampFromNode(this));
26193    };
26194  }
26195
26196  /*** Initialization ***/
26197
26198  /**
26199   * Initialize the controller by saving a reference to the calendar and
26200   * setting up the object that will be iterated by the virtual repeater.
26201   */
26202  CalendarMonthCtrl.prototype.initialize = function(calendarCtrl) {
26203    /**
26204     * Dummy array-like object for virtual-repeat to iterate over. The length is the total
26205     * number of months that can be viewed. We add 2 months: one to include the current month
26206     * and one for the last dummy month.
26207     *
26208     * This is shorter than ideal because of a (potential) Firefox bug
26209     * https://bugzilla.mozilla.org/show_bug.cgi?id=1181658.
26210     */
26211
26212    this.items = {
26213      length: this.dateUtil.getMonthDistance(
26214        calendarCtrl.firstRenderableDate,
26215        calendarCtrl.lastRenderableDate
26216      ) + 2
26217    };
26218
26219    this.calendarCtrl = calendarCtrl;
26220    this.attachScopeListeners();
26221    calendarCtrl.updateVirtualRepeat();
26222
26223    // Fire the initial render, since we might have missed it the first time it fired.
26224    calendarCtrl.ngModelCtrl && calendarCtrl.ngModelCtrl.$render();
26225  };
26226
26227  /**
26228   * Gets the "index" of the currently selected date as it would be in the virtual-repeat.
26229   * @returns {number}
26230   */
26231  CalendarMonthCtrl.prototype.getSelectedMonthIndex = function() {
26232    var calendarCtrl = this.calendarCtrl;
26233
26234    return this.dateUtil.getMonthDistance(
26235      calendarCtrl.firstRenderableDate,
26236      calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today
26237    );
26238  };
26239
26240  /**
26241   * Change the selected date in the calendar (ngModel value has already been changed).
26242   * @param {Date} date
26243   */
26244  CalendarMonthCtrl.prototype.changeSelectedDate = function(date) {
26245    var self = this;
26246    var calendarCtrl = self.calendarCtrl;
26247    var previousSelectedDate = calendarCtrl.selectedDate;
26248    calendarCtrl.selectedDate = date;
26249
26250    this.changeDisplayDate(date).then(function() {
26251      var selectedDateClass = calendarCtrl.SELECTED_DATE_CLASS;
26252      var namespace = 'month';
26253
26254      // Remove the selected class from the previously selected date, if any.
26255      if (previousSelectedDate) {
26256        var prevDateCell = document.getElementById(calendarCtrl.getDateId(previousSelectedDate, namespace));
26257        if (prevDateCell) {
26258          prevDateCell.classList.remove(selectedDateClass);
26259          prevDateCell.setAttribute('aria-selected', 'false');
26260        }
26261      }
26262
26263      // Apply the select class to the new selected date if it is set.
26264      if (date) {
26265        var dateCell = document.getElementById(calendarCtrl.getDateId(date, namespace));
26266        if (dateCell) {
26267          dateCell.classList.add(selectedDateClass);
26268          dateCell.setAttribute('aria-selected', 'true');
26269        }
26270      }
26271    });
26272  };
26273
26274  /**
26275   * Change the date that is being shown in the calendar. If the given date is in a different
26276   * month, the displayed month will be transitioned.
26277   * @param {Date} date
26278   */
26279  CalendarMonthCtrl.prototype.changeDisplayDate = function(date) {
26280    // Initialization is deferred until this function is called because we want to reflect
26281    // the starting value of ngModel.
26282    if (!this.isInitialized) {
26283      this.buildWeekHeader();
26284      this.calendarCtrl.hideVerticalScrollbar(this);
26285      this.isInitialized = true;
26286      return this.$q.when();
26287    }
26288
26289    // If trying to show an invalid date or a transition is in progress, do nothing.
26290    if (!this.dateUtil.isValidDate(date) || this.isMonthTransitionInProgress) {
26291      return this.$q.when();
26292    }
26293
26294    this.isMonthTransitionInProgress = true;
26295    var animationPromise = this.animateDateChange(date);
26296
26297    this.calendarCtrl.displayDate = date;
26298
26299    var self = this;
26300    animationPromise.then(function() {
26301      self.isMonthTransitionInProgress = false;
26302    });
26303
26304    return animationPromise;
26305  };
26306
26307  /**
26308   * Animates the transition from the calendar's current month to the given month.
26309   * @param {Date} date
26310   * @returns {angular.$q.Promise} The animation promise.
26311   */
26312  CalendarMonthCtrl.prototype.animateDateChange = function(date) {
26313    if (this.dateUtil.isValidDate(date)) {
26314      var monthDistance = this.dateUtil.getMonthDistance(this.calendarCtrl.firstRenderableDate, date);
26315      this.calendarScroller.scrollTop = monthDistance * TBODY_HEIGHT;
26316    }
26317
26318    return this.$q.when();
26319  };
26320
26321  /**
26322   * Builds and appends a day-of-the-week header to the calendar.
26323   * This should only need to be called once during initialization.
26324   */
26325  CalendarMonthCtrl.prototype.buildWeekHeader = function() {
26326    var firstDayOfWeek = this.dateLocale.firstDayOfWeek;
26327    var shortDays = this.dateLocale.shortDays;
26328
26329    var row = document.createElement('tr');
26330    for (var i = 0; i < 7; i++) {
26331      var th = document.createElement('th');
26332      th.textContent = shortDays[(i + firstDayOfWeek) % 7];
26333      row.appendChild(th);
26334    }
26335
26336    this.$element.find('thead').append(row);
26337  };
26338
26339  /**
26340   * Attaches listeners for the scope events that are broadcast by the calendar.
26341   */
26342  CalendarMonthCtrl.prototype.attachScopeListeners = function() {
26343    var self = this;
26344
26345    self.$scope.$on('md-calendar-parent-changed', function(event, value) {
26346      self.changeSelectedDate(value);
26347    });
26348
26349    self.$scope.$on('md-calendar-parent-action', angular.bind(this, this.handleKeyEvent));
26350  };
26351
26352  /**
26353   * Handles the month-specific keyboard interactions.
26354   * @param {Object} event Scope event object passed by the calendar.
26355   * @param {String} action Action, corresponding to the key that was pressed.
26356   */
26357  CalendarMonthCtrl.prototype.handleKeyEvent = function(event, action) {
26358    var calendarCtrl = this.calendarCtrl;
26359    var displayDate = calendarCtrl.displayDate;
26360
26361    if (action === 'select') {
26362      calendarCtrl.setNgModelValue(displayDate);
26363    } else {
26364      var date = null;
26365      var dateUtil = this.dateUtil;
26366
26367      switch (action) {
26368        case 'move-right': date = dateUtil.incrementDays(displayDate, 1); break;
26369        case 'move-left': date = dateUtil.incrementDays(displayDate, -1); break;
26370
26371        case 'move-page-down': date = dateUtil.incrementMonths(displayDate, 1); break;
26372        case 'move-page-up': date = dateUtil.incrementMonths(displayDate, -1); break;
26373
26374        case 'move-row-down': date = dateUtil.incrementDays(displayDate, 7); break;
26375        case 'move-row-up': date = dateUtil.incrementDays(displayDate, -7); break;
26376
26377        case 'start': date = dateUtil.getFirstDateOfMonth(displayDate); break;
26378        case 'end': date = dateUtil.getLastDateOfMonth(displayDate); break;
26379      }
26380
26381      if (date) {
26382        date = this.dateUtil.clampDate(date, calendarCtrl.minDate, calendarCtrl.maxDate);
26383
26384        this.changeDisplayDate(date).then(function() {
26385          calendarCtrl.focus(date);
26386        });
26387      }
26388    }
26389  };
26390})();
26391
26392})();
26393(function(){
26394"use strict";
26395
26396(function() {
26397  'use strict';
26398
26399  mdCalendarMonthBodyDirective.$inject = ["$compile", "$$mdSvgRegistry"];
26400  CalendarMonthBodyCtrl.$inject = ["$element", "$$mdDateUtil", "$mdDateLocale"];
26401  angular.module('material.components.datepicker')
26402      .directive('mdCalendarMonthBody', mdCalendarMonthBodyDirective);
26403
26404  /**
26405   * Private directive consumed by md-calendar-month. Having this directive lets the calender use
26406   * md-virtual-repeat and also cleanly separates the month DOM construction functions from
26407   * the rest of the calendar controller logic.
26408   * @ngInject
26409   */
26410  function mdCalendarMonthBodyDirective($compile, $$mdSvgRegistry) {
26411    var ARROW_ICON = $compile('<md-icon md-svg-src="' +
26412      $$mdSvgRegistry.mdTabsArrow + '"></md-icon>')({})[0];
26413
26414    return {
26415      require: ['^^mdCalendar', '^^mdCalendarMonth', 'mdCalendarMonthBody'],
26416      scope: { offset: '=mdMonthOffset' },
26417      controller: CalendarMonthBodyCtrl,
26418      controllerAs: 'mdMonthBodyCtrl',
26419      bindToController: true,
26420      link: function(scope, element, attrs, controllers) {
26421        var calendarCtrl = controllers[0];
26422        var monthCtrl = controllers[1];
26423        var monthBodyCtrl = controllers[2];
26424
26425        monthBodyCtrl.calendarCtrl = calendarCtrl;
26426        monthBodyCtrl.monthCtrl = monthCtrl;
26427        monthBodyCtrl.arrowIcon = ARROW_ICON.cloneNode(true);
26428
26429        // The virtual-repeat re-uses the same DOM elements, so there are only a limited number
26430        // of repeated items that are linked, and then those elements have their bindings updated.
26431        // Since the months are not generated by bindings, we simply regenerate the entire thing
26432        // when the binding (offset) changes.
26433        scope.$watch(function() { return monthBodyCtrl.offset; }, function(offset, oldOffset) {
26434          if (offset !== oldOffset) {
26435            monthBodyCtrl.generateContent();
26436          }
26437        });
26438      }
26439    };
26440  }
26441
26442  /**
26443   * Controller for a single calendar month.
26444   * @ngInject @constructor
26445   */
26446  function CalendarMonthBodyCtrl($element, $$mdDateUtil, $mdDateLocale) {
26447    /** @final {!angular.JQLite} */
26448    this.$element = $element;
26449
26450    /** @final */
26451    this.dateUtil = $$mdDateUtil;
26452
26453    /** @final */
26454    this.dateLocale = $mdDateLocale;
26455
26456    /** @type {Object} Reference to the month view. */
26457    this.monthCtrl = null;
26458
26459    /** @type {Object} Reference to the calendar. */
26460    this.calendarCtrl = null;
26461
26462    /**
26463     * Number of months from the start of the month "items" that the currently rendered month
26464     * occurs. Set via angular data binding.
26465     * @type {number}
26466     */
26467    this.offset = null;
26468
26469    /**
26470     * Date cell to focus after appending the month to the document.
26471     * @type {HTMLElement}
26472     */
26473    this.focusAfterAppend = null;
26474  }
26475
26476  /** Generate and append the content for this month to the directive element. */
26477  CalendarMonthBodyCtrl.prototype.generateContent = function() {
26478    var date = this.dateUtil.incrementMonths(this.calendarCtrl.firstRenderableDate, this.offset);
26479
26480    this.$element
26481      .empty()
26482      .append(this.buildCalendarForMonth(date));
26483
26484    if (this.focusAfterAppend) {
26485      this.focusAfterAppend.classList.add(this.calendarCtrl.FOCUSED_DATE_CLASS);
26486      this.focusAfterAppend.focus();
26487      this.focusAfterAppend = null;
26488    }
26489  };
26490
26491  /**
26492   * Creates a single cell to contain a date in the calendar with all appropriate
26493   * attributes and classes added. If a date is given, the cell content will be set
26494   * based on the date.
26495   * @param {Date=} opt_date
26496   * @returns {HTMLElement}
26497   */
26498  CalendarMonthBodyCtrl.prototype.buildDateCell = function(opt_date) {
26499    var monthCtrl = this.monthCtrl;
26500    var calendarCtrl = this.calendarCtrl;
26501
26502    // TODO(jelbourn): cloneNode is likely a faster way of doing this.
26503    var cell = document.createElement('td');
26504    cell.tabIndex = -1;
26505    cell.classList.add('md-calendar-date');
26506    cell.setAttribute('role', 'gridcell');
26507
26508    if (opt_date) {
26509      cell.setAttribute('tabindex', '-1');
26510      cell.setAttribute('aria-label', this.dateLocale.longDateFormatter(opt_date));
26511      cell.id = calendarCtrl.getDateId(opt_date, 'month');
26512
26513      // Use `data-timestamp` attribute because IE10 does not support the `dataset` property.
26514      cell.setAttribute('data-timestamp', opt_date.getTime());
26515
26516      // TODO(jelourn): Doing these comparisons for class addition during generation might be slow.
26517      // It may be better to finish the construction and then query the node and add the class.
26518      if (this.dateUtil.isSameDay(opt_date, calendarCtrl.today)) {
26519        cell.classList.add(calendarCtrl.TODAY_CLASS);
26520      }
26521
26522      if (this.dateUtil.isValidDate(calendarCtrl.selectedDate) &&
26523          this.dateUtil.isSameDay(opt_date, calendarCtrl.selectedDate)) {
26524        cell.classList.add(calendarCtrl.SELECTED_DATE_CLASS);
26525        cell.setAttribute('aria-selected', 'true');
26526      }
26527
26528      var cellText = this.dateLocale.dates[opt_date.getDate()];
26529
26530      if (this.isDateEnabled(opt_date)) {
26531        // Add a indicator for select, hover, and focus states.
26532        var selectionIndicator = document.createElement('span');
26533        selectionIndicator.classList.add('md-calendar-date-selection-indicator');
26534        selectionIndicator.textContent = cellText;
26535        cell.appendChild(selectionIndicator);
26536        cell.addEventListener('click', monthCtrl.cellClickHandler);
26537
26538        if (calendarCtrl.displayDate && this.dateUtil.isSameDay(opt_date, calendarCtrl.displayDate)) {
26539          this.focusAfterAppend = cell;
26540        }
26541      } else {
26542        cell.classList.add('md-calendar-date-disabled');
26543        cell.textContent = cellText;
26544      }
26545    }
26546
26547    return cell;
26548  };
26549
26550  /**
26551   * Check whether date is in range and enabled
26552   * @param {Date=} opt_date
26553   * @return {boolean} Whether the date is enabled.
26554   */
26555  CalendarMonthBodyCtrl.prototype.isDateEnabled = function(opt_date) {
26556    return this.dateUtil.isDateWithinRange(opt_date,
26557          this.calendarCtrl.minDate, this.calendarCtrl.maxDate) &&
26558          (!angular.isFunction(this.calendarCtrl.dateFilter)
26559           || this.calendarCtrl.dateFilter(opt_date));
26560  };
26561
26562  /**
26563   * Builds a `tr` element for the calendar grid.
26564   * @param rowNumber The week number within the month.
26565   * @returns {HTMLElement}
26566   */
26567  CalendarMonthBodyCtrl.prototype.buildDateRow = function(rowNumber) {
26568    var row = document.createElement('tr');
26569    row.setAttribute('role', 'row');
26570
26571    // Because of an NVDA bug (with Firefox), the row needs an aria-label in order
26572    // to prevent the entire row being read aloud when the user moves between rows.
26573    // See http://community.nvda-project.org/ticket/4643.
26574    row.setAttribute('aria-label', this.dateLocale.weekNumberFormatter(rowNumber));
26575
26576    return row;
26577  };
26578
26579  /**
26580   * Builds the <tbody> content for the given date's month.
26581   * @param {Date=} opt_dateInMonth
26582   * @returns {DocumentFragment} A document fragment containing the <tr> elements.
26583   */
26584  CalendarMonthBodyCtrl.prototype.buildCalendarForMonth = function(opt_dateInMonth) {
26585    var date = this.dateUtil.isValidDate(opt_dateInMonth) ? opt_dateInMonth : new Date();
26586
26587    var firstDayOfMonth = this.dateUtil.getFirstDateOfMonth(date);
26588    var firstDayOfTheWeek = this.getLocaleDay_(firstDayOfMonth);
26589    var numberOfDaysInMonth = this.dateUtil.getNumberOfDaysInMonth(date);
26590
26591    // Store rows for the month in a document fragment so that we can append them all at once.
26592    var monthBody = document.createDocumentFragment();
26593
26594    var rowNumber = 1;
26595    var row = this.buildDateRow(rowNumber);
26596    monthBody.appendChild(row);
26597
26598    // If this is the final month in the list of items, only the first week should render,
26599    // so we should return immediately after the first row is complete and has been
26600    // attached to the body.
26601    var isFinalMonth = this.offset === this.monthCtrl.items.length - 1;
26602
26603    // Add a label for the month. If the month starts on a Sun/Mon/Tues, the month label
26604    // goes on a row above the first of the month. Otherwise, the month label takes up the first
26605    // two cells of the first row.
26606    var blankCellOffset = 0;
26607    var monthLabelCell = document.createElement('td');
26608    var monthLabelCellContent = document.createElement('span');
26609
26610    monthLabelCellContent.textContent = this.dateLocale.monthHeaderFormatter(date);
26611    monthLabelCell.appendChild(monthLabelCellContent);
26612    monthLabelCell.classList.add('md-calendar-month-label');
26613    // If the entire month is after the max date, render the label as a disabled state.
26614    if (this.calendarCtrl.maxDate && firstDayOfMonth > this.calendarCtrl.maxDate) {
26615      monthLabelCell.classList.add('md-calendar-month-label-disabled');
26616    } else {
26617      monthLabelCell.addEventListener('click', this.monthCtrl.headerClickHandler);
26618      monthLabelCell.setAttribute('data-timestamp', firstDayOfMonth.getTime());
26619      monthLabelCell.setAttribute('aria-label', this.dateLocale.monthFormatter(date));
26620      monthLabelCell.appendChild(this.arrowIcon.cloneNode(true));
26621    }
26622
26623    if (firstDayOfTheWeek <= 2) {
26624      monthLabelCell.setAttribute('colspan', '7');
26625
26626      var monthLabelRow = this.buildDateRow();
26627      monthLabelRow.appendChild(monthLabelCell);
26628      monthBody.insertBefore(monthLabelRow, row);
26629
26630      if (isFinalMonth) {
26631        return monthBody;
26632      }
26633    } else {
26634      blankCellOffset = 3;
26635      monthLabelCell.setAttribute('colspan', '3');
26636      row.appendChild(monthLabelCell);
26637    }
26638
26639    // Add a blank cell for each day of the week that occurs before the first of the month.
26640    // For example, if the first day of the month is a Tuesday, add blank cells for Sun and Mon.
26641    // The blankCellOffset is needed in cases where the first N cells are used by the month label.
26642    for (var i = blankCellOffset; i < firstDayOfTheWeek; i++) {
26643      row.appendChild(this.buildDateCell());
26644    }
26645
26646    // Add a cell for each day of the month, keeping track of the day of the week so that
26647    // we know when to start a new row.
26648    var dayOfWeek = firstDayOfTheWeek;
26649    var iterationDate = firstDayOfMonth;
26650    for (var d = 1; d <= numberOfDaysInMonth; d++) {
26651      // If we've reached the end of the week, start a new row.
26652      if (dayOfWeek === 7) {
26653        // We've finished the first row, so we're done if this is the final month.
26654        if (isFinalMonth) {
26655          return monthBody;
26656        }
26657        dayOfWeek = 0;
26658        rowNumber++;
26659        row = this.buildDateRow(rowNumber);
26660        monthBody.appendChild(row);
26661      }
26662
26663      iterationDate.setDate(d);
26664      var cell = this.buildDateCell(iterationDate);
26665      row.appendChild(cell);
26666
26667      dayOfWeek++;
26668    }
26669
26670    // Ensure that the last row of the month has 7 cells.
26671    while (row.childNodes.length < 7) {
26672      row.appendChild(this.buildDateCell());
26673    }
26674
26675    // Ensure that all months have 6 rows. This is necessary for now because the virtual-repeat
26676    // requires that all items have exactly the same height.
26677    while (monthBody.childNodes.length < 6) {
26678      var whitespaceRow = this.buildDateRow();
26679      for (var j = 0; j < 7; j++) {
26680        whitespaceRow.appendChild(this.buildDateCell());
26681      }
26682      monthBody.appendChild(whitespaceRow);
26683    }
26684
26685    return monthBody;
26686  };
26687
26688  /**
26689   * Gets the day-of-the-week index for a date for the current locale.
26690   * @private
26691   * @param {Date} date
26692   * @returns {number} The column index of the date in the calendar.
26693   */
26694  CalendarMonthBodyCtrl.prototype.getLocaleDay_ = function(date) {
26695    return (date.getDay() + (7 - this.dateLocale.firstDayOfWeek)) % 7;
26696  };
26697})();
26698
26699})();
26700(function(){
26701"use strict";
26702
26703(function() {
26704  'use strict';
26705
26706  CalendarYearCtrl.$inject = ["$element", "$scope", "$animate", "$q", "$$mdDateUtil"];
26707  angular.module('material.components.datepicker')
26708    .directive('mdCalendarYear', calendarDirective);
26709
26710  /**
26711   * Height of one calendar year tbody. This must be made known to the virtual-repeat and is
26712   * subsequently used for scrolling to specific years.
26713   */
26714  var TBODY_HEIGHT = 88;
26715
26716  /** Private component, representing a list of years in the calendar. */
26717  function calendarDirective() {
26718    return {
26719      template:
26720        '<div class="md-calendar-scroll-mask">' +
26721          '<md-virtual-repeat-container class="md-calendar-scroll-container">' +
26722            '<table role="grid" tabindex="0" class="md-calendar" aria-readonly="true">' +
26723              '<tbody ' +
26724                  'md-calendar-year-body ' +
26725                  'role="rowgroup" ' +
26726                  'md-virtual-repeat="i in yearCtrl.items" ' +
26727                  'md-year-offset="$index" class="md-calendar-year" ' +
26728                  'md-start-index="yearCtrl.getFocusedYearIndex()" ' +
26729                  'md-item-size="' + TBODY_HEIGHT + '"></tbody>' +
26730            '</table>' +
26731          '</md-virtual-repeat-container>' +
26732        '</div>',
26733      require: ['^^mdCalendar', 'mdCalendarYear'],
26734      controller: CalendarYearCtrl,
26735      controllerAs: 'yearCtrl',
26736      bindToController: true,
26737      link: function(scope, element, attrs, controllers) {
26738        var calendarCtrl = controllers[0];
26739        var yearCtrl = controllers[1];
26740        yearCtrl.initialize(calendarCtrl);
26741      }
26742    };
26743  }
26744
26745  /**
26746   * Controller for the mdCalendar component.
26747   * @ngInject @constructor
26748   */
26749  function CalendarYearCtrl($element, $scope, $animate, $q, $$mdDateUtil) {
26750
26751    /** @final {!angular.JQLite} */
26752    this.$element = $element;
26753
26754    /** @final {!angular.Scope} */
26755    this.$scope = $scope;
26756
26757    /** @final {!angular.$animate} */
26758    this.$animate = $animate;
26759
26760    /** @final {!angular.$q} */
26761    this.$q = $q;
26762
26763    /** @final */
26764    this.dateUtil = $$mdDateUtil;
26765
26766    /** @final {HTMLElement} */
26767    this.calendarScroller = $element[0].querySelector('.md-virtual-repeat-scroller');
26768
26769    /** @type {boolean} */
26770    this.isInitialized = false;
26771
26772    /** @type {boolean} */
26773    this.isMonthTransitionInProgress = false;
26774
26775    var self = this;
26776
26777    /**
26778     * Handles a click event on a date cell.
26779     * Created here so that every cell can use the same function instance.
26780     * @this {HTMLTableCellElement} The cell that was clicked.
26781     */
26782    this.cellClickHandler = function() {
26783      self.calendarCtrl.setCurrentView('month', $$mdDateUtil.getTimestampFromNode(this));
26784    };
26785  }
26786
26787  /**
26788   * Initialize the controller by saving a reference to the calendar and
26789   * setting up the object that will be iterated by the virtual repeater.
26790   */
26791  CalendarYearCtrl.prototype.initialize = function(calendarCtrl) {
26792    /**
26793     * Dummy array-like object for virtual-repeat to iterate over. The length is the total
26794     * number of years that can be viewed. We add 1 extra in order to include the current year.
26795     */
26796    this.items = {
26797      length: this.dateUtil.getYearDistance(
26798        calendarCtrl.firstRenderableDate,
26799        calendarCtrl.lastRenderableDate
26800      ) + 1
26801    };
26802
26803    this.calendarCtrl = calendarCtrl;
26804    this.attachScopeListeners();
26805    calendarCtrl.updateVirtualRepeat();
26806
26807    // Fire the initial render, since we might have missed it the first time it fired.
26808    calendarCtrl.ngModelCtrl && calendarCtrl.ngModelCtrl.$render();
26809  };
26810
26811  /**
26812   * Gets the "index" of the currently selected date as it would be in the virtual-repeat.
26813   * @returns {number}
26814   */
26815  CalendarYearCtrl.prototype.getFocusedYearIndex = function() {
26816    var calendarCtrl = this.calendarCtrl;
26817
26818    return this.dateUtil.getYearDistance(
26819      calendarCtrl.firstRenderableDate,
26820      calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today
26821    );
26822  };
26823
26824  /**
26825   * Change the date that is highlighted in the calendar.
26826   * @param {Date} date
26827   */
26828  CalendarYearCtrl.prototype.changeDate = function(date) {
26829    // Initialization is deferred until this function is called because we want to reflect
26830    // the starting value of ngModel.
26831    if (!this.isInitialized) {
26832      this.calendarCtrl.hideVerticalScrollbar(this);
26833      this.isInitialized = true;
26834      return this.$q.when();
26835    } else if (this.dateUtil.isValidDate(date) && !this.isMonthTransitionInProgress) {
26836      var self = this;
26837      var animationPromise = this.animateDateChange(date);
26838
26839      self.isMonthTransitionInProgress = true;
26840      self.calendarCtrl.displayDate = date;
26841
26842      return animationPromise.then(function() {
26843        self.isMonthTransitionInProgress = false;
26844      });
26845    }
26846  };
26847
26848  /**
26849   * Animates the transition from the calendar's current month to the given month.
26850   * @param {Date} date
26851   * @returns {angular.$q.Promise} The animation promise.
26852   */
26853  CalendarYearCtrl.prototype.animateDateChange = function(date) {
26854    if (this.dateUtil.isValidDate(date)) {
26855      var monthDistance = this.dateUtil.getYearDistance(this.calendarCtrl.firstRenderableDate, date);
26856      this.calendarScroller.scrollTop = monthDistance * TBODY_HEIGHT;
26857    }
26858
26859    return this.$q.when();
26860  };
26861
26862  /**
26863   * Handles the year-view-specific keyboard interactions.
26864   * @param {Object} event Scope event object passed by the calendar.
26865   * @param {String} action Action, corresponding to the key that was pressed.
26866   */
26867  CalendarYearCtrl.prototype.handleKeyEvent = function(event, action) {
26868    var calendarCtrl = this.calendarCtrl;
26869    var displayDate = calendarCtrl.displayDate;
26870
26871    if (action === 'select') {
26872      this.changeDate(displayDate).then(function() {
26873        calendarCtrl.setCurrentView('month', displayDate);
26874        calendarCtrl.focus(displayDate);
26875      });
26876    } else {
26877      var date = null;
26878      var dateUtil = this.dateUtil;
26879
26880      switch (action) {
26881        case 'move-right': date = dateUtil.incrementMonths(displayDate, 1); break;
26882        case 'move-left': date = dateUtil.incrementMonths(displayDate, -1); break;
26883
26884        case 'move-row-down': date = dateUtil.incrementMonths(displayDate, 6); break;
26885        case 'move-row-up': date = dateUtil.incrementMonths(displayDate, -6); break;
26886      }
26887
26888      if (date) {
26889        var min = calendarCtrl.minDate ? dateUtil.getFirstDateOfMonth(calendarCtrl.minDate) : null;
26890        var max = calendarCtrl.maxDate ? dateUtil.getFirstDateOfMonth(calendarCtrl.maxDate) : null;
26891        date = dateUtil.getFirstDateOfMonth(this.dateUtil.clampDate(date, min, max));
26892
26893        this.changeDate(date).then(function() {
26894          calendarCtrl.focus(date);
26895        });
26896      }
26897    }
26898  };
26899
26900  /**
26901   * Attaches listeners for the scope events that are broadcast by the calendar.
26902   */
26903  CalendarYearCtrl.prototype.attachScopeListeners = function() {
26904    var self = this;
26905
26906    self.$scope.$on('md-calendar-parent-changed', function(event, value) {
26907      self.changeDate(value);
26908    });
26909
26910    self.$scope.$on('md-calendar-parent-action', angular.bind(self, self.handleKeyEvent));
26911  };
26912})();
26913
26914})();
26915(function(){
26916"use strict";
26917
26918(function() {
26919  'use strict';
26920
26921  CalendarYearBodyCtrl.$inject = ["$element", "$$mdDateUtil", "$mdDateLocale"];
26922  angular.module('material.components.datepicker')
26923      .directive('mdCalendarYearBody', mdCalendarYearDirective);
26924
26925  /**
26926   * Private component, consumed by the md-calendar-year, which separates the DOM construction logic
26927   * and allows for the year view to use md-virtual-repeat.
26928   */
26929  function mdCalendarYearDirective() {
26930    return {
26931      require: ['^^mdCalendar', '^^mdCalendarYear', 'mdCalendarYearBody'],
26932      scope: { offset: '=mdYearOffset' },
26933      controller: CalendarYearBodyCtrl,
26934      controllerAs: 'mdYearBodyCtrl',
26935      bindToController: true,
26936      link: function(scope, element, attrs, controllers) {
26937        var calendarCtrl = controllers[0];
26938        var yearCtrl = controllers[1];
26939        var yearBodyCtrl = controllers[2];
26940
26941        yearBodyCtrl.calendarCtrl = calendarCtrl;
26942        yearBodyCtrl.yearCtrl = yearCtrl;
26943
26944        scope.$watch(function() { return yearBodyCtrl.offset; }, function(offset, oldOffset) {
26945          if (offset !== oldOffset) {
26946            yearBodyCtrl.generateContent();
26947          }
26948        });
26949      }
26950    };
26951  }
26952
26953  /**
26954   * Controller for a single year.
26955   * @ngInject @constructor
26956   */
26957  function CalendarYearBodyCtrl($element, $$mdDateUtil, $mdDateLocale) {
26958    /** @final {!angular.JQLite} */
26959    this.$element = $element;
26960
26961    /** @final */
26962    this.dateUtil = $$mdDateUtil;
26963
26964    /** @final */
26965    this.dateLocale = $mdDateLocale;
26966
26967    /** @type {Object} Reference to the calendar. */
26968    this.calendarCtrl = null;
26969
26970    /** @type {Object} Reference to the year view. */
26971    this.yearCtrl = null;
26972
26973    /**
26974     * Number of months from the start of the month "items" that the currently rendered month
26975     * occurs. Set via angular data binding.
26976     * @type {number}
26977     */
26978    this.offset = null;
26979
26980    /**
26981     * Date cell to focus after appending the month to the document.
26982     * @type {HTMLElement}
26983     */
26984    this.focusAfterAppend = null;
26985  }
26986
26987  /** Generate and append the content for this year to the directive element. */
26988  CalendarYearBodyCtrl.prototype.generateContent = function() {
26989    var date = this.dateUtil.incrementYears(this.calendarCtrl.firstRenderableDate, this.offset);
26990
26991    this.$element
26992      .empty()
26993      .append(this.buildCalendarForYear(date));
26994
26995    if (this.focusAfterAppend) {
26996      this.focusAfterAppend.classList.add(this.calendarCtrl.FOCUSED_DATE_CLASS);
26997      this.focusAfterAppend.focus();
26998      this.focusAfterAppend = null;
26999    }
27000  };
27001
27002  /**
27003   * Creates a single cell to contain a year in the calendar.
27004   * @param {number} opt_year Four-digit year.
27005   * @param {number} opt_month Zero-indexed month.
27006   * @returns {HTMLElement}
27007   */
27008  CalendarYearBodyCtrl.prototype.buildMonthCell = function(year, month) {
27009    var calendarCtrl = this.calendarCtrl;
27010    var yearCtrl = this.yearCtrl;
27011    var cell = this.buildBlankCell();
27012
27013    // Represent this month/year as a date.
27014    var firstOfMonth = new Date(year, month, 1);
27015    cell.setAttribute('aria-label', this.dateLocale.monthFormatter(firstOfMonth));
27016    cell.id = calendarCtrl.getDateId(firstOfMonth, 'year');
27017
27018    // Use `data-timestamp` attribute because IE10 does not support the `dataset` property.
27019    cell.setAttribute('data-timestamp', firstOfMonth.getTime());
27020
27021    if (this.dateUtil.isSameMonthAndYear(firstOfMonth, calendarCtrl.today)) {
27022      cell.classList.add(calendarCtrl.TODAY_CLASS);
27023    }
27024
27025    if (this.dateUtil.isValidDate(calendarCtrl.selectedDate) &&
27026        this.dateUtil.isSameMonthAndYear(firstOfMonth, calendarCtrl.selectedDate)) {
27027      cell.classList.add(calendarCtrl.SELECTED_DATE_CLASS);
27028      cell.setAttribute('aria-selected', 'true');
27029    }
27030
27031    var cellText = this.dateLocale.shortMonths[month];
27032
27033    if (this.dateUtil.isMonthWithinRange(firstOfMonth,
27034        calendarCtrl.minDate, calendarCtrl.maxDate)) {
27035      var selectionIndicator = document.createElement('span');
27036      selectionIndicator.classList.add('md-calendar-date-selection-indicator');
27037      selectionIndicator.textContent = cellText;
27038      cell.appendChild(selectionIndicator);
27039      cell.addEventListener('click', yearCtrl.cellClickHandler);
27040
27041      if (calendarCtrl.displayDate && this.dateUtil.isSameMonthAndYear(firstOfMonth, calendarCtrl.displayDate)) {
27042        this.focusAfterAppend = cell;
27043      }
27044    } else {
27045      cell.classList.add('md-calendar-date-disabled');
27046      cell.textContent = cellText;
27047    }
27048
27049    return cell;
27050  };
27051
27052  /**
27053   * Builds a blank cell.
27054   * @return {HTMLTableCellElement}
27055   */
27056  CalendarYearBodyCtrl.prototype.buildBlankCell = function() {
27057    var cell = document.createElement('td');
27058    cell.tabIndex = -1;
27059    cell.classList.add('md-calendar-date');
27060    cell.setAttribute('role', 'gridcell');
27061
27062    cell.setAttribute('tabindex', '-1');
27063    return cell;
27064  };
27065
27066  /**
27067   * Builds the <tbody> content for the given year.
27068   * @param {Date} date Date for which the content should be built.
27069   * @returns {DocumentFragment} A document fragment containing the months within the year.
27070   */
27071  CalendarYearBodyCtrl.prototype.buildCalendarForYear = function(date) {
27072    // Store rows for the month in a document fragment so that we can append them all at once.
27073    var year = date.getFullYear();
27074    var yearBody = document.createDocumentFragment();
27075
27076    var monthCell, i;
27077    // First row contains label and Jan-Jun.
27078    var firstRow = document.createElement('tr');
27079    var labelCell = document.createElement('td');
27080    labelCell.className = 'md-calendar-month-label';
27081    labelCell.textContent = year;
27082    firstRow.appendChild(labelCell);
27083
27084    for (i = 0; i < 6; i++) {
27085      firstRow.appendChild(this.buildMonthCell(year, i));
27086    }
27087    yearBody.appendChild(firstRow);
27088
27089    // Second row contains a blank cell and Jul-Dec.
27090    var secondRow = document.createElement('tr');
27091    secondRow.appendChild(this.buildBlankCell());
27092    for (i = 6; i < 12; i++) {
27093      secondRow.appendChild(this.buildMonthCell(year, i));
27094    }
27095    yearBody.appendChild(secondRow);
27096
27097    return yearBody;
27098  };
27099})();
27100
27101})();
27102(function(){
27103"use strict";
27104
27105(function() {
27106  'use strict';
27107
27108  /**
27109   * @ngdoc service
27110   * @name $mdDateLocaleProvider
27111   * @module material.components.datepicker
27112   *
27113   * @description
27114   * The `$mdDateLocaleProvider` is the provider that creates the `$mdDateLocale` service.
27115   * This provider that allows the user to specify messages, formatters, and parsers for date
27116   * internationalization. The `$mdDateLocale` service itself is consumed by Angular Material
27117   * components that deal with dates.
27118   *
27119   * @property {(Array<string>)=} months Array of month names (in order).
27120   * @property {(Array<string>)=} shortMonths Array of abbreviated month names.
27121   * @property {(Array<string>)=} days Array of the days of the week (in order).
27122   * @property {(Array<string>)=} shortDays Array of abbreviated dayes of the week.
27123   * @property {(Array<string>)=} dates Array of dates of the month. Only necessary for locales
27124   *     using a numeral system other than [1, 2, 3...].
27125   * @property {(Array<string>)=} firstDayOfWeek The first day of the week. Sunday = 0, Monday = 1,
27126   *    etc.
27127   * @property {(function(string): Date)=} parseDate Function to parse a date object from a string.
27128   * @property {(function(Date): string)=} formatDate Function to format a date object to a string.
27129   * @property {(function(Date): string)=} monthHeaderFormatter Function that returns the label for
27130   *     a month given a date.
27131   * @property {(function(Date): string)=} monthFormatter Function that returns the full name of a month
27132   *     for a giben date.
27133   * @property {(function(number): string)=} weekNumberFormatter Function that returns a label for
27134   *     a week given the week number.
27135   * @property {(string)=} msgCalendar Translation of the label "Calendar" for the current locale.
27136   * @property {(string)=} msgOpenCalendar Translation of the button label "Open calendar" for the
27137   *     current locale.
27138   * @property {Date=} firstRenderableDate The date from which the datepicker calendar will begin
27139   * rendering. Note that this will be ignored if a minimum date is set. Defaults to January 1st 1880.
27140   * @property {Date=} lastRenderableDate The last date that will be rendered by the datepicker
27141   * calendar. Note that this will be ignored if a maximum date is set. Defaults to January 1st 2130.
27142   *
27143   * @usage
27144   * <hljs lang="js">
27145   * myAppModule.config(function($mdDateLocaleProvider) {
27146   *
27147   *     // Example of a French localization.
27148   *     $mdDateLocaleProvider.months = ['janvier', 'février', 'mars', ...];
27149   *     $mdDateLocaleProvider.shortMonths = ['janv', 'févr', 'mars', ...];
27150   *     $mdDateLocaleProvider.days = ['dimanche', 'lundi', 'mardi', ...];
27151   *     $mdDateLocaleProvider.shortDays = ['Di', 'Lu', 'Ma', ...];
27152   *
27153   *     // Can change week display to start on Monday.
27154   *     $mdDateLocaleProvider.firstDayOfWeek = 1;
27155   *
27156   *     // Optional.
27157   *     $mdDateLocaleProvider.dates = [1, 2, 3, 4, 5, 6, ...];
27158   *
27159   *     // Example uses moment.js to parse and format dates.
27160   *     $mdDateLocaleProvider.parseDate = function(dateString) {
27161   *       var m = moment(dateString, 'L', true);
27162   *       return m.isValid() ? m.toDate() : new Date(NaN);
27163   *     };
27164   *
27165   *     $mdDateLocaleProvider.formatDate = function(date) {
27166   *       var m = moment(date);
27167   *       return m.isValid() ? m.format('L') : '';
27168   *     };
27169   *
27170   *     $mdDateLocaleProvider.monthHeaderFormatter = function(date) {
27171   *       return myShortMonths[date.getMonth()] + ' ' + date.getFullYear();
27172   *     };
27173   *
27174   *     // In addition to date display, date components also need localized messages
27175   *     // for aria-labels for screen-reader users.
27176   *
27177   *     $mdDateLocaleProvider.weekNumberFormatter = function(weekNumber) {
27178   *       return 'Semaine ' + weekNumber;
27179   *     };
27180   *
27181   *     $mdDateLocaleProvider.msgCalendar = 'Calendrier';
27182   *     $mdDateLocaleProvider.msgOpenCalendar = 'Ouvrir le calendrier';
27183   *
27184   *     // You can also set when your calendar begins and ends.
27185   *     $mdDateLocaleProvider.firstRenderableDate = new Date(1776, 6, 4);
27186   *     $mdDateLocaleProvider.lastRenderableDate = new Date(2012, 11, 21);
27187   * });
27188   * </hljs>
27189   *
27190   */
27191  angular.module('material.components.datepicker').config(["$provide", function($provide) {
27192    // TODO(jelbourn): Assert provided values are correctly formatted. Need assertions.
27193
27194    /** @constructor */
27195    function DateLocaleProvider() {
27196      /** Array of full month names. E.g., ['January', 'Febuary', ...] */
27197      this.months = null;
27198
27199      /** Array of abbreviated month names. E.g., ['Jan', 'Feb', ...] */
27200      this.shortMonths = null;
27201
27202      /** Array of full day of the week names. E.g., ['Monday', 'Tuesday', ...] */
27203      this.days = null;
27204
27205      /** Array of abbreviated dat of the week names. E.g., ['M', 'T', ...] */
27206      this.shortDays = null;
27207
27208      /** Array of dates of a month (1 - 31). Characters might be different in some locales. */
27209      this.dates = null;
27210
27211      /** Index of the first day of the week. 0 = Sunday, 1 = Monday, etc. */
27212      this.firstDayOfWeek = 0;
27213
27214      /**
27215       * Function that converts the date portion of a Date to a string.
27216       * @type {(function(Date): string)}
27217       */
27218      this.formatDate = null;
27219
27220      /**
27221       * Function that converts a date string to a Date object (the date portion)
27222       * @type {function(string): Date}
27223       */
27224      this.parseDate = null;
27225
27226      /**
27227       * Function that formats a Date into a month header string.
27228       * @type {function(Date): string}
27229       */
27230      this.monthHeaderFormatter = null;
27231
27232      /**
27233       * Function that formats a week number into a label for the week.
27234       * @type {function(number): string}
27235       */
27236      this.weekNumberFormatter = null;
27237
27238      /**
27239       * Function that formats a date into a long aria-label that is read
27240       * when the focused date changes.
27241       * @type {function(Date): string}
27242       */
27243      this.longDateFormatter = null;
27244
27245      /**
27246       * ARIA label for the calendar "dialog" used in the datepicker.
27247       * @type {string}
27248       */
27249      this.msgCalendar = '';
27250
27251      /**
27252       * ARIA label for the datepicker's "Open calendar" buttons.
27253       * @type {string}
27254       */
27255      this.msgOpenCalendar = '';
27256    }
27257
27258    /**
27259     * Factory function that returns an instance of the dateLocale service.
27260     * @ngInject
27261     * @param $locale
27262     * @returns {DateLocale}
27263     */
27264    DateLocaleProvider.prototype.$get = function($locale, $filter) {
27265      /**
27266       * Default date-to-string formatting function.
27267       * @param {!Date} date
27268       * @returns {string}
27269       */
27270      function defaultFormatDate(date) {
27271        if (!date) {
27272          return '';
27273        }
27274
27275        // All of the dates created through ng-material *should* be set to midnight.
27276        // If we encounter a date where the localeTime shows at 11pm instead of midnight,
27277        // we have run into an issue with DST where we need to increment the hour by one:
27278        // var d = new Date(1992, 9, 8, 0, 0, 0);
27279        // d.toLocaleString(); // == "10/7/1992, 11:00:00 PM"
27280        var localeTime = date.toLocaleTimeString();
27281        var formatDate = date;
27282        if (date.getHours() == 0 &&
27283            (localeTime.indexOf('11:') !== -1 || localeTime.indexOf('23:') !== -1)) {
27284          formatDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 1, 0, 0);
27285        }
27286
27287        return $filter('date')(formatDate, 'M/d/yyyy');
27288      }
27289
27290      /**
27291       * Default string-to-date parsing function.
27292       * @param {string} dateString
27293       * @returns {!Date}
27294       */
27295      function defaultParseDate(dateString) {
27296        return new Date(dateString);
27297      }
27298
27299      /**
27300       * Default function to determine whether a string makes sense to be
27301       * parsed to a Date object.
27302       *
27303       * This is very permissive and is just a basic sanity check to ensure that
27304       * things like single integers aren't able to be parsed into dates.
27305       * @param {string} dateString
27306       * @returns {boolean}
27307       */
27308      function defaultIsDateComplete(dateString) {
27309        dateString = dateString.trim();
27310
27311        // Looks for three chunks of content (either numbers or text) separated
27312        // by delimiters.
27313        var re = /^(([a-zA-Z]{3,}|[0-9]{1,4})([ \.,]+|[\/\-])){2}([a-zA-Z]{3,}|[0-9]{1,4})$/;
27314        return re.test(dateString);
27315      }
27316
27317      /**
27318       * Default date-to-string formatter to get a month header.
27319       * @param {!Date} date
27320       * @returns {string}
27321       */
27322      function defaultMonthHeaderFormatter(date) {
27323        return service.shortMonths[date.getMonth()] + ' ' + date.getFullYear();
27324      }
27325
27326      /**
27327       * Default formatter for a month.
27328       * @param {!Date} date
27329       * @returns {string}
27330       */
27331      function defaultMonthFormatter(date) {
27332        return service.months[date.getMonth()] + ' ' + date.getFullYear();
27333      }
27334
27335      /**
27336       * Default week number formatter.
27337       * @param number
27338       * @returns {string}
27339       */
27340      function defaultWeekNumberFormatter(number) {
27341        return 'Week ' + number;
27342      }
27343
27344      /**
27345       * Default formatter for date cell aria-labels.
27346       * @param {!Date} date
27347       * @returns {string}
27348       */
27349      function defaultLongDateFormatter(date) {
27350        // Example: 'Thursday June 18 2015'
27351        return [
27352          service.days[date.getDay()],
27353          service.months[date.getMonth()],
27354          service.dates[date.getDate()],
27355          date.getFullYear()
27356        ].join(' ');
27357      }
27358
27359      // The default "short" day strings are the first character of each day,
27360      // e.g., "Monday" => "M".
27361      var defaultShortDays = $locale.DATETIME_FORMATS.SHORTDAY.map(function(day) {
27362        return day.substring(0, 1);
27363      });
27364
27365      // The default dates are simply the numbers 1 through 31.
27366      var defaultDates = Array(32);
27367      for (var i = 1; i <= 31; i++) {
27368        defaultDates[i] = i;
27369      }
27370
27371      // Default ARIA messages are in English (US).
27372      var defaultMsgCalendar = 'Calendar';
27373      var defaultMsgOpenCalendar = 'Open calendar';
27374
27375      // Default start/end dates that are rendered in the calendar.
27376      var defaultFirstRenderableDate = new Date(1880, 0, 1);
27377      var defaultLastRendereableDate = new Date(defaultFirstRenderableDate.getFullYear() + 250, 0, 1);
27378
27379      var service = {
27380        months: this.months || $locale.DATETIME_FORMATS.MONTH,
27381        shortMonths: this.shortMonths || $locale.DATETIME_FORMATS.SHORTMONTH,
27382        days: this.days || $locale.DATETIME_FORMATS.DAY,
27383        shortDays: this.shortDays || defaultShortDays,
27384        dates: this.dates || defaultDates,
27385        firstDayOfWeek: this.firstDayOfWeek || 0,
27386        formatDate: this.formatDate || defaultFormatDate,
27387        parseDate: this.parseDate || defaultParseDate,
27388        isDateComplete: this.isDateComplete || defaultIsDateComplete,
27389        monthHeaderFormatter: this.monthHeaderFormatter || defaultMonthHeaderFormatter,
27390        monthFormatter: this.monthFormatter || defaultMonthFormatter,
27391        weekNumberFormatter: this.weekNumberFormatter || defaultWeekNumberFormatter,
27392        longDateFormatter: this.longDateFormatter || defaultLongDateFormatter,
27393        msgCalendar: this.msgCalendar || defaultMsgCalendar,
27394        msgOpenCalendar: this.msgOpenCalendar || defaultMsgOpenCalendar,
27395        firstRenderableDate: this.firstRenderableDate || defaultFirstRenderableDate,
27396        lastRenderableDate: this.lastRenderableDate || defaultLastRendereableDate
27397      };
27398
27399      return service;
27400    };
27401    DateLocaleProvider.prototype.$get.$inject = ["$locale", "$filter"];
27402
27403    $provide.provider('$mdDateLocale', new DateLocaleProvider());
27404  }]);
27405})();
27406
27407})();
27408(function(){
27409"use strict";
27410
27411(function() {
27412  'use strict';
27413
27414  /**
27415   * Utility for performing date calculations to facilitate operation of the calendar and
27416   * datepicker.
27417   */
27418  angular.module('material.components.datepicker').factory('$$mdDateUtil', function() {
27419    return {
27420      getFirstDateOfMonth: getFirstDateOfMonth,
27421      getNumberOfDaysInMonth: getNumberOfDaysInMonth,
27422      getDateInNextMonth: getDateInNextMonth,
27423      getDateInPreviousMonth: getDateInPreviousMonth,
27424      isInNextMonth: isInNextMonth,
27425      isInPreviousMonth: isInPreviousMonth,
27426      getDateMidpoint: getDateMidpoint,
27427      isSameMonthAndYear: isSameMonthAndYear,
27428      getWeekOfMonth: getWeekOfMonth,
27429      incrementDays: incrementDays,
27430      incrementMonths: incrementMonths,
27431      getLastDateOfMonth: getLastDateOfMonth,
27432      isSameDay: isSameDay,
27433      getMonthDistance: getMonthDistance,
27434      isValidDate: isValidDate,
27435      setDateTimeToMidnight: setDateTimeToMidnight,
27436      createDateAtMidnight: createDateAtMidnight,
27437      isDateWithinRange: isDateWithinRange,
27438      incrementYears: incrementYears,
27439      getYearDistance: getYearDistance,
27440      clampDate: clampDate,
27441      getTimestampFromNode: getTimestampFromNode,
27442      isMonthWithinRange: isMonthWithinRange
27443    };
27444
27445    /**
27446     * Gets the first day of the month for the given date's month.
27447     * @param {Date} date
27448     * @returns {Date}
27449     */
27450    function getFirstDateOfMonth(date) {
27451      return new Date(date.getFullYear(), date.getMonth(), 1);
27452    }
27453
27454    /**
27455     * Gets the number of days in the month for the given date's month.
27456     * @param date
27457     * @returns {number}
27458     */
27459    function getNumberOfDaysInMonth(date) {
27460      return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
27461    }
27462
27463    /**
27464     * Get an arbitrary date in the month after the given date's month.
27465     * @param date
27466     * @returns {Date}
27467     */
27468    function getDateInNextMonth(date) {
27469      return new Date(date.getFullYear(), date.getMonth() + 1, 1);
27470    }
27471
27472    /**
27473     * Get an arbitrary date in the month before the given date's month.
27474     * @param date
27475     * @returns {Date}
27476     */
27477    function getDateInPreviousMonth(date) {
27478      return new Date(date.getFullYear(), date.getMonth() - 1, 1);
27479    }
27480
27481    /**
27482     * Gets whether two dates have the same month and year.
27483     * @param {Date} d1
27484     * @param {Date} d2
27485     * @returns {boolean}
27486     */
27487    function isSameMonthAndYear(d1, d2) {
27488      return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth();
27489    }
27490
27491    /**
27492     * Gets whether two dates are the same day (not not necesarily the same time).
27493     * @param {Date} d1
27494     * @param {Date} d2
27495     * @returns {boolean}
27496     */
27497    function isSameDay(d1, d2) {
27498      return d1.getDate() == d2.getDate() && isSameMonthAndYear(d1, d2);
27499    }
27500
27501    /**
27502     * Gets whether a date is in the month immediately after some date.
27503     * @param {Date} startDate The date from which to compare.
27504     * @param {Date} endDate The date to check.
27505     * @returns {boolean}
27506     */
27507    function isInNextMonth(startDate, endDate) {
27508      var nextMonth = getDateInNextMonth(startDate);
27509      return isSameMonthAndYear(nextMonth, endDate);
27510    }
27511
27512    /**
27513     * Gets whether a date is in the month immediately before some date.
27514     * @param {Date} startDate The date from which to compare.
27515     * @param {Date} endDate The date to check.
27516     * @returns {boolean}
27517     */
27518    function isInPreviousMonth(startDate, endDate) {
27519      var previousMonth = getDateInPreviousMonth(startDate);
27520      return isSameMonthAndYear(endDate, previousMonth);
27521    }
27522
27523    /**
27524     * Gets the midpoint between two dates.
27525     * @param {Date} d1
27526     * @param {Date} d2
27527     * @returns {Date}
27528     */
27529    function getDateMidpoint(d1, d2) {
27530      return createDateAtMidnight((d1.getTime() + d2.getTime()) / 2);
27531    }
27532
27533    /**
27534     * Gets the week of the month that a given date occurs in.
27535     * @param {Date} date
27536     * @returns {number} Index of the week of the month (zero-based).
27537     */
27538    function getWeekOfMonth(date) {
27539      var firstDayOfMonth = getFirstDateOfMonth(date);
27540      return Math.floor((firstDayOfMonth.getDay() + date.getDate() - 1) / 7);
27541    }
27542
27543    /**
27544     * Gets a new date incremented by the given number of days. Number of days can be negative.
27545     * @param {Date} date
27546     * @param {number} numberOfDays
27547     * @returns {Date}
27548     */
27549    function incrementDays(date, numberOfDays) {
27550      return new Date(date.getFullYear(), date.getMonth(), date.getDate() + numberOfDays);
27551    }
27552
27553    /**
27554     * Gets a new date incremented by the given number of months. Number of months can be negative.
27555     * If the date of the given month does not match the target month, the date will be set to the
27556     * last day of the month.
27557     * @param {Date} date
27558     * @param {number} numberOfMonths
27559     * @returns {Date}
27560     */
27561    function incrementMonths(date, numberOfMonths) {
27562      // If the same date in the target month does not actually exist, the Date object will
27563      // automatically advance *another* month by the number of missing days.
27564      // For example, if you try to go from Jan. 30 to Feb. 30, you'll end up on March 2.
27565      // So, we check if the month overflowed and go to the last day of the target month instead.
27566      var dateInTargetMonth = new Date(date.getFullYear(), date.getMonth() + numberOfMonths, 1);
27567      var numberOfDaysInMonth = getNumberOfDaysInMonth(dateInTargetMonth);
27568      if (numberOfDaysInMonth < date.getDate()) {
27569        dateInTargetMonth.setDate(numberOfDaysInMonth);
27570      } else {
27571        dateInTargetMonth.setDate(date.getDate());
27572      }
27573
27574      return dateInTargetMonth;
27575    }
27576
27577    /**
27578     * Get the integer distance between two months. This *only* considers the month and year
27579     * portion of the Date instances.
27580     *
27581     * @param {Date} start
27582     * @param {Date} end
27583     * @returns {number} Number of months between `start` and `end`. If `end` is before `start`
27584     *     chronologically, this number will be negative.
27585     */
27586    function getMonthDistance(start, end) {
27587      return (12 * (end.getFullYear() - start.getFullYear())) + (end.getMonth() - start.getMonth());
27588    }
27589
27590    /**
27591     * Gets the last day of the month for the given date.
27592     * @param {Date} date
27593     * @returns {Date}
27594     */
27595    function getLastDateOfMonth(date) {
27596      return new Date(date.getFullYear(), date.getMonth(), getNumberOfDaysInMonth(date));
27597    }
27598
27599    /**
27600     * Checks whether a date is valid.
27601     * @param {Date} date
27602     * @return {boolean} Whether the date is a valid Date.
27603     */
27604    function isValidDate(date) {
27605      return date != null && date.getTime && !isNaN(date.getTime());
27606    }
27607
27608    /**
27609     * Sets a date's time to midnight.
27610     * @param {Date} date
27611     */
27612    function setDateTimeToMidnight(date) {
27613      if (isValidDate(date)) {
27614        date.setHours(0, 0, 0, 0);
27615      }
27616    }
27617
27618    /**
27619     * Creates a date with the time set to midnight.
27620     * Drop-in replacement for two forms of the Date constructor:
27621     * 1. No argument for Date representing now.
27622     * 2. Single-argument value representing number of seconds since Unix Epoch
27623     * or a Date object.
27624     * @param {number|Date=} opt_value
27625     * @return {Date} New date with time set to midnight.
27626     */
27627    function createDateAtMidnight(opt_value) {
27628      var date;
27629      if (angular.isUndefined(opt_value)) {
27630        date = new Date();
27631      } else {
27632        date = new Date(opt_value);
27633      }
27634      setDateTimeToMidnight(date);
27635      return date;
27636    }
27637
27638     /**
27639      * Checks if a date is within a min and max range, ignoring the time component.
27640      * If minDate or maxDate are not dates, they are ignored.
27641      * @param {Date} date
27642      * @param {Date} minDate
27643      * @param {Date} maxDate
27644      */
27645     function isDateWithinRange(date, minDate, maxDate) {
27646       var dateAtMidnight = createDateAtMidnight(date);
27647       var minDateAtMidnight = isValidDate(minDate) ? createDateAtMidnight(minDate) : null;
27648       var maxDateAtMidnight = isValidDate(maxDate) ? createDateAtMidnight(maxDate) : null;
27649       return (!minDateAtMidnight || minDateAtMidnight <= dateAtMidnight) &&
27650           (!maxDateAtMidnight || maxDateAtMidnight >= dateAtMidnight);
27651     }
27652
27653    /**
27654     * Gets a new date incremented by the given number of years. Number of years can be negative.
27655     * See `incrementMonths` for notes on overflow for specific dates.
27656     * @param {Date} date
27657     * @param {number} numberOfYears
27658     * @returns {Date}
27659     */
27660     function incrementYears(date, numberOfYears) {
27661       return incrementMonths(date, numberOfYears * 12);
27662     }
27663
27664     /**
27665      * Get the integer distance between two years. This *only* considers the year portion of the
27666      * Date instances.
27667      *
27668      * @param {Date} start
27669      * @param {Date} end
27670      * @returns {number} Number of months between `start` and `end`. If `end` is before `start`
27671      *     chronologically, this number will be negative.
27672      */
27673     function getYearDistance(start, end) {
27674       return end.getFullYear() - start.getFullYear();
27675     }
27676
27677     /**
27678      * Clamps a date between a minimum and a maximum date.
27679      * @param {Date} date Date to be clamped
27680      * @param {Date=} minDate Minimum date
27681      * @param {Date=} maxDate Maximum date
27682      * @return {Date}
27683      */
27684     function clampDate(date, minDate, maxDate) {
27685       var boundDate = date;
27686       if (minDate && date < minDate) {
27687         boundDate = new Date(minDate.getTime());
27688       }
27689       if (maxDate && date > maxDate) {
27690         boundDate = new Date(maxDate.getTime());
27691       }
27692       return boundDate;
27693     }
27694
27695     /**
27696      * Extracts and parses the timestamp from a DOM node.
27697      * @param  {HTMLElement} node Node from which the timestamp will be extracted.
27698      * @return {number} Time since epoch.
27699      */
27700     function getTimestampFromNode(node) {
27701       if (node && node.hasAttribute('data-timestamp')) {
27702         return Number(node.getAttribute('data-timestamp'));
27703       }
27704     }
27705
27706     /**
27707      * Checks if a month is within a min and max range, ignoring the date and time components.
27708      * If minDate or maxDate are not dates, they are ignored.
27709      * @param {Date} date
27710      * @param {Date} minDate
27711      * @param {Date} maxDate
27712      */
27713     function isMonthWithinRange(date, minDate, maxDate) {
27714       var month = date.getMonth();
27715       var year = date.getFullYear();
27716
27717       return (!minDate || minDate.getFullYear() < year || minDate.getMonth() <= month) &&
27718        (!maxDate || maxDate.getFullYear() > year || maxDate.getMonth() >= month);
27719     }
27720  });
27721})();
27722
27723})();
27724(function(){
27725"use strict";
27726
27727(function() {
27728  'use strict';
27729
27730  // POST RELEASE
27731  // TODO(jelbourn): Demo that uses moment.js
27732  // TODO(jelbourn): make sure this plays well with validation and ngMessages.
27733  // TODO(jelbourn): calendar pane doesn't open up outside of visible viewport.
27734  // TODO(jelbourn): forward more attributes to the internal input (required, autofocus, etc.)
27735  // TODO(jelbourn): something better for mobile (calendar panel takes up entire screen?)
27736  // TODO(jelbourn): input behavior (masking? auto-complete?)
27737
27738
27739  DatePickerCtrl.$inject = ["$scope", "$element", "$attrs", "$window", "$mdConstant", "$mdTheming", "$mdUtil", "$mdDateLocale", "$$mdDateUtil", "$$rAF", "$mdGesture", "$filter"];
27740  datePickerDirective.$inject = ["$$mdSvgRegistry", "$mdUtil", "$mdAria", "inputDirective"];
27741  angular.module('material.components.datepicker')
27742      .directive('mdDatepicker', datePickerDirective);
27743
27744  /**
27745   * @ngdoc directive
27746   * @name mdDatepicker
27747   * @module material.components.datepicker
27748   *
27749   * @param {Date} ng-model The component's model. Expects a JavaScript Date object.
27750   * @param {Object=} ng-model-options Allows tuning of the way in which `ng-model` is being updated. Also allows
27751   * for a timezone to be specified. <a href="https://docs.angularjs.org/api/ng/directive/ngModelOptions#usage">Read more at the ngModelOptions docs.</a>
27752   * @param {expression=} ng-change Expression evaluated when the model value changes.
27753   * @param {expression=} ng-focus Expression evaluated when the input is focused or the calendar is opened.
27754   * @param {expression=} ng-blur Expression evaluated when focus is removed from the input or the calendar is closed.
27755   * @param {Date=} md-min-date Expression representing a min date (inclusive).
27756   * @param {Date=} md-max-date Expression representing a max date (inclusive).
27757   * @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not.
27758   * @param {String=} md-placeholder The date input placeholder value.
27759   * @param {String=} md-open-on-focus When present, the calendar will be opened when the input is focused.
27760   * @param {Boolean=} md-is-open Expression that can be used to open the datepicker's calendar on-demand.
27761   * @param {String=} md-current-view Default open view of the calendar pane. Can be either "month" or "year".
27762   * @param {String=} md-hide-icons Determines which datepicker icons should be hidden. Note that this may cause the
27763   * datepicker to not align properly with other components. **Use at your own risk.** Possible values are:
27764   * * `"all"` - Hides all icons.
27765   * * `"calendar"` - Only hides the calendar icon.
27766   * * `"triangle"` - Only hides the triangle icon.
27767   * @param {boolean=} ng-disabled Whether the datepicker is disabled.
27768   * @param {boolean=} ng-required Whether a value is required for the datepicker.
27769   *
27770   * @description
27771   * `<md-datepicker>` is a component used to select a single date.
27772   * For information on how to configure internationalization for the date picker,
27773   * see `$mdDateLocaleProvider`.
27774   *
27775   * This component supports [ngMessages](https://docs.angularjs.org/api/ngMessages/directive/ngMessages).
27776   * Supported attributes are:
27777   * * `required`: whether a required date is not set.
27778   * * `mindate`: whether the selected date is before the minimum allowed date.
27779   * * `maxdate`: whether the selected date is after the maximum allowed date.
27780   * * `debounceInterval`: ms to delay input processing (since last debounce reset); default value 500ms
27781   *
27782   * @usage
27783   * <hljs lang="html">
27784   *   <md-datepicker ng-model="birthday"></md-datepicker>
27785   * </hljs>
27786   *
27787   */
27788
27789  function datePickerDirective($$mdSvgRegistry, $mdUtil, $mdAria, inputDirective) {
27790    return {
27791      template: function(tElement, tAttrs) {
27792        // Buttons are not in the tab order because users can open the calendar via keyboard
27793        // interaction on the text input, and multiple tab stops for one component (picker)
27794        // may be confusing.
27795        var hiddenIcons = tAttrs.mdHideIcons;
27796        var ariaLabelValue = tAttrs.ariaLabel || tAttrs.mdPlaceholder;
27797
27798        var calendarButton = (hiddenIcons === 'all' || hiddenIcons === 'calendar') ? '' :
27799          '<md-button class="md-datepicker-button md-icon-button" type="button" ' +
27800              'tabindex="-1" aria-hidden="true" ' +
27801              'ng-click="ctrl.openCalendarPane($event)">' +
27802            '<md-icon class="md-datepicker-calendar-icon" aria-label="md-calendar" ' +
27803                     'md-svg-src="' + $$mdSvgRegistry.mdCalendar + '"></md-icon>' +
27804          '</md-button>';
27805
27806        var triangleButton = (hiddenIcons === 'all' || hiddenIcons === 'triangle') ? '' :
27807          '<md-button type="button" md-no-ink ' +
27808              'class="md-datepicker-triangle-button md-icon-button" ' +
27809              'ng-click="ctrl.openCalendarPane($event)" ' +
27810              'aria-label="{{::ctrl.dateLocale.msgOpenCalendar}}">' +
27811            '<div class="md-datepicker-expand-triangle"></div>' +
27812          '</md-button>';
27813
27814        return calendarButton +
27815        '<div class="md-datepicker-input-container" ng-class="{\'md-datepicker-focused\': ctrl.isFocused}">' +
27816          '<input ' +
27817            (ariaLabelValue ? 'aria-label="' + ariaLabelValue + '" ' : '') +
27818            'class="md-datepicker-input" ' +
27819            'aria-haspopup="true" ' +
27820            'ng-focus="ctrl.setFocused(true)" ' +
27821            'ng-blur="ctrl.setFocused(false)"> ' +
27822            triangleButton +
27823        '</div>' +
27824
27825        // This pane will be detached from here and re-attached to the document body.
27826        '<div class="md-datepicker-calendar-pane md-whiteframe-z1">' +
27827          '<div class="md-datepicker-input-mask">' +
27828            '<div class="md-datepicker-input-mask-opaque"></div>' +
27829          '</div>' +
27830          '<div class="md-datepicker-calendar">' +
27831            '<md-calendar role="dialog" aria-label="{{::ctrl.dateLocale.msgCalendar}}" ' +
27832                'md-current-view="{{::ctrl.currentView}}"' +
27833                'md-min-date="ctrl.minDate"' +
27834                'md-max-date="ctrl.maxDate"' +
27835                'md-date-filter="ctrl.dateFilter"' +
27836                'ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen">' +
27837            '</md-calendar>' +
27838          '</div>' +
27839        '</div>';
27840      },
27841      require: ['ngModel', 'mdDatepicker', '?^mdInputContainer', '?^form'],
27842      scope: {
27843        minDate: '=mdMinDate',
27844        maxDate: '=mdMaxDate',
27845        placeholder: '@mdPlaceholder',
27846        currentView: '@mdCurrentView',
27847        dateFilter: '=mdDateFilter',
27848        isOpen: '=?mdIsOpen',
27849        debounceInterval: '=mdDebounceInterval'
27850      },
27851      controller: DatePickerCtrl,
27852      controllerAs: 'ctrl',
27853      bindToController: true,
27854      link: function(scope, element, attr, controllers) {
27855        var ngModelCtrl = controllers[0];
27856        var mdDatePickerCtrl = controllers[1];
27857        var mdInputContainer = controllers[2];
27858        var parentForm = controllers[3];
27859        var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
27860
27861        mdDatePickerCtrl.configureNgModel(ngModelCtrl, mdInputContainer, inputDirective);
27862
27863        if (mdInputContainer) {
27864          // We need to move the spacer after the datepicker itself,
27865          // because md-input-container adds it after the
27866          // md-datepicker-input by default. The spacer gets wrapped in a
27867          // div, because it floats and gets aligned next to the datepicker.
27868          // There are easier ways of working around this with CSS (making the
27869          // datepicker 100% wide, change the `display` etc.), however they
27870          // break the alignment with any other form controls.
27871          var spacer = element[0].querySelector('.md-errors-spacer');
27872
27873          if (spacer) {
27874            element.after(angular.element('<div>').append(spacer));
27875          }
27876
27877          mdInputContainer.setHasPlaceholder(attr.mdPlaceholder);
27878          mdInputContainer.input = element;
27879          mdInputContainer.element
27880            .addClass(INPUT_CONTAINER_CLASS)
27881            .toggleClass(HAS_ICON_CLASS, attr.mdHideIcons !== 'calendar' && attr.mdHideIcons !== 'all');
27882
27883          if (!mdInputContainer.label) {
27884            $mdAria.expect(element, 'aria-label', attr.mdPlaceholder);
27885          } else if(!mdNoAsterisk) {
27886            attr.$observe('required', function(value) {
27887              mdInputContainer.label.toggleClass('md-required', !!value);
27888            });
27889          }
27890
27891          scope.$watch(mdInputContainer.isErrorGetter || function() {
27892            return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
27893          }, mdInputContainer.setInvalid);
27894        } else if (parentForm) {
27895          // If invalid, highlights the input when the parent form is submitted.
27896          var parentSubmittedWatcher = scope.$watch(function() {
27897            return parentForm.$submitted;
27898          }, function(isSubmitted) {
27899            if (isSubmitted) {
27900              mdDatePickerCtrl.updateErrorState();
27901              parentSubmittedWatcher();
27902            }
27903          });
27904        }
27905      }
27906    };
27907  }
27908
27909  /** Additional offset for the input's `size` attribute, which is updated based on its content. */
27910  var EXTRA_INPUT_SIZE = 3;
27911
27912  /** Class applied to the container if the date is invalid. */
27913  var INVALID_CLASS = 'md-datepicker-invalid';
27914
27915  /** Class applied to the datepicker when it's open. */
27916  var OPEN_CLASS = 'md-datepicker-open';
27917
27918  /** Class applied to the md-input-container, if a datepicker is placed inside it */
27919  var INPUT_CONTAINER_CLASS = '_md-datepicker-floating-label';
27920
27921  /** Class to be applied when the calendar icon is enabled. */
27922  var HAS_ICON_CLASS = '_md-datepicker-has-calendar-icon';
27923
27924  /** Default time in ms to debounce input event by. */
27925  var DEFAULT_DEBOUNCE_INTERVAL = 500;
27926
27927  /**
27928   * Height of the calendar pane used to check if the pane is going outside the boundary of
27929   * the viewport. See calendar.scss for how $md-calendar-height is computed; an extra 20px is
27930   * also added to space the pane away from the exact edge of the screen.
27931   *
27932   *  This is computed statically now, but can be changed to be measured if the circumstances
27933   *  of calendar sizing are changed.
27934   */
27935  var CALENDAR_PANE_HEIGHT = 368;
27936
27937  /**
27938   * Width of the calendar pane used to check if the pane is going outside the boundary of
27939   * the viewport. See calendar.scss for how $md-calendar-width is computed; an extra 20px is
27940   * also added to space the pane away from the exact edge of the screen.
27941   *
27942   *  This is computed statically now, but can be changed to be measured if the circumstances
27943   *  of calendar sizing are changed.
27944   */
27945  var CALENDAR_PANE_WIDTH = 360;
27946
27947  /**
27948   * Controller for md-datepicker.
27949   *
27950   * @ngInject @constructor
27951   */
27952  function DatePickerCtrl($scope, $element, $attrs, $window, $mdConstant,
27953    $mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF, $mdGesture, $filter) {
27954
27955    /** @final */
27956    this.$window = $window;
27957
27958    /** @final */
27959    this.dateLocale = $mdDateLocale;
27960
27961    /** @final */
27962    this.dateUtil = $$mdDateUtil;
27963
27964    /** @final */
27965    this.$mdConstant = $mdConstant;
27966
27967    /* @final */
27968    this.$mdUtil = $mdUtil;
27969
27970    /** @final */
27971    this.$$rAF = $$rAF;
27972
27973    /**
27974     * The root document element. This is used for attaching a top-level click handler to
27975     * close the calendar panel when a click outside said panel occurs. We use `documentElement`
27976     * instead of body because, when scrolling is disabled, some browsers consider the body element
27977     * to be completely off the screen and propagate events directly to the html element.
27978     * @type {!angular.JQLite}
27979     */
27980    this.documentElement = angular.element(document.documentElement);
27981
27982    /** @type {!angular.NgModelController} */
27983    this.ngModelCtrl = null;
27984
27985    /** @type {HTMLInputElement} */
27986    this.inputElement = $element[0].querySelector('input');
27987
27988    /** @final {!angular.JQLite} */
27989    this.ngInputElement = angular.element(this.inputElement);
27990
27991    /** @type {HTMLElement} */
27992    this.inputContainer = $element[0].querySelector('.md-datepicker-input-container');
27993
27994    /** @type {HTMLElement} Floating calendar pane. */
27995    this.calendarPane = $element[0].querySelector('.md-datepicker-calendar-pane');
27996
27997    /** @type {HTMLElement} Calendar icon button. */
27998    this.calendarButton = $element[0].querySelector('.md-datepicker-button');
27999
28000    /**
28001     * Element covering everything but the input in the top of the floating calendar pane.
28002     * @type {!angular.JQLite}
28003     */
28004    this.inputMask = angular.element($element[0].querySelector('.md-datepicker-input-mask-opaque'));
28005
28006    /** @final {!angular.JQLite} */
28007    this.$element = $element;
28008
28009    /** @final {!angular.Attributes} */
28010    this.$attrs = $attrs;
28011
28012    /** @final {!angular.Scope} */
28013    this.$scope = $scope;
28014
28015    /** @type {Date} */
28016    this.date = null;
28017
28018    /** @type {boolean} */
28019    this.isFocused = false;
28020
28021    /** @type {boolean} */
28022    this.isDisabled;
28023    this.setDisabled($element[0].disabled || angular.isString($attrs.disabled));
28024
28025    /** @type {boolean} Whether the date-picker's calendar pane is open. */
28026    this.isCalendarOpen = false;
28027
28028    /** @type {boolean} Whether the calendar should open when the input is focused. */
28029    this.openOnFocus = $attrs.hasOwnProperty('mdOpenOnFocus');
28030
28031    /** @final */
28032    this.mdInputContainer = null;
28033
28034    /**
28035     * Element from which the calendar pane was opened. Keep track of this so that we can return
28036     * focus to it when the pane is closed.
28037     * @type {HTMLElement}
28038     */
28039    this.calendarPaneOpenedFrom = null;
28040
28041    /** @type {String} Unique id for the calendar pane. */
28042    this.calendarPane.id = 'md-date-pane' + $mdUtil.nextUid();
28043
28044    /** Pre-bound click handler is saved so that the event listener can be removed. */
28045    this.bodyClickHandler = angular.bind(this, this.handleBodyClick);
28046
28047    /**
28048     * Name of the event that will trigger a close. Necessary to sniff the browser, because
28049     * the resize event doesn't make sense on mobile and can have a negative impact since it
28050     * triggers whenever the browser zooms in on a focused input.
28051     */
28052    this.windowEventName = ($mdGesture.isIos || $mdGesture.isAndroid) ? 'orientationchange' : 'resize';
28053
28054    /** Pre-bound close handler so that the event listener can be removed. */
28055    this.windowEventHandler = $mdUtil.debounce(angular.bind(this, this.closeCalendarPane), 100);
28056
28057    /** Pre-bound handler for the window blur event. Allows for it to be removed later. */
28058    this.windowBlurHandler = angular.bind(this, this.handleWindowBlur);
28059
28060    /** The built-in Angular date filter. */
28061    this.ngDateFilter = $filter('date');
28062
28063    /** @type {Number} Extra margin for the left side of the floating calendar pane. */
28064    this.leftMargin = 20;
28065
28066    /** @type {Number} Extra margin for the top of the floating calendar. Gets determined on the first open. */
28067    this.topMargin = null;
28068
28069    // Unless the user specifies so, the datepicker should not be a tab stop.
28070    // This is necessary because ngAria might add a tabindex to anything with an ng-model
28071    // (based on whether or not the user has turned that particular feature on/off).
28072    if ($attrs.tabindex) {
28073      this.ngInputElement.attr('tabindex', $attrs.tabindex);
28074      $attrs.$set('tabindex', null);
28075    } else {
28076      $attrs.$set('tabindex', '-1');
28077    }
28078
28079    $mdTheming($element);
28080    $mdTheming(angular.element(this.calendarPane));
28081
28082    this.installPropertyInterceptors();
28083    this.attachChangeListeners();
28084    this.attachInteractionListeners();
28085
28086    var self = this;
28087
28088    $scope.$on('$destroy', function() {
28089      self.detachCalendarPane();
28090    });
28091
28092    if ($attrs.mdIsOpen) {
28093      $scope.$watch('ctrl.isOpen', function(shouldBeOpen) {
28094        if (shouldBeOpen) {
28095          self.openCalendarPane({
28096            target: self.inputElement
28097          });
28098        } else {
28099          self.closeCalendarPane();
28100        }
28101      });
28102    }
28103  }
28104
28105  /**
28106   * Sets up the controller's reference to ngModelController and
28107   * applies Angular's `input[type="date"]` directive.
28108   * @param {!angular.NgModelController} ngModelCtrl Instance of the ngModel controller.
28109   * @param {Object} mdInputContainer Instance of the mdInputContainer controller.
28110   * @param {Object} inputDirective Config for Angular's `input` directive.
28111   */
28112  DatePickerCtrl.prototype.configureNgModel = function(ngModelCtrl, mdInputContainer, inputDirective) {
28113    this.ngModelCtrl = ngModelCtrl;
28114    this.mdInputContainer = mdInputContainer;
28115
28116    // The input needs to be [type="date"] in order to be picked up by Angular.
28117    this.$attrs.$set('type', 'date');
28118
28119    // Invoke the `input` directive link function, adding a stub for the element.
28120    // This allows us to re-use Angular's logic for setting the timezone via ng-model-options.
28121    // It works by calling the link function directly which then adds the proper `$parsers` and
28122    // `$formatters` to the ngModel controller.
28123    inputDirective[0].link.pre(this.$scope, {
28124      on: angular.noop,
28125      val: angular.noop,
28126      0: {}
28127    }, this.$attrs, [ngModelCtrl]);
28128
28129    var self = this;
28130
28131    // Responds to external changes to the model value.
28132    self.ngModelCtrl.$formatters.push(function(value) {
28133      if (value && !(value instanceof Date)) {
28134        throw Error('The ng-model for md-datepicker must be a Date instance. ' +
28135            'Currently the model is a: ' + (typeof value));
28136      }
28137
28138      self.date = value;
28139      self.inputElement.value = self.dateLocale.formatDate(value);
28140      self.mdInputContainer && self.mdInputContainer.setHasValue(!!value);
28141      self.resizeInputElement();
28142      self.updateErrorState();
28143
28144      return value;
28145    });
28146
28147    // Responds to external error state changes (e.g. ng-required based on another input).
28148    ngModelCtrl.$viewChangeListeners.unshift(angular.bind(this, this.updateErrorState));
28149  };
28150
28151  /**
28152   * Attach event listeners for both the text input and the md-calendar.
28153   * Events are used instead of ng-model so that updates don't infinitely update the other
28154   * on a change. This should also be more performant than using a $watch.
28155   */
28156  DatePickerCtrl.prototype.attachChangeListeners = function() {
28157    var self = this;
28158
28159    self.$scope.$on('md-calendar-change', function(event, date) {
28160      self.setModelValue(date);
28161      self.date = date;
28162      self.inputElement.value = self.dateLocale.formatDate(date);
28163      self.mdInputContainer && self.mdInputContainer.setHasValue(!!date);
28164      self.closeCalendarPane();
28165      self.resizeInputElement();
28166      self.updateErrorState();
28167    });
28168
28169    self.ngInputElement.on('input', angular.bind(self, self.resizeInputElement));
28170
28171    var debounceInterval = angular.isDefined(this.debounceInterval) ?
28172        this.debounceInterval : DEFAULT_DEBOUNCE_INTERVAL;
28173    self.ngInputElement.on('input', self.$mdUtil.debounce(self.handleInputEvent,
28174        debounceInterval, self));
28175  };
28176
28177  /** Attach event listeners for user interaction. */
28178  DatePickerCtrl.prototype.attachInteractionListeners = function() {
28179    var self = this;
28180    var $scope = this.$scope;
28181    var keyCodes = this.$mdConstant.KEY_CODE;
28182
28183    // Add event listener through angular so that we can triggerHandler in unit tests.
28184    self.ngInputElement.on('keydown', function(event) {
28185      if (event.altKey && event.keyCode == keyCodes.DOWN_ARROW) {
28186        self.openCalendarPane(event);
28187        $scope.$digest();
28188      }
28189    });
28190
28191    if (self.openOnFocus) {
28192      self.ngInputElement.on('focus', angular.bind(self, self.openCalendarPane));
28193      angular.element(self.$window).on('blur', self.windowBlurHandler);
28194
28195      $scope.$on('$destroy', function() {
28196        angular.element(self.$window).off('blur', self.windowBlurHandler);
28197      });
28198    }
28199
28200    $scope.$on('md-calendar-close', function() {
28201      self.closeCalendarPane();
28202    });
28203  };
28204
28205  /**
28206   * Capture properties set to the date-picker and imperitively handle internal changes.
28207   * This is done to avoid setting up additional $watches.
28208   */
28209  DatePickerCtrl.prototype.installPropertyInterceptors = function() {
28210    var self = this;
28211
28212    if (this.$attrs.ngDisabled) {
28213      // The expression is to be evaluated against the directive element's scope and not
28214      // the directive's isolate scope.
28215      var scope = this.$scope.$parent;
28216
28217      if (scope) {
28218        scope.$watch(this.$attrs.ngDisabled, function(isDisabled) {
28219          self.setDisabled(isDisabled);
28220        });
28221      }
28222    }
28223
28224    Object.defineProperty(this, 'placeholder', {
28225      get: function() { return self.inputElement.placeholder; },
28226      set: function(value) { self.inputElement.placeholder = value || ''; }
28227    });
28228  };
28229
28230  /**
28231   * Sets whether the date-picker is disabled.
28232   * @param {boolean} isDisabled
28233   */
28234  DatePickerCtrl.prototype.setDisabled = function(isDisabled) {
28235    this.isDisabled = isDisabled;
28236    this.inputElement.disabled = isDisabled;
28237
28238    if (this.calendarButton) {
28239      this.calendarButton.disabled = isDisabled;
28240    }
28241  };
28242
28243  /**
28244   * Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are:
28245   *   - mindate: whether the selected date is before the minimum date.
28246   *   - maxdate: whether the selected flag is after the maximum date.
28247   *   - filtered: whether the selected date is allowed by the custom filtering function.
28248   *   - valid: whether the entered text input is a valid date
28249   *
28250   * The 'required' flag is handled automatically by ngModel.
28251   *
28252   * @param {Date=} opt_date Date to check. If not given, defaults to the datepicker's model value.
28253   */
28254  DatePickerCtrl.prototype.updateErrorState = function(opt_date) {
28255    var date = opt_date || this.date;
28256
28257    // Clear any existing errors to get rid of anything that's no longer relevant.
28258    this.clearErrorState();
28259
28260    if (this.dateUtil.isValidDate(date)) {
28261      // Force all dates to midnight in order to ignore the time portion.
28262      date = this.dateUtil.createDateAtMidnight(date);
28263
28264      if (this.dateUtil.isValidDate(this.minDate)) {
28265        var minDate = this.dateUtil.createDateAtMidnight(this.minDate);
28266        this.ngModelCtrl.$setValidity('mindate', date >= minDate);
28267      }
28268
28269      if (this.dateUtil.isValidDate(this.maxDate)) {
28270        var maxDate = this.dateUtil.createDateAtMidnight(this.maxDate);
28271        this.ngModelCtrl.$setValidity('maxdate', date <= maxDate);
28272      }
28273
28274      if (angular.isFunction(this.dateFilter)) {
28275        this.ngModelCtrl.$setValidity('filtered', this.dateFilter(date));
28276      }
28277    } else {
28278      // The date is seen as "not a valid date" if there is *something* set
28279      // (i.e.., not null or undefined), but that something isn't a valid date.
28280      this.ngModelCtrl.$setValidity('valid', date == null);
28281    }
28282
28283    // TODO(jelbourn): Change this to classList.toggle when we stop using PhantomJS in unit tests
28284    // because it doesn't conform to the DOMTokenList spec.
28285    // See https://github.com/ariya/phantomjs/issues/12782.
28286    if (!this.ngModelCtrl.$valid) {
28287      this.inputContainer.classList.add(INVALID_CLASS);
28288    }
28289  };
28290
28291  /** Clears any error flags set by `updateErrorState`. */
28292  DatePickerCtrl.prototype.clearErrorState = function() {
28293    this.inputContainer.classList.remove(INVALID_CLASS);
28294    ['mindate', 'maxdate', 'filtered', 'valid'].forEach(function(field) {
28295      this.ngModelCtrl.$setValidity(field, true);
28296    }, this);
28297  };
28298
28299  /** Resizes the input element based on the size of its content. */
28300  DatePickerCtrl.prototype.resizeInputElement = function() {
28301    this.inputElement.size = this.inputElement.value.length + EXTRA_INPUT_SIZE;
28302  };
28303
28304  /**
28305   * Sets the model value if the user input is a valid date.
28306   * Adds an invalid class to the input element if not.
28307   */
28308  DatePickerCtrl.prototype.handleInputEvent = function() {
28309    var inputString = this.inputElement.value;
28310    var parsedDate = inputString ? this.dateLocale.parseDate(inputString) : null;
28311    this.dateUtil.setDateTimeToMidnight(parsedDate);
28312
28313    // An input string is valid if it is either empty (representing no date)
28314    // or if it parses to a valid date that the user is allowed to select.
28315    var isValidInput = inputString == '' || (
28316      this.dateUtil.isValidDate(parsedDate) &&
28317      this.dateLocale.isDateComplete(inputString) &&
28318      this.isDateEnabled(parsedDate)
28319    );
28320
28321    // The datepicker's model is only updated when there is a valid input.
28322    if (isValidInput) {
28323      this.setModelValue(parsedDate);
28324      this.date = parsedDate;
28325    }
28326
28327    this.updateErrorState(parsedDate);
28328  };
28329
28330  /**
28331   * Check whether date is in range and enabled
28332   * @param {Date=} opt_date
28333   * @return {boolean} Whether the date is enabled.
28334   */
28335  DatePickerCtrl.prototype.isDateEnabled = function(opt_date) {
28336    return this.dateUtil.isDateWithinRange(opt_date, this.minDate, this.maxDate) &&
28337          (!angular.isFunction(this.dateFilter) || this.dateFilter(opt_date));
28338  };
28339
28340  /** Position and attach the floating calendar to the document. */
28341  DatePickerCtrl.prototype.attachCalendarPane = function() {
28342    var calendarPane = this.calendarPane;
28343    var body = document.body;
28344
28345    calendarPane.style.transform = '';
28346    this.$element.addClass(OPEN_CLASS);
28347    this.mdInputContainer && this.mdInputContainer.element.addClass(OPEN_CLASS);
28348    angular.element(body).addClass('md-datepicker-is-showing');
28349
28350    var elementRect = this.inputContainer.getBoundingClientRect();
28351    var bodyRect = body.getBoundingClientRect();
28352
28353    if (!this.topMargin || this.topMargin < 0) {
28354      this.topMargin = (this.inputMask.parent().prop('clientHeight') - this.ngInputElement.prop('clientHeight')) / 2;
28355    }
28356
28357    // Check to see if the calendar pane would go off the screen. If so, adjust position
28358    // accordingly to keep it within the viewport.
28359    var paneTop = elementRect.top - bodyRect.top - this.topMargin;
28360    var paneLeft = elementRect.left - bodyRect.left - this.leftMargin;
28361
28362    // If ng-material has disabled body scrolling (for example, if a dialog is open),
28363    // then it's possible that the already-scrolled body has a negative top/left. In this case,
28364    // we want to treat the "real" top as (0 - bodyRect.top). In a normal scrolling situation,
28365    // though, the top of the viewport should just be the body's scroll position.
28366    var viewportTop = (bodyRect.top < 0 && document.body.scrollTop == 0) ?
28367        -bodyRect.top :
28368        document.body.scrollTop;
28369
28370    var viewportLeft = (bodyRect.left < 0 && document.body.scrollLeft == 0) ?
28371        -bodyRect.left :
28372        document.body.scrollLeft;
28373
28374    var viewportBottom = viewportTop + this.$window.innerHeight;
28375    var viewportRight = viewportLeft + this.$window.innerWidth;
28376
28377    // Creates an overlay with a hole the same size as element. We remove a pixel or two
28378    // on each end to make it overlap slightly. The overlay's background is added in
28379    // the theme in the form of a box-shadow with a huge spread.
28380    this.inputMask.css({
28381      position: 'absolute',
28382      left: this.leftMargin + 'px',
28383      top: this.topMargin + 'px',
28384      width: (elementRect.width - 1) + 'px',
28385      height: (elementRect.height - 2) + 'px'
28386    });
28387
28388    // If the right edge of the pane would be off the screen and shifting it left by the
28389    // difference would not go past the left edge of the screen. If the calendar pane is too
28390    // big to fit on the screen at all, move it to the left of the screen and scale the entire
28391    // element down to fit.
28392    if (paneLeft + CALENDAR_PANE_WIDTH > viewportRight) {
28393      if (viewportRight - CALENDAR_PANE_WIDTH > 0) {
28394        paneLeft = viewportRight - CALENDAR_PANE_WIDTH;
28395      } else {
28396        paneLeft = viewportLeft;
28397        var scale = this.$window.innerWidth / CALENDAR_PANE_WIDTH;
28398        calendarPane.style.transform = 'scale(' + scale + ')';
28399      }
28400
28401      calendarPane.classList.add('md-datepicker-pos-adjusted');
28402    }
28403
28404    // If the bottom edge of the pane would be off the screen and shifting it up by the
28405    // difference would not go past the top edge of the screen.
28406    if (paneTop + CALENDAR_PANE_HEIGHT > viewportBottom &&
28407        viewportBottom - CALENDAR_PANE_HEIGHT > viewportTop) {
28408      paneTop = viewportBottom - CALENDAR_PANE_HEIGHT;
28409      calendarPane.classList.add('md-datepicker-pos-adjusted');
28410    }
28411
28412    calendarPane.style.left = paneLeft + 'px';
28413    calendarPane.style.top = paneTop + 'px';
28414    document.body.appendChild(calendarPane);
28415
28416    // Add CSS class after one frame to trigger open animation.
28417    this.$$rAF(function() {
28418      calendarPane.classList.add('md-pane-open');
28419    });
28420  };
28421
28422  /** Detach the floating calendar pane from the document. */
28423  DatePickerCtrl.prototype.detachCalendarPane = function() {
28424    this.$element.removeClass(OPEN_CLASS);
28425    this.mdInputContainer && this.mdInputContainer.element.removeClass(OPEN_CLASS);
28426    angular.element(document.body).removeClass('md-datepicker-is-showing');
28427    this.calendarPane.classList.remove('md-pane-open');
28428    this.calendarPane.classList.remove('md-datepicker-pos-adjusted');
28429
28430    if (this.isCalendarOpen) {
28431      this.$mdUtil.enableScrolling();
28432    }
28433
28434    if (this.calendarPane.parentNode) {
28435      // Use native DOM removal because we do not want any of the
28436      // angular state of this element to be disposed.
28437      this.calendarPane.parentNode.removeChild(this.calendarPane);
28438    }
28439  };
28440
28441  /**
28442   * Open the floating calendar pane.
28443   * @param {Event} event
28444   */
28445  DatePickerCtrl.prototype.openCalendarPane = function(event) {
28446    if (!this.isCalendarOpen && !this.isDisabled && !this.inputFocusedOnWindowBlur) {
28447      this.isCalendarOpen = this.isOpen = true;
28448      this.calendarPaneOpenedFrom = event.target;
28449
28450      // Because the calendar pane is attached directly to the body, it is possible that the
28451      // rest of the component (input, etc) is in a different scrolling container, such as
28452      // an md-content. This means that, if the container is scrolled, the pane would remain
28453      // stationary. To remedy this, we disable scrolling while the calendar pane is open, which
28454      // also matches the native behavior for things like `<select>` on Mac and Windows.
28455      this.$mdUtil.disableScrollAround(this.calendarPane);
28456
28457      this.attachCalendarPane();
28458      this.focusCalendar();
28459      this.evalAttr('ngFocus');
28460
28461      // Attach click listener inside of a timeout because, if this open call was triggered by a
28462      // click, we don't want it to be immediately propogated up to the body and handled.
28463      var self = this;
28464      this.$mdUtil.nextTick(function() {
28465        // Use 'touchstart` in addition to click in order to work on iOS Safari, where click
28466        // events aren't propogated under most circumstances.
28467        // See http://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
28468        self.documentElement.on('click touchstart', self.bodyClickHandler);
28469      }, false);
28470
28471      window.addEventListener(this.windowEventName, this.windowEventHandler);
28472    }
28473  };
28474
28475  /** Close the floating calendar pane. */
28476  DatePickerCtrl.prototype.closeCalendarPane = function() {
28477    if (this.isCalendarOpen) {
28478      var self = this;
28479
28480      self.detachCalendarPane();
28481      self.ngModelCtrl.$setTouched();
28482      self.evalAttr('ngBlur');
28483
28484      self.documentElement.off('click touchstart', self.bodyClickHandler);
28485      window.removeEventListener(self.windowEventName, self.windowEventHandler);
28486
28487      self.calendarPaneOpenedFrom.focus();
28488      self.calendarPaneOpenedFrom = null;
28489
28490      if (self.openOnFocus) {
28491        // Ensures that all focus events have fired before resetting
28492        // the calendar. Prevents the calendar from reopening immediately
28493        // in IE when md-open-on-focus is set. Also it needs to trigger
28494        // a digest, in order to prevent issues where the calendar wasn't
28495        // showing up on the next open.
28496        self.$mdUtil.nextTick(reset);
28497      } else {
28498        reset();
28499      }
28500    }
28501
28502    function reset(){
28503      self.isCalendarOpen = self.isOpen = false;
28504    }
28505  };
28506
28507  /** Gets the controller instance for the calendar in the floating pane. */
28508  DatePickerCtrl.prototype.getCalendarCtrl = function() {
28509    return angular.element(this.calendarPane.querySelector('md-calendar')).controller('mdCalendar');
28510  };
28511
28512  /** Focus the calendar in the floating pane. */
28513  DatePickerCtrl.prototype.focusCalendar = function() {
28514    // Use a timeout in order to allow the calendar to be rendered, as it is gated behind an ng-if.
28515    var self = this;
28516    this.$mdUtil.nextTick(function() {
28517      self.getCalendarCtrl().focus();
28518    }, false);
28519  };
28520
28521  /**
28522   * Sets whether the input is currently focused.
28523   * @param {boolean} isFocused
28524   */
28525  DatePickerCtrl.prototype.setFocused = function(isFocused) {
28526    if (!isFocused) {
28527      this.ngModelCtrl.$setTouched();
28528    }
28529
28530    // The ng* expressions shouldn't be evaluated when mdOpenOnFocus is on,
28531    // because they also get called when the calendar is opened/closed.
28532    if (!this.openOnFocus) {
28533      this.evalAttr(isFocused ? 'ngFocus' : 'ngBlur');
28534    }
28535
28536    this.isFocused = isFocused;
28537  };
28538
28539  /**
28540   * Handles a click on the document body when the floating calendar pane is open.
28541   * Closes the floating calendar pane if the click is not inside of it.
28542   * @param {MouseEvent} event
28543   */
28544  DatePickerCtrl.prototype.handleBodyClick = function(event) {
28545    if (this.isCalendarOpen) {
28546      var isInCalendar = this.$mdUtil.getClosest(event.target, 'md-calendar');
28547
28548      if (!isInCalendar) {
28549        this.closeCalendarPane();
28550      }
28551
28552      this.$scope.$digest();
28553    }
28554  };
28555
28556  /**
28557   * Handles the event when the user navigates away from the current tab. Keeps track of
28558   * whether the input was focused when the event happened, in order to prevent the calendar
28559   * from re-opening.
28560   */
28561  DatePickerCtrl.prototype.handleWindowBlur = function() {
28562    this.inputFocusedOnWindowBlur = document.activeElement === this.inputElement;
28563  };
28564
28565  /**
28566   * Evaluates an attribute expression against the parent scope.
28567   * @param {String} attr Name of the attribute to be evaluated.
28568   */
28569  DatePickerCtrl.prototype.evalAttr = function(attr) {
28570    if (this.$attrs[attr]) {
28571      this.$scope.$parent.$eval(this.$attrs[attr]);
28572    }
28573  };
28574
28575  /**
28576   * Sets the ng-model value by first converting the date object into a strng. Converting it
28577   * is necessary, in order to pass Angular's `input[type="date"]` validations. Angular turns
28578   * the value into a Date object afterwards, before setting it on the model.
28579   * @param {Date=} value Date to be set as the model value.
28580   */
28581  DatePickerCtrl.prototype.setModelValue = function(value) {
28582    this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd'));
28583  };
28584})();
28585
28586})();
28587(function(){
28588"use strict";
28589
28590angular
28591  .module('material.components.icon')
28592  .directive('mdIcon', ['$mdIcon', '$mdTheming', '$mdAria', '$sce', mdIconDirective]);
28593
28594/**
28595 * @ngdoc directive
28596 * @name mdIcon
28597 * @module material.components.icon
28598 *
28599 * @restrict E
28600 *
28601 * @description
28602 * The `md-icon` directive makes it easier to use vector-based icons in your app (as opposed to
28603 * raster-based icons types like PNG). The directive supports both icon fonts and SVG icons.
28604 *
28605 * Icons should be considered view-only elements that should not be used directly as buttons; instead nest a `<md-icon>`
28606 * inside a `md-button` to add hover and click features.
28607 *
28608 * ### Icon fonts
28609 * Icon fonts are a technique in which you use a font where the glyphs in the font are
28610 * your icons instead of text. Benefits include a straightforward way to bundle everything into a
28611 * single HTTP request, simple scaling, easy color changing, and more.
28612 *
28613 * `md-icon` lets you consume an icon font by letting you reference specific icons in that font
28614 * by name rather than character code.
28615 *
28616 * ### SVG
28617 * For SVGs, the problem with using `<img>` or a CSS `background-image` is that you can't take
28618 * advantage of some SVG features, such as styling specific parts of the icon with CSS or SVG
28619 * animation.
28620 *
28621 * `md-icon` makes it easier to use SVG icons by *inlining* the SVG into an `<svg>` element in the
28622 * document. The most straightforward way of referencing an SVG icon is via URL, just like a
28623 * traditional `<img>`. `$mdIconProvider`, as a convenience, lets you _name_ an icon so you can
28624 * reference it by name instead of URL throughout your templates.
28625 *
28626 * Additionally, you may not want to make separate HTTP requests for every icon, so you can bundle
28627 * your SVG icons together and pre-load them with $mdIconProvider as an icon set. An icon set can
28628 * also be given a name, which acts as a namespace for individual icons, so you can reference them
28629 * like `"social:cake"`.
28630 *
28631 * When using SVGs, both external SVGs (via URLs) or sets of SVGs [from icon sets] can be
28632 * easily loaded and used.When use font-icons, developers must following three (3) simple steps:
28633 *
28634 * <ol>
28635 * <li>Load the font library. e.g.<br/>
28636 *    `<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
28637 *    rel="stylesheet">`
28638 * </li>
28639 * <li>
28640 *   Use either (a) font-icon class names or (b) font ligatures to render the font glyph by using
28641 *   its textual name
28642 * </li>
28643 * <li>
28644 *   Use `<md-icon md-font-icon="classname" />` or <br/>
28645 *   use `<md-icon md-font-set="font library classname or alias"> textual_name </md-icon>` or <br/>
28646 *   use `<md-icon md-font-set="font library classname or alias"> numerical_character_reference </md-icon>`
28647 * </li>
28648 * </ol>
28649 *
28650 * Full details for these steps can be found:
28651 *
28652 * <ul>
28653 * <li>http://google.github.io/material-design-icons/</li>
28654 * <li>http://google.github.io/material-design-icons/#icon-font-for-the-web</li>
28655 * </ul>
28656 *
28657 * The Material Design icon style <code>.material-icons</code> and the icon font references are published in
28658 * Material Design Icons:
28659 *
28660 * <ul>
28661 * <li>https://design.google.com/icons/</li>
28662 * <li>https://design.google.com/icons/#ic_accessibility</li>
28663 * </ul>
28664 *
28665 * <h2 id="material_design_icons">Material Design Icons</h2>
28666 * Using the Material Design Icon-Selector, developers can easily and quickly search for a Material Design font-icon and
28667 * determine its textual name and character reference code. Click on any icon to see the slide-up information
28668 * panel with details regarding a SVG download or information on the font-icon usage.
28669 *
28670 * <a href="https://www.google.com/design/icons/#ic_accessibility" target="_blank" style="border-bottom:none;">
28671 * <img src="https://cloud.githubusercontent.com/assets/210413/7902490/fe8dd14c-0780-11e5-98fb-c821cc6475e6.png"
28672 *      aria-label="Material Design Icon-Selector" style="max-width:75%;padding-left:10%">
28673 * </a>
28674 *
28675 * <span class="image_caption">
28676 *  Click on the image above to link to the
28677 *  <a href="https://design.google.com/icons/#ic_accessibility" target="_blank">Material Design Icon-Selector</a>.
28678 * </span>
28679 *
28680 * @param {string} md-font-icon String name of CSS icon associated with the font-face will be used
28681 * to render the icon. Requires the fonts and the named CSS styles to be preloaded.
28682 * @param {string} md-font-set CSS style name associated with the font library; which will be assigned as
28683 * the class for the font-icon ligature. This value may also be an alias that is used to lookup the classname;
28684 * internally use `$mdIconProvider.fontSet(<alias>)` to determine the style name.
28685 * @param {string} md-svg-src String URL (or expression) used to load, cache, and display an
28686 *     external SVG.
28687 * @param {string} md-svg-icon md-svg-icon String name used for lookup of the icon from the internal cache;
28688 *     interpolated strings or expressions may also be used. Specific set names can be used with
28689 *     the syntax `<set name>:<icon name>`.<br/><br/>
28690 * To use icon sets, developers are required to pre-register the sets using the `$mdIconProvider` service.
28691 * @param {string=} aria-label Labels icon for accessibility. If an empty string is provided, icon
28692 * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no aria-label on the icon
28693 * nor a label on the parent element, a warning will be logged to the console.
28694 * @param {string=} alt Labels icon for accessibility. If an empty string is provided, icon
28695 * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no alt on the icon
28696 * nor a label on the parent element, a warning will be logged to the console.
28697 *
28698 * @usage
28699 * When using SVGs:
28700 * <hljs lang="html">
28701 *
28702 *  <!-- Icon ID; may contain optional icon set prefix; icons must registered using $mdIconProvider -->
28703 *  <md-icon md-svg-icon="social:android"    aria-label="android " ></md-icon>
28704 *
28705 *  <!-- Icon urls; may be preloaded in templateCache -->
28706 *  <md-icon md-svg-src="/android.svg"       aria-label="android " ></md-icon>
28707 *  <md-icon md-svg-src="{{ getAndroid() }}" aria-label="android " ></md-icon>
28708 *
28709 * </hljs>
28710 *
28711 * Use the <code>$mdIconProvider</code> to configure your application with
28712 * svg iconsets.
28713 *
28714 * <hljs lang="js">
28715 *  angular.module('appSvgIconSets', ['ngMaterial'])
28716 *    .controller('DemoCtrl', function($scope) {})
28717 *    .config(function($mdIconProvider) {
28718 *      $mdIconProvider
28719 *         .iconSet('social', 'img/icons/sets/social-icons.svg', 24)
28720 *         .defaultIconSet('img/icons/sets/core-icons.svg', 24);
28721 *     });
28722 * </hljs>
28723 *
28724 *
28725 * When using Font Icons with classnames:
28726 * <hljs lang="html">
28727 *
28728 *  <md-icon md-font-icon="android" aria-label="android" ></md-icon>
28729 *  <md-icon class="icon_home"      aria-label="Home"    ></md-icon>
28730 *
28731 * </hljs>
28732 *
28733 * When using Material Font Icons with ligatures:
28734 * <hljs lang="html">
28735 *  <!--
28736 *  For Material Design Icons
28737 *  The class '.material-icons' is auto-added if a style has NOT been specified
28738 *  since `material-icons` is the default fontset. So your markup:
28739 *  -->
28740 *  <md-icon> face </md-icon>
28741 *  <!-- becomes this at runtime: -->
28742 *  <md-icon md-font-set="material-icons"> face </md-icon>
28743 *  <!-- If the fontset does not support ligature names, then we need to use the ligature unicode.-->
28744 *  <md-icon> &#xE87C; </md-icon>
28745 *  <!-- The class '.material-icons' must be manually added if other styles are also specified-->
28746 *  <md-icon class="material-icons md-light md-48"> face </md-icon>
28747 * </hljs>
28748 *
28749 * When using other Font-Icon libraries:
28750 *
28751 * <hljs lang="js">
28752 *  // Specify a font-icon style alias
28753 *  angular.config(function($mdIconProvider) {
28754 *    $mdIconProvider.fontSet('md', 'material-icons');
28755 *  });
28756 * </hljs>
28757 *
28758 * <hljs lang="html">
28759 *  <md-icon md-font-set="md">favorite</md-icon>
28760 * </hljs>
28761 *
28762 */
28763function mdIconDirective($mdIcon, $mdTheming, $mdAria, $sce) {
28764
28765  return {
28766    restrict: 'E',
28767    link : postLink
28768  };
28769
28770
28771  /**
28772   * Directive postLink
28773   * Supports embedded SVGs, font-icons, & external SVGs
28774   */
28775  function postLink(scope, element, attr) {
28776    $mdTheming(element);
28777    var lastFontIcon = attr.mdFontIcon;
28778    var lastFontSet = $mdIcon.fontSet(attr.mdFontSet);
28779
28780    prepareForFontIcon();
28781
28782    attr.$observe('mdFontIcon', fontIconChanged);
28783    attr.$observe('mdFontSet', fontIconChanged);
28784
28785    // Keep track of the content of the svg src so we can compare against it later to see if the
28786    // attribute is static (and thus safe).
28787    var originalSvgSrc = element[0].getAttribute(attr.$attr.mdSvgSrc);
28788
28789    // If using a font-icon, then the textual name of the icon itself
28790    // provides the aria-label.
28791
28792    var label = attr.alt || attr.mdFontIcon || attr.mdSvgIcon || element.text();
28793    var attrName = attr.$normalize(attr.$attr.mdSvgIcon || attr.$attr.mdSvgSrc || '');
28794
28795    if ( !attr['aria-label'] ) {
28796
28797      if (label !== '' && !parentsHaveText() ) {
28798
28799        $mdAria.expect(element, 'aria-label', label);
28800        $mdAria.expect(element, 'role', 'img');
28801
28802      } else if ( !element.text() ) {
28803        // If not a font-icon with ligature, then
28804        // hide from the accessibility layer.
28805
28806        $mdAria.expect(element, 'aria-hidden', 'true');
28807      }
28808    }
28809
28810    if (attrName) {
28811      // Use either pre-configured SVG or URL source, respectively.
28812      attr.$observe(attrName, function(attrVal) {
28813        element.empty();
28814        if (attrVal) {
28815          $mdIcon(attrVal)
28816            .then(function(svg) {
28817            element.empty();
28818            element.append(svg);
28819          });
28820        }
28821
28822      });
28823    }
28824
28825    function parentsHaveText() {
28826      var parent = element.parent();
28827      if (parent.attr('aria-label') || parent.text()) {
28828        return true;
28829      }
28830      else if(parent.parent().attr('aria-label') || parent.parent().text()) {
28831        return true;
28832      }
28833      return false;
28834    }
28835
28836    function prepareForFontIcon() {
28837      if (!attr.mdSvgIcon && !attr.mdSvgSrc) {
28838        if (attr.mdFontIcon) {
28839          element.addClass('md-font ' + attr.mdFontIcon);
28840        }
28841
28842        element.addClass(lastFontSet);
28843      }
28844    }
28845
28846    function fontIconChanged() {
28847      if (!attr.mdSvgIcon && !attr.mdSvgSrc) {
28848        if (attr.mdFontIcon) {
28849          element.removeClass(lastFontIcon);
28850          element.addClass(attr.mdFontIcon);
28851
28852          lastFontIcon = attr.mdFontIcon;
28853        }
28854
28855        var fontSet = $mdIcon.fontSet(attr.mdFontSet);
28856
28857        if (lastFontSet !== fontSet) {
28858          element.removeClass(lastFontSet);
28859          element.addClass(fontSet);
28860
28861          lastFontSet = fontSet;
28862        }
28863      }
28864    }
28865  }
28866}
28867
28868})();
28869(function(){
28870"use strict";
28871
28872  
28873MdIconService.$inject = ["config", "$templateRequest", "$q", "$log", "$mdUtil", "$sce"];angular
28874    .module('material.components.icon')
28875    .constant('$$mdSvgRegistry', {
28876        'mdTabsArrow':   'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwb2x5Z29uIHBvaW50cz0iMTUuNCw3LjQgMTQsNiA4LDEyIDE0LDE4IDE1LjQsMTYuNiAxMC44LDEyICIvPjwvZz48L3N2Zz4=',
28877        'mdClose':       'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xOSA2LjQxbC0xLjQxLTEuNDEtNS41OSA1LjU5LTUuNTktNS41OS0xLjQxIDEuNDEgNS41OSA1LjU5LTUuNTkgNS41OSAxLjQxIDEuNDEgNS41OS01LjU5IDUuNTkgNS41OSAxLjQxLTEuNDEtNS41OS01LjU5eiIvPjwvZz48L3N2Zz4=',
28878        'mdCancel':      'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xMiAyYy01LjUzIDAtMTAgNC40Ny0xMCAxMHM0LjQ3IDEwIDEwIDEwIDEwLTQuNDcgMTAtMTAtNC40Ny0xMC0xMC0xMHptNSAxMy41OWwtMS40MSAxLjQxLTMuNTktMy41OS0zLjU5IDMuNTktMS40MS0xLjQxIDMuNTktMy41OS0zLjU5LTMuNTkgMS40MS0xLjQxIDMuNTkgMy41OSAzLjU5LTMuNTkgMS40MSAxLjQxLTMuNTkgMy41OSAzLjU5IDMuNTl6Ii8+PC9nPjwvc3ZnPg==',
28879        'mdMenu':        'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zLDZIMjFWOEgzVjZNMywxMUgyMVYxM0gzVjExTTMsMTZIMjFWMThIM1YxNloiIC8+PC9zdmc+',
28880        'mdToggleArrow': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiPjxwYXRoIGQ9Ik0yNCAxNmwtMTIgMTIgMi44MyAyLjgzIDkuMTctOS4xNyA5LjE3IDkuMTcgMi44My0yLjgzeiIvPjxwYXRoIGQ9Ik0wIDBoNDh2NDhoLTQ4eiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==',
28881        'mdCalendar':    'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgM2gtMVYxaC0ydjJIOFYxSDZ2Mkg1Yy0xLjExIDAtMS45OS45LTEuOTkgMkwzIDE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY4aDE0djExek03IDEwaDV2NUg3eiIvPjwvc3ZnPg==',
28882        'mdChecked':     'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiLz48L2c+PC9zdmc+'
28883    })
28884    .provider('$mdIcon', MdIconProvider);
28885
28886/**
28887 * @ngdoc service
28888 * @name $mdIconProvider
28889 * @module material.components.icon
28890 *
28891 * @description
28892 * `$mdIconProvider` is used only to register icon IDs with URLs. These configuration features allow
28893 * icons and icon sets to be pre-registered and associated with source URLs **before** the `<md-icon />`
28894 * directives are compiled.
28895 *
28896 * If using font-icons, the developer is responsible for loading the fonts.
28897 *
28898 * If using SVGs, loading of the actual svg files are deferred to on-demand requests and are loaded
28899 * internally by the `$mdIcon` service using the `$templateRequest` service. When an SVG is
28900 * requested by name/ID, the `$mdIcon` service searches its registry for the associated source URL;
28901 * that URL is used to on-demand load and parse the SVG dynamically.
28902 *
28903 * The `$templateRequest` service expects the icons source to be loaded over trusted URLs.<br/>
28904 * This means, when loading icons from an external URL, you have to trust the URL in the `$sceDelegateProvider`.
28905 *
28906 * <hljs lang="js">
28907 *   app.config(function($sceDelegateProvider) {
28908 *     $sceDelegateProvider.resourceUrlWhitelist([
28909 *       // Adding 'self' to the whitelist, will allow requests from the current origin.
28910 *       'self',
28911 *       // Using double asterisks here, will allow all URLs to load.
28912 *       // We recommend to only specify the given domain you want to allow.
28913 *       '**'
28914 *     ]);
28915 *   });
28916 * </hljs>
28917 *
28918 * Read more about the [$sceDelegateProvider](https://docs.angularjs.org/api/ng/provider/$sceDelegateProvider).
28919 *
28920 * **Notice:** Most font-icons libraries do not support ligatures (for example `fontawesome`).<br/>
28921 *  In such cases you are not able to use the icon's ligature name - Like so:
28922 *
28923 *  <hljs lang="html">
28924 *    <md-icon md-font-set="fa">fa-bell</md-icon>
28925 *  </hljs>
28926 *
28927 * You should instead use the given unicode, instead of the ligature name.
28928 *
28929 * <p ng-hide="true"> ##// Notice we can't use a hljs element here, because the characters will be escaped.</p>
28930 *  ```html
28931 *    <md-icon md-font-set="fa">&#xf0f3</md-icon>
28932 *  ```
28933 *
28934 * All unicode ligatures are prefixed with the `&#x` string.
28935 *
28936 * @usage
28937 * <hljs lang="js">
28938 *   app.config(function($mdIconProvider) {
28939    *
28940    *     // Configure URLs for icons specified by [set:]id.
28941    *
28942    *     $mdIconProvider
28943    *          .defaultFontSet( 'fa' )                   // This sets our default fontset className.
28944    *          .defaultIconSet('my/app/icons.svg')       // Register a default set of SVG icons
28945    *          .iconSet('social', 'my/app/social.svg')   // Register a named icon set of SVGs
28946    *          .icon('android', 'my/app/android.svg')    // Register a specific icon (by name)
28947    *          .icon('work:chair', 'my/app/chair.svg');  // Register icon in a specific set
28948    *   });
28949 * </hljs>
28950 *
28951 * SVG icons and icon sets can be easily pre-loaded and cached using either (a) a build process or (b) a runtime
28952 * **startup** process (shown below):
28953 *
28954 * <hljs lang="js">
28955 *   app.config(function($mdIconProvider) {
28956    *
28957    *     // Register a default set of SVG icon definitions
28958    *     $mdIconProvider.defaultIconSet('my/app/icons.svg')
28959    *
28960    *   })
28961 *   .run(function($templateRequest){
28962    *
28963    *     // Pre-fetch icons sources by URL and cache in the $templateCache...
28964    *     // subsequent $templateRequest calls will look there first.
28965    *
28966    *     var urls = [ 'imy/app/icons.svg', 'img/icons/android.svg'];
28967    *
28968    *     angular.forEach(urls, function(url) {
28969    *       $templateRequest(url);
28970    *     });
28971    *
28972    *   });
28973 *
28974 * </hljs>
28975 *
28976 * > <b>Note:</b> The loaded SVG data is subsequently cached internally for future requests.
28977 *
28978 */
28979
28980/**
28981 * @ngdoc method
28982 * @name $mdIconProvider#icon
28983 *
28984 * @description
28985 * Register a source URL for a specific icon name; the name may include optional 'icon set' name prefix.
28986 * These icons  will later be retrieved from the cache using `$mdIcon( <icon name> )`
28987 *
28988 * @param {string} id Icon name/id used to register the icon
28989 * @param {string} url specifies the external location for the data file. Used internally by
28990 * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
28991 * was configured.
28992 * @param {number=} viewBoxSize Sets the width and height the icon's viewBox.
28993 * It is ignored for icons with an existing viewBox. Default size is 24.
28994 *
28995 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
28996 *
28997 * @usage
28998 * <hljs lang="js">
28999 *   app.config(function($mdIconProvider) {
29000    *
29001    *     // Configure URLs for icons specified by [set:]id.
29002    *
29003    *     $mdIconProvider
29004    *          .icon('android', 'my/app/android.svg')    // Register a specific icon (by name)
29005    *          .icon('work:chair', 'my/app/chair.svg');  // Register icon in a specific set
29006    *   });
29007 * </hljs>
29008 *
29009 */
29010/**
29011 * @ngdoc method
29012 * @name $mdIconProvider#iconSet
29013 *
29014 * @description
29015 * Register a source URL for a 'named' set of icons; group of SVG definitions where each definition
29016 * has an icon id. Individual icons can be subsequently retrieved from this cached set using
29017 * `$mdIcon(<icon set name>:<icon name>)`
29018 *
29019 * @param {string} id Icon name/id used to register the iconset
29020 * @param {string} url specifies the external location for the data file. Used internally by
29021 * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
29022 * was configured.
29023 * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set.
29024 * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size.
29025 * Default value is 24.
29026 *
29027 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
29028 *
29029 *
29030 * @usage
29031 * <hljs lang="js">
29032 *   app.config(function($mdIconProvider) {
29033    *
29034    *     // Configure URLs for icons specified by [set:]id.
29035    *
29036    *     $mdIconProvider
29037    *          .iconSet('social', 'my/app/social.svg')   // Register a named icon set
29038    *   });
29039 * </hljs>
29040 *
29041 */
29042/**
29043 * @ngdoc method
29044 * @name $mdIconProvider#defaultIconSet
29045 *
29046 * @description
29047 * Register a source URL for the default 'named' set of icons. Unless explicitly registered,
29048 * subsequent lookups of icons will failover to search this 'default' icon set.
29049 * Icon can be retrieved from this cached, default set using `$mdIcon(<name>)`
29050 *
29051 * @param {string} url specifies the external location for the data file. Used internally by
29052 * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
29053 * was configured.
29054 * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set.
29055 * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size.
29056 * Default value is 24.
29057 *
29058 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
29059 *
29060 * @usage
29061 * <hljs lang="js">
29062 *   app.config(function($mdIconProvider) {
29063    *
29064    *     // Configure URLs for icons specified by [set:]id.
29065    *
29066    *     $mdIconProvider
29067    *          .defaultIconSet( 'my/app/social.svg' )   // Register a default icon set
29068    *   });
29069 * </hljs>
29070 *
29071 */
29072/**
29073 * @ngdoc method
29074 * @name $mdIconProvider#defaultFontSet
29075 *
29076 * @description
29077 * When using Font-Icons, Angular Material assumes the the Material Design icons will be used and automatically
29078 * configures the default font-set == 'material-icons'. Note that the font-set references the font-icon library
29079 * class style that should be applied to the `<md-icon>`.
29080 *
29081 * Configuring the default means that the attributes
29082 * `md-font-set="material-icons"` or `class="material-icons"` do not need to be explicitly declared on the
29083 * `<md-icon>` markup. For example:
29084 *
29085 *  `<md-icon> face </md-icon>`
29086 *  will render as
29087 *  `<span class="material-icons"> face </span>`, and
29088 *
29089 *  `<md-icon md-font-set="fa"> face </md-icon>`
29090 *  will render as
29091 *  `<span class="fa"> face </span>`
29092 *
29093 * @param {string} name of the font-library style that should be applied to the md-icon DOM element
29094 *
29095 * @usage
29096 * <hljs lang="js">
29097 *   app.config(function($mdIconProvider) {
29098   *     $mdIconProvider.defaultFontSet( 'fa' );
29099   *   });
29100 * </hljs>
29101 *
29102 */
29103
29104/**
29105 * @ngdoc method
29106 * @name $mdIconProvider#fontSet
29107 *
29108 * @description
29109 * When using a font set for `<md-icon>` you must specify the correct font classname in the `md-font-set`
29110 * attribute. If the fonset className is really long, your markup may become cluttered... an easy
29111 * solution is to define an `alias` for your fontset:
29112 *
29113 * @param {string} alias of the specified fontset.
29114 * @param {string} className of the fontset.
29115 *
29116 * @usage
29117 * <hljs lang="js">
29118 *   app.config(function($mdIconProvider) {
29119   *     // In this case, we set an alias for the `material-icons` fontset.
29120   *     $mdIconProvider.fontSet('md', 'material-icons');
29121   *   });
29122 * </hljs>
29123 *
29124 */
29125
29126/**
29127 * @ngdoc method
29128 * @name $mdIconProvider#defaultViewBoxSize
29129 *
29130 * @description
29131 * While `<md-icon />` markup can also be style with sizing CSS, this method configures
29132 * the default width **and** height used for all icons; unless overridden by specific CSS.
29133 * The default sizing is (24px, 24px).
29134 * @param {number=} viewBoxSize Sets the width and height of the viewBox for an icon or an icon set.
29135 * All icons in a set should be the same size. The default value is 24.
29136 *
29137 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
29138 *
29139 * @usage
29140 * <hljs lang="js">
29141 *   app.config(function($mdIconProvider) {
29142    *
29143    *     // Configure URLs for icons specified by [set:]id.
29144    *
29145    *     $mdIconProvider
29146    *          .defaultViewBoxSize(36)   // Register a default icon size (width == height)
29147    *   });
29148 * </hljs>
29149 *
29150 */
29151
29152var config = {
29153  defaultViewBoxSize: 24,
29154  defaultFontSet: 'material-icons',
29155  fontSets: []
29156};
29157
29158function MdIconProvider() {
29159}
29160
29161MdIconProvider.prototype = {
29162  icon: function(id, url, viewBoxSize) {
29163    if (id.indexOf(':') == -1) id = '$default:' + id;
29164
29165    config[id] = new ConfigurationItem(url, viewBoxSize);
29166    return this;
29167  },
29168
29169  iconSet: function(id, url, viewBoxSize) {
29170    config[id] = new ConfigurationItem(url, viewBoxSize);
29171    return this;
29172  },
29173
29174  defaultIconSet: function(url, viewBoxSize) {
29175    var setName = '$default';
29176
29177    if (!config[setName]) {
29178      config[setName] = new ConfigurationItem(url, viewBoxSize);
29179    }
29180
29181    config[setName].viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
29182
29183    return this;
29184  },
29185
29186  defaultViewBoxSize: function(viewBoxSize) {
29187    config.defaultViewBoxSize = viewBoxSize;
29188    return this;
29189  },
29190
29191  /**
29192   * Register an alias name associated with a font-icon library style ;
29193   */
29194  fontSet: function fontSet(alias, className) {
29195    config.fontSets.push({
29196      alias: alias,
29197      fontSet: className || alias
29198    });
29199    return this;
29200  },
29201
29202  /**
29203   * Specify a default style name associated with a font-icon library
29204   * fallback to Material Icons.
29205   *
29206   */
29207  defaultFontSet: function defaultFontSet(className) {
29208    config.defaultFontSet = !className ? '' : className;
29209    return this;
29210  },
29211
29212  defaultIconSize: function defaultIconSize(iconSize) {
29213    config.defaultIconSize = iconSize;
29214    return this;
29215  },
29216
29217  $get: ['$templateRequest', '$q', '$log', '$mdUtil', '$sce', function($templateRequest, $q, $log, $mdUtil, $sce) {
29218    return MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce);
29219  }]
29220};
29221
29222/**
29223 *  Configuration item stored in the Icon registry; used for lookups
29224 *  to load if not already cached in the `loaded` cache
29225 */
29226function ConfigurationItem(url, viewBoxSize) {
29227  this.url = url;
29228  this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
29229}
29230
29231/**
29232 * @ngdoc service
29233 * @name $mdIcon
29234 * @module material.components.icon
29235 *
29236 * @description
29237 * The `$mdIcon` service is a function used to lookup SVG icons.
29238 *
29239 * @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
29240 * 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
29241 * performed. The Id may be a unique icon ID or may include an iconSet ID prefix.
29242 *
29243 * For the **id** query to work properly, this means that all id-to-URL mappings must have been previously configured
29244 * using the `$mdIconProvider`.
29245 *
29246 * @returns {angular.$q.Promise} A promise that gets resolved to a clone of the initial SVG DOM element; which was
29247 * created from the SVG markup in the SVG data file. If an error occurs (e.g. the icon cannot be found) the promise
29248 * will get rejected.
29249 *
29250 * @usage
29251 * <hljs lang="js">
29252 * function SomeDirective($mdIcon) {
29253  *
29254  *   // See if the icon has already been loaded, if not
29255  *   // then lookup the icon from the registry cache, load and cache
29256  *   // it for future requests.
29257  *   // NOTE: ID queries require configuration with $mdIconProvider
29258  *
29259  *   $mdIcon('android').then(function(iconEl)    { element.append(iconEl); });
29260  *   $mdIcon('work:chair').then(function(iconEl) { element.append(iconEl); });
29261  *
29262  *   // Load and cache the external SVG using a URL
29263  *
29264  *   $mdIcon('img/icons/android.svg').then(function(iconEl) {
29265  *     element.append(iconEl);
29266  *   });
29267  * };
29268 * </hljs>
29269 *
29270 * > <b>Note:</b> The `<md-icon>` directive internally uses the `$mdIcon` service to query, loaded,
29271 *   and instantiate SVG DOM elements.
29272 */
29273
29274/* @ngInject */
29275function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
29276  var iconCache = {};
29277  var svgCache = {};
29278  var urlRegex = /[-\w@:%\+.~#?&//=]{2,}\.[a-z]{2,4}\b(\/[-\w@:%\+.~#?&//=]*)?/i;
29279  var dataUrlRegex = /^data:image\/svg\+xml[\s*;\w\-\=]*?(base64)?,(.*)$/i;
29280
29281  Icon.prototype = {clone: cloneSVG, prepare: prepareAndStyle};
29282  getIcon.fontSet = findRegisteredFontSet;
29283
29284  // Publish service...
29285  return getIcon;
29286
29287  /**
29288   * Actual $mdIcon service is essentially a lookup function
29289   */
29290  function getIcon(id) {
29291    id = id || '';
29292
29293    // If the "id" provided is not a string, the only other valid value is a $sce trust wrapper
29294    // over a URL string. If the value is not trusted, this will intentionally throw an error
29295    // because the user is attempted to use an unsafe URL, potentially opening themselves up
29296    // to an XSS attack.
29297    if (!angular.isString(id)) {
29298      id = $sce.getTrustedUrl(id);
29299    }
29300
29301    // If already loaded and cached, use a clone of the cached icon.
29302    // Otherwise either load by URL, or lookup in the registry and then load by URL, and cache.
29303
29304    if (iconCache[id]) {
29305      return $q.when(transformClone(iconCache[id]));
29306    }
29307
29308    if (urlRegex.test(id) || dataUrlRegex.test(id)) {
29309      return loadByURL(id).then(cacheIcon(id));
29310    }
29311
29312    if (id.indexOf(':') == -1) {
29313      id = '$default:' + id;
29314    }
29315
29316    var load = config[id] ? loadByID : loadFromIconSet;
29317    return load(id)
29318      .then(cacheIcon(id));
29319  }
29320
29321  /**
29322   * Lookup registered fontSet style using its alias...
29323   * If not found,
29324   */
29325  function findRegisteredFontSet(alias) {
29326    var useDefault = angular.isUndefined(alias) || !(alias && alias.length);
29327    if (useDefault) return config.defaultFontSet;
29328
29329    var result = alias;
29330    angular.forEach(config.fontSets, function(it) {
29331      if (it.alias == alias) result = it.fontSet || result;
29332    });
29333
29334    return result;
29335  }
29336
29337  function transformClone(cacheElement) {
29338    var clone = cacheElement.clone();
29339    var cacheSuffix = '_cache' + $mdUtil.nextUid();
29340
29341    // We need to modify for each cached icon the id attributes.
29342    // This is needed because SVG id's are treated as normal DOM ids
29343    // and should not have a duplicated id.
29344    if (clone.id) clone.id += cacheSuffix;
29345    angular.forEach(clone.querySelectorAll('[id]'), function(item) {
29346      item.id += cacheSuffix;
29347    });
29348
29349    return clone;
29350  }
29351
29352  /**
29353   * Prepare and cache the loaded icon for the specified `id`
29354   */
29355  function cacheIcon(id) {
29356
29357    return function updateCache(icon) {
29358      iconCache[id] = isIcon(icon) ? icon : new Icon(icon, config[id]);
29359
29360      return iconCache[id].clone();
29361    };
29362  }
29363
29364  /**
29365   * Lookup the configuration in the registry, if !registered throw an error
29366   * otherwise load the icon [on-demand] using the registered URL.
29367   *
29368   */
29369  function loadByID(id) {
29370    var iconConfig = config[id];
29371    return loadByURL(iconConfig.url).then(function(icon) {
29372      return new Icon(icon, iconConfig);
29373    });
29374  }
29375
29376  /**
29377   *    Loads the file as XML and uses querySelector( <id> ) to find
29378   *    the desired node...
29379   */
29380  function loadFromIconSet(id) {
29381    var setName = id.substring(0, id.lastIndexOf(':')) || '$default';
29382    var iconSetConfig = config[setName];
29383
29384    return !iconSetConfig ? announceIdNotFound(id) : loadByURL(iconSetConfig.url).then(extractFromSet);
29385
29386    function extractFromSet(set) {
29387      var iconName = id.slice(id.lastIndexOf(':') + 1);
29388      var icon = set.querySelector('#' + iconName);
29389      return icon ? new Icon(icon, iconSetConfig) : announceIdNotFound(id);
29390    }
29391
29392    function announceIdNotFound(id) {
29393      var msg = 'icon ' + id + ' not found';
29394      $log.warn(msg);
29395
29396      return $q.reject(msg || id);
29397    }
29398  }
29399
29400  /**
29401   * Load the icon by URL (may use the $templateCache).
29402   * Extract the data for later conversion to Icon
29403   */
29404  function loadByURL(url) {
29405    /* Load the icon from embedded data URL. */
29406    function loadByDataUrl(url) {
29407      var results = dataUrlRegex.exec(url);
29408      var isBase64 = /base64/i.test(url);
29409      var data = isBase64 ? window.atob(results[2]) : results[2];
29410
29411      return $q.when(angular.element(data)[0]);
29412    }
29413
29414    /* Load the icon by URL using HTTP. */
29415    function loadByHttpUrl(url) {
29416      return $q(function(resolve, reject) {
29417        // Catch HTTP or generic errors not related to incorrect icon IDs.
29418        var announceAndReject = function(err) {
29419            var msg = angular.isString(err) ? err : (err.message || err.data || err.statusText);
29420            $log.warn(msg);
29421            reject(err);
29422          },
29423          extractSvg = function(response) {
29424            if (!svgCache[url]) {
29425              svgCache[url] = angular.element('<div>').append(response)[0].querySelector('svg');
29426            }
29427            resolve(svgCache[url]);
29428          };
29429
29430        $templateRequest(url, true).then(extractSvg, announceAndReject);
29431      });
29432    }
29433
29434    return dataUrlRegex.test(url)
29435      ? loadByDataUrl(url)
29436      : loadByHttpUrl(url);
29437  }
29438
29439  /**
29440   * Check target signature to see if it is an Icon instance.
29441   */
29442  function isIcon(target) {
29443    return angular.isDefined(target.element) && angular.isDefined(target.config);
29444  }
29445
29446  /**
29447   *  Define the Icon class
29448   */
29449  function Icon(el, config) {
29450    if (el && el.tagName != 'svg') {
29451      el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">').append(el.cloneNode(true))[0];
29452    }
29453
29454    // Inject the namespace if not available...
29455    if (!el.getAttribute('xmlns')) {
29456      el.setAttribute('xmlns', "http://www.w3.org/2000/svg");
29457    }
29458
29459    this.element = el;
29460    this.config = config;
29461    this.prepare();
29462  }
29463
29464  /**
29465   *  Prepare the DOM element that will be cached in the
29466   *  loaded iconCache store.
29467   */
29468  function prepareAndStyle() {
29469    var viewBoxSize = this.config ? this.config.viewBoxSize : config.defaultViewBoxSize;
29470    angular.forEach({
29471      'fit': '',
29472      'height': '100%',
29473      'width': '100%',
29474      'preserveAspectRatio': 'xMidYMid meet',
29475      'viewBox': this.element.getAttribute('viewBox') || ('0 0 ' + viewBoxSize + ' ' + viewBoxSize),
29476      'focusable': false // Disable IE11s default behavior to make SVGs focusable
29477    }, function(val, attr) {
29478      this.element.setAttribute(attr, val);
29479    }, this);
29480  }
29481
29482  /**
29483   * Clone the Icon DOM element.
29484   */
29485  function cloneSVG() {
29486    // If the element or any of its children have a style attribute, then a CSP policy without
29487    // 'unsafe-inline' in the style-src directive, will result in a violation.
29488    return this.element.cloneNode(true);
29489  }
29490
29491}
29492
29493})();
29494(function(){
29495"use strict";
29496
29497
29498
29499MenuController.$inject = ["$mdMenu", "$attrs", "$element", "$scope", "$mdUtil", "$timeout", "$rootScope", "$q"];
29500angular
29501    .module('material.components.menu')
29502    .controller('mdMenuCtrl', MenuController);
29503
29504/**
29505 * @ngInject
29506 */
29507function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout, $rootScope, $q) {
29508
29509  var prefixer = $mdUtil.prefixer();
29510  var menuContainer;
29511  var self = this;
29512  var triggerElement;
29513
29514  this.nestLevel = parseInt($attrs.mdNestLevel, 10) || 0;
29515
29516  /**
29517   * Called by our linking fn to provide access to the menu-content
29518   * element removed during link
29519   */
29520  this.init = function init(setMenuContainer, opts) {
29521    opts = opts || {};
29522    menuContainer = setMenuContainer;
29523
29524    // Default element for ARIA attributes has the ngClick or ngMouseenter expression
29525    triggerElement = $element[0].querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter']));
29526    triggerElement.setAttribute('aria-expanded', 'false');
29527
29528    this.isInMenuBar = opts.isInMenuBar;
29529    this.nestedMenus = $mdUtil.nodesToArray(menuContainer[0].querySelectorAll('.md-nested-menu'));
29530
29531    menuContainer.on('$mdInterimElementRemove', function() {
29532      self.isOpen = false;
29533      $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
29534    });
29535    $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
29536
29537    var menuContainerId = 'menu_container_' + $mdUtil.nextUid();
29538    menuContainer.attr('id', menuContainerId);
29539    angular.element(triggerElement).attr({
29540      'aria-owns': menuContainerId,
29541      'aria-haspopup': 'true'
29542    });
29543
29544    $scope.$on('$destroy', angular.bind(this, function() {
29545      this.disableHoverListener();
29546      $mdMenu.destroy();
29547    }));
29548
29549    menuContainer.on('$destroy', function() {
29550      $mdMenu.destroy();
29551    });
29552  };
29553
29554  var openMenuTimeout, menuItems, deregisterScopeListeners = [];
29555  this.enableHoverListener = function() {
29556    deregisterScopeListeners.push($rootScope.$on('$mdMenuOpen', function(event, el) {
29557      if (menuContainer[0].contains(el[0])) {
29558        self.currentlyOpenMenu = el.controller('mdMenu');
29559        self.isAlreadyOpening = false;
29560        self.currentlyOpenMenu.registerContainerProxy(self.triggerContainerProxy.bind(self));
29561      }
29562    }));
29563    deregisterScopeListeners.push($rootScope.$on('$mdMenuClose', function(event, el) {
29564      if (menuContainer[0].contains(el[0])) {
29565        self.currentlyOpenMenu = undefined;
29566      }
29567    }));
29568    menuItems = angular.element($mdUtil.nodesToArray(menuContainer[0].children[0].children));
29569    menuItems.on('mouseenter', self.handleMenuItemHover);
29570    menuItems.on('mouseleave', self.handleMenuItemMouseLeave);
29571  };
29572
29573  this.disableHoverListener = function() {
29574    while (deregisterScopeListeners.length) {
29575      deregisterScopeListeners.shift()();
29576    }
29577    menuItems && menuItems.off('mouseenter', self.handleMenuItemHover);
29578    menuItems && menuItems.off('mouseleave', self.handleMenuItemMouseLeave);
29579  };
29580
29581  this.handleMenuItemHover = function(event) {
29582    if (self.isAlreadyOpening) return;
29583    var nestedMenu = (
29584      event.target.querySelector('md-menu')
29585        || $mdUtil.getClosest(event.target, 'MD-MENU')
29586    );
29587    openMenuTimeout = $timeout(function() {
29588      if (nestedMenu) {
29589        nestedMenu = angular.element(nestedMenu).controller('mdMenu');
29590      }
29591
29592      if (self.currentlyOpenMenu && self.currentlyOpenMenu != nestedMenu) {
29593        var closeTo = self.nestLevel + 1;
29594        self.currentlyOpenMenu.close(true, { closeTo: closeTo });
29595        self.isAlreadyOpening = !!nestedMenu;
29596        nestedMenu && nestedMenu.open();
29597      } else if (nestedMenu && !nestedMenu.isOpen && nestedMenu.open) {
29598        self.isAlreadyOpening = !!nestedMenu;
29599        nestedMenu && nestedMenu.open();
29600      }
29601    }, nestedMenu ? 100 : 250);
29602    var focusableTarget = event.currentTarget.querySelector('.md-button:not([disabled])');
29603    focusableTarget && focusableTarget.focus();
29604  };
29605
29606  this.handleMenuItemMouseLeave = function() {
29607    if (openMenuTimeout) {
29608      $timeout.cancel(openMenuTimeout);
29609      openMenuTimeout = undefined;
29610    }
29611  };
29612
29613
29614  /**
29615   * Uses the $mdMenu interim element service to open the menu contents
29616   */
29617  this.open = function openMenu(ev) {
29618    ev && ev.stopPropagation();
29619    ev && ev.preventDefault();
29620    if (self.isOpen) return;
29621    self.enableHoverListener();
29622    self.isOpen = true;
29623    $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
29624    triggerElement = triggerElement || (ev ? ev.target : $element[0]);
29625    triggerElement.setAttribute('aria-expanded', 'true');
29626    $scope.$emit('$mdMenuOpen', $element);
29627    $mdMenu.show({
29628      scope: $scope,
29629      mdMenuCtrl: self,
29630      nestLevel: self.nestLevel,
29631      element: menuContainer,
29632      target: triggerElement,
29633      preserveElement: true,
29634      parent: 'body'
29635    }).finally(function() {
29636      triggerElement.setAttribute('aria-expanded', 'false');
29637      self.disableHoverListener();
29638    });
29639  };
29640
29641  // Expose a open function to the child scope for html to use
29642  $scope.$mdOpenMenu = this.open;
29643
29644  this.onIsOpenChanged = function(isOpen) {
29645    if (isOpen) {
29646      menuContainer.attr('aria-hidden', 'false');
29647      $element[0].classList.add('md-open');
29648      angular.forEach(self.nestedMenus, function(el) {
29649        el.classList.remove('md-open');
29650      });
29651    } else {
29652      menuContainer.attr('aria-hidden', 'true');
29653      $element[0].classList.remove('md-open');
29654    }
29655    $scope.$mdMenuIsOpen = self.isOpen;
29656  };
29657
29658  this.focusMenuContainer = function focusMenuContainer() {
29659    var focusTarget = menuContainer[0]
29660      .querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
29661
29662    if (!focusTarget) focusTarget = menuContainer[0].querySelector('.md-button');
29663    focusTarget.focus();
29664  };
29665
29666  this.registerContainerProxy = function registerContainerProxy(handler) {
29667    this.containerProxy = handler;
29668  };
29669
29670  this.triggerContainerProxy = function triggerContainerProxy(ev) {
29671    this.containerProxy && this.containerProxy(ev);
29672  };
29673
29674  this.destroy = function() {
29675    return self.isOpen ? $mdMenu.destroy() : $q.when(false);
29676  };
29677
29678  // Use the $mdMenu interim element service to close the menu contents
29679  this.close = function closeMenu(skipFocus, closeOpts) {
29680    if ( !self.isOpen ) return;
29681    self.isOpen = false;
29682    $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
29683
29684    var eventDetails = angular.extend({}, closeOpts, { skipFocus: skipFocus });
29685    $scope.$emit('$mdMenuClose', $element, eventDetails);
29686    $mdMenu.hide(null, closeOpts);
29687
29688    if (!skipFocus) {
29689      var el = self.restoreFocusTo || $element.find('button')[0];
29690      if (el instanceof angular.element) el = el[0];
29691      if (el) el.focus();
29692    }
29693  };
29694
29695  /**
29696   * Build a nice object out of our string attribute which specifies the
29697   * target mode for left and top positioning
29698   */
29699  this.positionMode = function positionMode() {
29700    var attachment = ($attrs.mdPositionMode || 'target').split(' ');
29701
29702    // If attachment is a single item, duplicate it for our second value.
29703    // ie. 'target' -> 'target target'
29704    if (attachment.length == 1) {
29705      attachment.push(attachment[0]);
29706    }
29707
29708    return {
29709      left: attachment[0],
29710      top: attachment[1]
29711    };
29712  };
29713
29714  /**
29715   * Build a nice object out of our string attribute which specifies
29716   * the offset of top and left in pixels.
29717   */
29718  this.offsets = function offsets() {
29719    var position = ($attrs.mdOffset || '0 0').split(' ').map(parseFloat);
29720    if (position.length == 2) {
29721      return {
29722        left: position[0],
29723        top: position[1]
29724      };
29725    } else if (position.length == 1) {
29726      return {
29727        top: position[0],
29728        left: position[0]
29729      };
29730    } else {
29731      throw Error('Invalid offsets specified. Please follow format <x, y> or <n>');
29732    }
29733  };
29734}
29735
29736})();
29737(function(){
29738"use strict";
29739
29740/**
29741 * @ngdoc directive
29742 * @name mdMenu
29743 * @module material.components.menu
29744 * @restrict E
29745 * @description
29746 *
29747 * Menus are elements that open when clicked. They are useful for displaying
29748 * additional options within the context of an action.
29749 *
29750 * Every `md-menu` must specify exactly two child elements. The first element is what is
29751 * left in the DOM and is used to open the menu. This element is called the trigger element.
29752 * The trigger element's scope has access to `$mdOpenMenu($event)`
29753 * which it may call to open the menu. By passing $event as argument, the
29754 * corresponding event is stopped from propagating up the DOM-tree.
29755 *
29756 * The second element is the `md-menu-content` element which represents the
29757 * contents of the menu when it is open. Typically this will contain `md-menu-item`s,
29758 * but you can do custom content as well.
29759 *
29760 * <hljs lang="html">
29761 * <md-menu>
29762 *  <!-- Trigger element is a md-button with an icon -->
29763 *  <md-button ng-click="$mdOpenMenu($event)" class="md-icon-button" aria-label="Open sample menu">
29764 *    <md-icon md-svg-icon="call:phone"></md-icon>
29765 *  </md-button>
29766 *  <md-menu-content>
29767 *    <md-menu-item><md-button ng-click="doSomething()">Do Something</md-button></md-menu-item>
29768 *  </md-menu-content>
29769 * </md-menu>
29770 * </hljs>
29771
29772 * ## Sizing Menus
29773 *
29774 * The width of the menu when it is open may be specified by specifying a `width`
29775 * attribute on the `md-menu-content` element.
29776 * See the [Material Design Spec](https://material.google.com/components/menus.html#menus-simple-menus)
29777 * for more information.
29778 *
29779 *
29780 * ## Aligning Menus
29781 *
29782 * When a menu opens, it is important that the content aligns with the trigger element.
29783 * Failure to align menus can result in jarring experiences for users as content
29784 * suddenly shifts. To help with this, `md-menu` provides serveral APIs to help
29785 * with alignment.
29786 *
29787 * ### Target Mode
29788 *
29789 * By default, `md-menu` will attempt to align the `md-menu-content` by aligning
29790 * designated child elements in both the trigger and the menu content.
29791 *
29792 * To specify the alignment element in the `trigger` you can use the `md-menu-origin`
29793 * attribute on a child element. If no `md-menu-origin` is specified, the `md-menu`
29794 * will be used as the origin element.
29795 *
29796 * Similarly, the `md-menu-content` may specify a `md-menu-align-target` for a
29797 * `md-menu-item` to specify the node that it should try and align with.
29798 *
29799 * In this example code, we specify an icon to be our origin element, and an
29800 * icon in our menu content to be our alignment target. This ensures that both
29801 * icons are aligned when the menu opens.
29802 *
29803 * <hljs lang="html">
29804 * <md-menu>
29805 *  <md-button ng-click="$mdOpenMenu($event)" class="md-icon-button" aria-label="Open some menu">
29806 *    <md-icon md-menu-origin md-svg-icon="call:phone"></md-icon>
29807 *  </md-button>
29808 *  <md-menu-content>
29809 *    <md-menu-item>
29810 *      <md-button ng-click="doSomething()" aria-label="Do something">
29811 *        <md-icon md-menu-align-target md-svg-icon="call:phone"></md-icon>
29812 *        Do Something
29813 *      </md-button>
29814 *    </md-menu-item>
29815 *  </md-menu-content>
29816 * </md-menu>
29817 * </hljs>
29818 *
29819 * Sometimes we want to specify alignment on the right side of an element, for example
29820 * if we have a menu on the right side a toolbar, we want to right align our menu content.
29821 *
29822 * We can specify the origin by using the `md-position-mode` attribute on both
29823 * the `x` and `y` axis. Right now only the `x-axis` has more than one option.
29824 * You may specify the default mode of `target target` or
29825 * `target-right target` to specify a right-oriented alignment target. See the
29826 * position section of the demos for more examples.
29827 *
29828 * ### Menu Offsets
29829 *
29830 * It is sometimes unavoidable to need to have a deeper level of control for
29831 * the positioning of a menu to ensure perfect alignment. `md-menu` provides
29832 * the `md-offset` attribute to allow pixel level specificty of adjusting the
29833 * exact positioning.
29834 *
29835 * This offset is provided in the format of `x y` or `n` where `n` will be used
29836 * in both the `x` and `y` axis.
29837 *
29838 * For example, to move a menu by `2px` from the top, we can use:
29839 * <hljs lang="html">
29840 * <md-menu md-offset="2 0">
29841 *   <!-- menu-content -->
29842 * </md-menu>
29843 * </hljs>
29844 *
29845 * ### Auto Focus
29846 * By default, when a menu opens, `md-menu` focuses the first button in the menu content.
29847 * 
29848 * But sometimes you would like to focus another specific menu item instead of the first.<br/>
29849 * This can be done by applying the `md-autofocus` directive on the given element.
29850 *
29851 * <hljs lang="html">
29852 * <md-menu-item>
29853 *   <md-button md-autofocus ng-click="doSomething()">
29854 *     Auto Focus
29855 *   </md-button>
29856 * </md-menu-item>
29857 * </hljs>
29858 *
29859 *
29860 * ### Preventing close
29861 *
29862 * Sometimes you would like to be able to click on a menu item without having the menu
29863 * close. To do this, ngMaterial exposes the `md-prevent-menu-close` attribute which
29864 * can be added to a button inside a menu to stop the menu from automatically closing.
29865 * You can then close the menu programatically by injecting `$mdMenu` and calling 
29866 * `$mdMenu.hide()`.
29867 *
29868 * <hljs lang="html">
29869 * <md-menu-item>
29870 *   <md-button ng-click="doSomething()" aria-label="Do something" md-prevent-menu-close="md-prevent-menu-close">
29871 *     <md-icon md-menu-align-target md-svg-icon="call:phone"></md-icon>
29872 *     Do Something
29873 *   </md-button>
29874 * </md-menu-item>
29875 * </hljs>
29876 *
29877 * @usage
29878 * <hljs lang="html">
29879 * <md-menu>
29880 *  <md-button ng-click="$mdOpenMenu($event)" class="md-icon-button">
29881 *    <md-icon md-svg-icon="call:phone"></md-icon>
29882 *  </md-button>
29883 *  <md-menu-content>
29884 *    <md-menu-item><md-button ng-click="doSomething()">Do Something</md-button></md-menu-item>
29885 *  </md-menu-content>
29886 * </md-menu>
29887 * </hljs>
29888 *
29889 * @param {string} md-position-mode The position mode in the form of
29890 *           `x`, `y`. Default value is `target`,`target`. Right now the `x` axis
29891 *           also suppports `target-right`.
29892 * @param {string} md-offset An offset to apply to the dropdown after positioning
29893 *           `x`, `y`. Default value is `0`,`0`.
29894 *
29895 */
29896
29897MenuDirective.$inject = ["$mdUtil"];
29898angular
29899    .module('material.components.menu')
29900    .directive('mdMenu', MenuDirective);
29901
29902/**
29903 * @ngInject
29904 */
29905function MenuDirective($mdUtil) {
29906  var INVALID_PREFIX = 'Invalid HTML for md-menu: ';
29907  return {
29908    restrict: 'E',
29909    require: ['mdMenu', '?^mdMenuBar'],
29910    controller: 'mdMenuCtrl', // empty function to be built by link
29911    scope: true,
29912    compile: compile
29913  };
29914
29915  function compile(templateElement) {
29916    templateElement.addClass('md-menu');
29917    var triggerElement = templateElement.children()[0];
29918    var prefixer = $mdUtil.prefixer();
29919
29920    if (!prefixer.hasAttribute(triggerElement, 'ng-click')) {
29921      triggerElement = triggerElement
29922          .querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter'])) || triggerElement;
29923    }
29924    if (triggerElement && (
29925      triggerElement.nodeName == 'MD-BUTTON' ||
29926      triggerElement.nodeName == 'BUTTON'
29927    ) && !triggerElement.hasAttribute('type')) {
29928      triggerElement.setAttribute('type', 'button');
29929    }
29930
29931    if (templateElement.children().length != 2) {
29932      throw Error(INVALID_PREFIX + 'Expected two children elements.');
29933    }
29934
29935    // Default element for ARIA attributes has the ngClick or ngMouseenter expression
29936    triggerElement && triggerElement.setAttribute('aria-haspopup', 'true');
29937
29938    var nestedMenus = templateElement[0].querySelectorAll('md-menu');
29939    var nestingDepth = parseInt(templateElement[0].getAttribute('md-nest-level'), 10) || 0;
29940    if (nestedMenus) {
29941      angular.forEach($mdUtil.nodesToArray(nestedMenus), function(menuEl) {
29942        if (!menuEl.hasAttribute('md-position-mode')) {
29943          menuEl.setAttribute('md-position-mode', 'cascade');
29944        }
29945        menuEl.classList.add('_md-nested-menu');
29946        menuEl.setAttribute('md-nest-level', nestingDepth + 1);
29947      });
29948    }
29949    return link;
29950  }
29951
29952  function link(scope, element, attr, ctrls) {
29953    var mdMenuCtrl = ctrls[0];
29954    var isInMenuBar = ctrls[1] != undefined;
29955    // Move everything into a md-menu-container and pass it to the controller
29956    var menuContainer = angular.element( '<div class="_md md-open-menu-container md-whiteframe-z2"></div>');
29957    var menuContents = element.children()[1];
29958
29959    element.addClass('_md');     // private md component indicator for styling
29960
29961    if (!menuContents.hasAttribute('role')) {
29962      menuContents.setAttribute('role', 'menu');
29963    }
29964    menuContainer.append(menuContents);
29965
29966    element.on('$destroy', function() {
29967      menuContainer.remove();
29968    });
29969
29970    element.append(menuContainer);
29971    menuContainer[0].style.display = 'none';
29972    mdMenuCtrl.init(menuContainer, { isInMenuBar: isInMenuBar });
29973
29974  }
29975}
29976
29977})();
29978(function(){
29979"use strict";
29980
29981
29982MenuProvider.$inject = ["$$interimElementProvider"];angular
29983  .module('material.components.menu')
29984  .provider('$mdMenu', MenuProvider);
29985
29986/*
29987 * Interim element provider for the menu.
29988 * Handles behavior for a menu while it is open, including:
29989 *    - handling animating the menu opening/closing
29990 *    - handling key/mouse events on the menu element
29991 *    - handling enabling/disabling scroll while the menu is open
29992 *    - handling redrawing during resizes and orientation changes
29993 *
29994 */
29995
29996function MenuProvider($$interimElementProvider) {
29997  menuDefaultOptions.$inject = ["$mdUtil", "$mdTheming", "$mdConstant", "$document", "$window", "$q", "$$rAF", "$animateCss", "$animate"];
29998  var MENU_EDGE_MARGIN = 8;
29999
30000  return $$interimElementProvider('$mdMenu')
30001    .setDefaults({
30002      methods: ['target'],
30003      options: menuDefaultOptions
30004    });
30005
30006  /* @ngInject */
30007  function menuDefaultOptions($mdUtil, $mdTheming, $mdConstant, $document, $window, $q, $$rAF, $animateCss, $animate) {
30008    var prefixer = $mdUtil.prefixer();
30009    var animator = $mdUtil.dom.animator;
30010
30011    return {
30012      parent: 'body',
30013      onShow: onShow,
30014      onRemove: onRemove,
30015      hasBackdrop: true,
30016      disableParentScroll: true,
30017      skipCompile: true,
30018      preserveScope: true,
30019      skipHide: true,
30020      themable: true
30021    };
30022
30023    /**
30024     * Show modal backdrop element...
30025     * @returns {function(): void} A function that removes this backdrop
30026     */
30027    function showBackdrop(scope, element, options) {
30028      if (options.nestLevel) return angular.noop;
30029
30030      // If we are not within a dialog...
30031      if (options.disableParentScroll && !$mdUtil.getClosest(options.target, 'MD-DIALOG')) {
30032        // !! DO this before creating the backdrop; since disableScrollAround()
30033        //    configures the scroll offset; which is used by mdBackDrop postLink()
30034        options.restoreScroll = $mdUtil.disableScrollAround(options.element, options.parent);
30035      } else {
30036        options.disableParentScroll = false;
30037      }
30038
30039      if (options.hasBackdrop) {
30040        options.backdrop = $mdUtil.createBackdrop(scope, "md-menu-backdrop md-click-catcher");
30041
30042        $animate.enter(options.backdrop, $document[0].body);
30043      }
30044
30045      /**
30046       * Hide and destroys the backdrop created by showBackdrop()
30047       */
30048      return function hideBackdrop() {
30049        if (options.backdrop) options.backdrop.remove();
30050        if (options.disableParentScroll) options.restoreScroll();
30051      };
30052    }
30053
30054    /**
30055     * Removing the menu element from the DOM and remove all associated event listeners
30056     * and backdrop
30057     */
30058    function onRemove(scope, element, opts) {
30059      opts.cleanupInteraction && opts.cleanupInteraction();
30060      opts.cleanupResizing();
30061      opts.hideBackdrop();
30062
30063      // For navigation $destroy events, do a quick, non-animated removal,
30064      // but for normal closes (from clicks, etc) animate the removal
30065
30066      return (opts.$destroy === true) ? detachAndClean() : animateRemoval().then( detachAndClean );
30067
30068      /**
30069       * For normal closes, animate the removal.
30070       * For forced closes (like $destroy events), skip the animations
30071       */
30072      function animateRemoval() {
30073        return $animateCss(element, {addClass: 'md-leave'}).start();
30074      }
30075
30076      /**
30077       * Detach the element
30078       */
30079      function detachAndClean() {
30080        element.removeClass('md-active');
30081        detachElement(element, opts);
30082        opts.alreadyOpen = false;
30083      }
30084
30085    }
30086
30087    /**
30088     * Inserts and configures the staged Menu element into the DOM, positioning it,
30089     * and wiring up various interaction events
30090     */
30091    function onShow(scope, element, opts) {
30092      sanitizeAndConfigure(opts);
30093
30094      // Wire up theming on our menu element
30095      $mdTheming.inherit(opts.menuContentEl, opts.target);
30096
30097      // Register various listeners to move menu on resize/orientation change
30098      opts.cleanupResizing = startRepositioningOnResize();
30099      opts.hideBackdrop = showBackdrop(scope, element, opts);
30100
30101      // Return the promise for when our menu is done animating in
30102      return showMenu()
30103        .then(function(response) {
30104          opts.alreadyOpen = true;
30105          opts.cleanupInteraction = activateInteraction();
30106          return response;
30107        });
30108
30109      /**
30110       * Place the menu into the DOM and call positioning related functions
30111       */
30112      function showMenu() {
30113        opts.parent.append(element);
30114        element[0].style.display = '';
30115
30116        return $q(function(resolve) {
30117          var position = calculateMenuPosition(element, opts);
30118
30119          element.removeClass('md-leave');
30120
30121          // Animate the menu scaling, and opacity [from its position origin (default == top-left)]
30122          // to normal scale.
30123          $animateCss(element, {
30124            addClass: 'md-active',
30125            from: animator.toCss(position),
30126            to: animator.toCss({transform: ''})
30127          })
30128          .start()
30129          .then(resolve);
30130
30131        });
30132      }
30133
30134      /**
30135       * Check for valid opts and set some sane defaults
30136       */
30137      function sanitizeAndConfigure() {
30138        if (!opts.target) {
30139          throw Error(
30140            '$mdMenu.show() expected a target to animate from in options.target'
30141          );
30142        }
30143        angular.extend(opts, {
30144          alreadyOpen: false,
30145          isRemoved: false,
30146          target: angular.element(opts.target), //make sure it's not a naked dom node
30147          parent: angular.element(opts.parent),
30148          menuContentEl: angular.element(element[0].querySelector('md-menu-content'))
30149        });
30150      }
30151
30152      /**
30153       * Configure various resize listeners for screen changes
30154       */
30155      function startRepositioningOnResize() {
30156
30157        var repositionMenu = (function(target, options) {
30158          return $$rAF.throttle(function() {
30159            if (opts.isRemoved) return;
30160            var position = calculateMenuPosition(target, options);
30161
30162            target.css(animator.toCss(position));
30163          });
30164        })(element, opts);
30165
30166        $window.addEventListener('resize', repositionMenu);
30167        $window.addEventListener('orientationchange', repositionMenu);
30168
30169        return function stopRepositioningOnResize() {
30170
30171          // Disable resizing handlers
30172          $window.removeEventListener('resize', repositionMenu);
30173          $window.removeEventListener('orientationchange', repositionMenu);
30174
30175        }
30176      }
30177
30178      /**
30179       * Activate interaction on the menu. Wire up keyboard listerns for
30180       * clicks, keypresses, backdrop closing, etc.
30181       */
30182      function activateInteraction() {
30183        element.addClass('md-clickable');
30184
30185        // close on backdrop click
30186        if (opts.backdrop) opts.backdrop.on('click', onBackdropClick);
30187
30188        // Wire up keyboard listeners.
30189        // - Close on escape,
30190        // - focus next item on down arrow,
30191        // - focus prev item on up
30192        opts.menuContentEl.on('keydown', onMenuKeyDown);
30193        opts.menuContentEl[0].addEventListener('click', captureClickListener, true);
30194
30195        // kick off initial focus in the menu on the first element
30196        var focusTarget = opts.menuContentEl[0]
30197          .querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
30198
30199        if ( !focusTarget ) {
30200          var firstChild = opts.menuContentEl[0].firstElementChild;
30201
30202          focusTarget = firstChild && (firstChild.querySelector('.md-button:not([disabled])') || firstChild.firstElementChild);
30203        }
30204
30205        focusTarget && focusTarget.focus();
30206
30207        return function cleanupInteraction() {
30208          element.removeClass('md-clickable');
30209          if (opts.backdrop) opts.backdrop.off('click', onBackdropClick);
30210          opts.menuContentEl.off('keydown', onMenuKeyDown);
30211          opts.menuContentEl[0].removeEventListener('click', captureClickListener, true);
30212        };
30213
30214        // ************************************
30215        // internal functions
30216        // ************************************
30217
30218        function onMenuKeyDown(ev) {
30219          var handled;
30220          switch (ev.keyCode) {
30221            case $mdConstant.KEY_CODE.ESCAPE:
30222              opts.mdMenuCtrl.close(false, { closeAll: true });
30223              handled = true;
30224              break;
30225            case $mdConstant.KEY_CODE.UP_ARROW:
30226              if (!focusMenuItem(ev, opts.menuContentEl, opts, -1) && !opts.nestLevel) {
30227                opts.mdMenuCtrl.triggerContainerProxy(ev);
30228              }
30229              handled = true;
30230              break;
30231            case $mdConstant.KEY_CODE.DOWN_ARROW:
30232              if (!focusMenuItem(ev, opts.menuContentEl, opts, 1) && !opts.nestLevel) {
30233                opts.mdMenuCtrl.triggerContainerProxy(ev);
30234              }
30235              handled = true;
30236              break;
30237            case $mdConstant.KEY_CODE.LEFT_ARROW:
30238              if (opts.nestLevel) {
30239                opts.mdMenuCtrl.close();
30240              } else {
30241                opts.mdMenuCtrl.triggerContainerProxy(ev);
30242              }
30243              handled = true;
30244              break;
30245            case $mdConstant.KEY_CODE.RIGHT_ARROW:
30246              var parentMenu = $mdUtil.getClosest(ev.target, 'MD-MENU');
30247              if (parentMenu && parentMenu != opts.parent[0]) {
30248                ev.target.click();
30249              } else {
30250                opts.mdMenuCtrl.triggerContainerProxy(ev);
30251              }
30252              handled = true;
30253              break;
30254          }
30255          if (handled) {
30256            ev.preventDefault();
30257            ev.stopImmediatePropagation();
30258          }
30259        }
30260
30261        function onBackdropClick(e) {
30262          e.preventDefault();
30263          e.stopPropagation();
30264          scope.$apply(function() {
30265            opts.mdMenuCtrl.close(true, { closeAll: true });
30266          });
30267        }
30268
30269        // Close menu on menu item click, if said menu-item is not disabled
30270        function captureClickListener(e) {
30271          var target = e.target;
30272          // Traverse up the event until we get to the menuContentEl to see if
30273          // there is an ng-click and that the ng-click is not disabled
30274          do {
30275            if (target == opts.menuContentEl[0]) return;
30276            if ((hasAnyAttribute(target, ['ng-click', 'ng-href', 'ui-sref']) ||
30277                target.nodeName == 'BUTTON' || target.nodeName == 'MD-BUTTON') && !hasAnyAttribute(target, ['md-prevent-menu-close'])) {
30278              var closestMenu = $mdUtil.getClosest(target, 'MD-MENU');
30279              if (!target.hasAttribute('disabled') && (!closestMenu || closestMenu == opts.parent[0])) {
30280                close();
30281              }
30282              break;
30283            }
30284          } while (target = target.parentNode)
30285
30286          function close() {
30287            scope.$apply(function() {
30288              opts.mdMenuCtrl.close(true, { closeAll: true });
30289            });
30290          }
30291
30292          function hasAnyAttribute(target, attrs) {
30293            if (!target) return false;
30294
30295            for (var i = 0, attr; attr = attrs[i]; ++i) {
30296              if (prefixer.hasAttribute(target, attr)) {
30297                return true;
30298              }
30299            }
30300
30301            return false;
30302          }
30303        }
30304
30305      }
30306    }
30307
30308    /**
30309     * Takes a keypress event and focuses the next/previous menu
30310     * item from the emitting element
30311     * @param {event} e - The origin keypress event
30312     * @param {angular.element} menuEl - The menu element
30313     * @param {object} opts - The interim element options for the mdMenu
30314     * @param {number} direction - The direction to move in (+1 = next, -1 = prev)
30315     */
30316    function focusMenuItem(e, menuEl, opts, direction) {
30317      var currentItem = $mdUtil.getClosest(e.target, 'MD-MENU-ITEM');
30318
30319      var items = $mdUtil.nodesToArray(menuEl[0].children);
30320      var currentIndex = items.indexOf(currentItem);
30321
30322      // Traverse through our elements in the specified direction (+/-1) and try to
30323      // focus them until we find one that accepts focus
30324      var didFocus;
30325      for (var i = currentIndex + direction; i >= 0 && i < items.length; i = i + direction) {
30326        var focusTarget = items[i].querySelector('.md-button');
30327        didFocus = attemptFocus(focusTarget);
30328        if (didFocus) {
30329          break;
30330        }
30331      }
30332      return didFocus;
30333    }
30334
30335    /**
30336     * Attempts to focus an element. Checks whether that element is the currently
30337     * focused element after attempting.
30338     * @param {HTMLElement} el - the element to attempt focus on
30339     * @returns {bool} - whether the element was successfully focused
30340     */
30341    function attemptFocus(el) {
30342      if (el && el.getAttribute('tabindex') != -1) {
30343        el.focus();
30344        return ($document[0].activeElement == el);
30345      }
30346    }
30347
30348    /**
30349     * Use browser to remove this element without triggering a $destroy event
30350     */
30351    function detachElement(element, opts) {
30352      if (!opts.preserveElement) {
30353        if (toNode(element).parentNode === toNode(opts.parent)) {
30354          toNode(opts.parent).removeChild(toNode(element));
30355        }
30356      } else {
30357        toNode(element).style.display = 'none';
30358      }
30359    }
30360
30361    /**
30362     * Computes menu position and sets the style on the menu container
30363     * @param {HTMLElement} el - the menu container element
30364     * @param {object} opts - the interim element options object
30365     */
30366    function calculateMenuPosition(el, opts) {
30367
30368      var containerNode = el[0],
30369        openMenuNode = el[0].firstElementChild,
30370        openMenuNodeRect = openMenuNode.getBoundingClientRect(),
30371        boundryNode = $document[0].body,
30372        boundryNodeRect = boundryNode.getBoundingClientRect();
30373
30374      var menuStyle = $window.getComputedStyle(openMenuNode);
30375
30376      var originNode = opts.target[0].querySelector(prefixer.buildSelector('md-menu-origin')) || opts.target[0],
30377        originNodeRect = originNode.getBoundingClientRect();
30378
30379      var bounds = {
30380        left: boundryNodeRect.left + MENU_EDGE_MARGIN,
30381        top: Math.max(boundryNodeRect.top, 0) + MENU_EDGE_MARGIN,
30382        bottom: Math.max(boundryNodeRect.bottom, Math.max(boundryNodeRect.top, 0) + boundryNodeRect.height) - MENU_EDGE_MARGIN,
30383        right: boundryNodeRect.right - MENU_EDGE_MARGIN
30384      };
30385
30386      var alignTarget, alignTargetRect = { top:0, left : 0, right:0, bottom:0 }, existingOffsets  = { top:0, left : 0, right:0, bottom:0  };
30387      var positionMode = opts.mdMenuCtrl.positionMode();
30388
30389      if (positionMode.top == 'target' || positionMode.left == 'target' || positionMode.left == 'target-right') {
30390        alignTarget = firstVisibleChild();
30391        if ( alignTarget ) {
30392          // TODO: Allow centering on an arbitrary node, for now center on first menu-item's child
30393          alignTarget = alignTarget.firstElementChild || alignTarget;
30394          alignTarget = alignTarget.querySelector(prefixer.buildSelector('md-menu-align-target')) || alignTarget;
30395          alignTargetRect = alignTarget.getBoundingClientRect();
30396
30397          existingOffsets = {
30398            top: parseFloat(containerNode.style.top || 0),
30399            left: parseFloat(containerNode.style.left || 0)
30400          };
30401        }
30402      }
30403
30404      var position = {};
30405      var transformOrigin = 'top ';
30406
30407      switch (positionMode.top) {
30408        case 'target':
30409          position.top = existingOffsets.top + originNodeRect.top - alignTargetRect.top;
30410          break;
30411        case 'cascade':
30412          position.top = originNodeRect.top - parseFloat(menuStyle.paddingTop) - originNode.style.top;
30413          break;
30414        case 'bottom':
30415          position.top = originNodeRect.top + originNodeRect.height;
30416          break;
30417        default:
30418          throw new Error('Invalid target mode "' + positionMode.top + '" specified for md-menu on Y axis.');
30419      }
30420
30421      var rtl = ($mdUtil.bidi() == 'rtl');
30422
30423      switch (positionMode.left) {
30424        case 'target':
30425          position.left = existingOffsets.left + originNodeRect.left - alignTargetRect.left;
30426          transformOrigin += rtl ? 'right'  : 'left';
30427          break;
30428        case 'target-left':
30429          position.left = originNodeRect.left;
30430          transformOrigin += 'left';
30431          break;
30432        case 'target-right':
30433          position.left = originNodeRect.right - openMenuNodeRect.width + (openMenuNodeRect.right - alignTargetRect.right);
30434          transformOrigin += 'right';
30435          break;
30436        case 'cascade':
30437          var willFitRight = rtl ? (originNodeRect.left - openMenuNodeRect.width) < bounds.left : (originNodeRect.right + openMenuNodeRect.width) < bounds.right;
30438          position.left = willFitRight ? originNodeRect.right - originNode.style.left : originNodeRect.left - originNode.style.left - openMenuNodeRect.width;
30439          transformOrigin += willFitRight ? 'left' : 'right';
30440          break;
30441        case 'right':
30442          if (rtl) {
30443            position.left = originNodeRect.right - originNodeRect.width;
30444            transformOrigin += 'left';
30445          } else {
30446            position.left = originNodeRect.right - openMenuNodeRect.width;
30447            transformOrigin += 'right';
30448          }
30449          break;
30450        case 'left':
30451          if (rtl) {
30452            position.left = originNodeRect.right - openMenuNodeRect.width;
30453            transformOrigin += 'right';
30454          } else {
30455            position.left = originNodeRect.left;
30456            transformOrigin += 'left';
30457          }
30458          break;
30459        default:
30460          throw new Error('Invalid target mode "' + positionMode.left + '" specified for md-menu on X axis.');
30461      }
30462
30463      var offsets = opts.mdMenuCtrl.offsets();
30464      position.top += offsets.top;
30465      position.left += offsets.left;
30466
30467      clamp(position);
30468
30469      var scaleX = Math.round(100 * Math.min(originNodeRect.width / containerNode.offsetWidth, 1.0)) / 100;
30470      var scaleY = Math.round(100 * Math.min(originNodeRect.height / containerNode.offsetHeight, 1.0)) / 100;
30471
30472      return {
30473        top: Math.round(position.top),
30474        left: Math.round(position.left),
30475        // Animate a scale out if we aren't just repositioning
30476        transform: !opts.alreadyOpen ? $mdUtil.supplant('scale({0},{1})', [scaleX, scaleY]) : undefined,
30477        transformOrigin: transformOrigin
30478      };
30479
30480      /**
30481       * Clamps the repositioning of the menu within the confines of
30482       * bounding element (often the screen/body)
30483       */
30484      function clamp(pos) {
30485        pos.top = Math.max(Math.min(pos.top, bounds.bottom - containerNode.offsetHeight), bounds.top);
30486        pos.left = Math.max(Math.min(pos.left, bounds.right - containerNode.offsetWidth), bounds.left);
30487      }
30488
30489      /**
30490       * Gets the first visible child in the openMenuNode
30491       * Necessary incase menu nodes are being dynamically hidden
30492       */
30493      function firstVisibleChild() {
30494        for (var i = 0; i < openMenuNode.children.length; ++i) {
30495          if ($window.getComputedStyle(openMenuNode.children[i]).display != 'none') {
30496            return openMenuNode.children[i];
30497          }
30498        }
30499      }
30500    }
30501  }
30502  function toNode(el) {
30503    if (el instanceof angular.element) {
30504      el = el[0];
30505    }
30506    return el;
30507  }
30508}
30509
30510})();
30511(function(){
30512"use strict";
30513
30514
30515MenuBarController.$inject = ["$scope", "$rootScope", "$element", "$attrs", "$mdConstant", "$document", "$mdUtil", "$timeout"];
30516angular
30517  .module('material.components.menuBar')
30518  .controller('MenuBarController', MenuBarController);
30519
30520var BOUND_MENU_METHODS = ['handleKeyDown', 'handleMenuHover', 'scheduleOpenHoveredMenu', 'cancelScheduledOpen'];
30521
30522/**
30523 * @ngInject
30524 */
30525function MenuBarController($scope, $rootScope, $element, $attrs, $mdConstant, $document, $mdUtil, $timeout) {
30526  this.$element = $element;
30527  this.$attrs = $attrs;
30528  this.$mdConstant = $mdConstant;
30529  this.$mdUtil = $mdUtil;
30530  this.$document = $document;
30531  this.$scope = $scope;
30532  this.$rootScope = $rootScope;
30533  this.$timeout = $timeout;
30534
30535  var self = this;
30536  angular.forEach(BOUND_MENU_METHODS, function(methodName) {
30537    self[methodName] = angular.bind(self, self[methodName]);
30538  });
30539}
30540
30541MenuBarController.prototype.init = function() {
30542  var $element = this.$element;
30543  var $mdUtil = this.$mdUtil;
30544  var $scope = this.$scope;
30545
30546  var self = this;
30547  var deregisterFns = [];
30548  $element.on('keydown', this.handleKeyDown);
30549  this.parentToolbar = $mdUtil.getClosest($element, 'MD-TOOLBAR');
30550
30551  deregisterFns.push(this.$rootScope.$on('$mdMenuOpen', function(event, el) {
30552    if (self.getMenus().indexOf(el[0]) != -1) {
30553      $element[0].classList.add('md-open');
30554      el[0].classList.add('md-open');
30555      self.currentlyOpenMenu = el.controller('mdMenu');
30556      self.currentlyOpenMenu.registerContainerProxy(self.handleKeyDown);
30557      self.enableOpenOnHover();
30558    }
30559  }));
30560
30561  deregisterFns.push(this.$rootScope.$on('$mdMenuClose', function(event, el, opts) {
30562    var rootMenus = self.getMenus();
30563    if (rootMenus.indexOf(el[0]) != -1) {
30564      $element[0].classList.remove('md-open');
30565      el[0].classList.remove('md-open');
30566    }
30567
30568    if ($element[0].contains(el[0])) {
30569      var parentMenu = el[0];
30570      while (parentMenu && rootMenus.indexOf(parentMenu) == -1) {
30571        parentMenu = $mdUtil.getClosest(parentMenu, 'MD-MENU', true);
30572      }
30573      if (parentMenu) {
30574        if (!opts.skipFocus) parentMenu.querySelector('button:not([disabled])').focus();
30575        self.currentlyOpenMenu = undefined;
30576        self.disableOpenOnHover();
30577        self.setKeyboardMode(true);
30578      }
30579    }
30580  }));
30581
30582  $scope.$on('$destroy', function() {
30583    self.disableOpenOnHover();
30584    while (deregisterFns.length) {
30585      deregisterFns.shift()();
30586    }
30587  });
30588
30589
30590  this.setKeyboardMode(true);
30591};
30592
30593MenuBarController.prototype.setKeyboardMode = function(enabled) {
30594  if (enabled) this.$element[0].classList.add('md-keyboard-mode');
30595  else this.$element[0].classList.remove('md-keyboard-mode');
30596};
30597
30598MenuBarController.prototype.enableOpenOnHover = function() {
30599  if (this.openOnHoverEnabled) return;
30600
30601  var self = this;
30602
30603  self.openOnHoverEnabled = true;
30604
30605  if (self.parentToolbar) {
30606    self.parentToolbar.classList.add('md-has-open-menu');
30607
30608    // Needs to be on the next tick so it doesn't close immediately.
30609    self.$mdUtil.nextTick(function() {
30610      angular.element(self.parentToolbar).on('click', self.handleParentClick);
30611    }, false);
30612  }
30613
30614  angular
30615    .element(self.getMenus())
30616    .on('mouseenter', self.handleMenuHover);
30617};
30618
30619MenuBarController.prototype.handleMenuHover = function(e) {
30620  this.setKeyboardMode(false);
30621  if (this.openOnHoverEnabled) {
30622    this.scheduleOpenHoveredMenu(e);
30623  }
30624};
30625
30626MenuBarController.prototype.disableOpenOnHover = function() {
30627  if (!this.openOnHoverEnabled) return;
30628
30629  this.openOnHoverEnabled = false;
30630
30631  if (this.parentToolbar) {
30632    this.parentToolbar.classList.remove('md-has-open-menu');
30633    angular.element(this.parentToolbar).off('click', this.handleParentClick);
30634  }
30635
30636  angular
30637    .element(this.getMenus())
30638    .off('mouseenter', this.handleMenuHover);
30639};
30640
30641MenuBarController.prototype.scheduleOpenHoveredMenu = function(e) {
30642  var menuEl = angular.element(e.currentTarget);
30643  var menuCtrl = menuEl.controller('mdMenu');
30644  this.setKeyboardMode(false);
30645  this.scheduleOpenMenu(menuCtrl);
30646};
30647
30648MenuBarController.prototype.scheduleOpenMenu = function(menuCtrl) {
30649  var self = this;
30650  var $timeout = this.$timeout;
30651  if (menuCtrl != self.currentlyOpenMenu) {
30652    $timeout.cancel(self.pendingMenuOpen);
30653    self.pendingMenuOpen = $timeout(function() {
30654      self.pendingMenuOpen = undefined;
30655      if (self.currentlyOpenMenu) {
30656        self.currentlyOpenMenu.close(true, { closeAll: true });
30657      }
30658      menuCtrl.open();
30659    }, 200, false);
30660  }
30661};
30662
30663MenuBarController.prototype.handleKeyDown = function(e) {
30664  var keyCodes = this.$mdConstant.KEY_CODE;
30665  var currentMenu = this.currentlyOpenMenu;
30666  var wasOpen = currentMenu && currentMenu.isOpen;
30667  this.setKeyboardMode(true);
30668  var handled, newMenu, newMenuCtrl;
30669  switch (e.keyCode) {
30670    case keyCodes.DOWN_ARROW:
30671      if (currentMenu) {
30672        currentMenu.focusMenuContainer();
30673      } else {
30674        this.openFocusedMenu();
30675      }
30676      handled = true;
30677      break;
30678    case keyCodes.UP_ARROW:
30679      currentMenu && currentMenu.close();
30680      handled = true;
30681      break;
30682    case keyCodes.LEFT_ARROW:
30683      newMenu = this.focusMenu(-1);
30684      if (wasOpen) {
30685        newMenuCtrl = angular.element(newMenu).controller('mdMenu');
30686        this.scheduleOpenMenu(newMenuCtrl);
30687      }
30688      handled = true;
30689      break;
30690    case keyCodes.RIGHT_ARROW:
30691      newMenu = this.focusMenu(+1);
30692      if (wasOpen) {
30693        newMenuCtrl = angular.element(newMenu).controller('mdMenu');
30694        this.scheduleOpenMenu(newMenuCtrl);
30695      }
30696      handled = true;
30697      break;
30698  }
30699  if (handled) {
30700    e && e.preventDefault && e.preventDefault();
30701    e && e.stopImmediatePropagation && e.stopImmediatePropagation();
30702  }
30703};
30704
30705MenuBarController.prototype.focusMenu = function(direction) {
30706  var menus = this.getMenus();
30707  var focusedIndex = this.getFocusedMenuIndex();
30708
30709  if (focusedIndex == -1) { focusedIndex = this.getOpenMenuIndex(); }
30710
30711  var changed = false;
30712
30713  if (focusedIndex == -1) { focusedIndex = 0; changed = true; }
30714  else if (
30715    direction < 0 && focusedIndex > 0 ||
30716    direction > 0 && focusedIndex < menus.length - direction
30717  ) {
30718    focusedIndex += direction;
30719    changed = true;
30720  }
30721  if (changed) {
30722    menus[focusedIndex].querySelector('button').focus();
30723    return menus[focusedIndex];
30724  }
30725};
30726
30727MenuBarController.prototype.openFocusedMenu = function() {
30728  var menu = this.getFocusedMenu();
30729  menu && angular.element(menu).controller('mdMenu').open();
30730};
30731
30732MenuBarController.prototype.getMenus = function() {
30733  var $element = this.$element;
30734  return this.$mdUtil.nodesToArray($element[0].children)
30735    .filter(function(el) { return el.nodeName == 'MD-MENU'; });
30736};
30737
30738MenuBarController.prototype.getFocusedMenu = function() {
30739  return this.getMenus()[this.getFocusedMenuIndex()];
30740};
30741
30742MenuBarController.prototype.getFocusedMenuIndex = function() {
30743  var $mdUtil = this.$mdUtil;
30744  var focusedEl = $mdUtil.getClosest(
30745    this.$document[0].activeElement,
30746    'MD-MENU'
30747  );
30748  if (!focusedEl) return -1;
30749
30750  var focusedIndex = this.getMenus().indexOf(focusedEl);
30751  return focusedIndex;
30752};
30753
30754MenuBarController.prototype.getOpenMenuIndex = function() {
30755  var menus = this.getMenus();
30756  for (var i = 0; i < menus.length; ++i) {
30757    if (menus[i].classList.contains('md-open')) return i;
30758  }
30759  return -1;
30760};
30761
30762MenuBarController.prototype.handleParentClick = function(event) {
30763  var openMenu = this.querySelector('md-menu.md-open');
30764
30765  if (openMenu && !openMenu.contains(event.target)) {
30766    angular.element(openMenu).controller('mdMenu').close();
30767  }
30768};
30769
30770
30771
30772
30773
30774
30775
30776})();
30777(function(){
30778"use strict";
30779
30780/**
30781 * @ngdoc directive
30782 * @name mdMenuBar
30783 * @module material.components.menu-bar
30784 * @restrict E
30785 * @description
30786 *
30787 * Menu bars are containers that hold multiple menus. They change the behavior and appearence
30788 * of the `md-menu` directive to behave similar to an operating system provided menu.
30789 *
30790 * @usage
30791 * <hljs lang="html">
30792 * <md-menu-bar>
30793 *   <md-menu>
30794 *     <button ng-click="$mdOpenMenu()">
30795 *       File
30796 *     </button>
30797 *     <md-menu-content>
30798 *       <md-menu-item>
30799 *         <md-button ng-click="ctrl.sampleAction('share', $event)">
30800 *           Share...
30801 *         </md-button>
30802 *       </md-menu-item>
30803 *       <md-menu-divider></md-menu-divider>
30804 *       <md-menu-item>
30805 *       <md-menu-item>
30806 *         <md-menu>
30807 *           <md-button ng-click="$mdOpenMenu()">New</md-button>
30808 *           <md-menu-content>
30809 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Document', $event)">Document</md-button></md-menu-item>
30810 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Spreadsheet', $event)">Spreadsheet</md-button></md-menu-item>
30811 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Presentation', $event)">Presentation</md-button></md-menu-item>
30812 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Form', $event)">Form</md-button></md-menu-item>
30813 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Drawing', $event)">Drawing</md-button></md-menu-item>
30814 *           </md-menu-content>
30815 *         </md-menu>
30816 *       </md-menu-item>
30817 *     </md-menu-content>
30818 *   </md-menu>
30819 * </md-menu-bar>
30820 * </hljs>
30821 *
30822 * ## Menu Bar Controls
30823 *
30824 * You may place `md-menu-items` that function as controls within menu bars.
30825 * There are two modes that are exposed via the `type` attribute of the `md-menu-item`.
30826 * `type="checkbox"` will function as a boolean control for the `ng-model` attribute of the
30827 * `md-menu-item`. `type="radio"` will function like a radio button, setting the `ngModel`
30828 * to the `string` value of the `value` attribute. If you need non-string values, you can use
30829 * `ng-value` to provide an expression (this is similar to how angular's native `input[type=radio]` works.
30830 *
30831 * <hljs lang="html">
30832 * <md-menu-bar>
30833 *  <md-menu>
30834 *    <button ng-click="$mdOpenMenu()">
30835 *      Sample Menu
30836 *    </button>
30837 *    <md-menu-content>
30838 *      <md-menu-item type="checkbox" ng-model="settings.allowChanges">Allow changes</md-menu-item>
30839 *      <md-menu-divider></md-menu-divider>
30840 *      <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 1</md-menu-item>
30841 *      <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 2</md-menu-item>
30842 *      <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 3</md-menu-item>
30843 *    </md-menu-content>
30844 *  </md-menu>
30845 * </md-menu-bar>
30846 * </hljs>
30847 *
30848 *
30849 * ### Nesting Menus
30850 *
30851 * Menus may be nested within menu bars. This is commonly called cascading menus.
30852 * To nest a menu place the nested menu inside the content of the `md-menu-item`.
30853 * <hljs lang="html">
30854 * <md-menu-item>
30855 *   <md-menu>
30856 *     <button ng-click="$mdOpenMenu()">New</md-button>
30857 *     <md-menu-content>
30858 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Document', $event)">Document</md-button></md-menu-item>
30859 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Spreadsheet', $event)">Spreadsheet</md-button></md-menu-item>
30860 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Presentation', $event)">Presentation</md-button></md-menu-item>
30861 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Form', $event)">Form</md-button></md-menu-item>
30862 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Drawing', $event)">Drawing</md-button></md-menu-item>
30863 *     </md-menu-content>
30864 *   </md-menu>
30865 * </md-menu-item>
30866 * </hljs>
30867 *
30868 */
30869
30870MenuBarDirective.$inject = ["$mdUtil", "$mdTheming"];
30871angular
30872  .module('material.components.menuBar')
30873  .directive('mdMenuBar', MenuBarDirective);
30874
30875/* @ngInject */
30876function MenuBarDirective($mdUtil, $mdTheming) {
30877  return {
30878    restrict: 'E',
30879    require: 'mdMenuBar',
30880    controller: 'MenuBarController',
30881
30882    compile: function compile(templateEl, templateAttrs) {
30883      if (!templateAttrs.ariaRole) {
30884        templateEl[0].setAttribute('role', 'menubar');
30885      }
30886      angular.forEach(templateEl[0].children, function(menuEl) {
30887        if (menuEl.nodeName == 'MD-MENU') {
30888          if (!menuEl.hasAttribute('md-position-mode')) {
30889            menuEl.setAttribute('md-position-mode', 'left bottom');
30890
30891            // Since we're in the compile function and actual `md-buttons` are not compiled yet,
30892            // we need to query for possible `md-buttons` as well.
30893            menuEl.querySelector('button, a, md-button').setAttribute('role', 'menuitem');
30894          }
30895          var contentEls = $mdUtil.nodesToArray(menuEl.querySelectorAll('md-menu-content'));
30896          angular.forEach(contentEls, function(contentEl) {
30897            contentEl.classList.add('md-menu-bar-menu');
30898            contentEl.classList.add('md-dense');
30899            if (!contentEl.hasAttribute('width')) {
30900              contentEl.setAttribute('width', 5);
30901            }
30902          });
30903        }
30904      });
30905
30906      // Mark the child menu items that they're inside a menu bar. This is necessary,
30907      // because mnMenuItem has special behaviour during compilation, depending on
30908      // whether it is inside a mdMenuBar. We can usually figure this out via the DOM,
30909      // however if a directive that uses documentFragment is applied to the child (e.g. ngRepeat),
30910      // the element won't have a parent and won't compile properly.
30911      templateEl.find('md-menu-item').addClass('md-in-menu-bar');
30912
30913      return function postLink(scope, el, attr, ctrl) {
30914        el.addClass('_md');     // private md component indicator for styling
30915        $mdTheming(scope, el);
30916        ctrl.init();
30917      };
30918    }
30919  };
30920
30921}
30922
30923})();
30924(function(){
30925"use strict";
30926
30927
30928angular
30929  .module('material.components.menuBar')
30930  .directive('mdMenuDivider', MenuDividerDirective);
30931
30932
30933function MenuDividerDirective() {
30934  return {
30935    restrict: 'E',
30936    compile: function(templateEl, templateAttrs) {
30937      if (!templateAttrs.role) {
30938        templateEl[0].setAttribute('role', 'separator');
30939      }
30940    }
30941  };
30942}
30943
30944})();
30945(function(){
30946"use strict";
30947
30948
30949MenuItemController.$inject = ["$scope", "$element", "$attrs"];
30950angular
30951  .module('material.components.menuBar')
30952  .controller('MenuItemController', MenuItemController);
30953
30954
30955/**
30956 * @ngInject
30957 */
30958function MenuItemController($scope, $element, $attrs) {
30959  this.$element = $element;
30960  this.$attrs = $attrs;
30961  this.$scope = $scope;
30962}
30963
30964MenuItemController.prototype.init = function(ngModel) {
30965  var $element = this.$element;
30966  var $attrs = this.$attrs;
30967
30968  this.ngModel = ngModel;
30969  if ($attrs.type == 'checkbox' || $attrs.type == 'radio') {
30970    this.mode  = $attrs.type;
30971    this.iconEl = $element[0].children[0];
30972    this.buttonEl = $element[0].children[1];
30973    if (ngModel) {
30974      // Clear ngAria set attributes
30975      this.initClickListeners();
30976    }
30977  }
30978};
30979
30980// ngAria auto sets attributes on a menu-item with a ngModel.
30981// We don't want this because our content (buttons) get the focus
30982// and set their own aria attributes appropritately. Having both
30983// breaks NVDA / JAWS. This undeoes ngAria's attrs.
30984MenuItemController.prototype.clearNgAria = function() {
30985  var el = this.$element[0];
30986  var clearAttrs = ['role', 'tabindex', 'aria-invalid', 'aria-checked'];
30987  angular.forEach(clearAttrs, function(attr) {
30988    el.removeAttribute(attr);
30989  });
30990};
30991
30992MenuItemController.prototype.initClickListeners = function() {
30993  var self = this;
30994  var ngModel = this.ngModel;
30995  var $scope = this.$scope;
30996  var $attrs = this.$attrs;
30997  var $element = this.$element;
30998  var mode = this.mode;
30999
31000  this.handleClick = angular.bind(this, this.handleClick);
31001
31002  var icon = this.iconEl;
31003  var button = angular.element(this.buttonEl);
31004  var handleClick = this.handleClick;
31005
31006  $attrs.$observe('disabled', setDisabled);
31007  setDisabled($attrs.disabled);
31008
31009  ngModel.$render = function render() {
31010    self.clearNgAria();
31011    if (isSelected()) {
31012      icon.style.display = '';
31013      button.attr('aria-checked', 'true');
31014    } else {
31015      icon.style.display = 'none';
31016      button.attr('aria-checked', 'false');
31017    }
31018  };
31019
31020  $scope.$$postDigest(ngModel.$render);
31021
31022  function isSelected() {
31023    if (mode == 'radio') {
31024      var val = $attrs.ngValue ? $scope.$eval($attrs.ngValue) : $attrs.value;
31025      return ngModel.$modelValue == val;
31026    } else {
31027      return ngModel.$modelValue;
31028    }
31029  }
31030
31031  function setDisabled(disabled) {
31032    if (disabled) {
31033      button.off('click', handleClick);
31034    } else {
31035      button.on('click', handleClick);
31036    }
31037  }
31038};
31039
31040MenuItemController.prototype.handleClick = function(e) {
31041  var mode = this.mode;
31042  var ngModel = this.ngModel;
31043  var $attrs = this.$attrs;
31044  var newVal;
31045  if (mode == 'checkbox') {
31046    newVal = !ngModel.$modelValue;
31047  } else if (mode == 'radio') {
31048    newVal = $attrs.ngValue ? this.$scope.$eval($attrs.ngValue) : $attrs.value;
31049  }
31050  ngModel.$setViewValue(newVal);
31051  ngModel.$render();
31052};
31053
31054})();
31055(function(){
31056"use strict";
31057
31058
31059MenuItemDirective.$inject = ["$mdUtil", "$$mdSvgRegistry"];
31060angular
31061  .module('material.components.menuBar')
31062  .directive('mdMenuItem', MenuItemDirective);
31063
31064 /* @ngInject */
31065function MenuItemDirective($mdUtil, $$mdSvgRegistry) {
31066  return {
31067    controller: 'MenuItemController',
31068    require: ['mdMenuItem', '?ngModel'],
31069    priority: 210, // ensure that our post link runs after ngAria
31070    compile: function(templateEl, templateAttrs) {
31071      var type = templateAttrs.type;
31072      var inMenuBarClass = 'md-in-menu-bar';
31073
31074      // Note: This allows us to show the `check` icon for the md-menu-bar items.
31075      // The `md-in-menu-bar` class is set by the mdMenuBar directive.
31076      if ((type == 'checkbox' || type == 'radio') && templateEl.hasClass(inMenuBarClass)) {
31077        var text = templateEl[0].textContent;
31078        var buttonEl = angular.element('<md-button type="button"></md-button>');
31079        var iconTemplate = '<md-icon md-svg-src="' + $$mdSvgRegistry.mdChecked + '"></md-icon>';
31080
31081        buttonEl.html(text);
31082        buttonEl.attr('tabindex', '0');
31083
31084        templateEl.html('');
31085        templateEl.append(angular.element(iconTemplate));
31086        templateEl.append(buttonEl);
31087        templateEl.addClass('md-indent').removeClass(inMenuBarClass);
31088
31089        setDefault('role', type == 'checkbox' ? 'menuitemcheckbox' : 'menuitemradio', buttonEl);
31090        moveAttrToButton('ng-disabled');
31091
31092      } else {
31093        setDefault('role', 'menuitem', templateEl[0].querySelector('md-button, button, a'));
31094      }
31095
31096
31097      return function(scope, el, attrs, ctrls) {
31098        var ctrl = ctrls[0];
31099        var ngModel = ctrls[1];
31100        ctrl.init(ngModel);
31101      };
31102
31103      function setDefault(attr, val, el) {
31104        el = el || templateEl;
31105        if (el instanceof angular.element) {
31106          el = el[0];
31107        }
31108        if (!el.hasAttribute(attr)) {
31109          el.setAttribute(attr, val);
31110        }
31111      }
31112
31113      function moveAttrToButton(attribute) {
31114        var attributes = $mdUtil.prefixer(attribute);
31115
31116        angular.forEach(attributes, function(attr) {
31117          if (templateEl[0].hasAttribute(attr)) {
31118            var val = templateEl[0].getAttribute(attr);
31119            buttonEl[0].setAttribute(attr, val);
31120            templateEl[0].removeAttribute(attr);
31121          }
31122        });
31123      }
31124    }
31125  };
31126}
31127
31128})();
31129(function(){
31130"use strict";
31131
31132/**
31133 * @ngdoc directive
31134 * @name mdProgressCircular
31135 * @module material.components.progressCircular
31136 * @restrict E
31137 *
31138 * @description
31139 * The circular progress directive is used to make loading content in your app as delightful and
31140 * painless as possible by minimizing the amount of visual change a user sees before they can view
31141 * and interact with content.
31142 *
31143 * For operations where the percentage of the operation completed can be determined, use a
31144 * determinate indicator. They give users a quick sense of how long an operation will take.
31145 *
31146 * For operations where the user is asked to wait a moment while something finishes up, and it’s
31147 * not necessary to expose what's happening behind the scenes and how long it will take, use an
31148 * indeterminate indicator.
31149 *
31150 * @param {string} md-mode Select from one of two modes: **'determinate'** and **'indeterminate'**.
31151 *
31152 * Note: if the `md-mode` value is set as undefined or specified as not 1 of the two (2) valid modes, then **'indeterminate'**
31153 * will be auto-applied as the mode.
31154 *
31155 * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute.
31156 * If `value=""` is also specified, however, then `md-mode="determinate"` would be auto-injected instead.
31157 * @param {number=} value In determinate mode, this number represents the percentage of the
31158 *     circular progress. Default: 0
31159 * @param {number=} md-diameter This specifies the diameter of the circular progress. The value
31160 * should be a pixel-size value (eg '100'). If this attribute is
31161 * not present then a default value of '50px' is assumed.
31162 *
31163 * @param {boolean=} ng-disabled Determines whether to disable the progress element.
31164 *
31165 * @usage
31166 * <hljs lang="html">
31167 * <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
31168 *
31169 * <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
31170 *
31171 * <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
31172 *
31173 * <md-progress-circular md-mode="indeterminate"></md-progress-circular>
31174 * </hljs>
31175 */
31176
31177MdProgressCircularDirective.$inject = ["$window", "$mdProgressCircular", "$mdTheming", "$mdUtil", "$interval", "$log"];
31178angular
31179  .module('material.components.progressCircular')
31180  .directive('mdProgressCircular', MdProgressCircularDirective);
31181
31182/* @ngInject */
31183function MdProgressCircularDirective($window, $mdProgressCircular, $mdTheming,
31184                                     $mdUtil, $interval, $log) {
31185
31186  // Note that this shouldn't use use $$rAF, because it can cause an infinite loop
31187  // in any tests that call $animate.flush.
31188  var rAF = $window.requestAnimationFrame ||
31189            $window.webkitRequestAnimationFrame ||
31190            angular.noop;
31191
31192  var cAF = $window.cancelAnimationFrame ||
31193            $window.webkitCancelAnimationFrame ||
31194            $window.webkitCancelRequestAnimationFrame ||
31195            angular.noop;
31196
31197  var DEGREE_IN_RADIANS = $window.Math.PI / 180;
31198  var MODE_DETERMINATE = 'determinate';
31199  var MODE_INDETERMINATE = 'indeterminate';
31200  var DISABLED_CLASS = '_md-progress-circular-disabled';
31201  var INDETERMINATE_CLASS = 'md-mode-indeterminate';
31202
31203  return {
31204    restrict: 'E',
31205    scope: {
31206      value: '@',
31207      mdDiameter: '@',
31208      mdMode: '@'
31209    },
31210    template:
31211      '<svg xmlns="http://www.w3.org/2000/svg">' +
31212        '<path fill="none"/>' +
31213      '</svg>',
31214    compile: function(element, attrs) {
31215      element.attr({
31216        'aria-valuemin': 0,
31217        'aria-valuemax': 100,
31218        'role': 'progressbar'
31219      });
31220
31221      if (angular.isUndefined(attrs.mdMode)) {
31222        var hasValue = angular.isDefined(attrs.value);
31223        var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE;
31224        var info = "Auto-adding the missing md-mode='{0}' to the ProgressCircular element";
31225
31226          // $log.debug( $mdUtil.supplant(info, [mode]) );
31227        attrs.$set('mdMode', mode);
31228      } else {
31229        attrs.$set('mdMode', attrs.mdMode.trim());
31230      }
31231
31232      return MdProgressCircularLink;
31233    }
31234  };
31235
31236  function MdProgressCircularLink(scope, element, attrs) {
31237    var node = element[0];
31238    var svg = angular.element(node.querySelector('svg'));
31239    var path = angular.element(node.querySelector('path'));
31240    var startIndeterminate = $mdProgressCircular.startIndeterminate;
31241    var endIndeterminate = $mdProgressCircular.endIndeterminate;
31242    var rotationIndeterminate = 0;
31243    var lastAnimationId = 0;
31244    var lastDrawFrame;
31245    var interval;
31246
31247    $mdTheming(element);
31248    element.toggleClass(DISABLED_CLASS, attrs.hasOwnProperty('disabled'));
31249
31250    // If the mode is indeterminate, it doesn't need to
31251    // wait for the next digest. It can start right away.
31252    if(scope.mdMode === MODE_INDETERMINATE){
31253      startIndeterminateAnimation();
31254    }
31255
31256    scope.$on('$destroy', function(){
31257      cleanupIndeterminateAnimation();
31258
31259      if (lastDrawFrame) {
31260        cAF(lastDrawFrame);
31261      }
31262    });
31263
31264    scope.$watchGroup(['value', 'mdMode', function() {
31265      var isDisabled = node.disabled;
31266
31267      // Sometimes the browser doesn't return a boolean, in
31268      // which case we should check whether the attribute is
31269      // present.
31270      if (isDisabled === true || isDisabled === false){
31271        return isDisabled;
31272      }
31273      return angular.isDefined(element.attr('disabled'));
31274
31275    }], function(newValues, oldValues) {
31276      var mode = newValues[1];
31277      var isDisabled = newValues[2];
31278      var wasDisabled = oldValues[2];
31279
31280      if (isDisabled !== wasDisabled) {
31281        element.toggleClass(DISABLED_CLASS, !!isDisabled);
31282      }
31283
31284      if (isDisabled) {
31285        cleanupIndeterminateAnimation();
31286      } else {
31287        if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
31288          mode = MODE_INDETERMINATE;
31289          attrs.$set('mdMode', mode);
31290        }
31291
31292        if (mode === MODE_INDETERMINATE) {
31293          startIndeterminateAnimation();
31294        } else {
31295          var newValue = clamp(newValues[0]);
31296
31297          cleanupIndeterminateAnimation();
31298
31299          element.attr('aria-valuenow', newValue);
31300          renderCircle(clamp(oldValues[0]), newValue);
31301        }
31302      }
31303
31304    });
31305
31306    // This is in a separate watch in order to avoid layout, unless
31307    // the value has actually changed.
31308    scope.$watch('mdDiameter', function(newValue) {
31309      var diameter = getSize(newValue);
31310      var strokeWidth = getStroke(diameter);
31311      var transformOrigin = (diameter / 2) + 'px';
31312      var dimensions = {
31313        width: diameter + 'px',
31314        height: diameter + 'px'
31315      };
31316
31317      // The viewBox has to be applied via setAttribute, because it is
31318      // case-sensitive. If jQuery is included in the page, `.attr` lowercases
31319      // all attribute names.
31320      svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);
31321
31322      // Usually viewBox sets the dimensions for the SVG, however that doesn't
31323      // seem to be the case on IE10.
31324      // Important! The transform origin has to be set from here and it has to
31325      // be in the format of "Ypx Ypx Ypx", otherwise the rotation wobbles in
31326      // IE and Edge, because they don't account for the stroke width when
31327      // rotating. Also "center" doesn't help in this case, it has to be a
31328      // precise value.
31329      svg
31330        .css(dimensions)
31331        .css('transform-origin', transformOrigin + ' ' + transformOrigin + ' ' + transformOrigin);
31332
31333      element.css(dimensions);
31334      path.css('stroke-width',  strokeWidth + 'px');
31335    });
31336
31337    function renderCircle(animateFrom, animateTo, easing, duration, rotation) {
31338      var id = ++lastAnimationId;
31339      var startTime = $mdUtil.now();
31340      var changeInValue = animateTo - animateFrom;
31341      var diameter = getSize(scope.mdDiameter);
31342      var pathDiameter = diameter - getStroke(diameter);
31343      var ease = easing || $mdProgressCircular.easeFn;
31344      var animationDuration = duration || $mdProgressCircular.duration;
31345
31346      // No need to animate it if the values are the same
31347      if (animateTo === animateFrom) {
31348        path.attr('d', getSvgArc(animateTo, diameter, pathDiameter, rotation));
31349      } else {
31350        lastDrawFrame = rAF(function animation() {
31351          var currentTime = $window.Math.max(0, $window.Math.min($mdUtil.now() - startTime, animationDuration));
31352
31353          path.attr('d', getSvgArc(
31354            ease(currentTime, animateFrom, changeInValue, animationDuration),
31355            diameter,
31356            pathDiameter,
31357            rotation
31358          ));
31359
31360          // Do not allow overlapping animations
31361          if (id === lastAnimationId && currentTime < animationDuration) {
31362            lastDrawFrame = rAF(animation);
31363          }
31364        });
31365      }
31366    }
31367
31368    function animateIndeterminate() {
31369      renderCircle(
31370        startIndeterminate,
31371        endIndeterminate,
31372        $mdProgressCircular.easeFnIndeterminate,
31373        $mdProgressCircular.durationIndeterminate,
31374        rotationIndeterminate
31375      );
31376
31377      // The % 100 technically isn't necessary, but it keeps the rotation
31378      // under 100, instead of becoming a crazy large number.
31379      rotationIndeterminate = (rotationIndeterminate + endIndeterminate) % 100;
31380
31381      var temp = startIndeterminate;
31382      startIndeterminate = -endIndeterminate;
31383      endIndeterminate = -temp;
31384    }
31385
31386    function startIndeterminateAnimation() {
31387      if (!interval) {
31388        // Note that this interval isn't supposed to trigger a digest.
31389        interval = $interval(
31390          animateIndeterminate,
31391          $mdProgressCircular.durationIndeterminate + 50,
31392          0,
31393          false
31394        );
31395
31396        animateIndeterminate();
31397
31398        element
31399          .addClass(INDETERMINATE_CLASS)
31400          .removeAttr('aria-valuenow');
31401      }
31402    }
31403
31404    function cleanupIndeterminateAnimation() {
31405      if (interval) {
31406        $interval.cancel(interval);
31407        interval = null;
31408        element.removeClass(INDETERMINATE_CLASS);
31409      }
31410    }
31411  }
31412
31413  /**
31414   * Generates an arc following the SVG arc syntax.
31415   * Syntax spec: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
31416   *
31417   * @param {number} current Current value between 0 and 100.
31418   * @param {number} diameter Diameter of the container.
31419   * @param {number} pathDiameter Diameter of the path element.
31420   * @param {number=0} rotation The point at which the semicircle should start rendering.
31421   * Used for doing the indeterminate animation.
31422   *
31423   * @returns {string} String representation of an SVG arc.
31424   */
31425  function getSvgArc(current, diameter, pathDiameter, rotation) {
31426    // The angle can't be exactly 360, because the arc becomes hidden.
31427    var maximumAngle = 359.99 / 100;
31428    var startPoint = rotation || 0;
31429    var radius = diameter / 2;
31430    var pathRadius = pathDiameter / 2;
31431
31432    var startAngle = startPoint * maximumAngle;
31433    var endAngle = current * maximumAngle;
31434    var start = polarToCartesian(radius, pathRadius, startAngle);
31435    var end = polarToCartesian(radius, pathRadius, endAngle + startAngle);
31436    var arcSweep = endAngle < 0 ? 0 : 1;
31437    var largeArcFlag;
31438
31439    if (endAngle < 0) {
31440      largeArcFlag = endAngle >= -180 ? 0 : 1;
31441    } else {
31442      largeArcFlag = endAngle <= 180 ? 0 : 1;
31443    }
31444
31445    return 'M' + start + 'A' + pathRadius + ',' + pathRadius +
31446      ' 0 ' + largeArcFlag + ',' + arcSweep + ' ' + end;
31447  }
31448
31449  /**
31450   * Converts Polar coordinates to Cartesian.
31451   *
31452   * @param {number} radius Radius of the container.
31453   * @param {number} pathRadius Radius of the path element
31454   * @param {number} angleInDegress Angle at which to place the point.
31455   *
31456   * @returns {string} Cartesian coordinates in the format of `x,y`.
31457   */
31458  function polarToCartesian(radius, pathRadius, angleInDegrees) {
31459    var angleInRadians = (angleInDegrees - 90) * DEGREE_IN_RADIANS;
31460
31461    return (radius + (pathRadius * $window.Math.cos(angleInRadians))) +
31462      ',' + (radius + (pathRadius * $window.Math.sin(angleInRadians)));
31463  }
31464
31465  /**
31466   * Limits a value between 0 and 100.
31467   */
31468  function clamp(value) {
31469    return $window.Math.max(0, $window.Math.min(value || 0, 100));
31470  }
31471
31472  /**
31473   * Determines the size of a progress circle, based on the provided
31474   * value in the following formats: `X`, `Ypx`, `Z%`.
31475   */
31476  function getSize(value) {
31477    var defaultValue = $mdProgressCircular.progressSize;
31478
31479    if (value) {
31480      var parsed = parseFloat(value);
31481
31482      if (value.lastIndexOf('%') === value.length - 1) {
31483        parsed = (parsed / 100) * defaultValue;
31484      }
31485
31486      return parsed;
31487    }
31488
31489    return defaultValue;
31490  }
31491
31492  /**
31493   * Determines the circle's stroke width, based on
31494   * the provided diameter.
31495   */
31496  function getStroke(diameter) {
31497    return $mdProgressCircular.strokeWidth / 100 * diameter;
31498  }
31499}
31500
31501})();
31502(function(){
31503"use strict";
31504
31505/**
31506 * @ngdoc service
31507 * @name $mdProgressCircular
31508 * @module material.components.progressCircular
31509 *
31510 * @description
31511 * Allows the user to specify the default options for the `progressCircular` directive.
31512 *
31513 * @property {number} progressSize Diameter of the progress circle in pixels.
31514 * @property {number} strokeWidth Width of the circle's stroke as a percentage of the circle's size.
31515 * @property {number} duration Length of the circle animation in milliseconds.
31516 * @property {function} easeFn Default easing animation function.
31517 * @property {object} easingPresets Collection of pre-defined easing functions.
31518 *
31519 * @property {number} durationIndeterminate Duration of the indeterminate animation.
31520 * @property {number} startIndeterminate Indeterminate animation start point.
31521 * @property {number} endIndeterminate Indeterminate animation end point.
31522 * @property {function} easeFnIndeterminate Easing function to be used when animating
31523 * between the indeterminate values.
31524 *
31525 * @property {(function(object): object)} configure Used to modify the default options.
31526 *
31527 * @usage
31528 * <hljs lang="js">
31529 *   myAppModule.config(function($mdProgressCircularProvider) {
31530 *
31531 *     // Example of changing the default progress options.
31532 *     $mdProgressCircularProvider.configure({
31533 *       progressSize: 100,
31534 *       strokeWidth: 20,
31535 *       duration: 800
31536 *     });
31537 * });
31538 * </hljs>
31539 *
31540 */
31541
31542angular
31543  .module('material.components.progressCircular')
31544  .provider("$mdProgressCircular", MdProgressCircularProvider);
31545
31546function MdProgressCircularProvider() {
31547  var progressConfig = {
31548    progressSize: 50,
31549    strokeWidth: 10,
31550    duration: 100,
31551    easeFn: linearEase,
31552
31553    durationIndeterminate: 500,
31554    startIndeterminate: 3,
31555    endIndeterminate: 80,
31556    easeFnIndeterminate: materialEase,
31557
31558    easingPresets: {
31559      linearEase: linearEase,
31560      materialEase: materialEase
31561    }
31562  };
31563
31564  return {
31565    configure: function(options) {
31566      progressConfig = angular.extend(progressConfig, options || {});
31567      return progressConfig;
31568    },
31569    $get: function() { return progressConfig; }
31570  };
31571
31572  function linearEase(t, b, c, d) {
31573    return c * t / d + b;
31574  }
31575
31576  function materialEase(t, b, c, d) {
31577    // via http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
31578    // with settings of [0, 0, 1, 1]
31579    var ts = (t /= d) * t;
31580    var tc = ts * t;
31581    return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
31582  }
31583}
31584
31585})();
31586(function(){
31587"use strict";
31588
31589/**
31590 * @ngdoc directive
31591 * @name mdTab
31592 * @module material.components.tabs
31593 *
31594 * @restrict E
31595 *
31596 * @description
31597 * Use the `<md-tab>` a nested directive used within `<md-tabs>` to specify a tab with a **label** and optional *view content*.
31598 *
31599 * If the `label` attribute is not specified, then an optional `<md-tab-label>` tag can be used to specify more
31600 * complex tab header markup. If neither the **label** nor the **md-tab-label** are specified, then the nested
31601 * markup of the `<md-tab>` is used as the tab header markup.
31602 *
31603 * Please note that if you use `<md-tab-label>`, your content **MUST** be wrapped in the `<md-tab-body>` tag.  This
31604 * is to define a clear separation between the tab content and the tab label.
31605 *
31606 * This container is used by the TabsController to show/hide the active tab's content view. This synchronization is
31607 * automatically managed by the internal TabsController whenever the tab selection changes. Selection changes can
31608 * be initiated via data binding changes, programmatic invocation, or user gestures.
31609 *
31610 * @param {string=} label Optional attribute to specify a simple string as the tab label
31611 * @param {boolean=} ng-disabled If present and expression evaluates to truthy, disabled tab selection.
31612 * @param {expression=} md-on-deselect Expression to be evaluated after the tab has been de-selected.
31613 * @param {expression=} md-on-select Expression to be evaluated after the tab has been selected.
31614 * @param {boolean=} md-active When true, sets the active tab.  Note: There can only be one active tab at a time.
31615 *
31616 *
31617 * @usage
31618 *
31619 * <hljs lang="html">
31620 * <md-tab label="" ng-disabled md-on-select="" md-on-deselect="" >
31621 *   <h3>My Tab content</h3>
31622 * </md-tab>
31623 *
31624 * <md-tab >
31625 *   <md-tab-label>
31626 *     <h3>My Tab content</h3>
31627 *   </md-tab-label>
31628 *   <md-tab-body>
31629 *     <p>
31630 *       Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
31631 *       totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
31632 *       dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
31633 *       sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
31634 *     </p>
31635 *   </md-tab-body>
31636 * </md-tab>
31637 * </hljs>
31638 *
31639 */
31640angular
31641    .module('material.components.tabs')
31642    .directive('mdTab', MdTab);
31643
31644function MdTab () {
31645  return {
31646    require:  '^?mdTabs',
31647    terminal: true,
31648    compile:  function (element, attr) {
31649      var label = firstChild(element, 'md-tab-label'),
31650          body  = firstChild(element, 'md-tab-body');
31651
31652      if (label.length == 0) {
31653        label = angular.element('<md-tab-label></md-tab-label>');
31654        if (attr.label) label.text(attr.label);
31655        else label.append(element.contents());
31656
31657        if (body.length == 0) {
31658          var contents = element.contents().detach();
31659          body         = angular.element('<md-tab-body></md-tab-body>');
31660          body.append(contents);
31661        }
31662      }
31663
31664      element.append(label);
31665      if (body.html()) element.append(body);
31666
31667      return postLink;
31668    },
31669    scope:    {
31670      active:   '=?mdActive',
31671      disabled: '=?ngDisabled',
31672      select:   '&?mdOnSelect',
31673      deselect: '&?mdOnDeselect'
31674    }
31675  };
31676
31677  function postLink (scope, element, attr, ctrl) {
31678    if (!ctrl) return;
31679    var index = ctrl.getTabElementIndex(element),
31680        body  = firstChild(element, 'md-tab-body').remove(),
31681        label = firstChild(element, 'md-tab-label').remove(),
31682        data  = ctrl.insertTab({
31683          scope:    scope,
31684          parent:   scope.$parent,
31685          index:    index,
31686          element:  element,
31687          template: body.html(),
31688          label:    label.html()
31689        }, index);
31690
31691    scope.select   = scope.select || angular.noop;
31692    scope.deselect = scope.deselect || angular.noop;
31693
31694    scope.$watch('active', function (active) { if (active) ctrl.select(data.getIndex(), true); });
31695    scope.$watch('disabled', function () { ctrl.refreshIndex(); });
31696    scope.$watch(
31697        function () {
31698          return ctrl.getTabElementIndex(element);
31699        },
31700        function (newIndex) {
31701          data.index = newIndex;
31702          ctrl.updateTabOrder();
31703        }
31704    );
31705    scope.$on('$destroy', function () { ctrl.removeTab(data); });
31706  }
31707
31708  function firstChild (element, tagName) {
31709    var children = element[0].children;
31710    for (var i = 0, len = children.length; i < len; i++) {
31711      var child = children[i];
31712      if (child.tagName === tagName.toUpperCase()) return angular.element(child);
31713    }
31714    return angular.element();
31715  }
31716}
31717
31718})();
31719(function(){
31720"use strict";
31721
31722angular
31723    .module('material.components.tabs')
31724    .directive('mdTabItem', MdTabItem);
31725
31726function MdTabItem () {
31727  return {
31728    require: '^?mdTabs',
31729    link:    function link (scope, element, attr, ctrl) {
31730      if (!ctrl) return;
31731      ctrl.attachRipple(scope, element);
31732    }
31733  };
31734}
31735
31736})();
31737(function(){
31738"use strict";
31739
31740angular
31741    .module('material.components.tabs')
31742    .directive('mdTabLabel', MdTabLabel);
31743
31744function MdTabLabel () {
31745  return { terminal: true };
31746}
31747
31748
31749})();
31750(function(){
31751"use strict";
31752
31753
31754MdTabScroll.$inject = ["$parse"];angular.module('material.components.tabs')
31755    .directive('mdTabScroll', MdTabScroll);
31756
31757function MdTabScroll ($parse) {
31758  return {
31759    restrict: 'A',
31760    compile: function ($element, attr) {
31761      var fn = $parse(attr.mdTabScroll, null, true);
31762      return function ngEventHandler (scope, element) {
31763        element.on('mousewheel', function (event) {
31764          scope.$apply(function () { fn(scope, { $event: event }); });
31765        });
31766      };
31767    }
31768  }
31769}
31770
31771})();
31772(function(){
31773"use strict";
31774
31775
31776MdTabsController.$inject = ["$scope", "$element", "$window", "$mdConstant", "$mdTabInkRipple", "$mdUtil", "$animateCss", "$attrs", "$compile", "$mdTheming"];angular
31777    .module('material.components.tabs')
31778    .controller('MdTabsController', MdTabsController);
31779
31780/**
31781 * @ngInject
31782 */
31783function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipple,
31784                           $mdUtil, $animateCss, $attrs, $compile, $mdTheming) {
31785  // define private properties
31786  var ctrl      = this,
31787      locked    = false,
31788      elements  = getElements(),
31789      queue     = [],
31790      destroyed = false,
31791      loaded    = false;
31792
31793  // define one-way bindings
31794  defineOneWayBinding('stretchTabs', handleStretchTabs);
31795
31796  // define public properties with change handlers
31797  defineProperty('focusIndex', handleFocusIndexChange, ctrl.selectedIndex || 0);
31798  defineProperty('offsetLeft', handleOffsetChange, 0);
31799  defineProperty('hasContent', handleHasContent, false);
31800  defineProperty('maxTabWidth', handleMaxTabWidth, getMaxTabWidth());
31801  defineProperty('shouldPaginate', handleShouldPaginate, false);
31802
31803  // define boolean attributes
31804  defineBooleanAttribute('noInkBar', handleInkBar);
31805  defineBooleanAttribute('dynamicHeight', handleDynamicHeight);
31806  defineBooleanAttribute('noPagination');
31807  defineBooleanAttribute('swipeContent');
31808  defineBooleanAttribute('noDisconnect');
31809  defineBooleanAttribute('autoselect');
31810  defineBooleanAttribute('noSelectClick');
31811  defineBooleanAttribute('centerTabs', handleCenterTabs, false);
31812  defineBooleanAttribute('enableDisconnect');
31813
31814  // define public properties
31815  ctrl.scope             = $scope;
31816  ctrl.parent            = $scope.$parent;
31817  ctrl.tabs              = [];
31818  ctrl.lastSelectedIndex = null;
31819  ctrl.hasFocus          = false;
31820  ctrl.lastClick         = true;
31821  ctrl.shouldCenterTabs  = shouldCenterTabs();
31822
31823  // define public methods
31824  ctrl.updatePagination   = $mdUtil.debounce(updatePagination, 100);
31825  ctrl.redirectFocus      = redirectFocus;
31826  ctrl.attachRipple       = attachRipple;
31827  ctrl.insertTab          = insertTab;
31828  ctrl.removeTab          = removeTab;
31829  ctrl.select             = select;
31830  ctrl.scroll             = scroll;
31831  ctrl.nextPage           = nextPage;
31832  ctrl.previousPage       = previousPage;
31833  ctrl.keydown            = keydown;
31834  ctrl.canPageForward     = canPageForward;
31835  ctrl.canPageBack        = canPageBack;
31836  ctrl.refreshIndex       = refreshIndex;
31837  ctrl.incrementIndex     = incrementIndex;
31838  ctrl.getTabElementIndex = getTabElementIndex;
31839  ctrl.updateInkBarStyles = $mdUtil.debounce(updateInkBarStyles, 100);
31840  ctrl.updateTabOrder     = $mdUtil.debounce(updateTabOrder, 100);
31841
31842  init();
31843
31844  /**
31845   * Perform initialization for the controller, setup events and watcher(s)
31846   */
31847  function init () {
31848    ctrl.selectedIndex = ctrl.selectedIndex || 0;
31849    compileTemplate();
31850    configureWatchers();
31851    bindEvents();
31852    $mdTheming($element);
31853    $mdUtil.nextTick(function () {
31854      // Note that the element references need to be updated, because certain "browsers"
31855      // (IE/Edge) lose them and start throwing "Invalid calling object" errors, when we
31856      // compile the element contents down in `compileElement`.
31857      elements = getElements();
31858      updateHeightFromContent();
31859      adjustOffset();
31860      updateInkBarStyles();
31861      ctrl.tabs[ ctrl.selectedIndex ] && ctrl.tabs[ ctrl.selectedIndex ].scope.select();
31862      loaded = true;
31863      updatePagination();
31864    });
31865  }
31866
31867  /**
31868   * Compiles the template provided by the user.  This is passed as an attribute from the tabs
31869   * directive's template function.
31870   */
31871  function compileTemplate () {
31872    var template = $attrs.$mdTabsTemplate,
31873        element  = angular.element($element[0].querySelector('md-tab-data'));
31874
31875    element.html(template);
31876    $compile(element.contents())(ctrl.parent);
31877    delete $attrs.$mdTabsTemplate;
31878  }
31879
31880  /**
31881   * Binds events used by the tabs component.
31882   */
31883  function bindEvents () {
31884    angular.element($window).on('resize', handleWindowResize);
31885    $scope.$on('$destroy', cleanup);
31886  }
31887
31888  /**
31889   * Configure watcher(s) used by Tabs
31890   */
31891  function configureWatchers () {
31892    $scope.$watch('$mdTabsCtrl.selectedIndex', handleSelectedIndexChange);
31893  }
31894
31895  /**
31896   * Creates a one-way binding manually rather than relying on Angular's isolated scope
31897   * @param key
31898   * @param handler
31899   */
31900  function defineOneWayBinding (key, handler) {
31901    var attr = $attrs.$normalize('md-' + key);
31902    if (handler) defineProperty(key, handler);
31903    $attrs.$observe(attr, function (newValue) { ctrl[ key ] = newValue; });
31904  }
31905
31906  /**
31907   * Defines boolean attributes with default value set to true.  (ie. md-stretch-tabs with no value
31908   * will be treated as being truthy)
31909   * @param key
31910   * @param handler
31911   */
31912  function defineBooleanAttribute (key, handler) {
31913    var attr = $attrs.$normalize('md-' + key);
31914    if (handler) defineProperty(key, handler);
31915    if ($attrs.hasOwnProperty(attr)) updateValue($attrs[attr]);
31916    $attrs.$observe(attr, updateValue);
31917    function updateValue (newValue) {
31918      ctrl[ key ] = newValue !== 'false';
31919    }
31920  }
31921
31922  /**
31923   * Remove any events defined by this controller
31924   */
31925  function cleanup () {
31926    destroyed = true;
31927    angular.element($window).off('resize', handleWindowResize);
31928  }
31929
31930  // Change handlers
31931
31932  /**
31933   * Toggles stretch tabs class and updates inkbar when tab stretching changes
31934   * @param stretchTabs
31935   */
31936  function handleStretchTabs (stretchTabs) {
31937    var elements = getElements();
31938    angular.element(elements.wrapper).toggleClass('md-stretch-tabs', shouldStretchTabs());
31939    updateInkBarStyles();
31940  }
31941
31942  function handleCenterTabs (newValue) {
31943    ctrl.shouldCenterTabs = shouldCenterTabs();
31944  }
31945
31946  function handleMaxTabWidth (newWidth, oldWidth) {
31947    if (newWidth !== oldWidth) {
31948      var elements = getElements();
31949
31950      angular.forEach(elements.tabs, function(tab) {
31951        tab.style.maxWidth = newWidth + 'px';
31952      });
31953      $mdUtil.nextTick(ctrl.updateInkBarStyles);
31954    }
31955  }
31956
31957  function handleShouldPaginate (newValue, oldValue) {
31958    if (newValue !== oldValue) {
31959      ctrl.maxTabWidth      = getMaxTabWidth();
31960      ctrl.shouldCenterTabs = shouldCenterTabs();
31961      $mdUtil.nextTick(function () {
31962        ctrl.maxTabWidth = getMaxTabWidth();
31963        adjustOffset(ctrl.selectedIndex);
31964      });
31965    }
31966  }
31967
31968  /**
31969   * Add/remove the `md-no-tab-content` class depending on `ctrl.hasContent`
31970   * @param hasContent
31971   */
31972  function handleHasContent (hasContent) {
31973    $element[ hasContent ? 'removeClass' : 'addClass' ]('md-no-tab-content');
31974  }
31975
31976  /**
31977   * Apply ctrl.offsetLeft to the paging element when it changes
31978   * @param left
31979   */
31980  function handleOffsetChange (left) {
31981    var elements = getElements();
31982    var newValue = ctrl.shouldCenterTabs ? '' : '-' + left + 'px';
31983
31984    angular.element(elements.paging).css($mdConstant.CSS.TRANSFORM, 'translate3d(' + newValue + ', 0, 0)');
31985    $scope.$broadcast('$mdTabsPaginationChanged');
31986  }
31987
31988  /**
31989   * Update the UI whenever `ctrl.focusIndex` is updated
31990   * @param newIndex
31991   * @param oldIndex
31992   */
31993  function handleFocusIndexChange (newIndex, oldIndex) {
31994    if (newIndex === oldIndex) return;
31995    if (!getElements().tabs[ newIndex ]) return;
31996    adjustOffset();
31997    redirectFocus();
31998  }
31999
32000  /**
32001   * Update the UI whenever the selected index changes. Calls user-defined select/deselect methods.
32002   * @param newValue
32003   * @param oldValue
32004   */
32005  function handleSelectedIndexChange (newValue, oldValue) {
32006    if (newValue === oldValue) return;
32007
32008    ctrl.selectedIndex     = getNearestSafeIndex(newValue);
32009    ctrl.lastSelectedIndex = oldValue;
32010    ctrl.updateInkBarStyles();
32011    updateHeightFromContent();
32012    adjustOffset(newValue);
32013    $scope.$broadcast('$mdTabsChanged');
32014    ctrl.tabs[ oldValue ] && ctrl.tabs[ oldValue ].scope.deselect();
32015    ctrl.tabs[ newValue ] && ctrl.tabs[ newValue ].scope.select();
32016  }
32017
32018  function getTabElementIndex(tabEl){
32019    var tabs = $element[0].getElementsByTagName('md-tab');
32020    return Array.prototype.indexOf.call(tabs, tabEl[0]);
32021  }
32022
32023  /**
32024   * Queues up a call to `handleWindowResize` when a resize occurs while the tabs component is
32025   * hidden.
32026   */
32027  function handleResizeWhenVisible () {
32028    // if there is already a watcher waiting for resize, do nothing
32029    if (handleResizeWhenVisible.watcher) return;
32030    // otherwise, we will abuse the $watch function to check for visible
32031    handleResizeWhenVisible.watcher = $scope.$watch(function () {
32032      // since we are checking for DOM size, we use $mdUtil.nextTick() to wait for after the DOM updates
32033      $mdUtil.nextTick(function () {
32034        // if the watcher has already run (ie. multiple digests in one cycle), do nothing
32035        if (!handleResizeWhenVisible.watcher) return;
32036
32037        if ($element.prop('offsetParent')) {
32038          handleResizeWhenVisible.watcher();
32039          handleResizeWhenVisible.watcher = null;
32040
32041          handleWindowResize();
32042        }
32043      }, false);
32044    });
32045  }
32046
32047  // Event handlers / actions
32048
32049  /**
32050   * Handle user keyboard interactions
32051   * @param event
32052   */
32053  function keydown (event) {
32054    switch (event.keyCode) {
32055      case $mdConstant.KEY_CODE.LEFT_ARROW:
32056        event.preventDefault();
32057        incrementIndex(-1, true);
32058        break;
32059      case $mdConstant.KEY_CODE.RIGHT_ARROW:
32060        event.preventDefault();
32061        incrementIndex(1, true);
32062        break;
32063      case $mdConstant.KEY_CODE.SPACE:
32064      case $mdConstant.KEY_CODE.ENTER:
32065        event.preventDefault();
32066        if (!locked) select(ctrl.focusIndex);
32067        break;
32068    }
32069    ctrl.lastClick = false;
32070  }
32071
32072  /**
32073   * Update the selected index. Triggers a click event on the original `md-tab` element in order
32074   * to fire user-added click events if canSkipClick or `md-no-select-click` are false.
32075   * @param index
32076   * @param canSkipClick Optionally allow not firing the click event if `md-no-select-click` is also true.
32077   */
32078  function select (index, canSkipClick) {
32079    if (!locked) ctrl.focusIndex = ctrl.selectedIndex = index;
32080    ctrl.lastClick = true;
32081    // skip the click event if noSelectClick is enabled
32082    if (canSkipClick && ctrl.noSelectClick) return;
32083    // nextTick is required to prevent errors in user-defined click events
32084    $mdUtil.nextTick(function () {
32085      ctrl.tabs[ index ].element.triggerHandler('click');
32086    }, false);
32087  }
32088
32089  /**
32090   * When pagination is on, this makes sure the selected index is in view.
32091   * @param event
32092   */
32093  function scroll (event) {
32094    if (!ctrl.shouldPaginate) return;
32095    event.preventDefault();
32096    ctrl.offsetLeft = fixOffset(ctrl.offsetLeft - event.wheelDelta);
32097  }
32098
32099  /**
32100   * Slides the tabs over approximately one page forward.
32101   */
32102  function nextPage () {
32103    var elements = getElements();
32104    var viewportWidth = elements.canvas.clientWidth,
32105        totalWidth    = viewportWidth + ctrl.offsetLeft,
32106        i, tab;
32107    for (i = 0; i < elements.tabs.length; i++) {
32108      tab = elements.tabs[ i ];
32109      if (tab.offsetLeft + tab.offsetWidth > totalWidth) break;
32110    }
32111    
32112    if (viewportWidth > tab.offsetWidth) {
32113      //Canvas width *greater* than tab width: usual positioning
32114      ctrl.offsetLeft = fixOffset(tab.offsetLeft);
32115    } else {
32116      /**
32117       * Canvas width *smaller* than tab width: positioning at the *end* of current tab to let 
32118       * pagination "for loop" to proceed correctly on next tab when nextPage() is called again
32119       */
32120      ctrl.offsetLeft = fixOffset(tab.offsetLeft + (tab.offsetWidth - viewportWidth + 1));
32121    }
32122  }
32123
32124  /**
32125   * Slides the tabs over approximately one page backward.
32126   */
32127  function previousPage () {
32128    var i, tab, elements = getElements();
32129
32130    for (i = 0; i < elements.tabs.length; i++) {
32131      tab = elements.tabs[ i ];
32132      if (tab.offsetLeft + tab.offsetWidth >= ctrl.offsetLeft) break;
32133    }
32134    
32135    if (elements.canvas.clientWidth > tab.offsetWidth) {
32136      //Canvas width *greater* than tab width: usual positioning
32137      ctrl.offsetLeft = fixOffset(tab.offsetLeft + tab.offsetWidth - elements.canvas.clientWidth);
32138    } else {
32139      /**
32140       * Canvas width *smaller* than tab width: positioning at the *beginning* of current tab to let 
32141       * pagination "for loop" to break correctly on previous tab when previousPage() is called again
32142       */
32143      ctrl.offsetLeft = fixOffset(tab.offsetLeft);  
32144    }
32145  }
32146
32147  /**
32148   * Update size calculations when the window is resized.
32149   */
32150  function handleWindowResize () {
32151    ctrl.lastSelectedIndex = ctrl.selectedIndex;
32152    ctrl.offsetLeft        = fixOffset(ctrl.offsetLeft);
32153    $mdUtil.nextTick(function () {
32154      ctrl.updateInkBarStyles();
32155      updatePagination();
32156    });
32157  }
32158
32159  function handleInkBar (hide) {
32160    angular.element(getElements().inkBar).toggleClass('ng-hide', hide);
32161  }
32162
32163  /**
32164   * Toggle dynamic height class when value changes
32165   * @param value
32166   */
32167  function handleDynamicHeight (value) {
32168    $element.toggleClass('md-dynamic-height', value);
32169  }
32170
32171  /**
32172   * Remove a tab from the data and select the nearest valid tab.
32173   * @param tabData
32174   */
32175  function removeTab (tabData) {
32176    if (destroyed) return;
32177    var selectedIndex = ctrl.selectedIndex,
32178        tab           = ctrl.tabs.splice(tabData.getIndex(), 1)[ 0 ];
32179    refreshIndex();
32180    // when removing a tab, if the selected index did not change, we have to manually trigger the
32181    //   tab select/deselect events
32182    if (ctrl.selectedIndex === selectedIndex) {
32183      tab.scope.deselect();
32184      ctrl.tabs[ ctrl.selectedIndex ] && ctrl.tabs[ ctrl.selectedIndex ].scope.select();
32185    }
32186    $mdUtil.nextTick(function () {
32187      updatePagination();
32188      ctrl.offsetLeft = fixOffset(ctrl.offsetLeft);
32189    });
32190  }
32191
32192  /**
32193   * Create an entry in the tabs array for a new tab at the specified index.
32194   * @param tabData
32195   * @param index
32196   * @returns {*}
32197   */
32198  function insertTab (tabData, index) {
32199    var hasLoaded = loaded;
32200    var proto     = {
32201          getIndex:     function () { return ctrl.tabs.indexOf(tab); },
32202          isActive:     function () { return this.getIndex() === ctrl.selectedIndex; },
32203          isLeft:       function () { return this.getIndex() < ctrl.selectedIndex; },
32204          isRight:      function () { return this.getIndex() > ctrl.selectedIndex; },
32205          shouldRender: function () { return !ctrl.noDisconnect || this.isActive(); },
32206          hasFocus:     function () {
32207            return !ctrl.lastClick
32208                && ctrl.hasFocus && this.getIndex() === ctrl.focusIndex;
32209          },
32210          id:           $mdUtil.nextUid()
32211        },
32212        tab       = angular.extend(proto, tabData);
32213    if (angular.isDefined(index)) {
32214      ctrl.tabs.splice(index, 0, tab);
32215    } else {
32216      ctrl.tabs.push(tab);
32217    }
32218    processQueue();
32219    updateHasContent();
32220    $mdUtil.nextTick(function () {
32221      updatePagination();
32222      // if autoselect is enabled, select the newly added tab
32223      if (hasLoaded && ctrl.autoselect) $mdUtil.nextTick(function () {
32224        $mdUtil.nextTick(function () { select(ctrl.tabs.indexOf(tab)); });
32225      });
32226    });
32227    return tab;
32228  }
32229
32230  // Getter methods
32231
32232  /**
32233   * Gathers references to all of the DOM elements used by this controller.
32234   * @returns {{}}
32235   */
32236  function getElements () {
32237    var elements = {};
32238    var node = $element[0];
32239
32240    // gather tab bar elements
32241    elements.wrapper = node.querySelector('md-tabs-wrapper');
32242    elements.canvas  = elements.wrapper.querySelector('md-tabs-canvas');
32243    elements.paging  = elements.canvas.querySelector('md-pagination-wrapper');
32244    elements.inkBar  = elements.paging.querySelector('md-ink-bar');
32245
32246    elements.contents = node.querySelectorAll('md-tabs-content-wrapper > md-tab-content');
32247    elements.tabs    = elements.paging.querySelectorAll('md-tab-item');
32248    elements.dummies = elements.canvas.querySelectorAll('md-dummy-tab');
32249
32250    return elements;
32251  }
32252
32253  /**
32254   * Determines whether or not the left pagination arrow should be enabled.
32255   * @returns {boolean}
32256   */
32257  function canPageBack () {
32258    return ctrl.offsetLeft > 0;
32259  }
32260
32261  /**
32262   * Determines whether or not the right pagination arrow should be enabled.
32263   * @returns {*|boolean}
32264   */
32265  function canPageForward () {
32266    var elements = getElements();
32267    var lastTab = elements.tabs[ elements.tabs.length - 1 ];
32268    return lastTab && lastTab.offsetLeft + lastTab.offsetWidth > elements.canvas.clientWidth +
32269        ctrl.offsetLeft;
32270  }
32271
32272  /**
32273   * Determines if the UI should stretch the tabs to fill the available space.
32274   * @returns {*}
32275   */
32276  function shouldStretchTabs () {
32277    switch (ctrl.stretchTabs) {
32278      case 'always':
32279        return true;
32280      case 'never':
32281        return false;
32282      default:
32283        return !ctrl.shouldPaginate
32284            && $window.matchMedia('(max-width: 600px)').matches;
32285    }
32286  }
32287
32288  /**
32289   * Determines if the tabs should appear centered.
32290   * @returns {string|boolean}
32291   */
32292  function shouldCenterTabs () {
32293    return ctrl.centerTabs && !ctrl.shouldPaginate;
32294  }
32295
32296  /**
32297   * Determines if pagination is necessary to display the tabs within the available space.
32298   * @returns {boolean}
32299   */
32300  function shouldPaginate () {
32301    if (ctrl.noPagination || !loaded) return false;
32302    var canvasWidth = $element.prop('clientWidth');
32303
32304    angular.forEach(getElements().dummies, function (tab) {
32305      canvasWidth -= tab.offsetWidth;
32306    });
32307
32308    return canvasWidth < 0;
32309  }
32310
32311  /**
32312   * Finds the nearest tab index that is available.  This is primarily used for when the active
32313   * tab is removed.
32314   * @param newIndex
32315   * @returns {*}
32316   */
32317  function getNearestSafeIndex (newIndex) {
32318    if (newIndex === -1) return -1;
32319    var maxOffset = Math.max(ctrl.tabs.length - newIndex, newIndex),
32320        i, tab;
32321    for (i = 0; i <= maxOffset; i++) {
32322      tab = ctrl.tabs[ newIndex + i ];
32323      if (tab && (tab.scope.disabled !== true)) return tab.getIndex();
32324      tab = ctrl.tabs[ newIndex - i ];
32325      if (tab && (tab.scope.disabled !== true)) return tab.getIndex();
32326    }
32327    return newIndex;
32328  }
32329
32330  // Utility methods
32331
32332  /**
32333   * Defines a property using a getter and setter in order to trigger a change handler without
32334   * using `$watch` to observe changes.
32335   * @param key
32336   * @param handler
32337   * @param value
32338   */
32339  function defineProperty (key, handler, value) {
32340    Object.defineProperty(ctrl, key, {
32341      get: function () { return value; },
32342      set: function (newValue) {
32343        var oldValue = value;
32344        value        = newValue;
32345        handler && handler(newValue, oldValue);
32346      }
32347    });
32348  }
32349
32350  /**
32351   * Updates whether or not pagination should be displayed.
32352   */
32353  function updatePagination () {
32354    updatePagingWidth();
32355    ctrl.maxTabWidth = getMaxTabWidth();
32356    ctrl.shouldPaginate = shouldPaginate();
32357  }
32358
32359  /**
32360   * Sets or clears fixed width for md-pagination-wrapper depending on if tabs should stretch.
32361   */
32362  function updatePagingWidth() {
32363    var elements = getElements();
32364    if (shouldStretchTabs()) {
32365      angular.element(elements.paging).css('width', '');
32366    } else {
32367      angular.element(elements.paging).css('width', calcPagingWidth() + 'px');
32368    }
32369  }
32370
32371  /**
32372   * Calculates the width of the pagination wrapper by summing the widths of the dummy tabs.
32373   * @returns {number}
32374   */
32375  function calcPagingWidth () {
32376    return calcTabsWidth(getElements().dummies);
32377  }
32378
32379  function calcTabsWidth(tabs) {
32380    var width = 0;
32381
32382    angular.forEach(tabs, function (tab) {
32383      //-- Uses the larger value between `getBoundingClientRect().width` and `offsetWidth`.  This
32384      //   prevents `offsetWidth` value from being rounded down and causing wrapping issues, but
32385      //   also handles scenarios where `getBoundingClientRect()` is inaccurate (ie. tabs inside
32386      //   of a dialog)
32387      width += Math.max(tab.offsetWidth, tab.getBoundingClientRect().width);
32388    });
32389
32390    return Math.ceil(width);
32391  }
32392
32393  function getMaxTabWidth () {
32394    return $element.prop('clientWidth');
32395  }
32396
32397  /**
32398   * Re-orders the tabs and updates the selected and focus indexes to their new positions.
32399   * This is triggered by `tabDirective.js` when the user's tabs have been re-ordered.
32400   */
32401  function updateTabOrder () {
32402    var selectedItem   = ctrl.tabs[ ctrl.selectedIndex ],
32403        focusItem      = ctrl.tabs[ ctrl.focusIndex ];
32404    ctrl.tabs          = ctrl.tabs.sort(function (a, b) {
32405      return a.index - b.index;
32406    });
32407    ctrl.selectedIndex = ctrl.tabs.indexOf(selectedItem);
32408    ctrl.focusIndex    = ctrl.tabs.indexOf(focusItem);
32409  }
32410
32411  /**
32412   * This moves the selected or focus index left or right.  This is used by the keydown handler.
32413   * @param inc
32414   */
32415  function incrementIndex (inc, focus) {
32416    var newIndex,
32417        key   = focus ? 'focusIndex' : 'selectedIndex',
32418        index = ctrl[ key ];
32419    for (newIndex = index + inc;
32420         ctrl.tabs[ newIndex ] && ctrl.tabs[ newIndex ].scope.disabled;
32421         newIndex += inc) {}
32422    if (ctrl.tabs[ newIndex ]) {
32423      ctrl[ key ] = newIndex;
32424    }
32425  }
32426
32427  /**
32428   * This is used to forward focus to dummy elements.  This method is necessary to avoid animation
32429   * issues when attempting to focus an item that is out of view.
32430   */
32431  function redirectFocus () {
32432    getElements().dummies[ ctrl.focusIndex ].focus();
32433  }
32434
32435  /**
32436   * Forces the pagination to move the focused tab into view.
32437   */
32438  function adjustOffset (index) {
32439    var elements = getElements();
32440
32441    if (index == null) index = ctrl.focusIndex;
32442    if (!elements.tabs[ index ]) return;
32443    if (ctrl.shouldCenterTabs) return;
32444    var tab         = elements.tabs[ index ],
32445        left        = tab.offsetLeft,
32446        right       = tab.offsetWidth + left;
32447    ctrl.offsetLeft = Math.max(ctrl.offsetLeft, fixOffset(right - elements.canvas.clientWidth + 32 * 2));
32448    ctrl.offsetLeft = Math.min(ctrl.offsetLeft, fixOffset(left));
32449  }
32450
32451  /**
32452   * Iterates through all queued functions and clears the queue.  This is used for functions that
32453   * are called before the UI is ready, such as size calculations.
32454   */
32455  function processQueue () {
32456    queue.forEach(function (func) { $mdUtil.nextTick(func); });
32457    queue = [];
32458  }
32459
32460  /**
32461   * Determines if the tab content area is needed.
32462   */
32463  function updateHasContent () {
32464    var hasContent  = false;
32465    angular.forEach(ctrl.tabs, function (tab) {
32466      if (tab.template) hasContent = true;
32467    });
32468    ctrl.hasContent = hasContent;
32469  }
32470
32471  /**
32472   * Moves the indexes to their nearest valid values.
32473   */
32474  function refreshIndex () {
32475    ctrl.selectedIndex = getNearestSafeIndex(ctrl.selectedIndex);
32476    ctrl.focusIndex    = getNearestSafeIndex(ctrl.focusIndex);
32477  }
32478
32479  /**
32480   * Calculates the content height of the current tab.
32481   * @returns {*}
32482   */
32483  function updateHeightFromContent () {
32484    if (!ctrl.dynamicHeight) return $element.css('height', '');
32485    if (!ctrl.tabs.length) return queue.push(updateHeightFromContent);
32486
32487    var elements = getElements();
32488
32489    var tabContent    = elements.contents[ ctrl.selectedIndex ],
32490        contentHeight = tabContent ? tabContent.offsetHeight : 0,
32491        tabsHeight    = elements.wrapper.offsetHeight,
32492        newHeight     = contentHeight + tabsHeight,
32493        currentHeight = $element.prop('clientHeight');
32494
32495    if (currentHeight === newHeight) return;
32496
32497    // Adjusts calculations for when the buttons are bottom-aligned since this relies on absolute
32498    // positioning.  This should probably be cleaned up if a cleaner solution is possible.
32499    if ($element.attr('md-align-tabs') === 'bottom') {
32500      currentHeight -= tabsHeight;
32501      newHeight -= tabsHeight;
32502      // Need to include bottom border in these calculations
32503      if ($element.attr('md-border-bottom') !== undefined) ++currentHeight;
32504    }
32505
32506    // Lock during animation so the user can't change tabs
32507    locked = true;
32508
32509    var fromHeight = { height: currentHeight + 'px' },
32510        toHeight = { height: newHeight + 'px' };
32511
32512    // Set the height to the current, specific pixel height to fix a bug on iOS where the height
32513    // first animates to 0, then back to the proper height causing a visual glitch
32514    $element.css(fromHeight);
32515
32516    // Animate the height from the old to the new
32517    $animateCss($element, {
32518      from: fromHeight,
32519      to: toHeight,
32520      easing: 'cubic-bezier(0.35, 0, 0.25, 1)',
32521      duration: 0.5
32522    }).start().done(function () {
32523      // Then (to fix the same iOS issue as above), disable transitions and remove the specific
32524      // pixel height so the height can size with browser width/content changes, etc.
32525      $element.css({
32526        transition: 'none',
32527        height: ''
32528      });
32529
32530      // In the next tick, re-allow transitions (if we do it all at once, $element.css is "smart"
32531      // enough to batch it for us instead of doing it immediately, which undoes the original
32532      // transition: none)
32533      $mdUtil.nextTick(function() {
32534        $element.css('transition', '');
32535      });
32536
32537      // And unlock so tab changes can occur
32538      locked = false;
32539    });
32540  }
32541
32542  /**
32543   * Repositions the ink bar to the selected tab.
32544   * @returns {*}
32545   */
32546  function updateInkBarStyles () {
32547    var elements = getElements();
32548
32549    if (!elements.tabs[ ctrl.selectedIndex ]) {
32550      angular.element(elements.inkBar).css({ left: 'auto', right: 'auto' });
32551      return;
32552    }
32553
32554    if (!ctrl.tabs.length) return queue.push(ctrl.updateInkBarStyles);
32555    // if the element is not visible, we will not be able to calculate sizes until it is
32556    // we should treat that as a resize event rather than just updating the ink bar
32557    if (!$element.prop('offsetParent')) return handleResizeWhenVisible();
32558
32559    var index      = ctrl.selectedIndex,
32560        totalWidth = elements.paging.offsetWidth,
32561        tab        = elements.tabs[ index ],
32562        left       = tab.offsetLeft,
32563        right      = totalWidth - left - tab.offsetWidth;
32564
32565    if (ctrl.shouldCenterTabs) {
32566      // We need to use the same calculate process as in the pagination wrapper, to avoid rounding deviations.
32567      var tabWidth = calcTabsWidth(elements.tabs);
32568
32569      if (totalWidth > tabWidth) {
32570        $mdUtil.nextTick(updateInkBarStyles, false);
32571      }
32572    }
32573    updateInkBarClassName();
32574    angular.element(elements.inkBar).css({ left: left + 'px', right: right + 'px' });
32575  }
32576
32577  /**
32578   * Adds left/right classes so that the ink bar will animate properly.
32579   */
32580  function updateInkBarClassName () {
32581    var elements = getElements();
32582    var newIndex = ctrl.selectedIndex,
32583        oldIndex = ctrl.lastSelectedIndex,
32584        ink      = angular.element(elements.inkBar);
32585    if (!angular.isNumber(oldIndex)) return;
32586    ink
32587        .toggleClass('md-left', newIndex < oldIndex)
32588        .toggleClass('md-right', newIndex > oldIndex);
32589  }
32590
32591  /**
32592   * Takes an offset value and makes sure that it is within the min/max allowed values.
32593   * @param value
32594   * @returns {*}
32595   */
32596  function fixOffset (value) {
32597    var elements = getElements();
32598
32599    if (!elements.tabs.length || !ctrl.shouldPaginate) return 0;
32600    var lastTab    = elements.tabs[ elements.tabs.length - 1 ],
32601        totalWidth = lastTab.offsetLeft + lastTab.offsetWidth;
32602    value          = Math.max(0, value);
32603    value          = Math.min(totalWidth - elements.canvas.clientWidth, value);
32604    return value;
32605  }
32606
32607  /**
32608   * Attaches a ripple to the tab item element.
32609   * @param scope
32610   * @param element
32611   */
32612  function attachRipple (scope, element) {
32613    var elements = getElements();
32614    var options = { colorElement: angular.element(elements.inkBar) };
32615    $mdTabInkRipple.attach(scope, element, options);
32616  }
32617}
32618
32619})();
32620(function(){
32621"use strict";
32622
32623/**
32624 * @ngdoc directive
32625 * @name mdTabs
32626 * @module material.components.tabs
32627 *
32628 * @restrict E
32629 *
32630 * @description
32631 * The `<md-tabs>` directive serves as the container for 1..n `<md-tab>` child directives to
32632 * produces a Tabs components. In turn, the nested `<md-tab>` directive is used to specify a tab
32633 * label for the **header button** and a [optional] tab view content that will be associated with
32634 * each tab button.
32635 *
32636 * Below is the markup for its simplest usage:
32637 *
32638 *  <hljs lang="html">
32639 *  <md-tabs>
32640 *    <md-tab label="Tab #1"></md-tab>
32641 *    <md-tab label="Tab #2"></md-tab>
32642 *    <md-tab label="Tab #3"></md-tab>
32643 *  </md-tabs>
32644 *  </hljs>
32645 *
32646 * Tabs supports three (3) usage scenarios:
32647 *
32648 *  1. Tabs (buttons only)
32649 *  2. Tabs with internal view content
32650 *  3. Tabs with external view content
32651 *
32652 * **Tab-only** support is useful when tab buttons are used for custom navigation regardless of any
32653 * other components, content, or views.
32654 *
32655 * <i><b>Note:</b> If you are using the Tabs component for page-level navigation, please take a look
32656 * at the <a ng-href="./api/directive/mdNavBar">NavBar component</a> instead as it can handle this
32657 * case a bit more natively.</i>
32658 *
32659 * **Tabs with internal views** are the traditional usages where each tab has associated view
32660 * content and the view switching is managed internally by the Tabs component.
32661 *
32662 * **Tabs with external view content** is often useful when content associated with each tab is
32663 * independently managed and data-binding notifications announce tab selection changes.
32664 *
32665 * Additional features also include:
32666 *
32667 * *  Content can include any markup.
32668 * *  If a tab is disabled while active/selected, then the next tab will be auto-selected.
32669 *
32670 * ### Explanation of tab stretching
32671 *
32672 * 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.
32673 *
32674 * On mobile devices, tabs will be expanded to fill the available horizontal space.  When this happens, all tabs will become the same size.
32675 *
32676 * On desktops, by default, stretching will never occur.
32677 *
32678 * This default behavior can be overridden through the `md-stretch-tabs` attribute.  Here is a table showing when stretching will occur:
32679 *
32680 * `md-stretch-tabs` | mobile    | desktop
32681 * ------------------|-----------|--------
32682 * `auto`            | stretched | ---
32683 * `always`          | stretched | stretched
32684 * `never`           | ---       | ---
32685 *
32686 * @param {integer=} md-selected Index of the active/selected tab
32687 * @param {boolean=} md-no-ink If present, disables ink ripple effects.
32688 * @param {boolean=} md-no-ink-bar If present, disables the selection ink bar.
32689 * @param {string=}  md-align-tabs Attribute to indicate position of tab buttons: `bottom` or `top`; default is `top`
32690 * @param {string=} md-stretch-tabs Attribute to indicate whether or not to stretch tabs: `auto`, `always`, or `never`; default is `auto`
32691 * @param {boolean=} md-dynamic-height When enabled, the tab wrapper will resize based on the contents of the selected tab
32692 * @param {boolean=} md-border-bottom If present, shows a solid `1px` border between the tabs and their content
32693 * @param {boolean=} md-center-tabs When enabled, tabs will be centered provided there is no need for pagination
32694 * @param {boolean=} md-no-pagination When enabled, pagination will remain off
32695 * @param {boolean=} md-swipe-content When enabled, swipe gestures will be enabled for the content area to jump between tabs
32696 * @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.
32697 * @param {boolean=} md-autoselect When present, any tabs added after the initial load will be automatically selected
32698 * @param {boolean=} md-no-select-click When enabled, click events will not be fired when selecting tabs
32699 *
32700 * @usage
32701 * <hljs lang="html">
32702 * <md-tabs md-selected="selectedIndex" >
32703 *   <img ng-src="img/angular.png" class="centered">
32704 *   <md-tab
32705 *       ng-repeat="tab in tabs | orderBy:predicate:reversed"
32706 *       md-on-select="onTabSelected(tab)"
32707 *       md-on-deselect="announceDeselected(tab)"
32708 *       ng-disabled="tab.disabled">
32709 *     <md-tab-label>
32710 *       {{tab.title}}
32711 *       <img src="img/removeTab.png" ng-click="removeTab(tab)" class="delete">
32712 *     </md-tab-label>
32713 *     <md-tab-body>
32714 *       {{tab.content}}
32715 *     </md-tab-body>
32716 *   </md-tab>
32717 * </md-tabs>
32718 * </hljs>
32719 *
32720 */
32721MdTabs.$inject = ["$$mdSvgRegistry"];
32722angular
32723    .module('material.components.tabs')
32724    .directive('mdTabs', MdTabs);
32725
32726function MdTabs ($$mdSvgRegistry) {
32727  return {
32728    scope:            {
32729      selectedIndex: '=?mdSelected'
32730    },
32731    template:         function (element, attr) {
32732      attr[ "$mdTabsTemplate" ] = element.html();
32733      return '' +
32734        '<md-tabs-wrapper> ' +
32735          '<md-tab-data></md-tab-data> ' +
32736          '<md-prev-button ' +
32737              'tabindex="-1" ' +
32738              'role="button" ' +
32739              'aria-label="Previous Page" ' +
32740              'aria-disabled="{{!$mdTabsCtrl.canPageBack()}}" ' +
32741              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageBack() }" ' +
32742              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
32743              'ng-click="$mdTabsCtrl.previousPage()"> ' +
32744            '<md-icon md-svg-src="'+ $$mdSvgRegistry.mdTabsArrow +'"></md-icon> ' +
32745          '</md-prev-button> ' +
32746          '<md-next-button ' +
32747              'tabindex="-1" ' +
32748              'role="button" ' +
32749              'aria-label="Next Page" ' +
32750              'aria-disabled="{{!$mdTabsCtrl.canPageForward()}}" ' +
32751              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageForward() }" ' +
32752              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
32753              'ng-click="$mdTabsCtrl.nextPage()"> ' +
32754            '<md-icon md-svg-src="'+ $$mdSvgRegistry.mdTabsArrow +'"></md-icon> ' +
32755          '</md-next-button> ' +
32756          '<md-tabs-canvas ' +
32757              'tabindex="{{ $mdTabsCtrl.hasFocus ? -1 : 0 }}" ' +
32758              'aria-activedescendant="tab-item-{{$mdTabsCtrl.tabs[$mdTabsCtrl.focusIndex].id}}" ' +
32759              'ng-focus="$mdTabsCtrl.redirectFocus()" ' +
32760              'ng-class="{ ' +
32761                  '\'md-paginated\': $mdTabsCtrl.shouldPaginate, ' +
32762                  '\'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs ' +
32763              '}" ' +
32764              'ng-keydown="$mdTabsCtrl.keydown($event)" ' +
32765              'role="tablist"> ' +
32766            '<md-pagination-wrapper ' +
32767                'ng-class="{ \'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs }" ' +
32768                'md-tab-scroll="$mdTabsCtrl.scroll($event)"> ' +
32769              '<md-tab-item ' +
32770                  'tabindex="-1" ' +
32771                  'class="md-tab" ' +
32772                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
32773                  'role="tab" ' +
32774                  'aria-controls="tab-content-{{::tab.id}}" ' +
32775                  'aria-selected="{{tab.isActive()}}" ' +
32776                  'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
32777                  'ng-click="$mdTabsCtrl.select(tab.getIndex())" ' +
32778                  'ng-class="{ ' +
32779                      '\'md-active\':    tab.isActive(), ' +
32780                      '\'md-focused\':   tab.hasFocus(), ' +
32781                      '\'md-disabled\':  tab.scope.disabled ' +
32782                  '}" ' +
32783                  'ng-disabled="tab.scope.disabled" ' +
32784                  'md-swipe-left="$mdTabsCtrl.nextPage()" ' +
32785                  'md-swipe-right="$mdTabsCtrl.previousPage()" ' +
32786                  'md-tabs-template="::tab.label" ' +
32787                  'md-scope="::tab.parent"></md-tab-item> ' +
32788              '<md-ink-bar></md-ink-bar> ' +
32789            '</md-pagination-wrapper> ' +
32790            '<md-tabs-dummy-wrapper class="md-visually-hidden md-dummy-wrapper"> ' +
32791              '<md-dummy-tab ' +
32792                  'class="md-tab" ' +
32793                  'tabindex="-1" ' +
32794                  'id="tab-item-{{::tab.id}}" ' +
32795                  'role="tab" ' +
32796                  'aria-controls="tab-content-{{::tab.id}}" ' +
32797                  'aria-selected="{{tab.isActive()}}" ' +
32798                  'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
32799                  'ng-focus="$mdTabsCtrl.hasFocus = true" ' +
32800                  'ng-blur="$mdTabsCtrl.hasFocus = false" ' +
32801                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
32802                  'md-tabs-template="::tab.label" ' +
32803                  'md-scope="::tab.parent"></md-dummy-tab> ' +
32804            '</md-tabs-dummy-wrapper> ' +
32805          '</md-tabs-canvas> ' +
32806        '</md-tabs-wrapper> ' +
32807        '<md-tabs-content-wrapper ng-show="$mdTabsCtrl.hasContent && $mdTabsCtrl.selectedIndex >= 0" class="_md"> ' +
32808          '<md-tab-content ' +
32809              'id="tab-content-{{::tab.id}}" ' +
32810              'class="_md" ' +
32811              'role="tabpanel" ' +
32812              'aria-labelledby="tab-item-{{::tab.id}}" ' +
32813              'md-swipe-left="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(1)" ' +
32814              'md-swipe-right="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(-1)" ' +
32815              'ng-if="$mdTabsCtrl.hasContent" ' +
32816              'ng-repeat="(index, tab) in $mdTabsCtrl.tabs" ' +
32817              'ng-class="{ ' +
32818                '\'md-no-transition\': $mdTabsCtrl.lastSelectedIndex == null, ' +
32819                '\'md-active\':        tab.isActive(), ' +
32820                '\'md-left\':          tab.isLeft(), ' +
32821                '\'md-right\':         tab.isRight(), ' +
32822                '\'md-no-scroll\':     $mdTabsCtrl.dynamicHeight ' +
32823              '}"> ' +
32824            '<div ' +
32825                'md-tabs-template="::tab.template" ' +
32826                'md-connected-if="tab.isActive()" ' +
32827                'md-scope="::tab.parent" ' +
32828                'ng-if="$mdTabsCtrl.enableDisconnect || tab.shouldRender()"></div> ' +
32829          '</md-tab-content> ' +
32830        '</md-tabs-content-wrapper>';
32831    },
32832    controller:       'MdTabsController',
32833    controllerAs:     '$mdTabsCtrl',
32834    bindToController: true
32835  };
32836}
32837
32838})();
32839(function(){
32840"use strict";
32841
32842
32843MdTabsDummyWrapper.$inject = ["$mdUtil", "$window"];angular
32844  .module('material.components.tabs')
32845  .directive('mdTabsDummyWrapper', MdTabsDummyWrapper);
32846
32847/**
32848 * @private
32849 *
32850 * @param $mdUtil
32851 * @param $window
32852 * @returns {{require: string, link: link}}
32853 * @constructor
32854 *
32855 * @ngInject
32856 */
32857function MdTabsDummyWrapper ($mdUtil, $window) {
32858  return {
32859    require: '^?mdTabs',
32860    link:    function link (scope, element, attr, ctrl) {
32861      if (!ctrl) return;
32862
32863      var observer;
32864      var disconnect;
32865
32866      var mutationCallback = function() {
32867        ctrl.updatePagination();
32868        ctrl.updateInkBarStyles();
32869      };
32870
32871      if('MutationObserver' in $window) {
32872        var config = {
32873          childList: true,
32874          subtree: true,
32875          // Per https://bugzilla.mozilla.org/show_bug.cgi?id=1138368, browsers will not fire
32876          // the childList mutation, once a <span> element's innerText changes.
32877          // The characterData of the <span> element will change.
32878          characterData: true
32879        };
32880
32881        observer = new MutationObserver(mutationCallback);
32882        observer.observe(element[0], config);
32883        disconnect = observer.disconnect.bind(observer);
32884      } else {
32885        var debounced = $mdUtil.debounce(mutationCallback, 15, null, false);
32886        
32887        element.on('DOMSubtreeModified', debounced);
32888        disconnect = element.off.bind(element, 'DOMSubtreeModified', debounced);
32889      }
32890
32891      // Disconnect the observer
32892      scope.$on('$destroy', function() {
32893        disconnect();
32894      });
32895    }
32896  };
32897}
32898
32899})();
32900(function(){
32901"use strict";
32902
32903
32904MdTabsTemplate.$inject = ["$compile", "$mdUtil"];angular
32905    .module('material.components.tabs')
32906    .directive('mdTabsTemplate', MdTabsTemplate);
32907
32908function MdTabsTemplate ($compile, $mdUtil) {
32909  return {
32910    restrict: 'A',
32911    link:     link,
32912    scope:    {
32913      template:     '=mdTabsTemplate',
32914      connected:    '=?mdConnectedIf',
32915      compileScope: '=mdScope'
32916    },
32917    require:  '^?mdTabs'
32918  };
32919  function link (scope, element, attr, ctrl) {
32920    if (!ctrl) return;
32921
32922    var compileScope = ctrl.enableDisconnect ? scope.compileScope.$new() : scope.compileScope;
32923
32924    element.html(scope.template);
32925    $compile(element.contents())(compileScope);
32926
32927    return $mdUtil.nextTick(handleScope);
32928
32929    function handleScope () {
32930      scope.$watch('connected', function (value) { value === false ? disconnect() : reconnect(); });
32931      scope.$on('$destroy', reconnect);
32932    }
32933
32934    function disconnect () {
32935      if (ctrl.enableDisconnect) $mdUtil.disconnectScope(compileScope);
32936    }
32937
32938    function reconnect () {
32939      if (ctrl.enableDisconnect) $mdUtil.reconnectScope(compileScope);
32940    }
32941  }
32942}
32943
32944})();
32945(function(){ 
32946angular.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:hover, .md-autocomplete-suggestions-container.md-THEME_NAME-theme li.selected {      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}}'; }  md-bottom-sheet.md-THEME_NAME-theme .md-subheader {    color: '{{foreground-1}}'; }.md-button.md-THEME_NAME-theme:not([disabled]):hover {  background-color: '{{background-500-0.2}}'; }.md-button.md-THEME_NAME-theme:not([disabled]).md-focused {  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 {  background-color: '{{accent-color}}';  color: '{{accent-contrast}}'; }  .md-button.md-THEME_NAME-theme.md-fab md-icon {    color: '{{accent-contrast}}'; }  .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover {    background-color: '{{accent-A700}}'; }  .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused {    background-color: '{{accent-A700}}'; }.md-button.md-THEME_NAME-theme.md-primary {  color: '{{primary-color}}'; }  .md-button.md-THEME_NAME-theme.md-primary.md-raised, .md-button.md-THEME_NAME-theme.md-primary.md-fab {    color: '{{primary-contrast}}';    background-color: '{{primary-color}}'; }    .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]) md-icon {      color: '{{primary-contrast}}'; }    .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]):hover {      background-color: '{{primary-600}}'; }    .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]).md-focused {      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]):hover {    background-color: '{{accent-A700}}'; }  .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused {    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-raised, .md-button.md-THEME_NAME-theme.md-warn.md-fab {    color: '{{warn-contrast}}';    background-color: '{{warn-color}}'; }    .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]) md-icon {      color: '{{warn-contrast}}'; }    .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]):hover {      background-color: '{{warn-600}}'; }    .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]).md-focused {      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-raised, .md-button.md-THEME_NAME-theme.md-accent.md-fab {    color: '{{accent-contrast}}';    background-color: '{{accent-color}}'; }    .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]) md-icon {      color: '{{accent-contrast}}'; }    .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]):hover {      background-color: '{{accent-A700}}'; }    .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]).md-focused {      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[disabled], .md-button.md-THEME_NAME-theme.md-raised[disabled], .md-button.md-THEME_NAME-theme.md-fab[disabled], .md-button.md-THEME_NAME-theme.md-accent[disabled], .md-button.md-THEME_NAME-theme.md-warn[disabled] {  color: '{{foreground-3}}';  cursor: default; }  .md-button.md-THEME_NAME-theme[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-raised[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-fab[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-accent[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-warn[disabled] md-icon {    color: '{{foreground-3}}'; }.md-button.md-THEME_NAME-theme.md-raised[disabled], .md-button.md-THEME_NAME-theme.md-fab[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 {  color: '{{accent-color}}'; }  ._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 {    color: '{{foreground-2}}'; }  md-card.md-THEME_NAME-theme md-card-title md-card-title-text:not(:only-child) .md-subhead {    color: '{{foreground-2}}'; }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::-webkit-input-placeholder {      color: '{{foreground-3}}'; }    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::-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-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-content.md-THEME_NAME-theme {  color: '{{foreground-1}}';  background-color: '{{background-default}}'; }/** Theme styles for mdCalendar. */.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}}'; }/** Theme styles for mdDatepicker. */.md-THEME_NAME-theme .md-datepicker-input {  color: '{{foreground-1}}'; }  .md-THEME_NAME-theme .md-datepicker-input::-webkit-input-placeholder {    color: '{{foreground-3}}'; }  .md-THEME_NAME-theme .md-datepicker-input:-moz-placeholder {    color: '{{foreground-3}}'; }  .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-warn .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused {      border-bottom-color: '{{warn-A700}}'; }  .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-invalid {    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-3}}'; }.md-THEME_NAME-theme .md-datepicker-triangle-button:hover .md-datepicker-expand-triangle {  border-top-color: '{{foreground-2}}'; }.md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon {  color: '{{primary-color}}'; }.md-THEME_NAME-theme .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-accent .md-THEME_NAME-theme .md-datepicker-open .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 {    border-top-color: '{{foreground-4}}'; }md-divider.md-THEME_NAME-theme {  border-top-color: '{{foreground-4}}'; }.layout-row > md-divider.md-THEME_NAME-theme,.layout-xs-row > md-divider.md-THEME_NAME-theme, .layout-gt-xs-row > md-divider.md-THEME_NAME-theme,.layout-sm-row > md-divider.md-THEME_NAME-theme, .layout-gt-sm-row > md-divider.md-THEME_NAME-theme,.layout-md-row > md-divider.md-THEME_NAME-theme, .layout-gt-md-row > md-divider.md-THEME_NAME-theme,.layout-lg-row > md-divider.md-THEME_NAME-theme, .layout-gt-lg-row > md-divider.md-THEME_NAME-theme,.layout-xl-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::-webkit-input-placeholder {    color: '{{foreground-3}}'; }  md-input-container.md-THEME_NAME-theme .md-input:-moz-placeholder {    color: '{{foreground-3}}'; }  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 label,md-input-container.md-THEME_NAME-theme .md-placeholder {  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-messages-animation, md-input-container.md-THEME_NAME-theme .md-input-message-animation {  color: '{{warn-A700}}'; }  md-input-container.md-THEME_NAME-theme .md-input-messages-animation .md-char-counter, md-input-container.md-THEME_NAME-theme .md-input-message-animation .md-char-counter {    color: '{{foreground-1}}'; }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 label,md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input-message-animation,md-input-container.md-THEME_NAME-theme.md-input-invalid .md-char-counter {  color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme .md-input[disabled],[disabled] md-input-container.md-THEME_NAME-theme .md-input {  border-bottom-color: transparent;  color: '{{foreground-3}}';  background-image: linear-gradient(to right, \"{{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] {      color: '{{background-A200-0.25}}'; }      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:hover,md-menu-bar.md-THEME_NAME-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:focus {  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-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-panel {  background-color: '{{background-900-0.0}}'; }  .md-panel._md-panel-backdrop.md-THEME_NAME-theme {    background-color: '{{background-900-1.0}}'; }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-group.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-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-on {  background-color: '{{primary-color-0.87}}'; }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,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 {  border-color: '{{primary-color-0.87}}'; }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,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 {  color: '{{primary-color-0.87}}'; }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,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 {  color: '{{primary-600}}'; }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,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 {  background-color: '{{warn-color-0.87}}'; }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,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 {  border-color: '{{warn-color-0.87}}'; }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,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 {  color: '{{warn-color-0.87}}'; }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,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 {  color: '{{warn-600}}'; }md-radio-group.md-THEME_NAME-theme[disabled],md-radio-button.md-THEME_NAME-theme[disabled] {  color: '{{foreground-3}}'; }  md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-off,  md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-off {    border-color: '{{foreground-3}}'; }  md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-on,  md-radio-button.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-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-THEME_NAME-theme .md-checked:not([disabled]).md-primary .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-primary .md-checked .md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-primary .md-container:before {  background-color: '{{primary-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty).md-warn .md-checked .md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-warn .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 {  color: '{{primary-color}}'; }  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(to right, \"{{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-value {  color: '{{foreground-3}}'; }  md-select.md-THEME_NAME-theme[disabled] .md-select-value.md-select-placeholder {    color: '{{foreground-3}}'; }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-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]) {    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]) {    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]) {    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]) {    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]) {    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]) {    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-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-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-tooltip.md-THEME_NAME-theme {  color: '{{background-700-contrast}}'; }  md-tooltip.md-THEME_NAME-theme .md-content {    background-color: '{{background-700}}'; }/*  Only used with Theme processes */html.md-THEME_NAME-theme, body.md-THEME_NAME-theme {  color: '{{foreground-1}}';  background-color: '{{background-color}}'; }"); 
32947})();
32948
32949
32950})(window, window.angular);;window.ngMaterial={version:{full: "1.1.1"}};