PageSourceSearch

https://www.aslogliastra.it/wp-content/themes/asl/static/js/angular-material.min.js

js aslogliastra.it collected 2026-10-05 20:02:51 UTC 1,242,507 bytes, 36,404 lines download raw bytes

vendor: 70,541 bytes, lines 1-2382
1/*!
2 * AngularJS Material Design
3 * https://github.com/angular/material
4 * @license MIT
5 * v1.1.4-master-baa869a
6 */
7(function( window, angular, undefined ){
8"use strict";
9
10(function(){
11"use strict";
12
13angular.module('ngMaterial', ["ng","ngAnimate","ngAria","material.core","material.core.gestures","material.core.interaction","material.core.layout","material.core.meta","material.core.theming.palette","material.core.theming","material.core.animate","material.components.autocomplete","material.components.backdrop","material.components.bottomSheet","material.components.button","material.components.card","material.components.checkbox","material.components.chips","material.components.colors","material.components.content","material.components.datepicker","material.components.dialog","material.components.divider","material.components.fabActions","material.components.fabShared","material.components.fabSpeedDial","material.components.fabToolbar","material.components.gridList","material.components.icon","material.components.input","material.components.list","material.components.menu","material.components.menuBar","material.components.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.truncate","material.components.virtualRepeat","material.components.whiteframe"]);
14})();
15(function(){
16"use strict";
17
18/**
19 * Initialization function that validates environment
20 * requirements.
21 */
22DetectNgTouch.$inject = ["$log", "$injector"];
23MdCoreConfigure.$inject = ["$provide", "$mdThemingProvider"];
24rAFDecorator.$inject = ["$delegate"];
25qDecorator.$inject = ["$delegate"];
26angular
27  .module('material.core', [
28    'ngAnimate',
29    'material.core.animate',
30    'material.core.layout',
31    'material.core.interaction',
32    'material.core.gestures',
33    'material.core.theming'
34  ])
35  .config(MdCoreConfigure)
36  .run(DetectNgTouch);
37
38
39/**
40 * Detect if the ng-Touch module is also being used.
41 * Warn if detected.
42 * @ngInject
43 */
44function DetectNgTouch($log, $injector) {
45  if ( $injector.has('$swipe') ) {
46    var msg = "" +
47      "You are using the ngTouch module. \n" +
48      "AngularJS Material already has mobile click, tap, and swipe support... \n" +
49      "ngTouch is not supported with AngularJS Material!";
50    $log.warn(msg);
51  }
52}
53
54/**
55 * @ngInject
56 */
57function MdCoreConfigure($provide, $mdThemingProvider) {
58
59  $provide.decorator('$$rAF', ['$delegate', rAFDecorator]);
60  $provide.decorator('$q', ['$delegate', qDecorator]);
61
62  $mdThemingProvider.theme('default')
63    .primaryPalette('indigo')
64    .accentPalette('pink')
65    .warnPalette('deep-orange')
66    .backgroundPalette('grey');
67}
68
69/**
70 * @ngInject
71 */
72function rAFDecorator($delegate) {
73  /**
74   * Use this to throttle events that come in often.
75   * The throttled function will always use the *last* invocation before the
76   * coming frame.
77   *
78   * For example, window resize events that fire many times a second:
79   * If we set to use an raf-throttled callback on window resize, then
80   * our callback will only be fired once per frame, with the last resize
81   * event that happened before that frame.
82   *
83   * @param {function} callback function to debounce
84   */
85  $delegate.throttle = function(cb) {
86    var queuedArgs, alreadyQueued, queueCb, context;
87    return function debounced() {
88      queuedArgs = arguments;
89      context = this;
90      queueCb = cb;
91      if (!alreadyQueued) {
92        alreadyQueued = true;
93        $delegate(function() {
94          queueCb.apply(context, Array.prototype.slice.call(queuedArgs));
95          alreadyQueued = false;
96        });
97      }
98    };
99  };
100  return $delegate;
101}
102
103/**
104 * @ngInject
105 */
106function qDecorator($delegate) {
107  /**
108   * Adds a shim for $q.resolve for AngularJS version that don't have it,
109   * so we don't have to think about it.
110   *
111   * via https://github.com/angular/angular.js/pull/11987
112   */
113
114  // TODO(crisbeto): this won't be necessary once we drop AngularJS 1.3
115  if (!$delegate.resolve) {
116    $delegate.resolve = $delegate.when;
117  }
118  return $delegate;
119}
120
121})();
122(function(){
123"use strict";
124
125
126MdAutofocusDirective.$inject = ["$parse"];angular.module('material.core')
127  .directive('mdAutofocus', MdAutofocusDirective)
128
129  // Support the deprecated md-auto-focus and md-sidenav-focus as well
130  .directive('mdAutoFocus', MdAutofocusDirective)
131  .directive('mdSidenavFocus', MdAutofocusDirective);
132
133/**
134 * @ngdoc directive
135 * @name mdAutofocus
136 * @module material.core.util
137 *
138 * @description
139 *
140 * `[md-autofocus]` provides an optional way to identify the focused element when a `$mdDialog`,
141 * `$mdBottomSheet`, `$mdMenu` or `$mdSidenav` opens or upon page load for input-like elements.
142 *
143 * When one of these opens, it will find the first nested element with the `[md-autofocus]`
144 * attribute directive and optional expression. An expression may be specified as the directive
145 * value to enable conditional activation of the autofocus.
146 *
147 * @usage
148 *
149 * ### Dialog
150 * <hljs lang="html">
151 * <md-dialog>
152 *   <form>
153 *     <md-input-container>
154 *       <label for="testInput">Label</label>
155 *       <input id="testInput" type="text" md-autofocus>
156 *     </md-input-container>
157 *   </form>
158 * </md-dialog>
159 * </hljs>
160 *
161 * ### Bottomsheet
162 * <hljs lang="html">
163 * <md-bottom-sheet class="md-list md-has-header">
164 *  <md-subheader>Comment Actions</md-subheader>
165 *  <md-list>
166 *    <md-list-item ng-repeat="item in items">
167 *
168 *      <md-button md-autofocus="$index == 2">
169 *        <md-icon md-svg-src="{{item.icon}}"></md-icon>
170 *        <span class="md-inline-list-icon-label">{{ item.name }}</span>
171 *      </md-button>
172 *
173 *    </md-list-item>
174 *  </md-list>
175 * </md-bottom-sheet>
176 * </hljs>
177 *
178 * ### Autocomplete
179 * <hljs lang="html">
180 *   <md-autocomplete
181 *       md-autofocus
182 *       md-selected-item="selectedItem"
183 *       md-search-text="searchText"
184 *       md-items="item in getMatches(searchText)"
185 *       md-item-text="item.display">
186 *     <span md-highlight-text="searchText">{{item.display}}</span>
187 *   </md-autocomplete>
188 * </hljs>
189 *
190 * ### Sidenav
191 * <hljs lang="html">
192 * <div layout="row" ng-controller="MyController">
193 *   <md-sidenav md-component-id="left" class="md-sidenav-left">
194 *     Left Nav!
195 *   </md-sidenav>
196 *
197 *   <md-content>
198 *     Center Content
199 *     <md-button ng-click="openLeftMenu()">
200 *       Open Left Menu
201 *     </md-button>
202 *   </md-content>
203 *
204 *   <md-sidenav md-component-id="right"
205 *     md-is-locked-open="$mdMedia('min-width: 333px')"
206 *     class="md-sidenav-right">
207 *     <form>
208 *       <md-input-container>
209 *         <label for="testInput">Test input</label>
210 *         <input id="testInput" type="text"
211 *                ng-model="data" md-autofocus>
212 *       </md-input-container>
213 *     </form>
214 *   </md-sidenav>
215 * </div>
216 * </hljs>
217 **/
218function MdAutofocusDirective($parse) {
219  return {
220    restrict: 'A',
221    link: {
222      pre: preLink
223    }
224  };
225
226  function preLink(scope, element, attr) {
227    var attrExp = attr.mdAutoFocus || attr.mdAutofocus || attr.mdSidenavFocus;
228
229    // Initially update the expression by manually parsing the expression as per $watch source.
230    updateExpression($parse(attrExp)(scope));
231
232    // Only watch the expression if it is not empty.
233    if (attrExp) {
234      scope.$watch(attrExp, updateExpression);
235    }
236
237    /**
238     * Updates the autofocus class which is used to determine whether the attribute
239     * expression evaluates to true or false.
240     * @param {string|boolean} value Attribute Value
241     */
242    function updateExpression(value) {
243
244      // Rather than passing undefined to the jqLite toggle class function we explicitly set the
245      // value to true. Otherwise the class will be just toggled instead of being forced.
246      if (angular.isUndefined(value)) {
247        value = true;
248      }
249
250      element.toggleClass('md-autofocus', !!value);
251    }
252  }
253
254}
255
256})();
257(function(){
258"use strict";
259
260/**
261 * @ngdoc module
262 * @name material.core.colorUtil
263 * @description
264 * Color Util
265 */
266angular
267  .module('material.core')
268  .factory('$mdColorUtil', ColorUtilFactory);
269
270function ColorUtilFactory() {
271  /**
272   * Converts hex value to RGBA string
273   * @param color {string}
274   * @returns {string}
275   */
276  function hexToRgba (color) {
277    var hex   = color[ 0 ] === '#' ? color.substr(1) : color,
278      dig   = hex.length / 3,
279      red   = hex.substr(0, dig),
280      green = hex.substr(dig, dig),
281      blue  = hex.substr(dig * 2);
282    if (dig === 1) {
283      red += red;
284      green += green;
285      blue += blue;
286    }
287    return 'rgba(' + parseInt(red, 16) + ',' + parseInt(green, 16) + ',' + parseInt(blue, 16) + ',0.1)';
288  }
289
290  /**
291   * Converts rgba value to hex string
292   * @param color {string}
293   * @returns {string}
294   */
295  function rgbaToHex(color) {
296    color = color.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
297
298    var hex = (color && color.length === 4) ? "#" +
299    ("0" + parseInt(color[1],10).toString(16)).slice(-2) +
300    ("0" + parseInt(color[2],10).toString(16)).slice(-2) +
301    ("0" + parseInt(color[3],10).toString(16)).slice(-2) : '';
302
303    return hex.toUpperCase();
304  }
305
306  /**
307   * Converts an RGB color to RGBA
308   * @param color {string}
309   * @returns {string}
310   */
311  function rgbToRgba (color) {
312    return color.replace(')', ', 0.1)').replace('(', 'a(');
313  }
314
315  /**
316   * Converts an RGBA color to RGB
317   * @param color {string}
318   * @returns {string}
319   */
320  function rgbaToRgb (color) {
321    return color
322      ? color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')')
323      : 'rgb(0,0,0)';
324  }
325
326  return {
327    rgbaToHex: rgbaToHex,
328    hexToRgba: hexToRgba,
329    rgbToRgba: rgbToRgba,
330    rgbaToRgb: rgbaToRgb
331  };
332}
333
334})();
335(function(){
336"use strict";
337
338angular.module('material.core')
339.factory('$mdConstant', MdConstantFactory);
340
341/**
342 * Factory function that creates the grab-bag $mdConstant service.
343 * @ngInject
344 */
345function MdConstantFactory() {
346
347  var prefixTestEl = document.createElement('div');
348  var vendorPrefix = getVendorPrefix(prefixTestEl);
349  var isWebkit = /webkit/i.test(vendorPrefix);
350  var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g;
351
352  function vendorProperty(name) {
353    // Add a dash between the prefix and name, to be able to transform the string into camelcase.
354    var prefixedName = vendorPrefix + '-' + name;
355    var ucPrefix = camelCase(prefixedName);
356    var lcPrefix = ucPrefix.charAt(0).toLowerCase() + ucPrefix.substring(1);
357
358    return hasStyleProperty(prefixTestEl, name)     ? name     :       // The current browser supports the un-prefixed property
359           hasStyleProperty(prefixTestEl, ucPrefix) ? ucPrefix :       // The current browser only supports the prefixed property.
360           hasStyleProperty(prefixTestEl, lcPrefix) ? lcPrefix : name; // Some browsers are only supporting the prefix in lowercase.
361  }
362
363  function hasStyleProperty(testElement, property) {
364    return angular.isDefined(testElement.style[property]);
365  }
366
367  function camelCase(input) {
368    return input.replace(SPECIAL_CHARS_REGEXP, function(matches, separator, letter, offset) {
369      return offset ? letter.toUpperCase() : letter;
370    });
371  }
372
373  function getVendorPrefix(testElement) {
374    var prop, match;
375    var vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/;
376
377    for (prop in testElement.style) {
378      if (match = vendorRegex.exec(prop)) {
379        return match[0];
380      }
381    }
382  }
383
384  var self = {
385    isInputKey : function(e) { return (e.keyCode >= 31 && e.keyCode <= 90); },
386    isNumPadKey : function(e) { return (3 === e.location && e.keyCode >= 97 && e.keyCode <= 105); },
387    isMetaKey: function(e) { return (e.keyCode >= 91 && e.keyCode <= 93); },
388    isFnLockKey: function(e) { return (e.keyCode >= 112 && e.keyCode <= 145); },
389    isNavigationKey : function(e) {
390      var kc = self.KEY_CODE, NAVIGATION_KEYS =  [kc.SPACE, kc.ENTER, kc.UP_ARROW, kc.DOWN_ARROW];
391      return (NAVIGATION_KEYS.indexOf(e.keyCode) != -1);
392    },
393    hasModifierKey: function(e) {
394      return e.ctrlKey || e.metaKey || e.altKey;
395    },
396
397    /**
398     * Maximum size, in pixels, that can be explicitly set to an element. The actual value varies
399     * between browsers, but IE11 has the very lowest size at a mere 1,533,917px. Ideally we could
400     * compute this value, but Firefox always reports an element to have a size of zero if it
401     * goes over the max, meaning that we'd have to binary search for the value.
402     */
403    ELEMENT_MAX_PIXELS: 1533917,
404
405    /**
406     * Priority for a directive that should run before the directives from ngAria.
407     */
408    BEFORE_NG_ARIA: 210,
409
410    /**
411     * Common Keyboard actions and their associated keycode.
412     */
413    KEY_CODE: {
414      COMMA: 188,
415      SEMICOLON : 186,
416      ENTER: 13,
417      ESCAPE: 27,
418      SPACE: 32,
419      PAGE_UP: 33,
420      PAGE_DOWN: 34,
421      END: 35,
422      HOME: 36,
423      LEFT_ARROW : 37,
424      UP_ARROW : 38,
425      RIGHT_ARROW : 39,
426      DOWN_ARROW : 40,
427      TAB : 9,
428      BACKSPACE: 8,
429      DELETE: 46
430    },
431
432    /**
433     * Vendor prefixed CSS properties to be used to support the given functionality in older browsers
434     * as well.
435     */
436    CSS: {
437      /* Constants */
438      TRANSITIONEND: 'transitionend' + (isWebkit ? ' webkitTransitionEnd' : ''),
439      ANIMATIONEND: 'animationend' + (isWebkit ? ' webkitAnimationEnd' : ''),
440
441      TRANSFORM: vendorProperty('transform'),
442      TRANSFORM_ORIGIN: vendorProperty('transformOrigin'),
443      TRANSITION: vendorProperty('transition'),
444      TRANSITION_DURATION: vendorProperty('transitionDuration'),
445      ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'),
446      ANIMATION_DURATION: vendorProperty('animationDuration'),
447      ANIMATION_NAME: vendorProperty('animationName'),
448      ANIMATION_TIMING: vendorProperty('animationTimingFunction'),
449      ANIMATION_DIRECTION: vendorProperty('animationDirection')
450    },
451
452    /**
453     * As defined in core/style/variables.scss
454     *
455     * $layout-breakpoint-xs:     600px !default;
456     * $layout-breakpoint-sm:     960px !default;
457     * $layout-breakpoint-md:     1280px !default;
458     * $layout-breakpoint-lg:     1920px !default;
459     *
460     */
461    MEDIA: {
462      'xs'        : '(max-width: 599px)'                         ,
463      'gt-xs'     : '(min-width: 600px)'                         ,
464      'sm'        : '(min-width: 600px) and (max-width: 959px)'  ,
465      'gt-sm'     : '(min-width: 960px)'                         ,
466      'md'        : '(min-width: 960px) and (max-width: 1279px)' ,
467      'gt-md'     : '(min-width: 1280px)'                        ,
468      'lg'        : '(min-width: 1280px) and (max-width: 1919px)',
469      'gt-lg'     : '(min-width: 1920px)'                        ,
470      'xl'        : '(min-width: 1920px)'                        ,
471      'landscape' : '(orientation: landscape)'                   ,
472      'portrait'  : '(orientation: portrait)'                    ,
473      'print' : 'print'
474    },
475
476    MEDIA_PRIORITY: [
477      'xl',
478      'gt-lg',
479      'lg',
480      'gt-md',
481      'md',
482      'gt-sm',
483      'sm',
484      'gt-xs',
485      'xs',
486      'landscape',
487      'portrait',
488      'print'
489    ]
490  };
491
492  return self;
493}
494
495})();
496(function(){
497"use strict";
498
499  angular
500    .module('material.core')
501    .config( ["$provide", function($provide){
502       $provide.decorator('$mdUtil', ['$delegate', function ($delegate){
503           /**
504            * Inject the iterator facade to easily support iteration and accessors
505            * @see iterator below
506            */
507           $delegate.iterator = MdIterator;
508
509           return $delegate;
510         }
511       ]);
512     }]);
513
514  /**
515   * iterator is a list facade to easily support iteration and accessors
516   *
517   * @param items Array list which this iterator will enumerate
518   * @param reloop Boolean enables iterator to consider the list as an endless reloop
519   */
520  function MdIterator(items, reloop) {
521    var trueFn = function() { return true; };
522
523    if (items && !angular.isArray(items)) {
524      items = Array.prototype.slice.call(items);
525    }
526
527    reloop = !!reloop;
528    var _items = items || [ ];
529
530    // Published API
531    return {
532      items: getItems,
533      count: count,
534
535      inRange: inRange,
536      contains: contains,
537      indexOf: indexOf,
538      itemAt: itemAt,
539
540      findBy: findBy,
541
542      add: add,
543      remove: remove,
544
545      first: first,
546      last: last,
547      next: angular.bind(null, findSubsequentItem, false),
548      previous: angular.bind(null, findSubsequentItem, true),
549
550      hasPrevious: hasPrevious,
551      hasNext: hasNext
552
553    };
554
555    /**
556     * Publish copy of the enumerable set
557     * @returns {Array|*}
558     */
559    function getItems() {
560      return [].concat(_items);
561    }
562
563    /**
564     * Determine length of the list
565     * @returns {Array.length|*|number}
566     */
567    function count() {
568      return _items.length;
569    }
570
571    /**
572     * Is the index specified valid
573     * @param index
574     * @returns {Array.length|*|number|boolean}
575     */
576    function inRange(index) {
577      return _items.length && ( index > -1 ) && (index < _items.length );
578    }
579
580    /**
581     * Can the iterator proceed to the next item in the list; relative to
582     * the specified item.
583     *
584     * @param item
585     * @returns {Array.length|*|number|boolean}
586     */
587    function hasNext(item) {
588      return item ? inRange(indexOf(item) + 1) : false;
589    }
590
591    /**
592     * Can the iterator proceed to the previous item in the list; relative to
593     * the specified item.
594     *
595     * @param item
596     * @returns {Array.length|*|number|boolean}
597     */
598    function hasPrevious(item) {
599      return item ? inRange(indexOf(item) - 1) : false;
600    }
601
602    /**
603     * Get item at specified index/position
604     * @param index
605     * @returns {*}
606     */
607    function itemAt(index) {
608      return inRange(index) ? _items[index] : null;
609    }
610
611    /**
612     * Find all elements matching the key/value pair
613     * otherwise return null
614     *
615     * @param val
616     * @param key
617     *
618     * @return array
619     */
620    function findBy(key, val) {
621      return _items.filter(function(item) {
622        return item[key] === val;
623      });
624    }
625
626    /**
627     * Add item to list
628     * @param item
629     * @param index
630     * @returns {*}
631     */
632    function add(item, index) {
633      if ( !item ) return -1;
634
635      if (!angular.isNumber(index)) {
636        index = _items.length;
637      }
638
639      _items.splice(index, 0, item);
640
641      return indexOf(item);
642    }
643
644    /**
645     * Remove item from list...
646     * @param item
647     */
648    function remove(item) {
649      if ( contains(item) ){
650        _items.splice(indexOf(item), 1);
651      }
652    }
653
654    /**
655     * Get the zero-based index of the target item
656     * @param item
657     * @returns {*}
658     */
659    function indexOf(item) {
660      return _items.indexOf(item);
661    }
662
663    /**
664     * Boolean existence check
665     * @param item
666     * @returns {boolean}
667     */
668    function contains(item) {
669      return item && (indexOf(item) > -1);
670    }
671
672    /**
673     * Return first item in the list
674     * @returns {*}
675     */
676    function first() {
677      return _items.length ? _items[0] : null;
678    }
679
680    /**
681     * Return last item in the list...
682     * @returns {*}
683     */
684    function last() {
685      return _items.length ? _items[_items.length - 1] : null;
686    }
687
688    /**
689     * Find the next item. If reloop is true and at the end of the list, it will go back to the
690     * first item. If given, the `validate` callback will be used to determine whether the next item
691     * is valid. If not valid, it will try to find the next item again.
692     *
693     * @param {boolean} backwards Specifies the direction of searching (forwards/backwards)
694     * @param {*} item The item whose subsequent item we are looking for
695     * @param {Function=} validate The `validate` function
696     * @param {integer=} limit The recursion limit
697     *
698     * @returns {*} The subsequent item or null
699     */
700    function findSubsequentItem(backwards, item, validate, limit) {
701      validate = validate || trueFn;
702
703      var curIndex = indexOf(item);
704      while (true) {
705        if (!inRange(curIndex)) return null;
706
707        var nextIndex = curIndex + (backwards ? -1 : 1);
708        var foundItem = null;
709        if (inRange(nextIndex)) {
710          foundItem = _items[nextIndex];
711        } else if (reloop) {
712          foundItem = backwards ? last() : first();
713          nextIndex = indexOf(foundItem);
714        }
715
716        if ((foundItem === null) || (nextIndex === limit)) return null;
717        if (validate(foundItem)) return foundItem;
718
719        if (angular.isUndefined(limit)) limit = nextIndex;
720
721        curIndex = nextIndex;
722      }
723    }
724  }
725
726
727})();
728(function(){
729"use strict";
730
731
732mdMediaFactory.$inject = ["$mdConstant", "$rootScope", "$window"];angular.module('material.core')
733.factory('$mdMedia', mdMediaFactory);
734
735/**
736 * @ngdoc service
737 * @name $mdMedia
738 * @module material.core
739 *
740 * @description
741 * `$mdMedia` is used to evaluate whether a given media query is true or false given the
742 * current device's screen / window size. The media query will be re-evaluated on resize, allowing
743 * you to register a watch.
744 *
745 * `$mdMedia` also has pre-programmed support for media queries that match the layout breakpoints:
746 *
747 *  <table class="md-api-table">
748 *    <thead>
749 *    <tr>
750 *      <th>Breakpoint</th>
751 *      <th>mediaQuery</th>
752 *    </tr>
753 *    </thead>
754 *    <tbody>
755 *    <tr>
756 *      <td>xs</td>
757 *      <td>(max-width: 599px)</td>
758 *    </tr>
759 *    <tr>
760 *      <td>gt-xs</td>
761 *      <td>(min-width: 600px)</td>
762 *    </tr>
763 *    <tr>
764 *      <td>sm</td>
765 *      <td>(min-width: 600px) and (max-width: 959px)</td>
766 *    </tr>
767 *    <tr>
768 *      <td>gt-sm</td>
769 *      <td>(min-width: 960px)</td>
770 *    </tr>
771 *    <tr>
772 *      <td>md</td>
773 *      <td>(min-width: 960px) and (max-width: 1279px)</td>
774 *    </tr>
775 *    <tr>
776 *      <td>gt-md</td>
777 *      <td>(min-width: 1280px)</td>
778 *    </tr>
779 *    <tr>
780 *      <td>lg</td>
781 *      <td>(min-width: 1280px) and (max-width: 1919px)</td>
782 *    </tr>
783 *    <tr>
784 *      <td>gt-lg</td>
785 *      <td>(min-width: 1920px)</td>
786 *    </tr>
787 *    <tr>
788 *      <td>xl</td>
789 *      <td>(min-width: 1920px)</td>
790 *    </tr>
791 *    <tr>
792 *      <td>landscape</td>
793 *      <td>landscape</td>
794 *    </tr>
795 *    <tr>
796 *      <td>portrait</td>
797 *      <td>portrait</td>
798 *    </tr>
799 *    <tr>
800 *      <td>print</td>
801 *      <td>print</td>
802 *    </tr>
803 *    </tbody>
804 *  </table>
805 *
806 *  See Material Design's <a href="https://material.google.com/layout/responsive-ui.html">Layout - Adaptive UI</a> for more details.
807 *
808 *  <a href="https://www.google.com/design/spec/layout/adaptive-ui.html">
809 *  <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>
810 *  </a>
811 *
812 * @returns {boolean} a boolean representing whether or not the given media query is true or false.
813 *
814 * @usage
815 * <hljs lang="js">
816 * app.controller('MyController', function($mdMedia, $scope) {
817 *   $scope.$watch(function() { return $mdMedia('lg'); }, function(big) {
818 *     $scope.bigScreen = big;
819 *   });
820 *
821 *   $scope.screenIsSmall = $mdMedia('sm');
822 *   $scope.customQuery = $mdMedia('(min-width: 1234px)');
823 *   $scope.anotherCustom = $mdMedia('max-width: 300px');
824 * });
825 * </hljs>
826 */
827
828/* @ngInject */
829function mdMediaFactory($mdConstant, $rootScope, $window) {
830  var queries = {};
831  var mqls = {};
832  var results = {};
833  var normalizeCache = {};
834
835  $mdMedia.getResponsiveAttribute = getResponsiveAttribute;
836  $mdMedia.getQuery = getQuery;
837  $mdMedia.watchResponsiveAttributes = watchResponsiveAttributes;
838
839  return $mdMedia;
840
841  function $mdMedia(query) {
842    var validated = queries[query];
843    if (angular.isUndefined(validated)) {
844      validated = queries[query] = validate(query);
845    }
846
847    var result = results[validated];
848    if (angular.isUndefined(result)) {
849      result = add(validated);
850    }
851
852    return result;
853  }
854
855  function validate(query) {
856    return $mdConstant.MEDIA[query] ||
857           ((query.charAt(0) !== '(') ? ('(' + query + ')') : query);
858  }
859
860  function add(query) {
861    var result = mqls[query];
862    if ( !result ) {
863      result = mqls[query] = $window.matchMedia(query);
864    }
865
866    result.addListener(onQueryChange);
867    return (results[result.media] = !!result.matches);
868  }
869
870  function onQueryChange(query) {
871    $rootScope.$evalAsync(function() {
872      results[query.media] = !!query.matches;
873    });
874  }
875
876  function getQuery(name) {
877    return mqls[name];
878  }
879
880  function getResponsiveAttribute(attrs, attrName) {
881    for (var i = 0; i < $mdConstant.MEDIA_PRIORITY.length; i++) {
882      var mediaName = $mdConstant.MEDIA_PRIORITY[i];
883      if (!mqls[queries[mediaName]].matches) {
884        continue;
885      }
886
887      var normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
888      if (attrs[normalizedName]) {
889        return attrs[normalizedName];
890      }
891    }
892
893    // fallback on unprefixed
894    return attrs[getNormalizedName(attrs, attrName)];
895  }
896
897  function watchResponsiveAttributes(attrNames, attrs, watchFn) {
898    var unwatchFns = [];
899    attrNames.forEach(function(attrName) {
900      var normalizedName = getNormalizedName(attrs, attrName);
901      if (angular.isDefined(attrs[normalizedName])) {
902        unwatchFns.push(
903            attrs.$observe(normalizedName, angular.bind(void 0, watchFn, null)));
904      }
905
906      for (var mediaName in $mdConstant.MEDIA) {
907        normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
908        if (angular.isDefined(attrs[normalizedName])) {
909          unwatchFns.push(
910              attrs.$observe(normalizedName, angular.bind(void 0, watchFn, mediaName)));
911        }
912      }
913    });
914
915    return function unwatch() {
916      unwatchFns.forEach(function(fn) { fn(); });
917    };
918  }
919
920  // Improves performance dramatically
921  function getNormalizedName(attrs, attrName) {
922    return normalizeCache[attrName] ||
923        (normalizeCache[attrName] = attrs.$normalize(attrName));
924  }
925}
926
927})();
928(function(){
929"use strict";
930
931angular
932  .module('material.core')
933  .config( ["$provide", function($provide) {
934    $provide.decorator('$mdUtil', ['$delegate', function ($delegate) {
935
936      // Inject the prefixer into our original $mdUtil service.
937      $delegate.prefixer = MdPrefixer;
938
939      return $delegate;
940    }]);
941  }]);
942
943function MdPrefixer(initialAttributes, buildSelector) {
944  var PREFIXES = ['data', 'x'];
945
946  if (initialAttributes) {
947    // The prefixer also accepts attributes as a parameter, and immediately builds a list or selector for
948    // the specified attributes.
949    return buildSelector ? _buildSelector(initialAttributes) : _buildList(initialAttributes);
950  }
951
952  return {
953    buildList: _buildList,
954    buildSelector: _buildSelector,
955    hasAttribute: _hasAttribute,
956    removeAttribute: _removeAttribute
957  };
958
959  function _buildList(attributes) {
960    attributes = angular.isArray(attributes) ? attributes : [attributes];
961
962    attributes.forEach(function(item) {
963      PREFIXES.forEach(function(prefix) {
964        attributes.push(prefix + '-' + item);
965      });
966    });
967
968    return attributes;
969  }
970
971  function _buildSelector(attributes) {
972    attributes = angular.isArray(attributes) ? attributes : [attributes];
973
974    return _buildList(attributes)
975      .map(function(item) {
976        return '[' + item + ']';
977      })
978      .join(',');
979  }
980
981  function _hasAttribute(element, attribute) {
982    element = _getNativeElement(element);
983
984    if (!element) {
985      return false;
986    }
987
988    var prefixedAttrs = _buildList(attribute);
989
990    for (var i = 0; i < prefixedAttrs.length; i++) {
991      if (element.hasAttribute(prefixedAttrs[i])) {
992        return true;
993      }
994    }
995
996    return false;
997  }
998
999  function _removeAttribute(element, attribute) {
1000    element = _getNativeElement(element);
1001
1002    if (!element) {
1003      return;
1004    }
1005
1006    _buildList(attribute).forEach(function(prefixedAttribute) {
1007      element.removeAttribute(prefixedAttribute);
1008    });
1009  }
1010
1011  /**
1012   * Transforms a jqLite or DOM element into a HTML element.
1013   * This is useful when supporting jqLite elements and DOM elements at
1014   * same time.
1015   * @param element {JQLite|Element} Element to be parsed
1016   * @returns {HTMLElement} Parsed HTMLElement
1017   */
1018  function _getNativeElement(element) {
1019    element =  element[0] || element;
1020
1021    if (element.nodeType) {
1022      return element;
1023    }
1024  }
1025
1026}
1027
1028})();
1029(function(){
1030"use strict";
1031
1032/*
1033 * This var has to be outside the angular factory, otherwise when
1034 * there are multiple material apps on the same page, each app
1035 * will create its own instance of this array and the app's IDs
1036 * will not be unique.
1037 */
1038UtilFactory.$inject = ["$document", "$timeout", "$compile", "$rootScope", "$$mdAnimate", "$interpolate", "$log", "$rootElement", "$window", "$$rAF"];
1039var nextUniqueId = 0;
1040
1041/**
1042 * @ngdoc module
1043 * @name material.core.util
1044 * @description
1045 * Util
1046 */
1047angular
1048  .module('material.core')
1049  .factory('$mdUtil', UtilFactory);
1050
1051/**
1052 * @ngInject
1053 */
1054function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $interpolate, $log, $rootElement, $window, $$rAF) {
1055  // Setup some core variables for the processTemplate method
1056  var startSymbol = $interpolate.startSymbol(),
1057    endSymbol = $interpolate.endSymbol(),
1058    usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}'));
1059
1060  /**
1061   * Checks if the target element has the requested style by key
1062   * @param {DOMElement|JQLite} target Target element
1063   * @param {string} key Style key
1064   * @param {string=} expectedVal Optional expected value
1065   * @returns {boolean} Whether the target element has the style or not
1066   */
1067  var hasComputedStyle = function (target, key, expectedVal) {
1068    var hasValue = false;
1069
1070    if ( target && target.length  ) {
1071      var computedStyles = $window.getComputedStyle(target[0]);
1072      hasValue = angular.isDefined(computedStyles[key]) && (expectedVal ? computedStyles[key] == expectedVal : true);
1073    }
1074
1075    return hasValue;
1076  };
1077
1078  function validateCssValue(value) {
1079    return !value       ? '0'   :
1080      hasPx(value) || hasPercent(value) ? value : value + 'px';
1081  }
1082
1083  function hasPx(value) {
1084    return String(value).indexOf('px') > -1;
1085  }
1086
1087  function hasPercent(value) {
1088    return String(value).indexOf('%') > -1;
1089
1090  }
1091
1092  var $mdUtil = {
1093    dom: {},
1094    now: window.performance && window.performance.now ?
1095      angular.bind(window.performance, window.performance.now) : Date.now || function() {
1096      return new Date().getTime();
1097    },
1098
1099    /**
1100     * Cross-version compatibility method to retrieve an option of a ngModel controller,
1101     * which supports the breaking changes in the AngularJS snapshot (SHA 87a2ff76af5d0a9268d8eb84db5755077d27c84c).
1102     * @param {!angular.ngModelCtrl} ngModelCtrl
1103     * @param {!string} optionName
1104     * @returns {Object|undefined}
1105     */
1106    getModelOption: function (ngModelCtrl, optionName) {
1107      if (!ngModelCtrl.$options) {
1108        return;
1109      }
1110
1111      var $options = ngModelCtrl.$options;
1112
1113      // The newer versions of AngularJS introduced a `getOption function and made the option values no longer
1114      // visible on the $options object.
1115      return $options.getOption ? $options.getOption(optionName) : $options[optionName]
1116    },
1117
1118    /**
1119     * Bi-directional accessor/mutator used to easily update an element's
1120     * property based on the current 'dir'ectional value.
1121     */
1122    bidi : function(element, property, lValue, rValue) {
1123      var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl');
1124
1125      // If accessor
1126      if ( arguments.length == 0 ) return ltr ? 'ltr' : 'rtl';
1127
1128      // If mutator
1129      var elem = angular.element(element);
1130
1131      if ( ltr && angular.isDefined(lValue)) {
1132        elem.css(property, validateCssValue(lValue));
1133      }
1134      else if ( !ltr && angular.isDefined(rValue)) {
1135        elem.css(property, validateCssValue(rValue) );
1136      }
1137    },
1138
1139    bidiProperty: function (element, lProperty, rProperty, value) {
1140      var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl');
1141
1142      var elem = angular.element(element);
1143
1144      if ( ltr && angular.isDefined(lProperty)) {
1145        elem.css(lProperty, validateCssValue(value));
1146        elem.css(rProperty, '');
1147      }
1148      else if ( !ltr && angular.isDefined(rProperty)) {
1149        elem.css(rProperty, validateCssValue(value) );
1150        elem.css(lProperty, '');
1151      }
1152    },
1153
1154    clientRect: function(element, offsetParent, isOffsetRect) {
1155      var node = getNode(element);
1156      offsetParent = getNode(offsetParent || node.offsetParent || document.body);
1157      var nodeRect = node.getBoundingClientRect();
1158
1159      // The user can ask for an offsetRect: a rect relative to the offsetParent,
1160      // or a clientRect: a rect relative to the page
1161      var offsetRect = isOffsetRect ?
1162        offsetParent.getBoundingClientRect() :
1163      {left: 0, top: 0, width: 0, height: 0};
1164      return {
1165        left: nodeRect.left - offsetRect.left,
1166        top: nodeRect.top - offsetRect.top,
1167        width: nodeRect.width,
1168        height: nodeRect.height
1169      };
1170    },
1171    offsetRect: function(element, offsetParent) {
1172      return $mdUtil.clientRect(element, offsetParent, true);
1173    },
1174
1175    // Annoying method to copy nodes to an array, thanks to IE
1176    nodesToArray: function(nodes) {
1177      nodes = nodes || [];
1178
1179      var results = [];
1180      for (var i = 0; i < nodes.length; ++i) {
1181        results.push(nodes.item(i));
1182      }
1183      return results;
1184    },
1185
1186    /**
1187     * Determines the absolute position of the viewport.
1188     * Useful when making client rectangles absolute.
1189     * @returns {number}
1190     */
1191    getViewportTop: function() {
1192      return window.scrollY || window.pageYOffset || 0;
1193    },
1194
1195    /**
1196     * Finds the proper focus target by searching the DOM.
1197     *
1198     * @param containerEl
1199     * @param attributeVal
1200     * @returns {*}
1201     */
1202    findFocusTarget: function(containerEl, attributeVal) {
1203      var AUTO_FOCUS = this.prefixer('md-autofocus', true);
1204      var elToFocus;
1205
1206      elToFocus = scanForFocusable(containerEl, attributeVal || AUTO_FOCUS);
1207
1208      if ( !elToFocus && attributeVal != AUTO_FOCUS) {
1209        // Scan for deprecated attribute
1210        elToFocus = scanForFocusable(containerEl, this.prefixer('md-auto-focus', true));
1211
1212        if ( !elToFocus ) {
1213          // Scan for fallback to 'universal' API
1214          elToFocus = scanForFocusable(containerEl, AUTO_FOCUS);
1215        }
1216      }
1217
1218      return elToFocus;
1219
1220      /**
1221       * Can target and nested children for specified Selector (attribute)
1222       * whose value may be an expression that evaluates to True/False.
1223       */
1224      function scanForFocusable(target, selector) {
1225        var elFound, items = target[0].querySelectorAll(selector);
1226
1227        // Find the last child element with the focus attribute
1228        if ( items && items.length ){
1229          items.length && angular.forEach(items, function(it) {
1230            it = angular.element(it);
1231
1232            // Check the element for the md-autofocus class to ensure any associated expression
1233            // evaluated to true.
1234            var isFocusable = it.hasClass('md-autofocus');
1235            if (isFocusable) elFound = it;
1236          });
1237        }
1238        return elFound;
1239      }
1240    },
1241
1242    /**
1243     * Disables scroll around the passed parent element.
1244     * @param element Unused
1245     * @param {!Element|!angular.JQLite} parent Element to disable scrolling within.
1246     *   Defaults to body if none supplied.
1247     * @param options Object of options to modify functionality
1248     *   - disableScrollMask Boolean of whether or not to create a scroll mask element or
1249     *     use the passed parent element.
1250     */
1251    disableScrollAround: function(element, parent, options) {
1252      options = options || {};
1253
1254      $mdUtil.disableScrollAround._count = Math.max(0, $mdUtil.disableScrollAround._count || 0);
1255      $mdUtil.disableScrollAround._count++;
1256
1257      if ($mdUtil.disableScrollAround._restoreScroll) {
1258        return $mdUtil.disableScrollAround._restoreScroll;
1259      }
1260
1261      var body = $document[0].body;
1262      var restoreBody = disableBodyScroll();
1263      var restoreElement = disableElementScroll(parent);
1264
1265      return $mdUtil.disableScrollAround._restoreScroll = function() {
1266        if (--$mdUtil.disableScrollAround._count <= 0) {
1267          restoreBody();
1268          restoreElement();
1269          delete $mdUtil.disableScrollAround._restoreScroll;
1270        }
1271      };
1272
1273      /**
1274       * Creates a virtual scrolling mask to prevent touchmove, keyboard, scrollbar clicking,
1275       * and wheel events
1276       */
1277      function disableElementScroll(element) {
1278        element = angular.element(element || body);
1279
1280        var scrollMask;
1281
1282        if (options.disableScrollMask) {
1283          scrollMask = element;
1284        } else {
1285          scrollMask = angular.element(
1286            '<div class="md-scroll-mask">' +
1287            '  <div class="md-scroll-mask-bar"></div>' +
1288            '</div>');
1289          element.append(scrollMask);
1290        }
1291
1292        scrollMask.on('wheel', preventDefault);
1293        scrollMask.on('touchmove', preventDefault);
1294
1295        return function restoreElementScroll() {
1296          scrollMask.off('wheel');
1297          scrollMask.off('touchmove');
1298
1299          if (!options.disableScrollMask && scrollMask[0].parentNode ) {
1300            scrollMask[0].parentNode.removeChild(scrollMask[0]);
1301          }
1302        };
1303
1304        function preventDefault(e) {
1305          e.preventDefault();
1306        }
1307      }
1308
1309      // Converts the body to a position fixed block and translate it to the proper scroll position
1310      function disableBodyScroll() {
1311        var documentElement = $document[0].documentElement;
1312
1313        var prevDocumentStyle = documentElement.style.cssText || '';
1314        var prevBodyStyle = body.style.cssText || '';
1315
1316        var viewportTop = $mdUtil.getViewportTop();
1317        var clientWidth = body.clientWidth;
1318        var hasVerticalScrollbar = body.scrollHeight > body.clientHeight + 1;
1319
1320        // Scroll may be set on <html> element (for example by overflow-y: scroll)
1321        // but Chrome is reporting the scrollTop position always on <body>.
1322        // scrollElement will allow to restore the scrollTop position to proper target.
1323        var scrollElement = documentElement.scrollTop > 0 ? documentElement : body;
1324
1325        if (hasVerticalScrollbar) {
1326          angular.element(body).css({
1327            position: 'fixed',
1328            width: '100%',
1329            top: -viewportTop + 'px'
1330          });
1331        }
1332
1333        if (body.clientWidth < clientWidth) {
1334          body.style.overflow = 'hidden';
1335        }
1336
1337        // This should be applied after the manipulation to the body, because
1338        // adding a scrollbar can potentially resize it, causing the measurement
1339        // to change.
1340        if (hasVerticalScrollbar) {
1341          documentElement.style.overflowY = 'scroll';
1342        }
1343
1344        return function restoreScroll() {
1345          // Reset the inline style CSS to the previous.
1346          body.style.cssText = prevBodyStyle;
1347          documentElement.style.cssText = prevDocumentStyle;
1348
1349          // The scroll position while being fixed
1350          scrollElement.scrollTop = viewportTop;
1351        };
1352      }
1353
1354    },
1355
1356    enableScrolling: function() {
1357      var restoreFn = this.disableScrollAround._restoreScroll;
1358      restoreFn && restoreFn();
1359    },
1360
1361    floatingScrollbars: function() {
1362      if (this.floatingScrollbars.cached === undefined) {
1363        var tempNode = angular.element('<div><div></div></div>').css({
1364          width: '100%',
1365          'z-index': -1,
1366          position: 'absolute',
1367          height: '35px',
1368          'overflow-y': 'scroll'
1369        });
1370        tempNode.children().css('height', '60px');
1371
1372        $document[0].body.appendChild(tempNode[0]);
1373        this.floatingScrollbars.cached = (tempNode[0].offsetWidth == tempNode[0].childNodes[0].offsetWidth);
1374        tempNode.remove();
1375      }
1376      return this.floatingScrollbars.cached;
1377    },
1378
1379    // Mobile safari only allows you to set focus in click event listeners...
1380    forceFocus: function(element) {
1381      var node = element[0] || element;
1382
1383      document.addEventListener('click', function focusOnClick(ev) {
1384        if (ev.target === node && ev.$focus) {
1385          node.focus();
1386          ev.stopImmediatePropagation();
1387          ev.preventDefault();
1388          node.removeEventListener('click', focusOnClick);
1389        }
1390      }, true);
1391
1392      var newEvent = document.createEvent('MouseEvents');
1393      newEvent.initMouseEvent('click', false, true, window, {}, 0, 0, 0, 0,
1394        false, false, false, false, 0, null);
1395      newEvent.$material = true;
1396      newEvent.$focus = true;
1397      node.dispatchEvent(newEvent);
1398    },
1399
1400    /**
1401     * facade to build md-backdrop element with desired styles
1402     * NOTE: Use $compile to trigger backdrop postLink function
1403     */
1404    createBackdrop: function(scope, addClass) {
1405      return $compile($mdUtil.supplant('<md-backdrop class="{0}">', [addClass]))(scope);
1406    },
1407
1408    /**
1409     * supplant() method from Crockford's `Remedial Javascript`
1410     * Equivalent to use of $interpolate; without dependency on
1411     * interpolation symbols and scope. Note: the '{<token>}' can
1412     * be property names, property chains, or array indices.
1413     */
1414    supplant: function(template, values, pattern) {
1415      pattern = pattern || /\{([^\{\}]*)\}/g;
1416      return template.replace(pattern, function(a, b) {
1417        var p = b.split('.'),
1418          r = values;
1419        try {
1420          for (var s in p) {
1421            if (p.hasOwnProperty(s) ) {
1422              r = r[p[s]];
1423            }
1424          }
1425        } catch (e) {
1426          r = a;
1427        }
1428        return (typeof r === 'string' || typeof r === 'number') ? r : a;
1429      });
1430    },
1431
1432    fakeNgModel: function() {
1433      return {
1434        $fake: true,
1435        $setTouched: angular.noop,
1436        $setViewValue: function(value) {
1437          this.$viewValue = value;
1438          this.$render(value);
1439          this.$viewChangeListeners.forEach(function(cb) {
1440            cb();
1441          });
1442        },
1443        $isEmpty: function(value) {
1444          return ('' + value).length === 0;
1445        },
1446        $parsers: [],
1447        $formatters: [],
1448        $viewChangeListeners: [],
1449        $render: angular.noop
1450      };
1451    },
1452
1453    // Returns a function, that, as long as it continues to be invoked, will not
1454    // be triggered. The function will be called after it stops being called for
1455    // N milliseconds.
1456    // @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs
1457    // @param invokeApply should the $timeout trigger $digest() dirty checking
1458    debounce: function(func, wait, scope, invokeApply) {
1459      var timer;
1460
1461      return function debounced() {
1462        var context = scope,
1463          args = Array.prototype.slice.call(arguments);
1464
1465        $timeout.cancel(timer);
1466        timer = $timeout(function() {
1467
1468          timer = undefined;
1469          func.apply(context, args);
1470
1471        }, wait || 10, invokeApply);
1472      };
1473    },
1474
1475    // Returns a function that can only be triggered every `delay` milliseconds.
1476    // In other words, the function will not be called unless it has been more
1477    // than `delay` milliseconds since the last call.
1478    throttle: function throttle(func, delay) {
1479      var recent;
1480      return function throttled() {
1481        var context = this;
1482        var args = arguments;
1483        var now = $mdUtil.now();
1484
1485        if (!recent || (now - recent > delay)) {
1486          func.apply(context, args);
1487          recent = now;
1488        }
1489      };
1490    },
1491
1492    /**
1493     * Measures the number of milliseconds taken to run the provided callback
1494     * function. Uses a high-precision timer if available.
1495     */
1496    time: function time(cb) {
1497      var start = $mdUtil.now();
1498      cb();
1499      return $mdUtil.now() - start;
1500    },
1501
1502    /**
1503     * Create an implicit getter that caches its `getter()`
1504     * lookup value
1505     */
1506    valueOnUse : function (scope, key, getter) {
1507      var value = null, args = Array.prototype.slice.call(arguments);
1508      var params = (args.length > 3) ? args.slice(3) : [ ];
1509
1510      Object.defineProperty(scope, key, {
1511        get: function () {
1512          if (value === null) value = getter.apply(scope, params);
1513          return value;
1514        }
1515      });
1516    },
1517
1518    /**
1519     * Get a unique ID.
1520     *
1521     * @returns {string} an unique numeric string
1522     */
1523    nextUid: function() {
1524      return '' + nextUniqueId++;
1525    },
1526
1527    // Stop watchers and events from firing on a scope without destroying it,
1528    // by disconnecting it from its parent and its siblings' linked lists.
1529    disconnectScope: function disconnectScope(scope) {
1530      if (!scope) return;
1531
1532      // we can't destroy the root scope or a scope that has been already destroyed
1533      if (scope.$root === scope) return;
1534      if (scope.$$destroyed) return;
1535
1536      var parent = scope.$parent;
1537      scope.$$disconnected = true;
1538
1539      // See Scope.$destroy
1540      if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling;
1541      if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling;
1542      if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
1543      if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
1544
1545      scope.$$nextSibling = scope.$$prevSibling = null;
1546
1547    },
1548
1549    // Undo the effects of disconnectScope above.
1550    reconnectScope: function reconnectScope(scope) {
1551      if (!scope) return;
1552
1553      // we can't disconnect the root node or scope already disconnected
1554      if (scope.$root === scope) return;
1555      if (!scope.$$disconnected) return;
1556
1557      var child = scope;
1558
1559      var parent = child.$parent;
1560      child.$$disconnected = false;
1561      // See Scope.$new for this logic...
1562      child.$$prevSibling = parent.$$childTail;
1563      if (parent.$$childHead) {
1564        parent.$$childTail.$$nextSibling = child;
1565        parent.$$childTail = child;
1566      } else {
1567        parent.$$childHead = parent.$$childTail = child;
1568      }
1569    },
1570
1571    /*
1572     * getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName
1573     *
1574     * @param el Element to start walking the DOM from
1575     * @param check Either a string or a function. If a string is passed, it will be evaluated against
1576     * each of the parent nodes' tag name. If a function is passed, the loop will call it with each of
1577     * the parents and will use the return value to determine whether the node is a match.
1578     * @param onlyParent Only start checking from the parent element, not `el`.
1579     */
1580    getClosest: function getClosest(el, validateWith, onlyParent) {
1581      if ( angular.isString(validateWith) ) {
1582        var tagName = validateWith.toUpperCase();
1583        validateWith = function(el) {
1584          return el.nodeName.toUpperCase() === tagName;
1585        };
1586      }
1587
1588      if (el instanceof angular.element) el = el[0];
1589      if (onlyParent) el = el.parentNode;
1590      if (!el) return null;
1591
1592      do {
1593        if (validateWith(el)) {
1594          return el;
1595        }
1596      } while (el = el.parentNode);
1597
1598      return null;
1599    },
1600
1601    /**
1602     * Build polyfill for the Node.contains feature (if needed)
1603     */
1604    elementContains: function(node, child) {
1605      var hasContains = (window.Node && window.Node.prototype && Node.prototype.contains);
1606      var findFn = hasContains ? angular.bind(node, node.contains) : angular.bind(node, function(arg) {
1607        // compares the positions of two nodes and returns a bitmask
1608        return (node === child) || !!(this.compareDocumentPosition(arg) & 16)
1609      });
1610
1611      return findFn(child);
1612    },
1613
1614    /**
1615     * Functional equivalent for $element.filter(‘md-bottom-sheet’)
1616     * useful with interimElements where the element and its container are important...
1617     *
1618     * @param {[]} elements to scan
1619     * @param {string} name of node to find (e.g. 'md-dialog')
1620     * @param {boolean=} optional flag to allow deep scans; defaults to 'false'.
1621     * @param {boolean=} optional flag to enable log warnings; defaults to false
1622     */
1623    extractElementByName: function(element, nodeName, scanDeep, warnNotFound) {
1624      var found = scanTree(element);
1625      if (!found && !!warnNotFound) {
1626        $log.warn( $mdUtil.supplant("Unable to find node '{0}' in element '{1}'.",[nodeName, element[0].outerHTML]) );
1627      }
1628
1629      return angular.element(found || element);
1630
1631      /**
1632       * Breadth-First tree scan for element with matching `nodeName`
1633       */
1634      function scanTree(element) {
1635        return scanLevel(element) || (!!scanDeep ? scanChildren(element) : null);
1636      }
1637
1638      /**
1639       * Case-insensitive scan of current elements only (do not descend).
1640       */
1641      function scanLevel(element) {
1642        if ( element ) {
1643          for (var i = 0, len = element.length; i < len; i++) {
1644            if (element[i].nodeName.toLowerCase() === nodeName) {
1645              return element[i];
1646            }
1647          }
1648        }
1649        return null;
1650      }
1651
1652      /**
1653       * Scan children of specified node
1654       */
1655      function scanChildren(element) {
1656        var found;
1657        if ( element ) {
1658          for (var i = 0, len = element.length; i < len; i++) {
1659            var target = element[i];
1660            if ( !found ) {
1661              for (var j = 0, numChild = target.childNodes.length; j < numChild; j++) {
1662                found = found || scanTree([target.childNodes[j]]);
1663              }
1664            }
1665          }
1666        }
1667        return found;
1668      }
1669
1670    },
1671
1672    /**
1673     * Give optional properties with no value a boolean true if attr provided or false otherwise
1674     */
1675    initOptionalProperties: function(scope, attr, defaults) {
1676      defaults = defaults || {};
1677      angular.forEach(scope.$$isolateBindings, function(binding, key) {
1678        if (binding.optional && angular.isUndefined(scope[key])) {
1679          var attrIsDefined = angular.isDefined(attr[binding.attrName]);
1680          scope[key] = angular.isDefined(defaults[key]) ? defaults[key] : attrIsDefined;
1681        }
1682      });
1683    },
1684
1685    /**
1686     * Alternative to $timeout calls with 0 delay.
1687     * nextTick() coalesces all calls within a single frame
1688     * to minimize $digest thrashing
1689     *
1690     * @param callback
1691     * @param digest
1692     * @returns {*}
1693     */
1694    nextTick: function(callback, digest, scope) {
1695      //-- grab function reference for storing state details
1696      var nextTick = $mdUtil.nextTick;
1697      var timeout = nextTick.timeout;
1698      var queue = nextTick.queue || [];
1699
1700      //-- add callback to the queue
1701      queue.push({scope: scope, callback: callback});
1702
1703      //-- set default value for digest
1704      if (digest == null) digest = true;
1705
1706      //-- store updated digest/queue values
1707      nextTick.digest = nextTick.digest || digest;
1708      nextTick.queue = queue;
1709
1710      //-- either return existing timeout or create a new one
1711      return timeout || (nextTick.timeout = $timeout(processQueue, 0, false));
1712
1713      /**
1714       * Grab a copy of the current queue
1715       * Clear the queue for future use
1716       * Process the existing queue
1717       * Trigger digest if necessary
1718       */
1719      function processQueue() {
1720        var queue = nextTick.queue;
1721        var digest = nextTick.digest;
1722
1723        nextTick.queue = [];
1724        nextTick.timeout = null;
1725        nextTick.digest = false;
1726
1727        queue.forEach(function(queueItem) {
1728          var skip = queueItem.scope && queueItem.scope.$$destroyed;
1729          if (!skip) {
1730            queueItem.callback();
1731          }
1732        });
1733
1734        if (digest) $rootScope.$digest();
1735      }
1736    },
1737
1738    /**
1739     * Processes a template and replaces the start/end symbols if the application has
1740     * overriden them.
1741     *
1742     * @param template The template to process whose start/end tags may be replaced.
1743     * @returns {*}
1744     */
1745    processTemplate: function(template) {
1746      if (usesStandardSymbols) {
1747        return template;
1748      } else {
1749        if (!template || !angular.isString(template)) return template;
1750        return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
1751      }
1752    },
1753
1754    /**
1755     * Scan up dom hierarchy for enabled parent;
1756     */
1757    getParentWithPointerEvents: function (element) {
1758      var parent = element.parent();
1759
1760      // jqLite might return a non-null, but still empty, parent; so check for parent and length
1761      while (hasComputedStyle(parent, 'pointer-events', 'none')) {
1762        parent = parent.parent();
1763      }
1764
1765      return parent;
1766    },
1767
1768    getNearestContentElement: function (element) {
1769      var current = element.parent()[0];
1770      // Look for the nearest parent md-content, stopping at the rootElement.
1771      while (current && current !== $rootElement[0] && current !== document.body && current.nodeName.toUpperCase() !== 'MD-CONTENT') {
1772        current = current.parentNode;
1773      }
1774      return current;
1775    },
1776
1777    /**
1778     * Checks if the current browser is natively supporting the `sticky` position.
1779     * @returns {string} supported sticky property name
1780     */
1781    checkStickySupport: function() {
1782      var stickyProp;
1783      var testEl = angular.element('<div>');
1784      $document[0].body.appendChild(testEl[0]);
1785
1786      var stickyProps = ['sticky', '-webkit-sticky'];
1787      for (var i = 0; i < stickyProps.length; ++i) {
1788        testEl.css({
1789          position: stickyProps[i],
1790          top: 0,
1791          'z-index': 2
1792        });
1793
1794        if (testEl.css('position') == stickyProps[i]) {
1795          stickyProp = stickyProps[i];
1796          break;
1797        }
1798      }
1799
1800      testEl.remove();
1801
1802      return stickyProp;
1803    },
1804
1805    /**
1806     * Parses an attribute value, mostly a string.
1807     * By default checks for negated values and returns `false´ if present.
1808     * Negated values are: (native falsy) and negative strings like:
1809     * `false` or `0`.
1810     * @param value Attribute value which should be parsed.
1811     * @param negatedCheck When set to false, won't check for negated values.
1812     * @returns {boolean}
1813     */
1814    parseAttributeBoolean: function(value, negatedCheck) {
1815      return value === '' || !!value && (negatedCheck === false || value !== 'false' && value !== '0');
1816    },
1817
1818    hasComputedStyle: hasComputedStyle,
1819
1820    /**
1821     * Returns true if the parent form of the element has been submitted.
1822     *
1823     * @param element An AngularJS or HTML5 element.
1824     *
1825     * @returns {boolean}
1826     */
1827    isParentFormSubmitted: function(element) {
1828      var parent = $mdUtil.getClosest(element, 'form');
1829      var form = parent ? angular.element(parent).controller('form') : null;
1830
1831      return form ? form.$submitted : false;
1832    },
1833
1834    /**
1835     * Animate the requested element's scrollTop to the requested scrollPosition with basic easing.
1836     *
1837     * @param {!HTMLElement} element The element to scroll.
1838     * @param {number} scrollEnd The new/final scroll position.
1839     * @param {number=} duration Duration of the scroll. Default is 1000ms.
1840     */
1841    animateScrollTo: function(element, scrollEnd, duration) {
1842      var scrollStart = element.scrollTop;
1843      var scrollChange = scrollEnd - scrollStart;
1844      var scrollingDown = scrollStart < scrollEnd;
1845      var startTime = $mdUtil.now();
1846
1847      $$rAF(scrollChunk);
1848
1849      function scrollChunk() {
1850        var newPosition = calculateNewPosition();
1851
1852        element.scrollTop = newPosition;
1853
1854        if (scrollingDown ? newPosition < scrollEnd : newPosition > scrollEnd) {
1855          $$rAF(scrollChunk);
1856        }
1857      }
1858
1859      function calculateNewPosition() {
1860        var easeDuration = duration || 1000;
1861        var currentTime = $mdUtil.now() - startTime;
1862
1863        return ease(currentTime, scrollStart, scrollChange, easeDuration);
1864      }
1865
1866      function ease(currentTime, start, change, duration) {
1867        // If the duration has passed (which can occur if our app loses focus due to $$rAF), jump
1868        // straight to the proper position
1869        if (currentTime > duration) {
1870          return start + change;
1871        }
1872
1873        var ts = (currentTime /= duration) * currentTime;
1874        var tc = ts * currentTime;
1875
1876        return start + change * (-2 * tc + 3 * ts);
1877      }
1878    },
1879
1880    /**
1881     * Provides an easy mechanism for removing duplicates from an array.
1882     *
1883     *    var myArray = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];
1884     *
1885     *    $mdUtil.uniq(myArray) => [1, 2, 3, 4]
1886     *
1887     * @param {array} array The array whose unique values should be returned.
1888     *
1889     * @returns {array} A copy of the array containing only unique values.
1890     */
1891    uniq: function(array) {
1892      if (!array) { return; }
1893
1894      return array.filter(function(value, index, self) {
1895        return self.indexOf(value) === index;
1896      });
1897    }
1898  };
1899
1900
1901// Instantiate other namespace utility methods
1902
1903  $mdUtil.dom.animator = $$mdAnimate($mdUtil);
1904
1905  return $mdUtil;
1906
1907  function getNode(el) {
1908    return el[0] || el;
1909  }
1910
1911}
1912
1913/*
1914 * Since removing jQuery from the demos, some code that uses `element.focus()` is broken.
1915 * We need to add `element.focus()`, because it's testable unlike `element[0].focus`.
1916 */
1917
1918angular.element.prototype.focus = angular.element.prototype.focus || function() {
1919    if (this.length) {
1920      this[0].focus();
1921    }
1922    return this;
1923  };
1924angular.element.prototype.blur = angular.element.prototype.blur || function() {
1925    if (this.length) {
1926      this[0].blur();
1927    }
1928    return this;
1929  };
1930
1931})();
1932(function(){
1933"use strict";
1934
1935/**
1936 * @ngdoc module
1937 * @name material.core.aria
1938 * @description
1939 * Aria Expectations for AngularJS Material components.
1940 */
1941MdAriaService.$inject = ["$$rAF", "$log", "$window", "$interpolate"];
1942angular
1943  .module('material.core')
1944  .provider('$mdAria', MdAriaProvider);
1945
1946/**
1947 * @ngdoc service
1948 * @name $mdAriaProvider
1949 * @module material.core.aria
1950 *
1951 * @description
1952 *
1953 * Modify options of the `$mdAria` service, which will be used by most of the AngularJS Material
1954 * components.
1955 *
1956 * You are able to disable `$mdAria` warnings, by using the following markup.
1957 *
1958 * <hljs lang="js">
1959 *   app.config(function($mdAriaProvider) {
1960 *     // Globally disables all ARIA warnings.
1961 *     $mdAriaProvider.disableWarnings();
1962 *   });
1963 * </hljs>
1964 *
1965 */
1966function MdAriaProvider() {
1967
1968  var config = {
1969    /** Whether we should show ARIA warnings in the console if labels are missing on the element */
1970    showWarnings: true
1971  };
1972
1973  return {
1974    disableWarnings: disableWarnings,
1975    $get: ["$$rAF", "$log", "$window", "$interpolate", function($$rAF, $log, $window, $interpolate) {
1976      return MdAriaService.apply(config, arguments);
1977    }]
1978  };
1979
1980  /**
1981   * @ngdoc method
1982   * @name $mdAriaProvider#disableWarnings
1983   * @description Disables all ARIA warnings generated by AngularJS Material.
1984   */
1985  function disableWarnings() {
1986    config.showWarnings = false;
1987  }
1988}
1989
1990/*
1991 * @ngInject
1992 */
1993function MdAriaService($$rAF, $log, $window, $interpolate) {
1994
1995  // Load the showWarnings option from the current context and store it inside of a scope variable,
1996  // because the context will be probably lost in some function calls.
1997  var showWarnings = this.showWarnings;
1998
1999  return {
2000    expect: expect,
2001    expectAsync: expectAsync,
2002    expectWithText: expectWithText,
2003    expectWithoutText: expectWithoutText,
2004    getText: getText,
2005    hasAriaLabel: hasAriaLabel,
2006    parentHasAriaLabel: parentHasAriaLabel
2007  };
2008
2009  /**
2010   * Check if expected attribute has been specified on the target element or child
2011   * @param element
2012   * @param attrName
2013   * @param {optional} defaultValue What to set the attr to if no value is found
2014   */
2015  function expect(element, attrName, defaultValue) {
2016
2017    var node = angular.element(element)[0] || element;
2018
2019    // if node exists and neither it nor its children have the attribute
2020    if (node &&
2021       ((!node.hasAttribute(attrName) ||
2022        node.getAttribute(attrName).length === 0) &&
2023        !childHasAttribute(node, attrName))) {
2024
2025      defaultValue = angular.isString(defaultValue) ? defaultValue.trim() : '';
2026      if (defaultValue.length) {
2027        element.attr(attrName, defaultValue);
2028      } else if (showWarnings) {
2029        $log.warn('ARIA: Attribute "', attrName, '", required for accessibility, is missing on node:', node);
2030      }
2031
2032    }
2033  }
2034
2035  function expectAsync(element, attrName, defaultValueGetter) {
2036    // Problem: when retrieving the element's contents synchronously to find the label,
2037    // the text may not be defined yet in the case of a binding.
2038    // There is a higher chance that a binding will be defined if we wait one frame.
2039    $$rAF(function() {
2040        expect(element, attrName, defaultValueGetter());
2041    });
2042  }
2043
2044  function expectWithText(element, attrName) {
2045    var content = getText(element) || "";
2046    var hasBinding = content.indexOf($interpolate.startSymbol()) > -1;
2047
2048    if (hasBinding) {
2049      expectAsync(element, attrName, function() {
2050        return getText(element);
2051      });
2052    } else {
2053      expect(element, attrName, content);
2054    }
2055  }
2056
2057  function expectWithoutText(element, attrName) {
2058    var content = getText(element);
2059    var hasBinding = content.indexOf($interpolate.startSymbol()) > -1;
2060
2061    if ( !hasBinding && !content) {
2062      expect(element, attrName, content);
2063    }
2064  }
2065
2066  function getText(element) {
2067    element = element[0] || element;
2068    var walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
2069    var text = '';
2070
2071    var node;
2072    while (node = walker.nextNode()) {
2073      if (!isAriaHiddenNode(node)) {
2074        text += node.textContent;
2075      }
2076    }
2077
2078    return text.trim() || '';
2079
2080    function isAriaHiddenNode(node) {
2081      while (node.parentNode && (node = node.parentNode) !== element) {
2082        if (node.getAttribute && node.getAttribute('aria-hidden') === 'true') {
2083          return true;
2084        }
2085      }
2086    }
2087  }
2088
2089  function childHasAttribute(node, attrName) {
2090    var hasChildren = node.hasChildNodes(),
2091        hasAttr = false;
2092
2093    function isHidden(el) {
2094      var style = el.currentStyle ? el.currentStyle : $window.getComputedStyle(el);
2095      return (style.display === 'none');
2096    }
2097
2098    if (hasChildren) {
2099      var children = node.childNodes;
2100      for (var i=0; i < children.length; i++) {
2101        var child = children[i];
2102        if (child.nodeType === 1 && child.hasAttribute(attrName)) {
2103          if (!isHidden(child)) {
2104            hasAttr = true;
2105          }
2106        }
2107      }
2108    }
2109    return hasAttr;
2110  }
2111
2112  /**
2113   * Check if expected element has aria label attribute
2114   * @param element
2115   */
2116  function hasAriaLabel(element) {
2117    var node = angular.element(element)[0] || element;
2118
2119    /* Check if compatible node type (ie: not HTML Document node) */
2120    if (!node.hasAttribute) {
2121      return false;
2122    }
2123
2124    /* Check label or description attributes */
2125    return node.hasAttribute('aria-label') || node.hasAttribute('aria-labelledby') || node.hasAttribute('aria-describedby');
2126  }
2127
2128  /**
2129   * Check if expected element's parent has aria label attribute and has valid role and tagName
2130   * @param element
2131   * @param {optional} level Number of levels deep search should be performed
2132   */
2133  function parentHasAriaLabel(element, level) {
2134    level = level || 1;
2135    var node = angular.element(element)[0] || element;
2136    if (!node.parentNode) {
2137      return false;
2138    }
2139    if (performCheck(node.parentNode)) {
2140      return true;
2141    }
2142    level--;
2143    if (level) {
2144      return parentHasAriaLabel(node.parentNode, level);
2145    }
2146    return false;
2147
2148    function performCheck(parentNode) {
2149      if (!hasAriaLabel(parentNode)) {
2150        return false;
2151      }
2152      /* Perform role blacklist check */
2153      if (parentNode.hasAttribute('role')) {
2154        switch(parentNode.getAttribute('role').toLowerCase()) {
2155          case 'command':
2156          case 'definition':
2157          case 'directory':
2158          case 'grid':
2159          case 'list':
2160          case 'listitem':
2161          case 'log':
2162          case 'marquee':
2163          case 'menu':
2164          case 'menubar':
2165          case 'note':
2166          case 'presentation':
2167          case 'separator':
2168          case 'scrollbar':
2169          case 'status':
2170          case 'tablist':
2171            return false;
2172        }
2173      }
2174      /* Perform tagName blacklist check */
2175      switch(parentNode.tagName.toLowerCase()) {
2176        case 'abbr':
2177        case 'acronym':
2178        case 'address':
2179        case 'applet':
2180        case 'audio':
2181        case 'b':
2182        case 'bdi':
2183        case 'bdo':
2184        case 'big':
2185        case 'blockquote':
2186        case 'br':
2187        case 'canvas':
2188        case 'caption':
2189        case 'center':
2190        case 'cite':
2191        case 'code':
2192        case 'col':
2193        case 'data':
2194        case 'dd':
2195        case 'del':
2196        case 'dfn':
2197        case 'dir':
2198        case 'div':
2199        case 'dl':
2200        case 'em':
2201        case 'embed':
2202        case 'fieldset':
2203        case 'figcaption':
2204        case 'font':
2205        case 'h1':
2206        case 'h2':
2207        case 'h3':
2208        case 'h4':
2209        case 'h5':
2210        case 'h6':
2211        case 'hgroup':
2212        case 'html':
2213        case 'i':
2214        case 'ins':
2215        case 'isindex':
2216        case 'kbd':
2217        case 'keygen':
2218        case 'label':
2219        case 'legend':
2220        case 'li':
2221        case 'map':
2222        case 'mark':
2223        case 'menu':
2224        case 'object':
2225        case 'ol':
2226        case 'output':
2227        case 'pre':
2228        case 'presentation':
2229        case 'q':
2230        case 'rt':
2231        case 'ruby':
2232        case 'samp':
2233        case 'small':
2234        case 'source':
2235        case 'span':
2236        case 'status':
2237        case 'strike':
2238        case 'strong':
2239        case 'sub':
2240        case 'sup':
2241        case 'svg':
2242        case 'tbody':
2243        case 'td':
2244        case 'th':
2245        case 'thead':
2246        case 'time':
2247        case 'tr':
2248        case 'track':
2249        case 'tt':
2250        case 'ul':
2251        case 'var':
2252          return false;
2253      }
2254      return true;
2255    }
2256  }
2257}
2258
2259})();
2260(function(){
2261"use strict";
2262
2263/**
2264 * @ngdoc module
2265 * @name material.core.compiler
2266 * @description
2267 * AngularJS Material template and element compiler.
2268 */
2269angular
2270  .module('material.core')
2271  .provider('$mdCompiler', MdCompilerProvider);
2272
2273/**
2274 * @ngdoc provider
2275 * @name $mdCompilerProvider
2276 *
2277 * @description
2278 */
2279/**
2280 * @ngdoc service
2281 * @name $mdCompiler
2282 * @module material.core.compiler
2283 * @description
2284 * The $mdCompiler service is an abstraction of AngularJS's compiler, that allows developers
2285 * to easily compile an element with options like in a Directive Definition Object.
2286 *
2287 * > The compiler powers a lot of components inside of AngularJS Material.
2288 * > Like the `$mdPanel` or `$mdDialog`.
2289 *
2290 * @usage
2291 *
2292 * Basic Usage with a template
2293 *
2294 * <hljs lang="js">
2295 *   $mdCompiler.compile({
2296 *     templateUrl: 'modal.html',
2297 *     controller: 'ModalCtrl',
2298 *     locals: {
2299 *       modal: myModalInstance;
2300 *     }
2301 *   }).then(function (compileData) {
2302 *     compileData.element; // Compiled DOM element
2303 *     compileData.link(myScope); // Instantiate controller and link element to scope.
2304 *   });
2305 * </hljs>
2306 *
2307 * Example with a content element
2308 *
2309 * <hljs lang="js">
2310 *
2311 *   // Create a virtual element and link it manually.
2312 *   // The compiler doesn't need to recompile the element each time.
2313 *   var myElement = $compile('<span>Test</span>')(myScope);
2314 *
2315 *   $mdCompiler.compile({
2316 *     contentElement: myElement
2317 *   }).then(function (compileData) {
2318 *     compileData.element // Content Element (same as above)
2319 *     compileData.link // This does nothing when using a contentElement.
2320 *   });
2321 * </hljs>
2322 *
2323 * > Content Element is a significant performance improvement when the developer already knows that the
2324 * > compiled element will be always the same and the scope will not change either.
2325 *
2326 * The `contentElement` option also supports DOM elements which will be temporary removed and restored
2327 * at its old position.
2328 *
2329 * <hljs lang="js">
2330 *   var domElement = document.querySelector('#myElement');
2331 *
2332 *   $mdCompiler.compile({
2333 *     contentElement: myElement
2334 *   }).then(function (compileData) {
2335 *     compileData.element // Content Element (same as above)
2336 *     compileData.link // This does nothing when using a contentElement.
2337 *   });
2338 * </hljs>
2339 *
2340 * The `$mdCompiler` can also query for the element in the DOM itself.
2341 *
2342 * <hljs lang="js">
2343 *   $mdCompiler.compile({
2344 *     contentElement: '#myElement'
2345 *   }).then(function (compileData) {
2346 *     compileData.element // Content Element (same as above)
2347 *     compileData.link // This does nothing when using a contentElement.
2348 *   });
2349 * </hljs>
2350 *
2351 */
2352MdCompilerProvider.$inject = ['$compileProvider'];
2353function MdCompilerProvider($compileProvider) {
2354
2355  var provider = this;
2356
2357  /**
2358   * @ngdoc method
2359   * @name  $mdCompilerProvider#respectPreAssignBindingsEnabled
2360   *
2361   * @param {boolean=} respected update the respectPreAssignBindingsEnabled state if provided, otherwise just return
2362   * the current Material preAssignBindingsEnabled state
2363   * @returns {*} current value if used as getter or itself (chaining) if used as setter
2364   *
2365   * @kind function
2366   *
2367   * @description
2368   * Call this method to enable/disable whether Material-specific (dialogs/toasts) controllers respect the AngularJS
2369   * `$compile.preAssignBindingsEnabled` flag. Note that this doesn't affect directives/components created via
2370   * regular AngularJS methods which constitute most Material & user-created components.
2371   *
2372   * @see [AngularJS documentation for `$compile.preAssignBindingsEnabled`
2373   * ](https://code.angularjs.org/1.6.4/docs/api/ng/provider/$compileProvider#preAssignBindingsEnabled)
2374   * for more information.
2375   *
2376   * If disabled (false), the compiler assigns the value of each of the bindings to the
2377   * properties of the controller object before the constructor of this object is called.
2378   *
2379   * If enabled (true) the behavior depends on the AngularJS version used:
2380   *
2381   *     - `<1.5.10` - bindings are pre-assigned
2382   *     - `>=1.5.10 <1.7` - behaves like set to whatever `$compileProvider.preAssignBindingsEnabled()` reports; if
2383   *                         the `$compileProvider` flag wasn't set manually, it defaults to pre-assigning bindings
2384   *                         with AngularJS `1.5.x` & to calling the constructor first with AngularJS `1.6.x`.
2385   *     - `>=1.7` - the compiler calls the constructor first before assigning bindings
2386   *
2387   * The default value is `false` but will change to `true` in AngularJS Material 1.2.
2388   *
2389   * It is recommended to set this flag to `true` in AngularJS Material 1.1.x; the only reason it's not set that way
2390   * by default is backwards compatibility. Not setting the flag to `true` when
2391   * `$compileProvider.preAssignBindingsEnabled()` is set to `false` (i.e. default behavior in AngularJS 1.6.0 or newer)
2392   * makes it hard to unit test Material Dialog/Toast controllers using the `$controller` helper as it always follows
2393   * the `$compileProvider.preAssignBindingsEnabled()` value.
2394   */
2395  // TODO change it to `true` in Material 1.2.
2396  var respectPreAssignBindingsEnabled = false;
2397  this.respectPreAssignBindingsEnabled = function(respected) {
2398    if (angular.isDefined(respected)) {
2399      respectPreAssignBindingsEnabled = respected;
2400      return this;
2401    }
2402
2403    return respectPreAssignBindingsEnabled;
2404  };
2405
2406  /**
2407   * @ngdoc function
2408   * @name  getPreAssignBindingsEnabled
2409   * @private
2410   *
2411   * @returns {*} current preAssignBindingsEnabled state
2412   *
2413   * @kind function
2414   *
2415   * @description
2416   * This function returns `true` if Material-specific (dialogs/toasts) controllers have bindings pre-assigned in
2417   * controller constructors and `false` otherwise.
2418   *
2419   * Note that this doesn't affect directives/components created via regular AngularJS methods which constitute most
2420   * Material & user-created components; their behavior can be checked via `$compileProvider.preAssignBindingsEnabled()`
2421   * in AngularJS `>=1.5.10 <1.7.0`.
2422   */
2423  function getPreAssignBindingsEnabled() {
2424    if (!respectPreAssignBindingsEnabled) {
2425      // respectPreAssignBindingsEnabled === false
2426      // We're ignoring the AngularJS `$compileProvider.preAssignBindingsEnabled()` value in this case.
2427      return true;
2428    }
2429
2430    // respectPreAssignBindingsEnabled === true
2431
2432    if (typeof $compileProvider.preAssignBindingsEnabled === 'function') {
2433      return $compileProvider.preAssignBindingsEnabled();
2434    }
2435
2436    // Flag respected but not present => apply logic based on AngularJS version used.
2437    if (angular.version.major === 1 && angular.version.minor < 6) {
2438      // AngularJS <1.5.10
2439      return true;
2440    }
2441
2442    // AngularJS >=1.7.0
2443    return false;
2444  }
2445
2446  this.$get = ["$q", "$templateRequest", "$injector", "$compile", "$controller",
2447    function($q, $templateRequest, $injector, $compile, $controller) {
2448      return new MdCompilerService($q, $templateRequest, $injector, $compile, $controller);
2449    }];
2450
2451  function MdCompilerService($q, $templateRequest, $injector, $compile, $controller) {
2452
2453    /** @private @const {!angular.$q} */
2454    this.$q = $q;
2455
2456    /** @private @const {!angular.$templateRequest} */
2457    this.$templateRequest = $templateRequest;
2458
2459    /** @private @const {!angular.$injector} */
2460    this.$injector = $injector;
2461
2462    /** @private @const {!angular.$compile} */
2463    this.$compile = $compile;
2464
2465    /** @private @const {!angular.$controller} */
2466    this.$controller = $controller;
2467  }
2468
2469  /**
2470   * @ngdoc method
2471   * @name $mdCompiler#compile
2472   * @description
2473   *
2474   * A method to compile a HTML template with the AngularJS compiler.
2475   * The `$mdCompiler` is wrapper around the AngularJS compiler and provides extra functionality
2476   * like controller instantiation or async resolves.
2477   *
2478   * @param {!Object} options An options object, with the following properties:
2479   *
2480   *    - `controller` - `{string|Function}` Controller fn that should be associated with
2481   *         newly created scope or the name of a registered controller if passed as a string.
2482   *    - `controllerAs` - `{string=}` A controller alias name. If present the controller will be
2483   *         published to scope under the `controllerAs` name.
2484   *    - `contentElement` - `{string|Element}`: Instead of using a template, which will be
2485   *         compiled each time, you can also use a DOM element.<br/>
2486   *    - `template` - `{string=}` An html template as a string.
2487   *    - `templateUrl` - `{string=}` A path to an html template.
2488   *    - `transformTemplate` - `{function(template)=}` A function which transforms the template after
2489   *        it is loaded. It will be given the template string as a parameter, and should
2490   *        return a a new string representing the transformed template.
2491   *    - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
2492   *        be injected into the controller. If any of these dependencies are promises, the compiler
2493   *        will wait for them all to be resolved, or if one is rejected before the controller is
2494   *        instantiated `compile()` will fail..
2495   *      * `key` - `{string}`: a name of a dependency to be injected into the controller.
2496   *      * `factory` - `{string|function}`: If `string` then it is an alias for a service.
2497   *        Otherwise if function, then it is injected and the return value is treated as the
2498   *        dependency. If the result is a promise, it is resolved before its value is
2499   *        injected into the controller.
2500   *
2501   * @returns {Object} promise A promise, which will be resolved with a `compileData` object.
2502   * `compileData` has the following properties:
2503   *
2504   *   - `element` - `{element}`: an uncompiled element matching the provided template.
2505   *   - `link` - `{function(scope)}`: A link function, which, when called, will compile
2506   *     the element and instantiate the provided controller (if given).
2507   *   - `locals` - `{object}`: The locals which will be passed into the controller once `link` is
2508   *     called. If `bindToController` is true, they will be coppied to the ctrl instead
2509   *
2510   */
2511  MdCompilerService.prototype.compile = function(options) {
2512
2513    if (options.contentElement) {
2514      return this._prepareContentElement(options);
2515    } else {
2516      return this._compileTemplate(options);
2517    }
2518
2519  };
2520
2521  /**
2522   * Instead of compiling any template, the compiler just fetches an existing HTML element from the DOM and
2523   * provides a restore function to put the element back it old DOM position.
2524   * @param {!Object} options Options to be used for the compiler.
2525   * @private
2526   */
2527  MdCompilerService.prototype._prepareContentElement = function(options) {
2528
2529    var contentElement = this._fetchContentElement(options);
2530
2531    return this.$q.resolve({
2532      element: contentElement.element,
2533      cleanup: contentElement.restore,
2534      locals: {},
2535      link: function() {
2536        return contentElement.element;
2537      }
2538    });
2539
2540  };
2541
2542  /**
2543   * Compiles a template by considering all options and waiting for all resolves to be ready.
2544   * @param {!Object} options Compile options
2545   * @returns {!Object} Compile data with link function.
2546   * @private
2547   */
2548  MdCompilerService.prototype._compileTemplate = function(options) {
2549
2550    var self = this;
2551    var templateUrl = options.templateUrl;
2552    var template = options.template || '';
2553    var resolve = angular.extend({}, options.resolve);
2554    var locals = angular.extend({}, options.locals);
2555    var transformTemplate = options.transformTemplate || angular.identity;
2556
2557    // Take resolve values and invoke them.
2558    // Resolves can either be a string (value: 'MyRegisteredAngularConst'),
2559    // or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {})
2560    angular.forEach(resolve, function(value, key) {
2561      if (angular.isString(value)) {
2562        resolve[key] = self.$injector.get(value);
2563      } else {
2564        resolve[key] = self.$injector.invoke(value);
2565      }
2566    });
2567
2568    // Add the locals, which are just straight values to inject
2569    // eg locals: { three: 3 }, will inject three into the controller
2570    angular.extend(resolve, locals);
2571
2572    if (templateUrl) {
2573      resolve.$$ngTemplate = this.$templateRequest(templateUrl);
2574    } else {
2575      resolve.$$ngTemplate = this.$q.when(template);
2576    }
2577
2578
2579    // Wait for all the resolves to finish if they are promises
2580    return this.$q.all(resolve).then(function(locals) {
2581
2582      var template = transformTemplate(locals.$$ngTemplate, options);
2583      var element = options.element || angular.element('<div>').html(template.trim()).contents();
2584
2585      return self._compileElement(locals, element, options);
2586    });
2587
2588  };
2589
2590  /**
2591   * Method to compile an element with the given options.
2592   * @param {!Object} locals Locals to be injected to the controller if present
2593   * @param {!JQLite} element Element to be compiled and linked
2594   * @param {!Object} options Options to be used for linking.
2595   * @returns {!Object} Compile data with link function.
2596   * @private
2597   */
2598  MdCompilerService.prototype._compileElement = function(locals, element, options) {
2599    var self = this;
2600    var ngLinkFn = this.$compile(element);
2601
2602    var compileData = {
2603      element: element,
2604      cleanup: element.remove.bind(element),
2605      locals: locals,
2606      link: linkFn
2607    };
2608
2609    function linkFn(scope) {
2610      locals.$scope = scope;
2611
2612      // Instantiate controller if the developer provided one.
2613      if (options.controller) {
2614
2615        var injectLocals = angular.extend({}, locals, {
2616          $element: element
2617        });
2618
2619        // Create the specified controller instance.
2620        var ctrl = self._createController(options, injectLocals, locals);
2621
2622        // Unique identifier for AngularJS Route ngView controllers.
2623        element.data('$ngControllerController', ctrl);
2624        element.children().data('$ngControllerController', ctrl);
2625
2626        // Expose the instantiated controller to the compile data
2627        compileData.controller = ctrl;
2628      }
2629
2630      // Invoke the AngularJS $compile link function.
2631      return ngLinkFn(scope);
2632    }
2633
2634    return compileData;
2635
2636  };
2637
2638  /**
2639   * Creates and instantiates a new controller with the specified options.
2640   * @param {!Object} options Options that include the controller
2641   * @param {!Object} injectLocals Locals to to be provided in the controller DI.
2642   * @param {!Object} locals Locals to be injected to the controller.
2643   * @returns {!Object} Created controller instance.
2644   * @private
2645   */
2646  MdCompilerService.prototype._createController = function(options, injectLocals, locals) {
2647    var invokeCtrl = this.$controller(options.controller, injectLocals, true, options.controllerAs);
2648
2649    if (getPreAssignBindingsEnabled() && options.bindToController) {
2650      angular.extend(invokeCtrl.instance, locals);
2651    }
2652
2653    // Instantiate and initialize the specified controller.
2654    var ctrl = invokeCtrl();
2655
2656    if (!getPreAssignBindingsEnabled() && options.bindToController) {
2657      angular.extend(invokeCtrl.instance, locals);
2658    }
2659
2660    // Call the $onInit hook if it's present on the controller.
2661    angular.isFunction(ctrl.$onInit) && ctrl.$onInit();
2662
2663    return ctrl;
2664  };
2665
2666  /**
2667   * Fetches an element removing it from the DOM and using it temporary for the compiler.
2668   * Elements which were fetched will be restored after use.
2669   * @param {!Object} options Options to be used for the compilation.
2670   * @returns {{element: !JQLite, restore: !Function}}
2671   * @private
2672   */
2673  MdCompilerService.prototype._fetchContentElement = function(options) {
2674
2675    var contentEl = options.contentElement;
2676    var restoreFn = null;
2677
2678    if (angular.isString(contentEl)) {
2679      contentEl = document.querySelector(contentEl);
2680      restoreFn = createRestoreFn(contentEl);
2681    } else {
2682      contentEl = contentEl[0] || contentEl;
2683
2684      // When the element is visible in the DOM, then we restore it at close of the dialog.
2685      // Otherwise it will be removed from the DOM after close.
2686      if (document.contains(contentEl)) {
2687        restoreFn = createRestoreFn(contentEl);
2688      } else {
2689        restoreFn = function() {
2690          if (contentEl.parentNode) {
2691            contentEl.parentNode.removeChild(contentEl);
2692          }
2693        }
2694      }
2695    }
2696
2697    return {
2698      element: angular.element(contentEl),
2699      restore: restoreFn
2700    };
2701
2702    function createRestoreFn(element) {
2703      var parent = element.parentNode;
2704      var nextSibling = element.nextElementSibling;
2705
2706      return function() {
2707        if (!nextSibling) {
2708          // When the element didn't had any sibling, then it can be simply appended to the
2709          // parent, because it plays no role, which index it had before.
2710          parent.appendChild(element);
2711        } else {
2712          // When the element had a sibling, which marks the previous position of the element
2713          // in the DOM, we insert it correctly before the sibling, to have the same index as
2714          // before.
2715          parent.insertBefore(element, nextSibling);
2716        }
2717      }
2718    }
2719  };
2720}
2721
2722
2723})();
2724(function(){
2725"use strict";
2726
2727
2728MdGesture.$inject = ["$$MdGestureHandler", "$$rAF", "$timeout"];
2729attachToDocument.$inject = ["$mdGesture", "$$MdGestureHandler"];var HANDLERS = {};
2730
2731/* The state of the current 'pointer'
2732 * The pointer represents the state of the current touch.
2733 * It contains normalized x and y coordinates from DOM events,
2734 * as well as other information abstracted from the DOM.
2735 */
2736
2737var pointer, lastPointer, forceSkipClickHijack = false, maxClickDistance = 6;
2738
2739/**
2740 * The position of the most recent click if that click was on a label element.
2741 * @type {{x: number, y: number}?}
2742 */
2743var lastLabelClickPos = null;
2744
2745// Used to attach event listeners once when multiple ng-apps are running.
2746var isInitialized = false;
2747
2748angular
2749  .module('material.core.gestures', [ ])
2750  .provider('$mdGesture', MdGestureProvider)
2751  .factory('$$MdGestureHandler', MdGestureHandler)
2752  .run(attachToDocument );
2753
2754/**
2755   * @ngdoc service
2756   * @name $mdGestureProvider
2757   * @module material.core.gestures
2758   *
2759   * @description
2760   * In some scenarios on Mobile devices (without jQuery), the click events should NOT be hijacked.
2761   * `$mdGestureProvider` is used to configure the Gesture module to ignore or skip click hijacking on mobile
2762   * devices.
2763   * You can also change max click distance (6px by default) if you have issues on some touch screens.
2764   *
2765   * <hljs lang="js">
2766   *   app.config(function($mdGestureProvider) {
2767   *
2768   *     // For mobile devices without jQuery loaded, do not
2769   *     // intercept click events during the capture phase.
2770   *     $mdGestureProvider.skipClickHijack();
2771   *
2772   *     // If hijcacking clicks, change default 6px click distance
2773   *     $mdGestureProvider.setMaxClickDistance(12);
2774   *
2775   *   });
2776   * </hljs>
2777   *
2778   */
2779function MdGestureProvider() { }
2780
2781MdGestureProvider.prototype = {
2782
2783  // Publish access to setter to configure a variable  BEFORE the
2784  // $mdGesture service is instantiated...
2785  skipClickHijack: function() {
2786    return forceSkipClickHijack = true;
2787  },
2788
2789  setMaxClickDistance: function(clickDistance) {
2790    maxClickDistance = parseInt(clickDistance);
2791  },
2792
2793  /**
2794   * $get is used to build an instance of $mdGesture
2795   * @ngInject
2796   */
2797  $get : ["$$MdGestureHandler", "$$rAF", "$timeout", function($$MdGestureHandler, $$rAF, $timeout) {
2798       return new MdGesture($$MdGestureHandler, $$rAF, $timeout);
2799  }]
2800};
2801
2802
2803
2804/**
2805 * MdGesture factory construction function
2806 * @ngInject
2807 */
2808function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
2809  var userAgent = navigator.userAgent || navigator.vendor || window.opera;
2810  var isIos = userAgent.match(/ipad|iphone|ipod/i);
2811  var isAndroid = userAgent.match(/android/i);
2812  var touchActionProperty = getTouchAction();
2813  var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
2814
2815  var self = {
2816    handler: addHandler,
2817    register: register,
2818    // On mobile w/out jQuery, we normally intercept clicks. Should we skip that?
2819    isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack
2820  };
2821
2822  if (self.isHijackingClicks) {
2823    self.handler('click', {
2824      options: {
2825        maxDistance: maxClickDistance
2826      },
2827      onEnd: checkDistanceAndEmit('click')
2828    });
2829
2830    self.handler('focus', {
2831      options: {
2832        maxDistance: maxClickDistance
2833      },
2834      onEnd: function(ev, pointer) {
2835        if (pointer.distance < this.state.options.maxDistance && canFocus(ev.target)) {
2836          this.dispatchEvent(ev, 'focus', pointer);
2837          ev.target.focus();
2838        }
2839      }
2840    });
2841
2842    self.handler('mouseup', {
2843      options: {
2844        maxDistance: maxClickDistance
2845      },
2846      onEnd: checkDistanceAndEmit('mouseup')
2847    });
2848
2849    self.handler('mousedown', {
2850      onStart: function(ev) {
2851        this.dispatchEvent(ev, 'mousedown');
2852      }
2853    });
2854  }
2855
2856  function checkDistanceAndEmit(eventName) {
2857    return function(ev, pointer) {
2858      if (pointer.distance < this.state.options.maxDistance) {
2859        this.dispatchEvent(ev, eventName, pointer);
2860      }
2861    };
2862  }
2863
2864  /*
2865   * Register an element to listen for a handler.
2866   * This allows an element to override the default options for a handler.
2867   * Additionally, some handlers like drag and hold only dispatch events if
2868   * the domEvent happens inside an element that's registered to listen for these events.
2869   *
2870   * @see GestureHandler for how overriding of default options works.
2871   * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horziontal: false })
2872   */
2873  function register(element, handlerName, options) {
2874    var handler = HANDLERS[handlerName.replace(/^\$md./, '')];
2875    if (!handler) {
2876      throw new Error('Failed to register element with handler ' + handlerName + '. ' +
2877      'Available handlers: ' + Object.keys(HANDLERS).join(', '));
2878    }
2879    return handler.registerElement(element, options);
2880  }
2881
2882  /*
2883   * add a handler to $mdGesture. see below.
vendor: 21,626 bytes, lines 2884-3502
2884   */
2885  function addHandler(name, definition) {
2886    var handler = new $$MdGestureHandler(name);
2887    angular.extend(handler, definition);
2888    HANDLERS[name] = handler;
2889
2890    return self;
2891  }
2892
2893  /*
2894   * Register handlers. These listen to touch/start/move events, interpret them,
2895   * and dispatch gesture events depending on options & conditions. These are all
2896   * instances of GestureHandler.
2897   * @see GestureHandler
2898   */
2899  return self
2900    /*
2901     * The press handler dispatches an event on touchdown/touchend.
2902     * It's a simple abstraction of touch/mouse/pointer start and end.
2903     */
2904    .handler('press', {
2905      onStart: function (ev, pointer) {
2906        this.dispatchEvent(ev, '$md.pressdown');
2907      },
2908      onEnd: function (ev, pointer) {
2909        this.dispatchEvent(ev, '$md.pressup');
2910      }
2911    })
2912
2913    /*
2914     * The hold handler dispatches an event if the user keeps their finger within
2915     * the same <maxDistance> area for <delay> ms.
2916     * The hold handler will only run if a parent of the touch target is registered
2917     * to listen for hold events through $mdGesture.register()
2918     */
2919    .handler('hold', {
2920      options: {
2921        maxDistance: 6,
2922        delay: 500
2923      },
2924      onCancel: function () {
2925        $timeout.cancel(this.state.timeout);
2926      },
2927      onStart: function (ev, pointer) {
2928        // For hold, require a parent to be registered with $mdGesture.register()
2929        // Because we prevent scroll events, this is necessary.
2930        if (!this.state.registeredParent) return this.cancel();
2931
2932        this.state.pos = {x: pointer.x, y: pointer.y};
2933        this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() {
2934          this.dispatchEvent(ev, '$md.hold');
2935          this.cancel(); //we're done!
2936        }), this.state.options.delay, false);
2937      },
2938      onMove: function (ev, pointer) {
2939        // Don't scroll while waiting for hold.
2940        // If we don't preventDefault touchmove events here, Android will assume we don't
2941        // want to listen to anymore touch events. It will start scrolling and stop sending
2942        // touchmove events.
2943        if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
2944
2945        // If the user moves greater than <maxDistance> pixels, stop the hold timer
2946        // set in onStart
2947        var dx = this.state.pos.x - pointer.x;
2948        var dy = this.state.pos.y - pointer.y;
2949        if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) {
2950          this.cancel();
2951        }
2952      },
2953      onEnd: function () {
2954        this.onCancel();
2955      }
2956    })
2957
2958    /*
2959     * The drag handler dispatches a drag event if the user holds and moves his finger greater than
2960     * <minDistance> px in the x or y direction, depending on options.horizontal.
2961     * The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in
2962     * the perpendicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>
2963     * pixels vertically, this handler won't consider the move part of a drag.
2964     */
2965    .handler('drag', {
2966      options: {
2967        minDistance: 6,
2968        horizontal: true,
2969        cancelMultiplier: 1.5
2970      },
2971      onSetup: function(element, options) {
2972        if (touchActionProperty) {
2973          // We check for horizontal to be false, because otherwise we would overwrite the default opts.
2974          this.oldTouchAction = element[0].style[touchActionProperty];
2975          element[0].style[touchActionProperty] = options.horizontal ? 'pan-y' : 'pan-x';
2976        }
2977      },
2978      onCleanup: function(element) {
2979        if (this.oldTouchAction) {
2980          element[0].style[touchActionProperty] = this.oldTouchAction;
2981        }
2982      },
2983      onStart: function (ev) {
2984        // For drag, require a parent to be registered with $mdGesture.register()
2985        if (!this.state.registeredParent) this.cancel();
2986      },
2987      onMove: function (ev, pointer) {
2988        var shouldStartDrag, shouldCancel;
2989        // Don't scroll while deciding if this touchmove qualifies as a drag event.
2990        // If we don't preventDefault touchmove events here, Android will assume we don't
2991        // want to listen to anymore touch events. It will start scrolling and stop sending
2992        // touchmove events.
2993        if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
2994
2995        if (!this.state.dragPointer) {
2996          if (this.state.options.horizontal) {
2997            shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance;
2998            shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier;
2999          } else {
3000            shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance;
3001            shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier;
3002          }
3003
3004          if (shouldStartDrag) {
3005            // Create a new pointer representing this drag, starting at this point where the drag started.
3006            this.state.dragPointer = makeStartPointer(ev);
3007            updatePointerState(ev, this.state.dragPointer);
3008            this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer);
3009
3010          } else if (shouldCancel) {
3011            this.cancel();
3012          }
3013        } else {
3014          this.dispatchDragMove(ev);
3015        }
3016      },
3017      // Only dispatch dragmove events every frame; any more is unnecessary
3018      dispatchDragMove: $$rAF.throttle(function (ev) {
3019        // Make sure the drag didn't stop while waiting for the next frame
3020        if (this.state.isRunning) {
3021          updatePointerState(ev, this.state.dragPointer);
3022          this.dispatchEvent(ev, '$md.drag', this.state.dragPointer);
3023        }
3024      }),
3025      onEnd: function (ev, pointer) {
3026        if (this.state.dragPointer) {
3027          updatePointerState(ev, this.state.dragPointer);
3028          this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer);
3029        }
3030      }
3031    })
3032
3033    /*
3034     * The swipe handler will dispatch a swipe event if, on the end of a touch,
3035     * the velocity and distance were high enough.
3036     */
3037    .handler('swipe', {
3038      options: {
3039        minVelocity: 0.65,
3040        minDistance: 10
3041      },
3042      onEnd: function (ev, pointer) {
3043        var eventType;
3044
3045        if (Math.abs(pointer.velocityX) > this.state.options.minVelocity &&
3046          Math.abs(pointer.distanceX) > this.state.options.minDistance) {
3047          eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight';
3048          this.dispatchEvent(ev, eventType);
3049        }
3050        else if (Math.abs(pointer.velocityY) > this.state.options.minVelocity &&
3051          Math.abs(pointer.distanceY) > this.state.options.minDistance) {
3052          eventType = pointer.directionY == 'up' ? '$md.swipeup' : '$md.swipedown';
3053          this.dispatchEvent(ev, eventType);
3054        }
3055      }
3056    });
3057
3058  function getTouchAction() {
3059    var testEl = document.createElement('div');
3060    var vendorPrefixes = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
3061
3062    for (var i = 0; i < vendorPrefixes.length; i++) {
3063      var prefix = vendorPrefixes[i];
3064      var property = prefix ? prefix + 'TouchAction' : 'touchAction';
3065      if (angular.isDefined(testEl.style[property])) {
3066        return property;
3067      }
3068    }
3069  }
3070
3071}
3072
3073/**
3074 * MdGestureHandler
3075 * A GestureHandler is an object which is able to dispatch custom dom events
3076 * based on native dom {touch,pointer,mouse}{start,move,end} events.
3077 *
3078 * A gesture will manage its lifecycle through the start,move,end, and cancel
3079 * functions, which are called by native dom events.
3080 *
3081 * A gesture has the concept of 'options' (eg a swipe's required velocity), which can be
3082 * overridden by elements registering through $mdGesture.register()
3083 */
3084function GestureHandler (name) {
3085  this.name = name;
3086  this.state = {};
3087}
3088
3089function MdGestureHandler() {
3090  var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
3091
3092  GestureHandler.prototype = {
3093    options: {},
3094    // jQuery listeners don't work with custom DOMEvents, so we have to dispatch events
3095    // differently when jQuery is loaded
3096    dispatchEvent: hasJQuery ?  jQueryDispatchEvent : nativeDispatchEvent,
3097
3098    // These are overridden by the registered handler
3099    onSetup: angular.noop,
3100    onCleanup: angular.noop,
3101    onStart: angular.noop,
3102    onMove: angular.noop,
3103    onEnd: angular.noop,
3104    onCancel: angular.noop,
3105
3106    // onStart sets up a new state for the handler, which includes options from the
3107    // nearest registered parent element of ev.target.
3108    start: function (ev, pointer) {
3109      if (this.state.isRunning) return;
3110      var parentTarget = this.getNearestParent(ev.target);
3111      // Get the options from the nearest registered parent
3112      var parentTargetOptions = parentTarget && parentTarget.$mdGesture[this.name] || {};
3113
3114      this.state = {
3115        isRunning: true,
3116        // Override the default options with the nearest registered parent's options
3117        options: angular.extend({}, this.options, parentTargetOptions),
3118        // Pass in the registered parent node to the state so the onStart listener can use
3119        registeredParent: parentTarget
3120      };
3121      this.onStart(ev, pointer);
3122    },
3123    move: function (ev, pointer) {
3124      if (!this.state.isRunning) return;
3125      this.onMove(ev, pointer);
3126    },
3127    end: function (ev, pointer) {
3128      if (!this.state.isRunning) return;
3129      this.onEnd(ev, pointer);
3130      this.state.isRunning = false;
3131    },
3132    cancel: function (ev, pointer) {
3133      this.onCancel(ev, pointer);
3134      this.state = {};
3135    },
3136
3137    // Find and return the nearest parent element that has been registered to
3138    // listen for this handler via $mdGesture.register(element, 'handlerName').
3139    getNearestParent: function (node) {
3140      var current = node;
3141      while (current) {
3142        if ((current.$mdGesture || {})[this.name]) {
3143          return current;
3144        }
3145        current = current.parentNode;
3146      }
3147      return null;
3148    },
3149
3150    // Called from $mdGesture.register when an element registers itself with a handler.
3151    // Store the options the user gave on the DOMElement itself. These options will
3152    // be retrieved with getNearestParent when the handler starts.
3153    registerElement: function (element, options) {
3154      var self = this;
3155      element[0].$mdGesture = element[0].$mdGesture || {};
3156      element[0].$mdGesture[this.name] = options || {};
3157      element.on('$destroy', onDestroy);
3158
3159      self.onSetup(element, options || {});
3160
3161      return onDestroy;
3162
3163      function onDestroy() {
3164        delete element[0].$mdGesture[self.name];
3165        element.off('$destroy', onDestroy);
3166
3167        self.onCleanup(element, options || {});
3168      }
3169    }
3170  };
3171
3172  return GestureHandler;
3173
3174  /*
3175   * Dispatch an event with jQuery
3176   * TODO: Make sure this sends bubbling events
3177   *
3178   * @param srcEvent the original DOM touch event that started this.
3179   * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
3180   * @param eventPointer the pointer object that matches this event.
3181   */
3182  function jQueryDispatchEvent(srcEvent, eventType, eventPointer) {
3183    eventPointer = eventPointer || pointer;
3184    var eventObj = new angular.element.Event(eventType);
3185
3186    eventObj.$material = true;
3187    eventObj.pointer = eventPointer;
3188    eventObj.srcEvent = srcEvent;
3189
3190    angular.extend(eventObj, {
3191      clientX: eventPointer.x,
3192      clientY: eventPointer.y,
3193      screenX: eventPointer.x,
3194      screenY: eventPointer.y,
3195      pageX: eventPointer.x,
3196      pageY: eventPointer.y,
3197      ctrlKey: srcEvent.ctrlKey,
3198      altKey: srcEvent.altKey,
3199      shiftKey: srcEvent.shiftKey,
3200      metaKey: srcEvent.metaKey
3201    });
3202    angular.element(eventPointer.target).trigger(eventObj);
3203  }
3204
3205  /*
3206   * NOTE: nativeDispatchEvent is very performance sensitive.
3207   * @param srcEvent the original DOM touch event that started this.
3208   * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
3209   * @param eventPointer the pointer object that matches this event.
3210   */
3211  function nativeDispatchEvent(srcEvent, eventType, eventPointer) {
3212    eventPointer = eventPointer || pointer;
3213    var eventObj;
3214
3215    if (eventType === 'click' || eventType == 'mouseup' || eventType == 'mousedown' ) {
3216      eventObj = document.createEvent('MouseEvents');
3217      eventObj.initMouseEvent(
3218        eventType, true, true, window, srcEvent.detail,
3219        eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y,
3220        srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey,
3221        srcEvent.button, srcEvent.relatedTarget || null
3222      );
3223
3224    } else {
3225      eventObj = document.createEvent('CustomEvent');
3226      eventObj.initCustomEvent(eventType, true, true, {});
3227    }
3228    eventObj.$material = true;
3229    eventObj.pointer = eventPointer;
3230    eventObj.srcEvent = srcEvent;
3231    eventPointer.target.dispatchEvent(eventObj);
3232  }
3233
3234}
3235
3236/**
3237 * Attach Gestures: hook document and check shouldHijack clicks
3238 * @ngInject
3239 */
3240function attachToDocument( $mdGesture, $$MdGestureHandler ) {
3241
3242  // Polyfill document.contains for IE11.
3243  // TODO: move to util
3244  document.contains || (document.contains = function (node) {
3245    return document.body.contains(node);
3246  });
3247
3248  if (!isInitialized && $mdGesture.isHijackingClicks ) {
3249    /*
3250     * If hijack clicks is true, we preventDefault any click that wasn't
3251     * sent by AngularJS Material. This is because on older Android & iOS, a false, or 'ghost',
3252     * click event will be sent ~400ms after a touchend event happens.
3253     * The only way to know if this click is real is to prevent any normal
3254     * click events, and add a flag to events sent by material so we know not to prevent those.
3255     *
3256     * Two exceptions to click events that should be prevented are:
3257     *  - click events sent by the keyboard (eg form submit)
3258     *  - events that originate from an Ionic app
3259     */
3260    document.addEventListener('click'    , clickHijacker     , true);
3261    document.addEventListener('mouseup'  , mouseInputHijacker, true);
3262    document.addEventListener('mousedown', mouseInputHijacker, true);
3263    document.addEventListener('focus'    , mouseInputHijacker, true);
3264
3265    isInitialized = true;
3266  }
3267
3268  function mouseInputHijacker(ev) {
3269    var isKeyClick = !ev.clientX && !ev.clientY;
3270
3271    if (
3272      !isKeyClick &&
3273      !ev.$material &&
3274      !ev.isIonicTap &&
3275      !isInputEventFromLabelClick(ev) &&
3276      (ev.type !== 'mousedown' || (!canFocus(ev.target) && !canFocus(document.activeElement)))
3277    ) {
3278      ev.preventDefault();
3279      ev.stopPropagation();
3280    }
3281  }
3282
3283  function clickHijacker(ev) {
3284    var isKeyClick = ev.clientX === 0 && ev.clientY === 0;
3285    var isSubmitEvent = ev.target && ev.target.type === 'submit';
3286    if (!isKeyClick && !ev.$material && !ev.isIonicTap
3287      && !isInputEventFromLabelClick(ev)
3288      && !isSubmitEvent) {
3289      ev.preventDefault();
3290      ev.stopPropagation();
3291      lastLabelClickPos = null;
3292    } else {
3293      lastLabelClickPos = null;
3294      if (ev.target.tagName.toLowerCase() == 'label') {
3295        lastLabelClickPos = {x: ev.x, y: ev.y};
3296      }
3297    }
3298  }
3299
3300
3301  // Listen to all events to cover all platforms.
3302  var START_EVENTS = 'mousedown touchstart pointerdown';
3303  var MOVE_EVENTS = 'mousemove touchmove pointermove';
3304  var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel';
3305
3306  angular.element(document)
3307    .on(START_EVENTS, gestureStart)
3308    .on(MOVE_EVENTS, gestureMove)
3309    .on(END_EVENTS, gestureEnd)
3310    // For testing
3311    .on('$$mdGestureReset', function gestureClearCache () {
3312      lastPointer = pointer = null;
3313    });
3314
3315  /*
3316   * When a DOM event happens, run all registered gesture handlers' lifecycle
3317   * methods which match the DOM event.
3318   * Eg when a 'touchstart' event happens, runHandlers('start') will call and
3319   * run `handler.cancel()` and `handler.start()` on all registered handlers.
3320   */
3321  function runHandlers(handlerEvent, event) {
3322    var handler;
3323    for (var name in HANDLERS) {
3324      handler = HANDLERS[name];
3325      if( handler instanceof $$MdGestureHandler ) {
3326
3327        if (handlerEvent === 'start') {
3328          // Run cancel to reset any handlers' state
3329          handler.cancel();
3330        }
3331        handler[handlerEvent](event, pointer);
3332
3333      }
3334    }
3335  }
3336
3337  /*
3338   * gestureStart vets if a start event is legitimate (and not part of a 'ghost click' from iOS/Android)
3339   * If it is legitimate, we initiate the pointer state and mark the current pointer's type
3340   * For example, for a touchstart event, mark the current pointer as a 'touch' pointer, so mouse events
3341   * won't effect it.
3342   */
3343  function gestureStart(ev) {
3344    // If we're already touched down, abort
3345    if (pointer) return;
3346
3347    var now = +Date.now();
3348
3349    // iOS & old android bug: after a touch event, a click event is sent 350 ms later.
3350    // If <400ms have passed, don't allow an event of a different type than the previous event
3351    if (lastPointer && !typesMatch(ev, lastPointer) && (now - lastPointer.endTime < 1500)) {
3352      return;
3353    }
3354
3355    pointer = makeStartPointer(ev);
3356
3357    runHandlers('start', ev);
3358  }
3359  /*
3360   * If a move event happens of the right type, update the pointer and run all the move handlers.
3361   * "of the right type": if a mousemove happens but our pointer started with a touch event, do nothing.
3362   */
3363  function gestureMove(ev) {
3364    if (!pointer || !typesMatch(ev, pointer)) return;
3365
3366    updatePointerState(ev, pointer);
3367    runHandlers('move', ev);
3368  }
3369  /*
3370   * If an end event happens of the right type, update the pointer, run endHandlers, and save the pointer as 'lastPointer'
3371   */
3372  function gestureEnd(ev) {
3373    if (!pointer || !typesMatch(ev, pointer)) return;
3374
3375    updatePointerState(ev, pointer);
3376    pointer.endTime = +Date.now();
3377
3378    if (ev.type !== 'pointercancel') {
3379      runHandlers('end', ev);
3380    }
3381
3382    lastPointer = pointer;
3383    pointer = null;
3384  }
3385
3386}
3387
3388// ********************
3389// Module Functions
3390// ********************
3391
3392/*
3393 * Initiate the pointer. x, y, and the pointer's type.
3394 */
3395function makeStartPointer(ev) {
3396  var point = getEventPoint(ev);
3397  var startPointer = {
3398    startTime: +Date.now(),
3399    target: ev.target,
3400    // 'p' for pointer events, 'm' for mouse, 't' for touch
3401    type: ev.type.charAt(0)
3402  };
3403  startPointer.startX = startPointer.x = point.pageX;
3404  startPointer.startY = startPointer.y = point.pageY;
3405  return startPointer;
3406}
3407
3408/*
3409 * return whether the pointer's type matches the event's type.
3410 * Eg if a touch event happens but the pointer has a mouse type, return false.
3411 */
3412function typesMatch(ev, pointer) {
3413  return ev && pointer && ev.type.charAt(0) === pointer.type;
3414}
3415
3416/**
3417 * Gets whether the given event is an input event that was caused by clicking on an
3418 * associated label element.
3419 *
3420 * This is necessary because the browser will, upon clicking on a label element, fire an
3421 * *extra* click event on its associated input (if any). mdGesture is able to flag the label
3422 * click as with `$material` correctly, but not the second input click.
3423 *
3424 * In order to determine whether an input event is from a label click, we compare the (x, y) for
3425 * the event to the (x, y) for the most recent label click (which is cleared whenever a non-label
3426 * click occurs). Unfortunately, there are no event properties that tie the input and the label
3427 * together (such as relatedTarget).
3428 *
3429 * @param {MouseEvent} event
3430 * @returns {boolean}
3431 */
3432function isInputEventFromLabelClick(event) {
3433  return lastLabelClickPos
3434      && lastLabelClickPos.x == event.x
3435      && lastLabelClickPos.y == event.y;
3436}
3437
3438/*
3439 * Update the given pointer based upon the given DOMEvent.
3440 * Distance, velocity, direction, duration, etc
3441 */
3442function updatePointerState(ev, pointer) {
3443  var point = getEventPoint(ev);
3444  var x = pointer.x = point.pageX;
3445  var y = pointer.y = point.pageY;
3446
3447  pointer.distanceX = x - pointer.startX;
3448  pointer.distanceY = y - pointer.startY;
3449  pointer.distance = Math.sqrt(
3450    pointer.distanceX * pointer.distanceX + pointer.distanceY * pointer.distanceY
3451  );
3452
3453  pointer.directionX = pointer.distanceX > 0 ? 'right' : pointer.distanceX < 0 ? 'left' : '';
3454  pointer.directionY = pointer.distanceY > 0 ? 'down' : pointer.distanceY < 0 ? 'up' : '';
3455
3456  pointer.duration = +Date.now() - pointer.startTime;
3457  pointer.velocityX = pointer.distanceX / pointer.duration;
3458  pointer.velocityY = pointer.distanceY / pointer.duration;
3459}
3460
3461/*
3462 * Normalize the point where the DOM event happened whether it's touch or mouse.
3463 * @returns point event obj with pageX and pageY on it.
3464 */
3465function getEventPoint(ev) {
3466  ev = ev.originalEvent || ev; // support jQuery events
3467  return (ev.touches && ev.touches[0]) ||
3468    (ev.changedTouches && ev.changedTouches[0]) ||
3469    ev;
3470}
3471
3472/** Checks whether an element can be focused. */
3473function canFocus(element) {
3474  return (
3475    !!element &&
3476    element.getAttribute('tabindex') != '-1' &&
3477    !element.hasAttribute('disabled') &&
3478    (
3479      element.hasAttribute('tabindex') ||
3480      element.hasAttribute('href') ||
3481      element.isContentEditable ||
3482      ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA', 'VIDEO', 'AUDIO'].indexOf(element.nodeName) != -1
3483    )
3484  );
3485}
3486
3487})();
3488(function(){
3489"use strict";
3490
3491/**
3492 * @ngdoc module
3493 * @name material.core.interaction
3494 * @description
3495 * User interaction detection to provide proper accessibility.
3496 */
3497MdInteractionService.$inject = ["$timeout", "$mdUtil"];
3498angular
3499  .module('material.core.interaction', [])
3500  .service('$mdInteraction', MdInteractionService);
3501
3502
3503/**
3504 * @ngdoc service
3505 * @name $mdInteraction
3506 * @module material.core.interaction
3507 *
3508 * @description
3509 *
3510 * Service which keeps track of the last interaction type and validates them for several browsers.
3511 * The service hooks into the document's body and listens for touch, mouse and keyboard events.
3512 *
3513 * The most recent interaction type can be retrieved by calling the `getLastInteractionType` method.
3514 *
3515 * Here is an example markup for using the interaction service.
3516 *
3517 * <hljs lang="js">
3518 *   var lastType = $mdInteraction.getLastInteractionType();
3519 *
3520 *   if (lastType === 'keyboard') {
3521 *     // We only restore the focus for keyboard users.
3522 *     restoreFocus();
3523 *   }
3524 * </hljs>
3525 *
3526 */
3527function MdInteractionService($timeout, $mdUtil) {
3528  this.$timeout = $timeout;
3529  this.$mdUtil = $mdUtil;
3530
3531  this.bodyElement = angular.element(document.body);
3532  this.isBuffering = false;
3533  this.bufferTimeout = null;
3534  this.lastInteractionType = null;
3535  this.lastInteractionTime = null;
3536
3537  // Type Mappings for the different events
3538  // There will be three three interaction types
3539  // `keyboard`, `mouse` and `touch`
3540  // type `pointer` will be evaluated in `pointerMap` for IE Browser events
3541  this.inputEventMap = {
3542    'keydown': 'keyboard',
3543    'mousedown': 'mouse',
3544    'mouseenter': 'mouse',
3545    'touchstart': 'touch',
3546    'pointerdown': 'pointer',
3547    'MSPointerDown': 'pointer'
3548  };
3549
3550  // IE PointerDown events will be validated in `touch` or `mouse`
3551  // Index numbers referenced here: https://msdn.microsoft.com/library/windows/apps/hh466130.aspx
3552  this.iePointerMap = {
3553    2: 'touch',
3554    3: 'touch',
3555    4: 'mouse'
3556  };
3557
3558  this.initializeEvents();
3559}
3560
3561/**
3562 * Initializes the interaction service, by registering all interaction events to the
3563 * body element.
3564 */
3565MdInteractionService.prototype.initializeEvents = function() {
3566  // IE browsers can also trigger pointer events, which also leads to an interaction.
3567  var pointerEvent = 'MSPointerEvent' in window ? 'MSPointerDown' : 'PointerEvent' in window ? 'pointerdown' : null;
3568
3569  this.bodyElement.on('keydown mousedown', this.onInputEvent.bind(this));
3570
3571  if ('ontouchstart' in document.documentElement) {
3572    this.bodyElement.on('touchstart', this.onBufferInputEvent.bind(this));
3573  }
3574
3575  if (pointerEvent) {
3576    this.bodyElement.on(pointerEvent, this.onInputEvent.bind(this));
3577  }
3578
3579};
3580
3581/**
3582 * Event listener for normal interaction events, which should be tracked.
3583 * @param event {MouseEvent|KeyboardEvent|PointerEvent|TouchEvent}
3584 */
3585MdInteractionService.prototype.onInputEvent = function(event) {
3586  if (this.isBuffering) {
3587    return;
3588  }
3589
3590  var type = this.inputEventMap[event.type];
3591
3592  if (type === 'pointer') {
3593    type = this.iePointerMap[event.pointerType] || event.pointerType;
3594  }
3595
3596  this.lastInteractionType = type;
3597  this.lastInteractionTime = this.$mdUtil.now();
3598};
3599
3600/**
3601 * Event listener for interaction events which should be buffered (touch events).
3602 * @param event {TouchEvent}
3603 */
3604MdInteractionService.prototype.onBufferInputEvent = function(event) {
3605  this.$timeout.cancel(this.bufferTimeout);
3606
3607  this.onInputEvent(event);
3608  this.isBuffering = true;
3609
3610  // The timeout of 650ms is needed to delay the touchstart, because otherwise the touch will call
3611  // the `onInput` function multiple times.
3612  this.bufferTimeout = this.$timeout(function() {
3613    this.isBuffering = false;
3614  }.bind(this), 650, false);
3615
3616};
3617
3618/**
3619 * @ngdoc method
3620 * @name $mdInteraction#getLastInteractionType
3621 * @description Retrieves the last interaction type triggered in body.
3622 * @returns {string|null} Last interaction type.
3623 */
3624MdInteractionService.prototype.getLastInteractionType = function() {
3625  return this.lastInteractionType;
3626};
3627
3628/**
3629 * @ngdoc method
3630 * @name $mdInteraction#isUserInvoked
3631 * @description Method to detect whether any interaction happened recently or not.
3632 * @param {number=} checkDelay Time to check for any interaction to have been triggered.
3633 * @returns {boolean} Whether there was any interaction or not.
3634 */
3635MdInteractionService.prototype.isUserInvoked = function(checkDelay) {
vendor: 38,151 bytes, lines 3636-4752
3636  var delay = angular.isNumber(checkDelay) ? checkDelay : 15;
3637
3638  // Check for any interaction to be within the specified check time.
3639  return this.lastInteractionTime >= this.$mdUtil.now() - delay;
3640};
3641
3642})();
3643(function(){
3644"use strict";
3645
3646angular.module('material.core')
3647  .provider('$$interimElement', InterimElementProvider);
3648
3649/*
3650 * @ngdoc service
3651 * @name $$interimElement
3652 * @module material.core
3653 *
3654 * @description
3655 *
3656 * Factory that contructs `$$interimElement.$service` services.
3657 * Used internally in material design for elements that appear on screen temporarily.
3658 * The service provides a promise-like API for interacting with the temporary
3659 * elements.
3660 *
3661 * ```js
3662 * app.service('$mdToast', function($$interimElement) {
3663 *   var $mdToast = $$interimElement(toastDefaultOptions);
3664 *   return $mdToast;
3665 * });
3666 * ```
3667 * @param {object=} defaultOptions Options used by default for the `show` method on the service.
3668 *
3669 * @returns {$$interimElement.$service}
3670 *
3671 */
3672
3673function InterimElementProvider() {
3674  InterimElementFactory.$inject = ["$document", "$q", "$rootScope", "$timeout", "$rootElement", "$animate", "$mdUtil", "$mdCompiler", "$mdTheming", "$injector", "$exceptionHandler"];
3675  createInterimElementProvider.$get = InterimElementFactory;
3676  return createInterimElementProvider;
3677
3678  /**
3679   * Returns a new provider which allows configuration of a new interimElement
3680   * service. Allows configuration of default options & methods for options,
3681   * as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method)
3682   */
3683  function createInterimElementProvider(interimFactoryName) {
3684    factory.$inject = ["$$interimElement", "$injector"];
3685    var EXPOSED_METHODS = ['onHide', 'onShow', 'onRemove'];
3686
3687    var customMethods = {};
3688    var providerConfig = {
3689      presets: {}
3690    };
3691
3692    var provider = {
3693      setDefaults: setDefaults,
3694      addPreset: addPreset,
3695      addMethod: addMethod,
3696      $get: factory
3697    };
3698
3699    /**
3700     * all interim elements will come with the 'build' preset
3701     */
3702    provider.addPreset('build', {
3703      methods: ['controller', 'controllerAs', 'resolve', 'multiple',
3704        'template', 'templateUrl', 'themable', 'transformTemplate', 'parent', 'contentElement']
3705    });
3706
3707    return provider;
3708
3709    /**
3710     * Save the configured defaults to be used when the factory is instantiated
3711     */
3712    function setDefaults(definition) {
3713      providerConfig.optionsFactory = definition.options;
3714      providerConfig.methods = (definition.methods || []).concat(EXPOSED_METHODS);
3715      return provider;
3716    }
3717
3718    /**
3719     * Add a method to the factory that isn't specific to any interim element operations
3720     */
3721
3722    function addMethod(name, fn) {
3723      customMethods[name] = fn;
3724      return provider;
3725    }
3726
3727    /**
3728     * Save the configured preset to be used when the factory is instantiated
3729     */
3730    function addPreset(name, definition) {
3731      definition = definition || {};
3732      definition.methods = definition.methods || [];
3733      definition.options = definition.options || function() { return {}; };
3734
3735      if (/^cancel|hide|show$/.test(name)) {
3736        throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!");
3737      }
3738      if (definition.methods.indexOf('_options') > -1) {
3739        throw new Error("Method '_options' in " + interimFactoryName + " is reserved!");
3740      }
3741      providerConfig.presets[name] = {
3742        methods: definition.methods.concat(EXPOSED_METHODS),
3743        optionsFactory: definition.options,
3744        argOption: definition.argOption
3745      };
3746      return provider;
3747    }
3748
3749    function addPresetMethod(presetName, methodName, method) {
3750      providerConfig.presets[presetName][methodName] = method;
3751    }
3752
3753    /**
3754     * Create a factory that has the given methods & defaults implementing interimElement
3755     */
3756    /* @ngInject */
3757    function factory($$interimElement, $injector) {
3758      var defaultMethods;
3759      var defaultOptions;
3760      var interimElementService = $$interimElement();
3761
3762      /*
3763       * publicService is what the developer will be using.
3764       * It has methods hide(), cancel(), show(), build(), and any other
3765       * presets which were set during the config phase.
3766       */
3767      var publicService = {
3768        hide: interimElementService.hide,
3769        cancel: interimElementService.cancel,
3770        show: showInterimElement,
3771
3772        // Special internal method to destroy an interim element without animations
3773        // used when navigation changes causes a $scope.$destroy() action
3774        destroy : destroyInterimElement
3775      };
3776
3777
3778      defaultMethods = providerConfig.methods || [];
3779      // This must be invoked after the publicService is initialized
3780      defaultOptions = invokeFactory(providerConfig.optionsFactory, {});
3781
3782      // Copy over the simple custom methods
3783      angular.forEach(customMethods, function(fn, name) {
3784        publicService[name] = fn;
3785      });
3786
3787      angular.forEach(providerConfig.presets, function(definition, name) {
3788        var presetDefaults = invokeFactory(definition.optionsFactory, {});
3789        var presetMethods = (definition.methods || []).concat(defaultMethods);
3790
3791        // Every interimElement built with a preset has a field called `$type`,
3792        // which matches the name of the preset.
3793        // Eg in preset 'confirm', options.$type === 'confirm'
3794        angular.extend(presetDefaults, { $type: name });
3795
3796        // This creates a preset class which has setter methods for every
3797        // method given in the `.addPreset()` function, as well as every
3798        // method given in the `.setDefaults()` function.
3799        //
3800        // @example
3801        // .setDefaults({
3802        //   methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'],
3803        //   options: dialogDefaultOptions
3804        // })
3805        // .addPreset('alert', {
3806        //   methods: ['title', 'ok'],
3807        //   options: alertDialogOptions
3808        // })
3809        //
3810        // Set values will be passed to the options when interimElement.show() is called.
3811        function Preset(opts) {
3812          this._options = angular.extend({}, presetDefaults, opts);
3813        }
3814        angular.forEach(presetMethods, function(name) {
3815          Preset.prototype[name] = function(value) {
3816            this._options[name] = value;
3817            return this;
3818          };
3819        });
3820
3821        // Create shortcut method for one-linear methods
3822        if (definition.argOption) {
3823          var methodName = 'show' + name.charAt(0).toUpperCase() + name.slice(1);
3824          publicService[methodName] = function(arg) {
3825            var config = publicService[name](arg);
3826            return publicService.show(config);
3827          };
3828        }
3829
3830        // eg $mdDialog.alert() will return a new alert preset
3831        publicService[name] = function(arg) {
3832          // If argOption is supplied, eg `argOption: 'content'`, then we assume
3833          // if the argument is not an options object then it is the `argOption` option.
3834          //
3835          // @example `$mdToast.simple('hello')` // sets options.content to hello
3836          //                                     // because argOption === 'content'
3837          if (arguments.length && definition.argOption &&
3838              !angular.isObject(arg) && !angular.isArray(arg))  {
3839
3840            return (new Preset())[definition.argOption](arg);
3841
3842          } else {
3843            return new Preset(arg);
3844          }
3845
3846        };
3847      });
3848
3849      return publicService;
3850
3851      /**
3852       *
3853       */
3854      function showInterimElement(opts) {
3855        // opts is either a preset which stores its options on an _options field,
3856        // or just an object made up of options
3857        opts = opts || { };
3858        if (opts._options) opts = opts._options;
3859
3860        return interimElementService.show(
3861          angular.extend({}, defaultOptions, opts)
3862        );
3863      }
3864
3865      /**
3866       *  Special method to hide and destroy an interimElement WITHOUT
3867       *  any 'leave` or hide animations ( an immediate force hide/remove )
3868       *
3869       *  NOTE: This calls the onRemove() subclass method for each component...
3870       *  which must have code to respond to `options.$destroy == true`
3871       */
3872      function destroyInterimElement(opts) {
3873          return interimElementService.destroy(opts);
3874      }
3875
3876      /**
3877       * Helper to call $injector.invoke with a local of the factory name for
3878       * this provider.
3879       * If an $mdDialog is providing options for a dialog and tries to inject
3880       * $mdDialog, a circular dependency error will happen.
3881       * We get around that by manually injecting $mdDialog as a local.
3882       */
3883      function invokeFactory(factory, defaultVal) {
3884        var locals = {};
3885        locals[interimFactoryName] = publicService;
3886        return $injector.invoke(factory || function() { return defaultVal; }, {}, locals);
3887      }
3888
3889    }
3890
3891  }
3892
3893  /* @ngInject */
3894  function InterimElementFactory($document, $q, $rootScope, $timeout, $rootElement, $animate,
3895                                 $mdUtil, $mdCompiler, $mdTheming, $injector, $exceptionHandler) {
3896    return function createInterimElementService() {
3897      var SHOW_CANCELLED = false;
3898
3899      /*
3900       * @ngdoc service
3901       * @name $$interimElement.$service
3902       *
3903       * @description
3904       * A service used to control inserting and removing an element into the DOM.
3905       *
3906       */
3907
3908      var service;
3909
3910      var showPromises = []; // Promises for the interim's which are currently opening.
3911      var hidePromises = []; // Promises for the interim's which are currently hiding.
3912      var showingInterims = []; // Interim elements which are currently showing up.
3913
3914      // Publish instance $$interimElement service;
3915      // ... used as $mdDialog, $mdToast, $mdMenu, and $mdSelect
3916
3917      return service = {
3918        show: show,
3919        hide: waitForInterim(hide),
3920        cancel: waitForInterim(cancel),
3921        destroy : destroy,
3922        $injector_: $injector
3923      };
3924
3925      /*
3926       * @ngdoc method
3927       * @name $$interimElement.$service#show
3928       * @kind function
3929       *
3930       * @description
3931       * Adds the `$interimElement` to the DOM and returns a special promise that will be resolved or rejected
3932       * with hide or cancel, respectively. To external cancel/hide, developers should use the
3933       *
3934       * @param {*} options is hashMap of settings
3935       * @returns a Promise
3936       *
3937       */
3938      function show(options) {
3939        options = options || {};
3940        var interimElement = new InterimElement(options || {});
3941
3942        // When an interim element is currently showing, we have to cancel it.
3943        // Just hiding it, will resolve the InterimElement's promise, the promise should be
3944        // rejected instead.
3945        var hideAction = options.multiple ? $q.resolve() : $q.all(showPromises);
3946
3947        if (!options.multiple) {
3948          // Wait for all opening interim's to finish their transition.
3949          hideAction = hideAction.then(function() {
3950            // Wait for all closing and showing interim's to be completely closed.
3951            var promiseArray = hidePromises.concat(showingInterims.map(service.cancel));
3952            return $q.all(promiseArray);
3953          });
3954        }
3955
3956        var showAction = hideAction.then(function() {
3957
3958          return interimElement
3959            .show()
3960            .catch(function(reason) { return reason; })
3961            .finally(function() {
3962              showPromises.splice(showPromises.indexOf(showAction), 1);
3963              showingInterims.push(interimElement);
3964            });
3965
3966        });
3967
3968        showPromises.push(showAction);
3969
3970        // In AngularJS 1.6+, exceptions inside promises will cause a rejection. We need to handle
3971        // the rejection and only log it if it's an error.
3972        interimElement.deferred.promise.catch(function(fault) {
3973          if (fault instanceof Error) {
3974            $exceptionHandler(fault);
3975          }
3976
3977          return fault;
3978        });
3979
3980        // Return a promise that will be resolved when the interim
3981        // element is hidden or cancelled...
3982        return interimElement.deferred.promise;
3983      }
3984
3985      /*
3986       * @ngdoc method
3987       * @name $$interimElement.$service#hide
3988       * @kind function
3989       *
3990       * @description
3991       * Removes the `$interimElement` from the DOM and resolves the promise returned from `show`
3992       *
3993       * @param {*} resolveParam Data to resolve the promise with
3994       * @returns a Promise that will be resolved after the element has been removed.
3995       *
3996       */
3997      function hide(reason, options) {
3998        options = options || {};
3999
4000        if (options.closeAll) {
4001          // We have to make a shallow copy of the array, because otherwise the map will break.
4002          return $q.all(showingInterims.slice().reverse().map(closeElement));
4003        } else if (options.closeTo !== undefined) {
4004          return $q.all(showingInterims.slice(options.closeTo).map(closeElement));
4005        }
4006
4007        // Hide the latest showing interim element.
4008        return closeElement(showingInterims[showingInterims.length - 1]);
4009
4010        function closeElement(interim) {
4011
4012          var hideAction = interim
4013            .remove(reason, false, options || { })
4014            .catch(function(reason) { return reason; })
4015            .finally(function() {
4016              hidePromises.splice(hidePromises.indexOf(hideAction), 1);
4017            });
4018
4019          showingInterims.splice(showingInterims.indexOf(interim), 1);
4020          hidePromises.push(hideAction);
4021
4022          return interim.deferred.promise;
4023        }
4024      }
4025
4026      /*
4027       * @ngdoc method
4028       * @name $$interimElement.$service#cancel
4029       * @kind function
4030       *
4031       * @description
4032       * Removes the `$interimElement` from the DOM and rejects the promise returned from `show`
4033       *
4034       * @param {*} reason Data to reject the promise with
4035       * @returns Promise that will be resolved after the element has been removed.
4036       *
4037       */
4038      function cancel(reason, options) {
4039        var interim = showingInterims.pop();
4040        if (!interim) {
4041          return $q.when(reason);
4042        }
4043
4044        var cancelAction = interim
4045          .remove(reason, true, options || {})
4046          .catch(function(reason) { return reason; })
4047          .finally(function() {
4048            hidePromises.splice(hidePromises.indexOf(cancelAction), 1);
4049          });
4050
4051        hidePromises.push(cancelAction);
4052
4053        // Since AngularJS 1.6.7, promises will be logged to $exceptionHandler when the promise
4054        // is not handling the rejection. We create a pseudo catch handler, which will prevent the
4055        // promise from being logged to the $exceptionHandler.
4056        return interim.deferred.promise.catch(angular.noop);
4057      }
4058
4059      /**
4060       * Creates a function to wait for at least one interim element to be available.
4061       * @param callbackFn Function to be used as callback
4062       * @returns {Function}
4063       */
4064      function waitForInterim(callbackFn) {
4065        return function() {
4066          var fnArguments = arguments;
4067
4068          if (!showingInterims.length) {
4069            // When there are still interim's opening, then wait for the first interim element to
4070            // finish its open animation.
4071            if (showPromises.length) {
4072              return showPromises[0].finally(function () {
4073                return callbackFn.apply(service, fnArguments);
4074              });
4075            }
4076
4077            return $q.when("No interim elements currently showing up.");
4078          }
4079
4080          return callbackFn.apply(service, fnArguments);
4081        };
4082      }
4083
4084      /*
4085       * Special method to quick-remove the interim element without animations
4086       * Note: interim elements are in "interim containers"
4087       */
4088      function destroy(targetEl) {
4089        var interim = !targetEl ? showingInterims.shift() : null;
4090
4091        var parentEl = angular.element(targetEl).length && angular.element(targetEl)[0].parentNode;
4092
4093        if (parentEl) {
4094          // Try to find the interim in the stack which corresponds to the supplied DOM element.
4095          var filtered = showingInterims.filter(function(entry) {
4096            return entry.options.element[0] === parentEl;
4097          });
4098
4099          // Note: This function might be called when the element already has been removed,
4100          // in which case we won't find any matches.
4101          if (filtered.length) {
4102            interim = filtered[0];
4103            showingInterims.splice(showingInterims.indexOf(interim), 1);
4104          }
4105        }
4106
4107        return interim ? interim.remove(SHOW_CANCELLED, false, { '$destroy': true }) :
4108                         $q.when(SHOW_CANCELLED);
4109      }
4110
4111      /*
4112       * Internal Interim Element Object
4113       * Used internally to manage the DOM element and related data
4114       */
4115      function InterimElement(options) {
4116        var self, element, showAction = $q.when(true);
4117
4118        options = configureScopeAndTransitions(options);
4119
4120        return self = {
4121          options : options,
4122          deferred: $q.defer(),
4123          show    : createAndTransitionIn,
4124          remove  : transitionOutAndRemove
4125        };
4126
4127        /**
4128         * Compile, link, and show this interim element
4129         * Use optional autoHided and transition-in effects
4130         */
4131        function createAndTransitionIn() {
4132          return $q(function(resolve, reject) {
4133
4134            // Trigger onCompiling callback before the compilation starts.
4135            // This is useful, when modifying options, which can be influenced by developers.
4136            options.onCompiling && options.onCompiling(options);
4137
4138            compileElement(options)
4139              .then(function( compiledData ) {
4140                element = linkElement( compiledData, options );
4141
4142                // Expose the cleanup function from the compiler.
4143                options.cleanupElement = compiledData.cleanup;
4144
4145                showAction = showElement(element, options, compiledData.controller)
4146                  .then(resolve, rejectAll);
4147              }).catch(rejectAll);
4148
4149            function rejectAll(fault) {
4150              // Force the '$md<xxx>.show()' promise to reject
4151              self.deferred.reject(fault);
4152
4153              // Continue rejection propagation
4154              reject(fault);
4155            }
4156          });
4157        }
4158
4159        /**
4160         * After the show process has finished/rejected:
4161         * - announce 'removing',
4162         * - perform the transition-out, and
4163         * - perform optional clean up scope.
4164         */
4165        function transitionOutAndRemove(response, isCancelled, opts) {
4166
4167          // abort if the show() and compile failed
4168          if ( !element ) return $q.when(false);
4169
4170          options = angular.extend(options || {}, opts || {});
4171          options.cancelAutoHide && options.cancelAutoHide();
4172          options.element.triggerHandler('$mdInterimElementRemove');
4173
4174          if ( options.$destroy === true ) {
4175
4176            return hideElement(options.element, options).then(function(){
4177              (isCancelled && rejectAll(response)) || resolveAll(response);
4178            });
4179
4180          } else {
4181            $q.when(showAction).finally(function() {
4182              hideElement(options.element, options).then(function() {
4183                isCancelled ? rejectAll(response) : resolveAll(response);
4184              }, rejectAll);
4185            });
4186
4187            return self.deferred.promise;
4188          }
4189
4190
4191          /**
4192           * The `show()` returns a promise that will be resolved when the interim
4193           * element is hidden or cancelled...
4194           */
4195          function resolveAll(response) {
4196            self.deferred.resolve(response);
4197          }
4198
4199          /**
4200           * Force the '$md<xxx>.show()' promise to reject
4201           */
4202          function rejectAll(fault) {
4203            self.deferred.reject(fault);
4204          }
4205        }
4206
4207        /**
4208         * Prepare optional isolated scope and prepare $animate with default enter and leave
4209         * transitions for the new element instance.
4210         */
4211        function configureScopeAndTransitions(options) {
4212          options = options || { };
4213          if ( options.template ) {
4214            options.template = $mdUtil.processTemplate(options.template);
4215          }
4216
4217          return angular.extend({
4218            preserveScope: false,
4219            cancelAutoHide : angular.noop,
4220            scope: options.scope || $rootScope.$new(options.isolateScope),
4221
4222            /**
4223             * Default usage to enable $animate to transition-in; can be easily overridden via 'options'
4224             */
4225            onShow: function transitionIn(scope, element, options) {
4226              return $animate.enter(element, options.parent);
4227            },
4228
4229            /**
4230             * Default usage to enable $animate to transition-out; can be easily overridden via 'options'
4231             */
4232            onRemove: function transitionOut(scope, element) {
4233              // Element could be undefined if a new element is shown before
4234              // the old one finishes compiling.
4235              return element && $animate.leave(element) || $q.when();
4236            }
4237          }, options );
4238
4239        }
4240
4241        /**
4242         * Compile an element with a templateUrl, controller, and locals
4243         */
4244        function compileElement(options) {
4245
4246          var compiled = !options.skipCompile ? $mdCompiler.compile(options) : null;
4247
4248          return compiled || $q(function (resolve) {
4249              resolve({
4250                locals: {},
4251                link: function () {
4252                  return options.element;
4253                }
4254              });
4255            });
4256        }
4257
4258        /**
4259         *  Link an element with compiled configuration
4260         */
4261        function linkElement(compileData, options){
4262          angular.extend(compileData.locals, options);
4263
4264          var element = compileData.link(options.scope);
4265
4266          // Search for parent at insertion time, if not specified
4267          options.element = element;
4268          options.parent = findParent(element, options);
4269          if (options.themable) $mdTheming(element);
4270
4271          return element;
4272        }
4273
4274        /**
4275         * Search for parent at insertion time, if not specified
4276         */
4277        function findParent(element, options) {
4278          var parent = options.parent;
4279
4280          // Search for parent at insertion time, if not specified
4281          if (angular.isFunction(parent)) {
4282            parent = parent(options.scope, element, options);
4283          } else if (angular.isString(parent)) {
4284            parent = angular.element($document[0].querySelector(parent));
4285          } else {
4286            parent = angular.element(parent);
4287          }
4288
4289          // If parent querySelector/getter function fails, or it's just null,
4290          // find a default.
4291          if (!(parent || {}).length) {
4292            var el;
4293            if ($rootElement[0] && $rootElement[0].querySelector) {
4294              el = $rootElement[0].querySelector(':not(svg) > body');
4295            }
4296            if (!el) el = $rootElement[0];
4297            if (el.nodeName == '#comment') {
4298              el = $document[0].body;
4299            }
4300            return angular.element(el);
4301          }
4302
4303          return parent;
4304        }
4305
4306        /**
4307         * If auto-hide is enabled, start timer and prepare cancel function
4308         */
4309        function startAutoHide() {
4310          var autoHideTimer, cancelAutoHide = angular.noop;
4311
4312          if (options.hideDelay) {
4313            autoHideTimer = $timeout(service.hide, options.hideDelay) ;
4314            cancelAutoHide = function() {
4315              $timeout.cancel(autoHideTimer);
4316            };
4317          }
4318
4319          // Cache for subsequent use
4320          options.cancelAutoHide = function() {
4321            cancelAutoHide();
4322            options.cancelAutoHide = undefined;
4323          };
4324        }
4325
4326        /**
4327         * Show the element ( with transitions), notify complete and start
4328         * optional auto-Hide
4329         */
4330        function showElement(element, options, controller) {
4331          // Trigger onShowing callback before the `show()` starts
4332          var notifyShowing = options.onShowing || angular.noop;
4333          // Trigger onComplete callback when the `show()` finishes
4334          var notifyComplete = options.onComplete || angular.noop;
4335
4336          // Necessary for consistency between AngularJS 1.5 and 1.6.
4337          try {
4338            notifyShowing(options.scope, element, options, controller);
4339          } catch (e) {
4340            return $q.reject(e);
4341          }
4342
4343          return $q(function (resolve, reject) {
4344            try {
4345              // Start transitionIn
4346              $q.when(options.onShow(options.scope, element, options, controller))
4347                .then(function () {
4348                  notifyComplete(options.scope, element, options);
4349                  startAutoHide();
4350
4351                  resolve(element);
4352                }, reject);
4353
4354            } catch (e) {
4355              reject(e.message);
4356            }
4357          });
4358        }
4359
4360        function hideElement(element, options) {
4361          var announceRemoving = options.onRemoving || angular.noop;
4362
4363          return $q(function (resolve, reject) {
4364            try {
4365              // Start transitionIn
4366              var action = $q.when( options.onRemove(options.scope, element, options) || true );
4367
4368              // Trigger callback *before* the remove operation starts
4369              announceRemoving(element, action);
4370
4371              if (options.$destroy) {
4372                // For $destroy, onRemove should be synchronous
4373                resolve(element);
4374
4375                if (!options.preserveScope && options.scope ) {
4376                  // scope destroy should still be be done after the current digest is done
4377                  action.then( function() { options.scope.$destroy(); });
4378                }
4379              } else {
4380                // Wait until transition-out is done
4381                action.then(function () {
4382                  if (!options.preserveScope && options.scope ) {
4383                    options.scope.$destroy();
4384                  }
4385
4386                  resolve(element);
4387                }, reject);
4388              }
4389            } catch (e) {
4390              reject(e.message);
4391            }
4392          });
4393        }
4394
4395      }
4396    };
4397
4398  }
4399
4400}
4401
4402})();
4403(function(){
4404"use strict";
4405
4406(function() {
4407  'use strict';
4408
4409  var $mdUtil, $interpolate, $log;
4410
4411  var SUFFIXES = /(-gt)?-(sm|md|lg|print)/g;
4412  var WHITESPACE = /\s+/g;
4413
4414  var FLEX_OPTIONS = ['grow', 'initial', 'auto', 'none', 'noshrink', 'nogrow' ];
4415  var LAYOUT_OPTIONS = ['row', 'column'];
4416  var ALIGNMENT_MAIN_AXIS= [ "", "start", "center", "end", "stretch", "space-around", "space-between" ];
4417  var ALIGNMENT_CROSS_AXIS= [ "", "start", "center", "end", "stretch" ];
4418
4419  var config = {
4420    /**
4421     * Enable directive attribute-to-class conversions
4422     * Developers can use `<body md-layout-css />` to quickly
4423     * disable the Layout directives and prohibit the injection of Layout classNames
4424     */
4425    enabled: true,
4426
4427    /**
4428     * List of mediaQuery breakpoints and associated suffixes
4429     *
4430     *   [
4431     *    { suffix: "sm", mediaQuery: "screen and (max-width: 599px)" },
4432     *    { suffix: "md", mediaQuery: "screen and (min-width: 600px) and (max-width: 959px)" }
4433     *   ]
4434     */
4435    breakpoints: []
4436  };
4437
4438  registerLayoutAPI( angular.module('material.core.layout', ['ng']) );
4439
4440  /**
4441   *   registerLayoutAPI()
4442   *
4443   *   The original AngularJS Material Layout solution used attribute selectors and CSS.
4444   *
4445   *  ```html
4446   *  <div layout="column"> My Content </div>
4447   *  ```
4448   *
4449   *  ```css
4450   *  [layout] {
4451   *    box-sizing: border-box;
4452   *    display:flex;
4453   *  }
4454   *  [layout=column] {
4455   *    flex-direction : column
4456   *  }
4457   *  ```
4458   *
4459   *  Use of attribute selectors creates significant performance impacts in some
4460   *  browsers... mainly IE.
4461   *
4462   *  This module registers directives that allow the same layout attributes to be
4463   *  interpreted and converted to class selectors. The directive will add equivalent classes to each element that
4464   *  contains a Layout directive.
4465   *
4466   * ```html
4467   *   <div layout="column" class="layout layout-column"> My Content </div>
4468   *```
4469   *
4470   *  ```css
4471   *  .layout {
4472   *    box-sizing: border-box;
4473   *    display:flex;
4474   *  }
4475   *  .layout-column {
4476   *    flex-direction : column
4477   *  }
4478   *  ```
4479   */
4480  function registerLayoutAPI(module){
4481    var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
4482    var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
4483
4484    // NOTE: these are also defined in constants::MEDIA_PRIORITY and constants::MEDIA
4485    var BREAKPOINTS     = [ "", "xs", "gt-xs", "sm", "gt-sm", "md", "gt-md", "lg", "gt-lg", "xl", "print" ];
4486    var API_WITH_VALUES = [ "layout", "flex", "flex-order", "flex-offset", "layout-align" ];
4487    var API_NO_VALUES   = [ "show", "hide", "layout-padding", "layout-margin" ];
4488
4489
4490    // Build directive registration functions for the standard Layout API... for all breakpoints.
4491    angular.forEach(BREAKPOINTS, function(mqb) {
4492
4493      // Attribute directives with expected, observable value(s)
4494      angular.forEach( API_WITH_VALUES, function(name){
4495        var fullName = mqb ? name + "-" + mqb : name;
4496        module.directive( directiveNormalize(fullName), attributeWithObserve(fullName));
4497      });
4498
4499      // Attribute directives with no expected value(s)
4500      angular.forEach( API_NO_VALUES, function(name){
4501        var fullName = mqb ? name + "-" + mqb : name;
4502        module.directive( directiveNormalize(fullName), attributeWithoutValue(fullName));
4503      });
4504
4505    });
4506
4507    // Register other, special directive functions for the Layout features:
4508    module
4509
4510      .provider('$$mdLayout'     , function() {
4511        // Publish internal service for Layouts
4512        return {
4513          $get : angular.noop,
4514          validateAttributeValue : validateAttributeValue,
4515          validateAttributeUsage : validateAttributeUsage,
4516          /**
4517           * Easy way to disable/enable the Layout API.
4518           * When disabled, this stops all attribute-to-classname generations
4519           */
4520          disableLayouts  : function(isDisabled) {
4521            config.enabled =  (isDisabled !== true);
4522          }
4523        };
4524      })
4525
4526      .directive('mdLayoutCss'        , disableLayoutDirective )
4527      .directive('ngCloak'            , buildCloakInterceptor('ng-cloak'))
4528
4529      .directive('layoutWrap'   , attributeWithoutValue('layout-wrap'))
4530      .directive('layoutNowrap' , attributeWithoutValue('layout-nowrap'))
4531      .directive('layoutNoWrap' , attributeWithoutValue('layout-no-wrap'))
4532      .directive('layoutFill'   , attributeWithoutValue('layout-fill'))
4533
4534      // !! Deprecated attributes: use the `-lt` (aka less-than) notations
4535
4536      .directive('layoutLtMd'     , warnAttrNotSupported('layout-lt-md', true))
4537      .directive('layoutLtLg'     , warnAttrNotSupported('layout-lt-lg', true))
4538      .directive('flexLtMd'       , warnAttrNotSupported('flex-lt-md', true))
4539      .directive('flexLtLg'       , warnAttrNotSupported('flex-lt-lg', true))
4540
4541      .directive('layoutAlignLtMd', warnAttrNotSupported('layout-align-lt-md'))
4542      .directive('layoutAlignLtLg', warnAttrNotSupported('layout-align-lt-lg'))
4543      .directive('flexOrderLtMd'  , warnAttrNotSupported('flex-order-lt-md'))
4544      .directive('flexOrderLtLg'  , warnAttrNotSupported('flex-order-lt-lg'))
4545      .directive('offsetLtMd'     , warnAttrNotSupported('flex-offset-lt-md'))
4546      .directive('offsetLtLg'     , warnAttrNotSupported('flex-offset-lt-lg'))
4547
4548      .directive('hideLtMd'       , warnAttrNotSupported('hide-lt-md'))
4549      .directive('hideLtLg'       , warnAttrNotSupported('hide-lt-lg'))
4550      .directive('showLtMd'       , warnAttrNotSupported('show-lt-md'))
4551      .directive('showLtLg'       , warnAttrNotSupported('show-lt-lg'))
4552
4553      // Determine if
4554      .config( detectDisabledLayouts );
4555
4556    /**
4557     * Converts snake_case to camelCase.
4558     * Also there is special case for Moz prefix starting with upper case letter.
4559     * @param name Name to normalize
4560     */
4561    function directiveNormalize(name) {
4562      return name
4563        .replace(PREFIX_REGEXP, '')
4564        .replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
4565          return offset ? letter.toUpperCase() : letter;
4566        });
4567    }
4568
4569  }
4570
4571
4572  /**
4573    * Detect if any of the HTML tags has a [md-layouts-disabled] attribute;
4574    * If yes, then immediately disable all layout API features
4575    *
4576    * Note: this attribute should be specified on either the HTML or BODY tags
4577    */
4578   /**
4579    * @ngInject
4580    */
4581   function detectDisabledLayouts() {
4582     var isDisabled = !!document.querySelector('[md-layouts-disabled]');
4583     config.enabled = !isDisabled;
4584   }
4585
4586  /**
4587   * Special directive that will disable ALL Layout conversions of layout
4588   * attribute(s) to classname(s).
4589   *
4590   * <link rel="stylesheet" href="angular-material.min.css">
4591   * <link rel="stylesheet" href="angular-material.layout.css">
4592   *
4593   * <body md-layout-css>
4594   *  ...
4595   * </body>
4596   *
4597   * Note: Using md-layout-css directive requires the developer to load the Material
4598   * Layout Attribute stylesheet (which only uses attribute selectors):
4599   *
4600   *       `angular-material.layout.css`
4601   *
4602   * Another option is to use the LayoutProvider to configure and disable the attribute
4603   * conversions; this would obviate the use of the `md-layout-css` directive
4604   *
4605   */
4606  function disableLayoutDirective() {
4607    // Return a 1x-only, first-match attribute directive
4608    config.enabled = false;
4609
4610    return {
4611      restrict : 'A',
4612      priority : '900'
4613    };
4614  }
4615
4616  /**
4617   * Tail-hook ngCloak to delay the uncloaking while Layout transformers
4618   * finish processing. Eliminates flicker with Material.Layouts
4619   */
4620  function buildCloakInterceptor(className) {
4621    return [ '$timeout', function($timeout){
4622      return {
4623        restrict : 'A',
4624        priority : -10,   // run after normal ng-cloak
4625        compile  : function( element ) {
4626          if (!config.enabled) return angular.noop;
4627
4628          // Re-add the cloak
4629          element.addClass(className);
4630
4631          return function( scope, element ) {
4632            // Wait while layout injectors configure, then uncloak
4633            // NOTE: $rAF does not delay enough... and this is a 1x-only event,
4634            //       $timeout is acceptable.
4635            $timeout( function(){
4636              element.removeClass(className);
4637            }, 10, false);
4638          };
4639        }
4640      };
4641    }];
4642  }
4643
4644
4645  // *********************************************************************************
4646  //
4647  // These functions create registration functions for AngularJS Material Layout attribute directives
4648  // This provides easy translation to switch AngularJS Material attribute selectors to
4649  // CLASS selectors and directives; which has huge performance implications
4650  // for IE Browsers
4651  //
4652  // *********************************************************************************
4653
4654  /**
4655   * Creates a directive registration function where a possible dynamic attribute
4656   * value will be observed/watched.
4657   * @param {string} className attribute name; eg `layout-gt-md` with value ="row"
4658   */
4659  function attributeWithObserve(className) {
4660
4661    return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) {
4662      $mdUtil = _$mdUtil_;
4663      $interpolate = _$interpolate_;
4664      $log = _$log_;
4665
4666      return {
4667        restrict: 'A',
4668        compile: function(element, attr) {
4669          var linkFn;
4670          if (config.enabled) {
4671            // immediately replace static (non-interpolated) invalid values...
4672
4673            validateAttributeUsage(className, attr, element, $log);
4674
4675            validateAttributeValue( className,
4676              getNormalizedAttrValue(className, attr, ""),
4677              buildUpdateFn(element, className, attr)
4678            );
4679
4680            linkFn = translateWithValueToCssClass;
4681          }
4682
4683          // Use for postLink to account for transforms after ng-transclude.
4684          return linkFn || angular.noop;
4685        }
4686      };
4687    }];
4688
4689    /**
4690     * Add as transformed class selector(s), then
4691     * remove the deprecated attribute selector
4692     */
4693    function translateWithValueToCssClass(scope, element, attrs) {
4694      var updateFn = updateClassWithValue(element, className, attrs);
4695      var unwatch = attrs.$observe(attrs.$normalize(className), updateFn);
4696
4697      updateFn(getNormalizedAttrValue(className, attrs, ""));
4698      scope.$on("$destroy", function() { unwatch(); });
4699    }
4700  }
4701
4702  /**
4703   * Creates a registration function for AngularJS Material Layout attribute directive.
4704   * This is a `simple` transpose of attribute usage to class usage; where we ignore
4705   * any attribute value
4706   */
4707  function attributeWithoutValue(className) {
4708    return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) {
4709      $mdUtil = _$mdUtil_;
4710      $interpolate = _$interpolate_;
4711      $log = _$log_;
4712
4713      return {
4714        restrict: 'A',
4715        compile: function(element, attr) {
4716          var linkFn;
4717          if (config.enabled) {
4718            // immediately replace static (non-interpolated) invalid values...
4719
4720            validateAttributeValue( className,
4721              getNormalizedAttrValue(className, attr, ""),
4722              buildUpdateFn(element, className, attr)
4723            );
4724
4725            translateToCssClass(null, element);
4726
4727            // Use for postLink to account for transforms after ng-transclude.
4728            linkFn = translateToCssClass;
4729          }
4730
4731          return linkFn || angular.noop;
4732        }
4733      };
4734    }];
4735
4736    /**
4737     * Add as transformed class selector, then
4738     * remove the deprecated attribute selector
4739     */
4740    function translateToCssClass(scope, element) {
4741      element.addClass(className);
4742    }
4743  }
4744
4745
4746
4747  /**
4748   * After link-phase, do NOT remove deprecated layout attribute selector.
4749   * Instead watch the attribute so interpolated data-bindings to layout
4750   * selectors will continue to be supported.
4751   *
4752   * $observe() the className and update with new class (after remov
4752ing the last one)
4753   *
4754   * e.g. `layout="{{layoutDemo.direction}}"` will update...
4755   *
4756   * NOTE: The value must match one of the specified styles in the CSS.
4757   * For example `flex-gt-md="{{size}}`  where `scope.size == 47` will NOT work since
4758   * only breakpoints for 0, 5, 10, 15... 100, 33, 34, 66, 67 are defined.
4759   *
4760   */
4761  function updateClassWithValue(element, className) {
4762    var lastClass;
4763
4764    return function updateClassFn(newValue) {
4765      var value = validateAttributeValue(className, newValue || "");
4766      if ( angular.isDefined(value) ) {
4767        if (lastClass) element.removeClass(lastClass);
4768        lastClass = !value ? className : className + "-" + value.trim().replace(WHITESPACE, "-");
4769        element.addClass(lastClass);
4770      }
4771    };
4772  }
4773
4774  /**
4775   * Provide console warning that this layout attribute has been deprecated
4776   *
4777   */
4778  function warnAttrNotSupported(className) {
4779    var parts = className.split("-");
4780    return ["$log", function($log) {
4781      $log.warn(className + "has been deprecated. Please use a `" + parts[0] + "-gt-<xxx>` variant.");
4782      return angular.noop;
4783    }];
4784  }
4785
4786  /**
4787   * Centralize warnings for known flexbox issues (especially IE-related issues)
4788   */
4789  function validateAttributeUsage(className, attr, element, $log){
4790    var message, usage, url;
4791    var nodeName = element[0].nodeName.toLowerCase();
4792
4793    switch(className.replace(SUFFIXES,"")) {
4794      case "flex":
4795        if ((nodeName == "md-button") || (nodeName == "fieldset")){
4796          // @see https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers
4797          // Use <div flex> wrapper inside (preferred) or outside
4798
4799          usage = "<" + nodeName + " " + className + "></" + nodeName + ">";
4800          url = "https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers";
4801          message = "Markup '{0}' may not work as expected in IE Browsers. Consult '{1}' for details.";
4802
4803          $log.warn( $mdUtil.supplant(message, [usage, url]) );
4804        }
4805    }
4806
4807  }
4808
4809
4810  /**
4811   * For the Layout attribute value, validate or replace with default
4812   * fallback value
4813   */
4814  function validateAttributeValue(className, value, updateFn) {
4815    var origValue;
4816
4817    if (!needsInterpolation(value)) {
4818      switch (className.replace(SUFFIXES,"")) {
4819        case 'layout'        :
4820          if ( !findIn(value, LAYOUT_OPTIONS) ) {
4821            value = LAYOUT_OPTIONS[0];    // 'row';
4822          }
4823          break;
4824
4825        case 'flex'          :
4826          if (!findIn(value, FLEX_OPTIONS)) {
4827            if (isNaN(value)) {
4828              value = '';
4829            }
4830          }
4831          break;
4832
4833        case 'flex-offset' :
4834        case 'flex-order'    :
4835          if (!value || isNaN(+value)) {
4836            value = '0';
4837          }
4838          break;
4839
4840        case 'layout-align'  :
4841          var axis = extractAlignAxis(value);
4842          value = $mdUtil.supplant("{main}-{cross}",axis);
4843          break;
4844
4845        case 'layout-padding' :
4846        case 'layout-margin'  :
4847        case 'layout-fill'    :
4848        case 'layout-wrap'    :
4849        case 'layout-nowrap'  :
4850        case 'layout-nowrap' :
4851          value = '';
4852          break;
4853      }
4854
4855      if (value != origValue) {
4856        (updateFn || angular.noop)(value);
4857      }
4858    }
4859
4860    return value ? value.trim() : "";
4861  }
4862
4863  /**
4864   * Replace current attribute value with fallback value
4865   */
4866  function buildUpdateFn(element, className, attrs) {
4867    return function updateAttrValue(fallback) {
4868      if (!needsInterpolation(fallback)) {
4869        // Do not modify the element's attribute value; so
4870        // uses '<ui-layout layout="/api/sidebar.html" />' will not
4871        // be affected. Just update the attrs value.
4872        attrs[attrs.$normalize(className)] = fallback;
4873      }
4874    };
4875  }
4876
4877  /**
4878   * See if the original value has interpolation symbols:
4879   * e.g.  flex-gt-md="{{triggerPoint}}"
4880   */
4881  function needsInterpolation(value) {
4882    return (value || "").indexOf($interpolate.startSymbol()) > -1;
4883  }
4884
4885  function getNormalizedAttrValue(className, attrs, defaultVal) {
4886    var normalizedAttr = attrs.$normalize(className);
4887    return attrs[normalizedAttr] ? attrs[normalizedAttr].trim().replace(WHITESPACE, "-") : defaultVal || null;
4888  }
4889
4890  function findIn(item, list, replaceWith) {
4891    item = replaceWith && item ? item.replace(WHITESPACE, replaceWith) : item;
4892
4893    var found = false;
vendor: 22,610 bytes, lines 4894-5699
4894    if (item) {
4895      list.forEach(function(it) {
4896        it = replaceWith ? it.replace(WHITESPACE, replaceWith) : it;
4897        found = found || (it === item);
4898      });
4899    }
4900    return found;
4901  }
4902
4903  function extractAlignAxis(attrValue) {
4904    var axis = {
4905      main : "start",
4906      cross: "stretch"
4907    }, values;
4908
4909    attrValue = (attrValue || "");
4910
4911    if ( attrValue.indexOf("-") === 0 || attrValue.indexOf(" ") === 0) {
4912      // For missing main-axis values
4913      attrValue = "none" + attrValue;
4914    }
4915
4916    values = attrValue.toLowerCase().trim().replace(WHITESPACE, "-").split("-");
4917    if ( values.length && (values[0] === "space") ) {
4918      // for main-axis values of "space-around" or "space-between"
4919      values = [ values[0]+"-"+values[1],values[2] ];
4920    }
4921
4922    if ( values.length > 0 ) axis.main  = values[0] || axis.main;
4923    if ( values.length > 1 ) axis.cross = values[1] || axis.cross;
4924
4925    if ( ALIGNMENT_MAIN_AXIS.indexOf(axis.main) < 0 )   axis.main = "start";
4926    if ( ALIGNMENT_CROSS_AXIS.indexOf(axis.cross) < 0 ) axis.cross = "stretch";
4927
4928    return axis;
4929  }
4930
4931
4932})();
4933
4934})();
4935(function(){
4936"use strict";
4937
4938/**
4939 * @ngdoc module
4940 * @name material.core.liveannouncer
4941 * @description
4942 * AngularJS Material Live Announcer to provide accessibility for Voice Readers.
4943 */
4944MdLiveAnnouncer.$inject = ["$timeout"];
4945angular
4946  .module('material.core')
4947  .service('$mdLiveAnnouncer', MdLiveAnnouncer);
4948
4949/**
4950 * @ngdoc service
4951 * @name $mdLiveAnnouncer
4952 * @module material.core.liveannouncer
4953 *
4954 * @description
4955 *
4956 * Service to announce messages to supported screenreaders.
4957 *
4958 * > The `$mdLiveAnnouncer` service is internally used for components to provide proper accessibility.
4959 *
4960 * <hljs lang="js">
4961 *   module.controller('AppCtrl', function($mdLiveAnnouncer) {
4962 *     // Basic announcement (Polite Mode)
4963 *     $mdLiveAnnouncer.announce('Hey Google');
4964 *
4965 *     // Custom announcement (Assertive Mode)
4966 *     $mdLiveAnnouncer.announce('Hey Google', 'assertive');
4967 *   });
4968 * </hljs>
4969 *
4970 */
4971function MdLiveAnnouncer($timeout) {
4972  /** @private @const @type {!angular.$timeout} */
4973  this._$timeout = $timeout;
4974
4975  /** @private @const @type {!HTMLElement} */
4976  this._liveElement = this._createLiveElement();
4977
4978  /** @private @const @type {!number} */
4979  this._announceTimeout = 100;
4980}
4981
4982/**
4983 * @ngdoc method
4984 * @name $mdLiveAnnouncer#announce
4985 * @description Announces messages to supported screenreaders.
4986 * @param {string} message Message to be announced to the screenreader
4987 * @param {'off'|'polite'|'assertive'} politeness The politeness of the announcer element.
4988 */
4989MdLiveAnnouncer.prototype.announce = function(message, politeness) {
4990  if (!politeness) {
4991    politeness = 'polite';
4992  }
4993
4994  var self = this;
4995
4996  self._liveElement.textContent = '';
4997  self._liveElement.setAttribute('aria-live', politeness);
4998
4999  // This 100ms timeout is necessary for some browser + screen-reader combinations:
5000  // - Both JAWS and NVDA over IE11 will not announce anything without a non-zero timeout.
5001  // - With Chrome and IE11 with NVDA or JAWS, a repeated (identical) message won't be read a
5002  //   second time without clearing and then using a non-zero delay.
5003  // (using JAWS 17 at time of this writing).
5004  self._$timeout(function() {
5005    self._liveElement.textContent = message;
5006  }, self._announceTimeout, false);
5007};
5008
5009/**
5010 * Creates a live announcer element, which listens for DOM changes and announces them
5011 * to the screenreaders.
5012 * @returns {!HTMLElement}
5013 * @private
5014 */
5015MdLiveAnnouncer.prototype._createLiveElement = function() {
5016  var liveEl = document.createElement('div');
5017
5018  liveEl.classList.add('md-visually-hidden');
5019  liveEl.setAttribute('role', 'status');
5020  liveEl.setAttribute('aria-atomic', 'true');
5021  liveEl.setAttribute('aria-live', 'polite');
5022
5023  document.body.appendChild(liveEl);
5024
5025  return liveEl;
5026};
5027
5028})();
5029(function(){
5030"use strict";
5031
5032/**
5033 * @ngdoc service
5034 * @name $$mdMeta
5035 * @module material.core.meta
5036 *
5037 * @description
5038 *
5039 * A provider and a service that simplifies meta tags access
5040 *
5041 * Note: This is intended only for use with dynamic meta tags such as browser color and title.
5042 * Tags that are only processed when the page is rendered (such as `charset`, and `http-equiv`)
5043 * will not work since `$$mdMeta` adds the tags after the page has already been loaded.
5044 *
5045 * ```js
5046 * app.config(function($$mdMetaProvider) {
5047 *   var removeMeta = $$mdMetaProvider.setMeta('meta-name', 'content');
5048 *   var metaValue  = $$mdMetaProvider.getMeta('meta-name'); // -> 'content'
5049 *
5050 *   removeMeta();
5051 * });
5052 *
5053 * app.controller('myController', function($$mdMeta) {
5054 *   var removeMeta = $$mdMeta.setMeta('meta-name', 'content');
5055 *   var metaValue  = $$mdMeta.getMeta('meta-name'); // -> 'content'
5056 *
5057 *   removeMeta();
5058 * });
5059 * ```
5060 *
5061 * @returns {$$mdMeta.$service}
5062 *
5063 */
5064angular.module('material.core.meta', [])
5065  .provider('$$mdMeta', function () {
5066    var head = angular.element(document.head);
5067    var metaElements = {};
5068
5069    /**
5070     * Checks if the requested element was written manually and maps it
5071     *
5072     * @param {string} name meta tag 'name' attribute value
5073     * @returns {boolean} returns true if there is an element with the requested name
5074     */
5075    function mapExistingElement(name) {
5076      if (metaElements[name]) {
5077        return true;
5078      }
5079
5080      var element = document.getElementsByName(name)[0];
5081
5082      if (!element) {
5083        return false;
5084      }
5085
5086      metaElements[name] = angular.element(element);
5087
5088      return true;
5089    }
5090
5091    /**
5092     * @ngdoc method
5093     * @name $$mdMeta#setMeta
5094     *
5095     * @description
5096     * Creates meta element with the 'name' and 'content' attributes,
5097     * if the meta tag is already created than we replace the 'content' value
5098     *
5099     * @param {string} name meta tag 'name' attribute value
5100     * @param {string} content meta tag 'content' attribute value
5101     * @returns {function} remove function
5102     *
5103     */
5104    function setMeta(name, content) {
5105      mapExistingElement(name);
5106
5107      if (!metaElements[name]) {
5108        var newMeta = angular.element('<meta name="' + name + '" content="' + content + '"/>');
5109        head.append(newMeta);
5110        metaElements[name] = newMeta;
5111      }
5112      else {
5113        metaElements[name].attr('content', content);
5114      }
5115
5116      return function () {
5117        metaElements[name].attr('content', '');
5118        metaElements[name].remove();
5119        delete metaElements[name];
5120      };
5121    }
5122
5123    /**
5124     * @ngdoc method
5125     * @name $$mdMeta#getMeta
5126     *
5127     * @description
5128     * Gets the 'content' attribute value of the wanted meta element
5129     *
5130     * @param {string} name meta tag 'name' attribute value
5131     * @returns {string} content attribute value
5132     */
5133    function getMeta(name) {
5134      if (!mapExistingElement(name)) {
5135        throw Error('$$mdMeta: could not find a meta tag with the name \'' + name + '\'');
5136      }
5137
5138      return metaElements[name].attr('content');
5139    }
5140
5141    var module = {
5142      setMeta: setMeta,
5143      getMeta: getMeta
5144    };
5145
5146    return angular.extend({}, module, {
5147      $get: function () {
5148        return module;
5149      }
5150    });
5151  });
5152})();
5153(function(){
5154"use strict";
5155
5156  /**
5157   * @ngdoc module
5158   * @name material.core.componentRegistry
5159   *
5160   * @description
5161   * A component instance registration service.
5162   * Note: currently this as a private service in the SideNav component.
5163   */
5164  ComponentRegistry.$inject = ["$log", "$q"];
5165  angular.module('material.core')
5166    .factory('$mdComponentRegistry', ComponentRegistry);
5167
5168  /*
5169   * @private
5170   * @ngdoc factory
5171   * @name ComponentRegistry
5172   * @module material.core.componentRegistry
5173   *
5174   */
5175  function ComponentRegistry($log, $q) {
5176
5177    var self;
5178    var instances = [ ];
5179    var pendings = { };
5180
5181    return self = {
5182      /**
5183       * Used to print an error when an instance for a handle isn't found.
5184       */
5185      notFoundError: function(handle, msgContext) {
5186        $log.error( (msgContext || "") + 'No instance found for handle', handle);
5187      },
5188      /**
5189       * Return all registered instances as an array.
5190       */
5191      getInstances: function() {
5192        return instances;
5193      },
5194
5195      /**
5196       * Get a registered instance.
5197       * @param handle the String handle to look up for a registered instance.
5198       */
5199      get: function(handle) {
5200        if ( !isValidID(handle) ) return null;
5201
5202        var i, j, instance;
5203        for(i = 0, j = instances.length; i < j; i++) {
5204          instance = instances[i];
5205          if(instance.$$mdHandle === handle) {
5206            return instance;
5207          }
5208        }
5209        return null;
5210      },
5211
5212      /**
5213       * Register an instance.
5214       * @param instance the instance to register
5215       * @param handle the handle to identify the instance under.
5216       */
5217      register: function(instance, handle) {
5218        if ( !handle ) return angular.noop;
5219
5220        instance.$$mdHandle = handle;
5221        instances.push(instance);
5222        resolveWhen();
5223
5224        return deregister;
5225
5226        /**
5227         * Remove registration for an instance
5228         */
5229        function deregister() {
5230          var index = instances.indexOf(instance);
5231          if (index !== -1) {
5232            instances.splice(index, 1);
5233          }
5234        }
5235
5236        /**
5237         * Resolve any pending promises for this instance
5238         */
5239        function resolveWhen() {
5240          var dfd = pendings[handle];
5241          if ( dfd ) {
5242            dfd.forEach(function (promise) {
5243              promise.resolve(instance);
5244            });
5245            delete pendings[handle];
5246          }
5247        }
5248      },
5249
5250      /**
5251       * Async accessor to registered component instance
5252       * If not available then a promise is created to notify
5253       * all listeners when the instance is registered.
5254       */
5255      when : function(handle) {
5256        if ( isValidID(handle) ) {
5257          var deferred = $q.defer();
5258          var instance = self.get(handle);
5259
5260          if ( instance )  {
5261            deferred.resolve( instance );
5262          } else {
5263            if (pendings[handle] === undefined) {
5264              pendings[handle] = [];
5265            }
5266            pendings[handle].push(deferred);
5267          }
5268
5269          return deferred.promise;
5270        }
5271        return $q.reject("Invalid `md-component-id` value.");
5272      }
5273
5274    };
5275
5276    function isValidID(handle){
5277      return handle && (handle !== "");
5278    }
5279
5280  }
5281
5282})();
5283(function(){
5284"use strict";
5285
5286(function() {
5287  'use strict';
5288
5289  /**
5290   * @ngdoc service
5291   * @name $mdButtonInkRipple
5292   * @module material.core
5293   *
5294   * @description
5295   * Provides ripple effects for md-button.  See $mdInkRipple service for all possible configuration options.
5296   *
5297   * @param {object=} scope Scope within the current context
5298   * @param {object=} element The element the ripple effect should be applied to
5299   * @param {object=} options (Optional) Configuration options to override the default ripple configuration
5300   */
5301
5302  MdButtonInkRipple.$inject = ["$mdInkRipple"];
5303  angular.module('material.core')
5304    .factory('$mdButtonInkRipple', MdButtonInkRipple);
5305
5306  function MdButtonInkRipple($mdInkRipple) {
5307    return {
5308      attach: function attachRipple(scope, element, options) {
5309        options = angular.extend(optionsForElement(element), options);
5310
5311        return $mdInkRipple.attach(scope, element, options);
5312      }
5313    };
5314
5315    function optionsForElement(element) {
5316      if (element.hasClass('md-icon-button')) {
5317        return {
5318          isMenuItem: element.hasClass('md-menu-item'),
5319          fitRipple: true,
5320          center: true
5321        };
5322      } else {
5323        return {
5324          isMenuItem: element.hasClass('md-menu-item'),
5325          dimBackground: true
5326        };
5327      }
5328    }
5329  }
5330})();
5331
5332})();
5333(function(){
5334"use strict";
5335
5336(function() {
5337  'use strict';
5338
5339    /**
5340   * @ngdoc service
5341   * @name $mdCheckboxInkRipple
5342   * @module material.core
5343   *
5344   * @description
5345   * Provides ripple effects for md-checkbox.  See $mdInkRipple service for all possible configuration options.
5346   *
5347   * @param {object=} scope Scope within the current context
5348   * @param {object=} element The element the ripple effect should be applied to
5349   * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
5350   */
5351
5352  MdCheckboxInkRipple.$inject = ["$mdInkRipple"];
5353  angular.module('material.core')
5354    .factory('$mdCheckboxInkRipple', MdCheckboxInkRipple);
5355
5356  function MdCheckboxInkRipple($mdInkRipple) {
5357    return {
5358      attach: attach
5359    };
5360
5361    function attach(scope, element, options) {
5362      return $mdInkRipple.attach(scope, element, angular.extend({
5363        center: true,
5364        dimBackground: false,
5365        fitRipple: true
5366      }, options));
5367    }
5368  }
5369})();
5370
5371})();
5372(function(){
5373"use strict";
5374
5375(function() {
5376  'use strict';
5377
5378  /**
5379   * @ngdoc service
5380   * @name $mdListInkRipple
5381   * @module material.core
5382   *
5383   * @description
5384   * Provides ripple effects for md-list.  See $mdInkRipple service for all possible configuration options.
5385   *
5386   * @param {object=} scope Scope within the current context
5387   * @param {object=} element The element the ripple effect should be applied to
5388   * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
5389   */
5390
5391  MdListInkRipple.$inject = ["$mdInkRipple"];
5392  angular.module('material.core')
5393    .factory('$mdListInkRipple', MdListInkRipple);
5394
5395  function MdListInkRipple($mdInkRipple) {
5396    return {
5397      attach: attach
5398    };
5399
5400    function attach(scope, element, options) {
5401      return $mdInkRipple.attach(scope, element, angular.extend({
5402        center: false,
5403        dimBackground: true,
5404        outline: false,
5405        rippleSize: 'full'
5406      }, options));
5407    }
5408  }
5409})();
5410
5411})();
5412(function(){
5413"use strict";
5414
5415/**
5416 * @ngdoc module
5417 * @name material.core.ripple
5418 * @description
5419 * Ripple
5420 */
5421InkRippleCtrl.$inject = ["$scope", "$element", "rippleOptions", "$window", "$timeout", "$mdUtil", "$mdColorUtil"];
5422InkRippleDirective.$inject = ["$mdButtonInkRipple", "$mdCheckboxInkRipple"];
5423angular.module('material.core')
5424    .provider('$mdInkRipple', InkRippleProvider)
5425    .directive('mdInkRipple', InkRippleDirective)
5426    .directive('mdNoInk', attrNoDirective)
5427    .directive('mdNoBar', attrNoDirective)
5428    .directive('mdNoStretch', attrNoDirective);
5429
5430var DURATION = 450;
5431
5432/**
5433 * @ngdoc directive
5434 * @name mdInkRipple
5435 * @module material.core.ripple
5436 *
5437 * @description
5438 * The `md-ink-ripple` directive allows you to specify the ripple color or id a ripple is allowed.
5439 *
5440 * @param {string|boolean} md-ink-ripple A color string `#FF0000` or boolean (`false` or `0`) for preventing ripple
5441 *
5442 * @usage
5443 * ### String values
5444 * <hljs lang="html">
5445 *   <ANY md-ink-ripple="#FF0000">
5446 *     Ripples in red
5447 *   </ANY>
5448 *
5449 *   <ANY md-ink-ripple="false">
5450 *     Not rippling
5451 *   </ANY>
5452 * </hljs>
5453 *
5454 * ### Interpolated values
5455 * <hljs lang="html">
5456 *   <ANY md-ink-ripple="{{ randomColor() }}">
5457 *     Ripples with the return value of 'randomColor' function
5458 *   </ANY>
5459 *
5460 *   <ANY md-ink-ripple="{{ canRipple() }}">
5461 *     Ripples if 'canRipple' function return value is not 'false' or '0'
5462 *   </ANY>
5463 * </hljs>
5464 */
5465function InkRippleDirective ($mdButtonInkRipple, $mdCheckboxInkRipple) {
5466  return {
5467    controller: angular.noop,
5468    link:       function (scope, element, attr) {
5469      attr.hasOwnProperty('mdInkRippleCheckbox')
5470          ? $mdCheckboxInkRipple.attach(scope, element)
5471          : $mdButtonInkRipple.attach(scope, element);
5472    }
5473  };
5474}
5475
5476/**
5477 * @ngdoc service
5478 * @name $mdInkRipple
5479 * @module material.core.ripple
5480 *
5481 * @description
5482 * `$mdInkRipple` is a service for adding ripples to any element
5483 *
5484 * @usage
5485 * <hljs lang="js">
5486 * app.factory('$myElementInkRipple', function($mdInkRipple) {
5487 *   return {
5488 *     attach: function (scope, element, options) {
5489 *       return $mdInkRipple.attach(scope, element, angular.extend({
5490 *         center: false,
5491 *         dimBackground: true
5492 *       }, options));
5493 *     }
5494 *   };
5495 * });
5496 *
5497 * app.controller('myController', function ($scope, $element, $myElementInkRipple) {
5498 *   $scope.onClick = function (ev) {
5499 *     $myElementInkRipple.attach($scope, angular.element(ev.target), { center: true });
5500 *   }
5501 * });
5502 * </hljs>
5503 *
5504 * ### Disabling ripples globally
5505 * If you want to disable ink ripples globally, for all components, you can call the
5506 * `disableInkRipple` method in your app's config.
5507 *
5508 * <hljs lang="js">
5509 * app.config(function ($mdInkRippleProvider) {
5510 *   $mdInkRippleProvider.disableInkRipple();
5511 * });
5512 */
5513
5514function InkRippleProvider () {
5515  var isDisabledGlobally = false;
5516
5517  return {
5518    disableInkRipple: disableInkRipple,
5519    $get: ["$injector", function($injector) {
5520      return { attach: attach };
5521
5522      /**
5523       * @ngdoc method
5524       * @name $mdInkRipple#attach
5525       *
5526       * @description
5527       * Attaching given scope, element and options to inkRipple controller
5528       *
5529       * @param {object=} scope Scope within the current context
5530       * @param {object=} element The element the ripple effect should be applied to
5531       * @param {object=} options (Optional) Configuration options to override the defaultRipple configuration
5532       * * `center` -  Whether the ripple should start from the center of the container element
5533       * * `dimBackground` - Whether the background should be dimmed with the ripple color
5534       * * `colorElement` - The element the ripple should take its color from, defined by css property `color`
5535       * * `fitRipple` - Whether the ripple should fill the element
5536       */
5537      function attach (scope, element, options) {
5538        if (isDisabledGlobally || element.controller('mdNoInk')) return angular.noop;
5539        return $injector.instantiate(InkRippleCtrl, {
5540          $scope:        scope,
5541          $element:      element,
5542          rippleOptions: options
5543        });
5544      }
5545    }]
5546  };
5547
5548  /**
5549   * @ngdoc method
5550   * @name $mdInkRipple#disableInkRipple
5551   *
5552   * @description
5553   * A config-time method that, when called, disables ripples globally.
5554   */
5555  function disableInkRipple () {
5556    isDisabledGlobally = true;
5557  }
5558}
5559
5560/**
5561 * Controller used by the ripple service in order to apply ripples
5562 * @ngInject
5563 */
5564function InkRippleCtrl ($scope, $element, rippleOptions, $window, $timeout, $mdUtil, $mdColorUtil) {
5565  this.$window    = $window;
5566  this.$timeout   = $timeout;
5567  this.$mdUtil    = $mdUtil;
5568  this.$mdColorUtil    = $mdColorUtil;
5569  this.$scope     = $scope;
5570  this.$element   = $element;
5571  this.options    = rippleOptions;
5572  this.mousedown  = false;
5573  this.ripples    = [];
5574  this.timeout    = null; // Stores a reference to the most-recent ripple timeout
5575  this.lastRipple = null;
5576
5577  $mdUtil.valueOnUse(this, 'container', this.createContainer);
5578
5579  this.$element.addClass('md-ink-ripple');
5580
5581  // attach method for unit tests
5582  ($element.controller('mdInkRipple') || {}).createRipple = angular.bind(this, this.createRipple);
5583  ($element.controller('mdInkRipple') || {}).setColor = angular.bind(this, this.color);
5584
5585  this.bindEvents();
5586}
5587
5588
5589/**
5590 * Either remove or unlock any remaining ripples when the user mouses off of the element (either by
5591 * mouseup or mouseleave event)
5592 */
5593function autoCleanup (self, cleanupFn) {
5594
5595  if ( self.mousedown || self.lastRipple ) {
5596    self.mousedown = false;
5597    self.$mdUtil.nextTick( angular.bind(self, cleanupFn), false);
5598  }
5599
5600}
5601
5602
5603/**
5604 * Returns the color that the ripple should be (either based on CSS or hard-coded)
5605 * @returns {string}
5606 */
5607InkRippleCtrl.prototype.color = function (value) {
5608  var self = this;
5609
5610  // If assigning a color value, apply it to background and the ripple color
5611  if (angular.isDefined(value)) {
5612    self._color = self._parseColor(value);
5613  }
5614
5615  // If color lookup, use assigned, defined, or inherited
5616  return self._color || self._parseColor( self.inkRipple() ) || self._parseColor( getElementColor() );
5617
5618  /**
5619   * Finds the color element and returns its text color for use as default ripple color
5620   * @returns {string}
5621   */
5622  function getElementColor () {
5623    var items = self.options && self.options.colorElement ? self.options.colorElement : [];
5624    var elem =  items.length ? items[ 0 ] : self.$element[ 0 ];
5625
5626    return elem ? self.$window.getComputedStyle(elem).color : 'rgb(0,0,0)';
5627  }
5628};
5629
5630/**
5631 * Updating the ripple colors based on the current inkRipple value
5632 * or the element's computed style color
5633 */
5634InkRippleCtrl.prototype.calculateColor = function () {
5635  return this.color();
5636};
5637
5638
5639/**
5640 * Takes a string color and converts it to RGBA format
5641 * @param color {string}
5642 * @param [multiplier] {int}
5643 * @returns {string}
5644 */
5645
5646InkRippleCtrl.prototype._parseColor = function parseColor (color, multiplier) {
5647  multiplier = multiplier || 1;
5648  var colorUtil = this.$mdColorUtil;
5649
5650  if (!color) return;
5651  if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, (0.1 * multiplier).toString() + ')');
5652  if (color.indexOf('rgb') === 0) return colorUtil.rgbToRgba(color);
5653  if (color.indexOf('#') === 0) return colorUtil.hexToRgba(color);
5654
5655};
5656
5657/**
5658 * Binds events to the root element for
5659 */
5660InkRippleCtrl.prototype.bindEvents = function () {
5661  this.$element.on('mousedown', angular.bind(this, this.handleMousedown));
5662  this.$element.on('mouseup touchend', angular.bind(this, this.handleMouseup));
5663  this.$element.on('mouseleave', angular.bind(this, this.handleMouseup));
5664  this.$element.on('touchmove', angular.bind(this, this.handleTouchmove));
5665};
5666
5667/**
5668 * Create a new ripple on every mousedown event from the root element
5669 * @param event {MouseEvent}
5670 */
5671InkRippleCtrl.prototype.handleMousedown = function (event) {
5672  if ( this.mousedown ) return;
5673
5674  // When jQuery is loaded, we have to get the original event
5675  if (event.hasOwnProperty('originalEvent')) event = event.originalEvent;
5676  this.mousedown = true;
5677  if (this.options.center) {
5678    this.createRipple(this.container.prop('clientWidth') / 2, this.container.prop('clientWidth') / 2);
5679  } else {
5680
5681    // We need to calculate the relative coordinates if the target is a sublayer of the ripple element
5682    if (event.srcElement !== this.$element[0]) {
5683      var layerRect = this.$element[0].getBoundingClientRect();
5684      var layerX = event.clientX - layerRect.left;
5685      var layerY = event.clientY - layerRect.top;
5686
5687      this.createRipple(layerX, layerY);
5688    } else {
5689      this.createRipple(event.offsetX, event.offsetY);
5690    }
5691  }
5692};
5693
5694/**
5695 * Either remove or unlock any remaining ripples when the user mouses off of the element (either by
5696 * mouseup, touchend or mouseleave event)
5697 */
5698InkRippleCtrl.prototype.handleMouseup = function () {
5699  autoCleanup(this, this.clearRipples
vendor: 6,620 bytes, lines 5699-5941
5699);
5700};
5701
5702/**
5703 * Either remove or unlock any remaining ripples when the user mouses off of the element (by
5704 * touchmove)
5705 */
5706InkRippleCtrl.prototype.handleTouchmove = function () {
5707  autoCleanup(this, this.deleteRipples);
5708};
5709
5710/**
5711 * Cycles through all ripples and attempts to remove them.
5712 */
5713InkRippleCtrl.prototype.deleteRipples = function () {
5714  for (var i = 0; i < this.ripples.length; i++) {
5715    this.ripples[ i ].remove();
5716  }
5717};
5718
5719/**
5720 * Cycles through all ripples and attempts to remove them with fade.
5721 * Depending on logic within `fadeInComplete`, some removals will be postponed.
5722 */
5723InkRippleCtrl.prototype.clearRipples = function () {
5724  for (var i = 0; i < this.ripples.length; i++) {
5725    this.fadeInComplete(this.ripples[ i ]);
5726  }
5727};
5728
5729/**
5730 * Creates the ripple container element
5731 * @returns {*}
5732 */
5733InkRippleCtrl.prototype.createContainer = function () {
5734  var container = angular.element('<div class="md-ripple-container"></div>');
5735  this.$element.append(container);
5736  return container;
5737};
5738
5739InkRippleCtrl.prototype.clearTimeout = function () {
5740  if (this.timeout) {
5741    this.$timeout.cancel(this.timeout);
5742    this.timeout = null;
5743  }
5744};
5745
5746InkRippleCtrl.prototype.isRippleAllowed = function () {
5747  var element = this.$element[0];
5748  do {
5749    if (!element.tagName || element.tagName === 'BODY') break;
5750
5751    if (element && angular.isFunction(element.hasAttribute)) {
5752      if (element.hasAttribute('disabled')) return false;
5753      if (this.inkRipple() === 'false' || this.inkRipple() === '0') return false;
5754    }
5755
5756  } while (element = element.parentNode);
5757  return true;
5758};
5759
5760/**
5761 * The attribute `md-ink-ripple` may be a static or interpolated
5762 * color value OR a boolean indicator (used to disable ripples)
5763 */
5764InkRippleCtrl.prototype.inkRipple = function () {
5765  return this.$element.attr('md-ink-ripple');
5766};
5767
5768/**
5769 * Creates a new ripple and adds it to the container.  Also tracks ripple in `this.ripples`.
5770 * @param left
5771 * @param top
5772 */
5773InkRippleCtrl.prototype.createRipple = function (left, top) {
5774  if (!this.isRippleAllowed()) return;
5775
5776  var ctrl        = this;
5777  var colorUtil   = ctrl.$mdColorUtil;
5778  var ripple      = angular.element('<div class="md-ripple"></div>');
5779  var width       = this.$element.prop('clientWidth');
5780  var height      = this.$element.prop('clientHeight');
5781  var x           = Math.max(Math.abs(width - left), left) * 2;
5782  var y           = Math.max(Math.abs(height - top), top) * 2;
5783  var size        = getSize(this.options.fitRipple, x, y);
5784  var color       = this.calculateColor();
5785
5786  ripple.css({
5787    left:            left + 'px',
5788    top:             top + 'px',
5789    background:      'black',
5790    width:           size + 'px',
5791    height:          size + 'px',
5792    backgroundColor: colorUtil.rgbaToRgb(color),
5793    borderColor:     colorUtil.rgbaToRgb(color)
5794  });
5795  this.lastRipple = ripple;
5796
5797  // we only want one timeout to be running at a time
5798  this.clearTimeout();
5799  this.timeout    = this.$timeout(function () {
5800    ctrl.clearTimeout();
5801    if (!ctrl.mousedown) ctrl.fadeInComplete(ripple);
5802  }, DURATION * 0.35, false);
5803
5804  if (this.options.dimBackground) this.container.css({ backgroundColor: color });
5805  this.container.append(ripple);
5806  this.ripples.push(ripple);
5807  ripple.addClass('md-ripple-placed');
5808
5809  this.$mdUtil.nextTick(function () {
5810
5811    ripple.addClass('md-ripple-scaled md-ripple-active');
5812    ctrl.$timeout(function () {
5813      ctrl.clearRipples();
5814    }, DURATION, false);
5815
5816  }, false);
5817
5818  function getSize (fit, x, y) {
5819    return fit
5820        ? Math.max(x, y)
5821        : Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
5822  }
5823};
5824
5825
5826
5827/**
5828 * After fadeIn finishes, either kicks off the fade-out animation or queues the element for removal on mouseup
5829 * @param ripple
5830 */
5831InkRippleCtrl.prototype.fadeInComplete = function (ripple) {
5832  if (this.lastRipple === ripple) {
5833    if (!this.timeout && !this.mousedown) {
5834      this.removeRipple(ripple);
5835    }
5836  } else {
5837    this.removeRipple(ripple);
5838  }
5839};
5840
5841/**
5842 * Kicks off the animation for removing a ripple
5843 * @param ripple {Element}
5844 */
5845InkRippleCtrl.prototype.removeRipple = function (ripple) {
5846  var ctrl  = this;
5847  var index = this.ripples.indexOf(ripple);
5848  if (index < 0) return;
5849  this.ripples.splice(this.ripples.indexOf(ripple), 1);
5850  ripple.removeClass('md-ripple-active');
5851  ripple.addClass('md-ripple-remove');
5852  if (this.ripples.length === 0) this.container.css({ backgroundColor: '' });
5853  // use a 2-second timeout in order to allow for the animation to finish
5854  // we don't actually care how long the animation takes
5855  this.$timeout(function () {
5856    ctrl.fadeOutComplete(ripple);
5857  }, DURATION, false);
5858};
5859
5860/**
5861 * Removes the provided ripple from the DOM
5862 * @param ripple
5863 */
5864InkRippleCtrl.prototype.fadeOutComplete = function (ripple) {
5865  ripple.remove();
5866  this.lastRipple = null;
5867};
5868
5869/**
5870 * Used to create an empty directive.  This is used to track flag-directives whose children may have
5871 * functionality based on them.
5872 *
5873 * Example: `md-no-ink` will potentially be used by all child directives.
5874 */
5875function attrNoDirective () {
5876  return { controller: angular.noop };
5877}
5878
5879})();
5880(function(){
5881"use strict";
5882
5883(function() {
5884  'use strict';
5885
5886    /**
5887   * @ngdoc service
5888   * @name $mdTabInkRipple
5889   * @module material.core
5890   *
5891   * @description
5892   * Provides ripple effects for md-tabs.  See $mdInkRipple service for all possible configuration options.
5893   *
5894   * @param {object=} scope Scope within the current context
5895   * @param {object=} element The element the ripple effect should be applied to
5896   * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
5897   */
5898
5899  MdTabInkRipple.$inject = ["$mdInkRipple"];
5900  angular.module('material.core')
5901    .factory('$mdTabInkRipple', MdTabInkRipple);
5902
5903  function MdTabInkRipple($mdInkRipple) {
5904    return {
5905      attach: attach
5906    };
5907
5908    function attach(scope, element, options) {
5909      return $mdInkRipple.attach(scope, element, angular.extend({
5910        center: false,
5911        dimBackground: true,
5912        outline: false,
5913        rippleSize: 'full'
5914      }, options));
5915    }
5916  }
5917})();
5918
5919})();
5920(function(){
5921"use strict";
5922
5923angular.module('material.core.theming.palette', [])
5924.constant('$mdColorPalette', {
5925  'red': {
5926    '50': '#ffebee',
5927    '100': '#ffcdd2',
5928    '200': '#ef9a9a',
5929    '300': '#e57373',
5930    '400': '#ef5350',
5931    '500': '#f44336',
5932    '600': '#e53935',
5933    '700': '#d32f2f',
5934    '800': '#c62828',
5935    '900': '#b71c1c',
5936    'A100': '#ff8a80',
5937    'A200': '#ff5252',
5938    'A400': '#ff1744',
5939    'A700': '#d50000',
5940    'contrastDefaultColor': 'light',
5941    'contrastDarkColors': '50 100 200 300 
5941A100',
5942    'contrastStrongLightColors': '400 500 600 700 A200 A400 A700'
5943  },
5944  'pink': {
5945    '50': '#fce4ec',
5946    '100': '#f8bbd0',
5947    '200': '#f48fb1',
5948    '300': '#f06292',
5949    '400': '#ec407a',
5950    '500': '#e91e63',
5951    '600': '#d81b60',
5952    '700': '#c2185b',
5953    '800': '#ad1457',
5954    '900': '#880e4f',
5955    'A100': '#ff80ab',
5956    'A200': '#ff4081',
5957    'A400': '#f50057',
5958    'A700': '#c51162',
5959    'contrastDefaultColor': 'light',
5960    'contrastDarkColors': '50 100 200 A100',
5961    'contrastStrongLightColors': '500 600 A200 A400 A700'
5962  },
5963  'purple': {
5964    '50': '#f3e5f5',
5965    '100': '#e1bee7',
5966    '200': '#ce93d8',
5967    '300': '#ba68c8',
5968    '400': '#ab47bc',
5969    '500': '#9c27b0',
5970    '600': '#8e24aa',
5971    '700': '#7b1fa2',
5972    '800': '#6a1b9a',
5973    '900': '#4a148c',
5974    'A100': '#ea80fc',
5975    'A200': '#e040fb',
5976    'A400': '#d500f9',
5977    'A700': '#aa00ff',
5978    'contrastDefaultColor': 'light',
5979    'contrastDarkColors': '50 100 200 A100',
5980    'contrastStrongLightColors': '300 400 A200 A400 A700'
5981  },
5982  'deep-purple': {
5983    '50': '#ede7f6',
5984    '100': '#d1c4e9',
5985    '200': '#b39ddb',
5986    '300': '#9575cd',
5987    '400': '#7e57c2',
5988    '500': '#673ab7',
5989    '600': '#5e35b1',
5990    '700': '#512da8',
5991    '800': '#4527a0',
5992    '900': '#311b92',
5993    'A100': '#b388ff',
5994    'A200': '#7c4dff',
5995    'A400': '#651fff',
5996    'A700': '#6200ea',
5997    'contrastDefaultColor': 'light',
5998    'contrastDarkColors': '50 100 200 A100',
5999    'contrastStrongLightColors': '300 400 A200'
6000  },
6001  'indigo': {
6002    '50': '#e8eaf6',
6003    '100': '#c5cae9',
6004    '200': '#9fa8da',
6005    '300': '#7986cb',
6006    '400': '#5c6bc0',
6007    '500': '#3f51b5',
6008    '600': '#3949ab',
6009    '700': '#303f9f',
6010    '800': '#283593',
6011    '900': '#1a237e',
6012    'A100': '#8c9eff',
6013    'A200': '#536dfe',
6014    'A400': '#3d5afe',
6015    'A700': '#304ffe',
6016    'contrastDefaultColor': 'light',
6017    'contrastDarkColors': '50 100 200 A100',
6018    'contrastStrongLightColors': '300 400 A200 A400'
6019  },
6020  'blue': {
6021    '50': '#e3f2fd',
6022    '100': '#bbdefb',
6023    '200': '#90caf9',
6024    '300': '#64b5f6',
6025    '400': '#42a5f5',
6026    '500': '#2196f3',
6027    '600': '#1e88e5',
6028    '700': '#1976d2',
6029    '800': '#1565c0',
6030    '900': '#0d47a1',
6031    'A100': '#82b1ff',
6032    'A200': '#448aff',
6033    'A400': '#2979ff',
6034    'A700': '#2962ff',
6035    'contrastDefaultColor': 'light',
6036    'contrastDarkColors': '50 100 200 300 400 A100',
6037    'contrastStrongLightColors': '500 600 700 A200 A400 A700'
6038  },
6039  'light-blue': {
6040    '50': '#e1f5fe',
6041    '100': '#b3e5fc',
6042    '200': '#81d4fa',
6043    '300': '#4fc3f7',
6044    '400': '#29b6f6',
6045    '500': '#03a9f4',
6046    '600': '#039be5',
6047    '700': '#0288d1',
6048    '800': '#0277bd',
6049    '900': '#01579b',
6050    'A100': '#80d8ff',
6051    'A200': '#40c4ff',
6052    'A400': '#00b0ff',
6053    'A700': '#0091ea',
6054    'contrastDefaultColor': 'dark',
6055    'contrastLightColors': '600 700 800 900 A700',
6056    'contrastStrongLightColors': '600 700 800 A700'
6057  },
6058  'cyan': {
6059    '50': '#e0f7fa',
6060    '100': '#b2ebf2',
6061    '200': '#80deea',
6062    '300': '#4dd0e1',
6063    '400': '#26c6da',
6064    '500': '#00bcd4',
6065    '600': '#00acc1',
6066    '700': '#0097a7',
6067    '800': '#00838f',
6068    '900': '#006064',
6069    'A100': '#84ffff',
6070    'A200': '#18ffff',
6071    'A400': '#00e5ff',
6072    'A700': '#00b8d4',
6073    'contrastDefaultColor': 'dark',
6074    'contrastLightColors': '700 800 900',
6075    'contrastStrongLightColors': '700 800 900'
6076  },
6077  'teal': {
6078    '50': '#e0f2f1',
6079    '100': '#b2dfdb',
6080    '200': '#80cbc4',
6081    '300': '#4db6ac',
6082    '400': '#26a69a',
6083    '500': '#009688',
6084    '600': '#00897b',
6085    '700': '#00796b',
6086    '800': '#00695c',
6087    '900': '#004d40',
6088    'A100': '#a7ffeb',
6089    'A200': '#64ffda',
6090    'A400': '#1de9b6',
6091    'A700': '#00bfa5',
6092    'contrastDefaultColor': 'dark',
6093    'contrastLightColors': '500 600 700 800 900',
6094    'contrastStrongLightColors': '500 600 700'
6095  },
6096  'green': {
6097    '50': '#e8f5e9',
6098    '100': '#c8e6c9',
6099    '200': '#a5d6a7',
6100    '300': '#81c784',
6101    '400': '#66bb6a',
6102    '500': '#4caf50',
6103    '600': '#43a047',
6104    '700': '#388e3c',
6105    '800': '#2e7d32',
6106    '900': '#1b5e20',
6107    'A100': '#b9f6ca',
6108    'A200': '#69f0ae',
6109    'A400': '#00e676',
6110    'A700': '#00c853',
6111    'contrastDefaultColor': 'dark',
6112    'contrastLightColors': '500 600 700 800 900',
6113    'contrastStrongLightColors': '500 600 700'
6114  },
6115  'light-green': {
6116    '50': '#f1f8e9',
6117    '100': '#dcedc8',
6118    '200': '#c5e1a5',
6119    '300': '#aed581',
6120    '400': '#9ccc65',
6121    '500': '#8bc34a',
6122    '600': '#7cb342',
6123    '700': '#689f38',
6124    '800': '#558b2f',
6125    '900': '#33691e',
6126    'A100': '#ccff90',
6127    'A200': '#b2ff59',
6128    'A400': '#76ff03',
6129    'A700': '#64dd17',
6130    'contrastDefaultColor': 'dark',
6131    'contrastLightColors': '700 800 900',
6132    'contrastStrongLightColors': '700 800 900'
6133  },
6134  'lime': {
6135    '50': '#f9fbe7',
6136    '100': '#f0f4c3',
6137    '200': '#e6ee9c',
6138    '300': '#dce775',
6139    '400': '#d4e157',
6140    '500': '#cddc39',
6141    '600': '#c0ca33',
6142    '700': '#afb42b',
6143    '800': '#9e9d24',
6144    '900': '#827717',
6145    'A100': '#f4ff81',
6146    'A200': '#eeff41',
6147    'A400': '#c6ff00',
6148    'A700': '#aeea00',
6149    'contrastDefaultColor': 'dark',
6150    'contrastLightColors': '900',
6151    'contrastStrongLightColors': '900'
6152  },
6153  'yellow': {
6154    '50': '#fffde7',
6155    '100': '#fff9c4',
6156    '200': '#fff59d',
6157    '300': '#fff176',
6158    '400': '#ffee58',
6159    '500': '#ffeb3b',
6160    '600': '#fdd835',
6161    '700': '#fbc02d',
6162    '800': '#f9a825',
6163    '900': '#f57f17',
6164    'A100': '#ffff8d',
6165    'A200': '#ffff00',
6166    'A400': '#ffea00',
6167    'A700': '#ffd600',
6168    'contrastDefaultColor': 'dark'
6169  },
6170  'amber': {
6171    '50': '#fff8e1',
6172    '100': '#ffecb3',
6173    '200': '#ffe082',
6174    '300': '#ffd54f',
6175    '400': '#ffca28',
6176    '500': '#ffc107',
6177    '600': '#ffb300',
6178    '700': '#ffa000',
6179    '800': '#ff8f00',
6180    '900': '#ff6f00',
6181    'A100': '#ffe57f',
6182    'A200': '#ffd740',
6183    'A400': '#ffc400',
6184    'A700': '#ffab00',
6185    'contrastDefaultColor': 'dark'
6186  },
6187  'orange': {
6188    '50': '#fff3e0',
6189    '100': '#ffe0b2',
6190    '200': '#ffcc80',
6191    '300': '#ffb74d',
6192    '400': '#ffa726',
6193    '500': '#ff9800',
6194    '600': '#fb8c00',
6195    '700': '#f57c00',
6196    '800': '#ef6c00',
6197    '900': '#e65100',
6198    'A100': '#ffd180',
6199    'A200': '#ffab40',
6200    'A400': '#ff9100',
6201    'A700': '#ff6d00',
6202    'contrastDefaultColor': 'dark',
6203    'contrastLightColors': '800 900',
6204    'contrastStrongLightColors': '800 900'
6205  },
6206  'deep-orange': {
6207    '50': '#fbe9e7',
6208    '100': '#ffccbc',
6209    '200': '#ffab91',
6210    '300': '#ff8a65',
6211    '400': '#ff7043',
6212    '500': '#ff5722',
6213    '600': '#f4511e',
6214    '700': '#e64a19',
6215    '800': '#d84315',
6216    '900': '#bf360c',
6217    'A100': '#ff9e80',
6218    'A200': '#ff6e40',
6219    'A400': '#ff3d00',
6220    'A700': '#dd2c00',
6221    'contrastDefaultColor': 'light',
6222    'contrastDarkColors': '50 100 200 300 400 A100 A200',
6223    'contrastStrongLightColors': '500 600 700 800 900 A400 A700'
6224  },
6225  'brown': {
6226    '50': '#efebe9',
6227    '100': '#d7ccc8',
6228    '200': '#bcaaa4',
6229    '300': '#a1887f',
6230    '400': '#8d6e63',
6231    '500': '#795548',
6232    '600': '#6d4c41',
6233    '700': '#5d4037',
6234    '800': '#4e342e',
6235    '900': '#3e2723',
6236    'A100': '#d7ccc8',
6237    'A200': '#bcaaa4',
6238    'A400': '#8d6e63',
6239    'A700': '#5d4037',
6240    'contrastDefaultColor': 'light',
6241    'contrastDarkColors': '50 100 200 A100 A200',
6242    'contrastStrongLightColors': '300 400'
6243  },
6244  'grey': {
6245    '50': '#fafafa',
6246    '100': '#f5f5f5',
6247    '200': '#eeeeee',
6248    '300': '#e0e0e0',
6249    '400': '#bdbdbd',
6250    '500': '#9e9e9e',
6251    '600': '#757575',
6252    '700': '#616161',
6253    '800': '#424242',
6254    '900': '#212121',
6255    'A100': '#ffffff',
6256    'A200': '#000000',
6257    'A400': '#303030',
6258    'A700': '#616161',
6259    'contrastDefaultColor': 'dark',
6260    'contrastLightColors': '600 700 800 900 A200 A400 A700'
6261  },
6262  'blue-grey': {
6263    '50': '#eceff1',
6264    '100': '#cfd8dc',
6265    '200': '#b0bec5',
6266    '300': '#90a4ae',
6267    '400': '#78909c',
6268    '500': '#607d8b',
6269    '600': '#546e7a',
6270    '700': '#455a64',
6271    '800': '#37474f',
6272    '900': '#263238',
6273    'A100': '#cfd8dc',
6274    'A200': '#b0bec5',
6275    'A400': '#78909c',
6276    'A700': '#455a64',
6277    'contrastDefaultColor': 'light',
6278    'contrastDarkColors': '50 100 200 300 A100 A200',
6279    'contrastStrongLightColors': '400 500 700'
6280  }
6281});
6282
6283})();
6284(function(){
6285"use strict";
6286
6287(function(angular) {
6288  'use strict';
6289/**
6290 * @ngdoc module
6291 * @name material.core.theming
6292 * @description
6293 * Theming
6294 */
6295detectDisabledThemes.$inject = ["$mdThemingProvider"];
6296ThemingDirective.$inject = ["$mdTheming", "$interpolate", "$parse", "$mdUtil", "$q", "$log"];
6297ThemableDirective.$inject = ["$mdTheming"];
6298ThemingProvider.$inject = ["$mdColorPalette", "$$mdMetaProvider"];
6299generateAllThemes.$inject = ["$injector", "$mdTheming"];
6300angular.module('material.core.theming', ['material.core.theming.palette', 'material.core.meta'])
6301  .directive('mdTheme', ThemingDirective)
6302  .directive('mdThemable', ThemableDirective)
6303  .directive('mdThemesDisabled', disableThemesDirective )
6304  .provider('$mdTheming', ThemingProvider)
6305  .config( detectDisabledThemes )
6306  .run(generateAllThemes);
6307
6308/**
6309 * Detect if the HTML or the BODY tags has a [md-themes-disabled] attribute
6310 * If yes, then immediately disable all theme stylesheet generation and DOM injection
6311 */
6312/**
6313 * @ngInject
6314 */
6315function detectDisabledThemes($mdThemingProvider) {
6316  var isDisabled = !!document.querySelector('[md-themes-disabled]');
6317  $mdThemingProvider.disableTheming(isDisabled);
6318}
6319
6320/**
6321 * @ngdoc service
6322 * @name $mdThemingProvider
6323 * @module material.core.theming
6324 *
6325 * @description Provider to configure the `$mdTheming` service.
6326 *
6327 * ### Default Theme
6328 * The `$mdThemingProvider` uses by default the following theme configuration:
6329 *
6330 * - Primary Palette: `Blue`
6331 * - Accent Palette: `Pink`
6332 * - Warn Palette: `Deep-Orange`
6333 * - Background Palette: `Grey`
6334 *
6335 * If you don't want to use the `md-theme` directive on the elements itself, you may want to overwrite
6336 * the default theme.<br/>
6337 * This can be done by using the following markup.
6338 *
6339 * <hljs lang="js">
6340 *   myAppModule.config(function($mdThemingProvider) {
6341 *     $mdThemingProvider
6342 *       .theme('default')
6343 *       .primaryPalette('blue')
6344 *       .accentPalette('teal')
6345 *       .warnPalette('red')
6346 *       .backgroundPalette('grey');
6347 *   });
6348 * </hljs>
6349 *
6350
6351 * ### Dynamic Themes
6352 *
6353 * By default, if you change a theme at runtime, the `$mdTheming` service will not detect those changes.<br/>
6354 * If you have an application, which changes its theme on runtime, you have to enable theme watching.
6355 *
6356 * <hljs lang="js">
6357 *   myAppModule.config(function($mdThemingProvider) {
6358 *     // Enable theme watching.
6359 *     $mdThemingProvider.alwaysWatchTheme(true);
6360 *   });
6361 * </hljs>
6362 *
6363 * ### Custom Theme Styles
6364 *
6365 * Sometimes you may want to use your own theme styles for some custom components.<br/>
6366 * You are able to register your own styles by using the following markup.
6367 *
6368 * <hljs lang="js">
6369 *   myAppModule.config(function($mdThemingProvider) {
6370 *     // Register our custom stylesheet into the theming provider.
6371 *     $mdThemingProvider.registerStyles(STYLESHEET);
6372 *   });
6373 * </hljs>
6374 *
6375 * The `registerStyles` method only accepts strings as value, so you're actually not able to load an external
6376 * stylesheet file into the `$mdThemingProvider`.
6377 *
6378 * If it's necessary to load an external stylesheet, we suggest using a bundler, which supports including raw content,
6379 * like [raw-loader](https://github.com/webpack/raw-loader) for `webpack`.
6380 *
6381 * <hljs lang="js">
6382 *   myAppModule.config(function($mdThemingProvider) {
6383 *     // Register your custom stylesheet into the theming provider.
6384 *     $mdThemingProvider.registerStyles(require('../styles/my-component.theme.css'));
6385 *   });
6386 * </hljs>
6387 *
6388 * ### Browser color
6389 *
6390 * Enables browser header coloring
6391 * for more info please visit:
6392 * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
6393 *
6394 * Options parameter: <br/>
6395 * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
6396 * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
6397 *             'accent', 'background' and 'warn'. Default is `primary`. <br/>
6398 * `hue`     - The hue from the selected palette. Default is `800`<br/>
6399 *
6400 * <hljs lang="js">
6401 *   myAppModule.config(function($mdThemingProvider) {
6402 *     // Enable browser color
6403 *     $mdThemingProvider.enableBrowserColor({
6404 *       theme: 'myTheme', // Default is 'default'
6405 *       palette: 'accent', // Default is 'primary', any basic material palette and extended palettes are available
6406 *       hue: '200' // Default is '800'
6407 *     });
6408 *   });
6409 * </hljs>
6410 */
6411
6412/**
6413 * @ngdoc method
6414 * @name $mdThemingProvider#registerStyles
6415 * @param {string} styles The styles to be appended to AngularJS Material's built in theme css.
6416 */
6417/**
6418 * @ngdoc method
6419 * @name $mdThemingProvider#setNonce
6420 * @param {string} nonceValue The nonce to be added as an attribute to the theme style tags.
6421 * Setting a value allows the use of CSP policy without using the unsafe-inline directive.
6422 */
6423
6424/**
6425 * @ngdoc method
6426 * @name $mdThemingProvider#setDefaultTheme
6427 * @param {string} themeName Default theme name to be applied to elements. Default value is `default`.
6428 */
6429
6430/**
6431 * @ngdoc method
6432 * @name $mdThemingProvider#alwaysWatchTheme
6433 * @param {boolean} watch Whether or not to always watch themes for changes and re-apply
6434 * classes when they change. Default is `false`. Enabling can reduce performance.
6435 */
6436
6437/**
6438 * @ngdoc method
6439 * @name $mdThemingProvider#enableBrowserColor
6440 * @param {Object=} options Options object for the browser color<br/>
6441 * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
6442 * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
6443 *             'accent', 'background' and 'warn'. Default is `primary`. <br/>
6444 * `hue`     - The hue from the selected palette. Default is `800`<br/>
6445 * @returns {Function} remove function of the browser color
6446 */
6447
6448/* Some Example Valid Theming Expressions
6449 * =======================================
6450 *
6451 * Intention group expansion: (valid for primary, accent, warn, background)
6452 *
6453 * {{primary-100}} - grab shade 100 from the primary palette
6454 * {{primary-100-0.7}} - grab shade 100, apply opacity of 0.7
6455 * {{primary-100-contrast}} - grab shade 100's contrast color
6456 * {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette
6457 * {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-1
6458 * {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue
6459 * {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules
6460 * {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue
6461 * {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules
6462 *
6463 * Foreground expansion: Applies rgba to black/white foreground text
6464 *
6465 * {{foreground-1}} - used for primary text
6466 * {{foreground-2}} - used for secondary text/divider
6467 * {{foreground-3}} - used for disabled text
6468 * {{foreground-4}}
vendor: 9,814 bytes, lines 6468-6776
6468 - used for dividers
6469 *
6470 */
6471
6472// In memory generated CSS rules; registered by theme.name
6473var GENERATED = { };
6474
6475// In memory storage of defined themes and color palettes (both loaded by CSS, and user specified)
6476var PALETTES;
6477
6478// Text Colors on light and dark backgrounds
6479// @see https://www.google.com/design/spec/style/color.html#color-text-background-colors
6480var DARK_FOREGROUND = {
6481  name: 'dark',
6482  '1': 'rgba(0,0,0,0.87)',
6483  '2': 'rgba(0,0,0,0.54)',
6484  '3': 'rgba(0,0,0,0.38)',
6485  '4': 'rgba(0,0,0,0.12)'
6486};
6487var LIGHT_FOREGROUND = {
6488  name: 'light',
6489  '1': 'rgba(255,255,255,1.0)',
6490  '2': 'rgba(255,255,255,0.7)',
6491  '3': 'rgba(255,255,255,0.5)',
6492  '4': 'rgba(255,255,255,0.12)'
6493};
6494
6495var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)';
6496var LIGHT_SHADOW = '';
6497
6498var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)');
6499var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87)');
6500var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)');
6501
6502var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background'];
6503var DEFAULT_COLOR_TYPE = 'primary';
6504
6505// A color in a theme will use these hues by default, if not specified by user.
6506var LIGHT_DEFAULT_HUES = {
6507  'accent': {
6508    'default': 'A200',
6509    'hue-1': 'A100',
6510    'hue-2': 'A400',
6511    'hue-3': 'A700'
6512  },
6513  'background': {
6514    'default': '50',
6515    'hue-1': 'A100',
6516    'hue-2': '100',
6517    'hue-3': '300'
6518  }
6519};
6520
6521var DARK_DEFAULT_HUES = {
6522  'background': {
6523    'default': 'A400',
6524    'hue-1': '800',
6525    'hue-2': '900',
6526    'hue-3': 'A200'
6527  }
6528};
6529THEME_COLOR_TYPES.forEach(function(colorType) {
6530  // Color types with unspecified default hues will use these default hue values
6531  var defaultDefaultHues = {
6532    'default': '500',
6533    'hue-1': '300',
6534    'hue-2': '800',
6535    'hue-3': 'A100'
6536  };
6537  if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues;
6538  if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues;
6539});
6540
6541var VALID_HUE_VALUES = [
6542  '50', '100', '200', '300', '400', '500', '600',
6543  '700', '800', '900', 'A100', 'A200', 'A400', 'A700'
6544];
6545
6546var themeConfig = {
6547  disableTheming : false,   // Generate our themes at run time; also disable stylesheet DOM injection
6548  generateOnDemand : false, // Whether or not themes are to be generated on-demand (vs. eagerly).
6549  registeredStyles : [],    // Custom styles registered to be used in the theming of custom components.
6550  nonce : null              // Nonce to be added as an attribute to the generated themes style tags.
6551};
6552
6553/**
6554 *
6555 */
6556function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
6557  ThemingService.$inject = ["$rootScope", "$mdUtil", "$q", "$log"];
6558  PALETTES = { };
6559  var THEMES = { };
6560
6561  var themingProvider;
6562
6563  var alwaysWatchTheme = false;
6564  var defaultTheme = 'default';
6565
6566  // Load JS Defined Palettes
6567  angular.extend(PALETTES, $mdColorPalette);
6568
6569  // Default theme defined in core.js
6570
6571  /**
6572   * Adds `theme-color` and `msapplication-navbutton-color` meta tags with the color parameter
6573   * @param {string} color Hex value of the wanted browser color
6574   * @returns {Function} Remove function of the meta tags
6575   */
6576  var setBrowserColor = function (color) {
6577    // Chrome, Firefox OS and Opera
6578    var removeChrome = $$mdMetaProvider.setMeta('theme-color', color);
6579    // Windows Phone
6580    var removeWindows = $$mdMetaProvider.setMeta('msapplication-navbutton-color', color);
6581
6582    return function () {
6583      removeChrome();
6584      removeWindows();
6585    };
6586  };
6587
6588  /**
6589   * Enables browser header coloring
6590   * for more info please visit:
6591   * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
6592   *
6593   * The default color is `800` from `primary` palette of the `default` theme
6594   *
6595   * options are:
6596   * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme
6597   * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
6598   *             'accent', 'background' and 'warn'. Default is `primary`
6599   * `hue`     - The hue from the selected palette. Default is `800`
6600   *
6601   * @param {Object=} options Options object for the browser color
6602   * @returns {Function} remove function of the browser color
6603   */
6604  var enableBrowserColor = function (options) {
6605    options = angular.isObject(options) ? options : {};
6606
6607    var theme = options.theme || 'default';
6608    var hue = options.hue || '800';
6609
6610    var palette = PALETTES[options.palette] ||
6611      PALETTES[THEMES[theme].colors[options.palette || 'primary'].name];
6612
6613    var color = angular.isObject(palette[hue]) ? palette[hue].hex : palette[hue];
6614
6615    return setBrowserColor(color);
6616  };
6617
6618  return themingProvider = {
6619    definePalette: definePalette,
6620    extendPalette: extendPalette,
6621    theme: registerTheme,
6622
6623    /**
6624     * return a read-only clone of the current theme configuration
6625     */
6626    configuration : function() {
6627      return angular.extend( { }, themeConfig, {
6628        defaultTheme : defaultTheme,
6629        alwaysWatchTheme : alwaysWatchTheme,
6630        registeredStyles : [].concat(themeConfig.registeredStyles)
6631      });
6632    },
6633
6634    /**
6635     * Easy way to disable theming without having to use
6636     * `.constant("$MD_THEME_CSS","");` This disables
6637     * all dynamic theme style sheet generations and injections...
6638     */
6639    disableTheming: function(isDisabled) {
6640      themeConfig.disableTheming = angular.isUndefined(isDisabled) || !!isDisabled;
6641    },
6642
6643    registerStyles: function(styles) {
6644      themeConfig.registeredStyles.push(styles);
6645    },
6646
6647    setNonce: function(nonceValue) {
6648      themeConfig.nonce = nonceValue;
6649    },
6650
6651    generateThemesOnDemand: function(onDemand) {
6652      themeConfig.generateOnDemand = onDemand;
6653    },
6654
6655    setDefaultTheme: function(theme) {
6656      defaultTheme = theme;
6657    },
6658
6659    alwaysWatchTheme: function(alwaysWatch) {
6660      alwaysWatchTheme = alwaysWatch;
6661    },
6662
6663    enableBrowserColor: enableBrowserColor,
6664
6665    $get: ThemingService,
6666    _LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,
6667    _DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,
6668    _PALETTES: PALETTES,
6669    _THEMES: THEMES,
6670    _parseRules: parseRules,
6671    _rgba: rgba
6672  };
6673
6674  // Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... });
6675  function definePalette(name, map) {
6676    map = map || {};
6677    PALETTES[name] = checkPaletteValid(name, map);
6678    return themingProvider;
6679  }
6680
6681  // Returns an new object which is a copy of a given palette `name` with variables from
6682  // `map` overwritten
6683  // Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' });
6684  function extendPalette(name, map) {
6685    return checkPaletteValid(name,  angular.extend({}, PALETTES[name] || {}, map) );
6686  }
6687
6688  // Make sure that palette has all required hues
6689  function checkPaletteValid(name, map) {
6690    var missingColors = VALID_HUE_VALUES.filter(function(field) {
6691      return !map[field];
6692    });
6693    if (missingColors.length) {
6694      throw new Error("Missing colors %1 in palette %2!"
6695                      .replace('%1', missingColors.join(', '))
6696                      .replace('%2', name));
6697    }
6698
6699    return map;
6700  }
6701
6702  // Register a theme (which is a collection of color palettes to use with various states
6703  // ie. warn, accent, primary )
6704  // Optionally inherit from an existing theme
6705  // $mdThemingProvider.theme('custom-theme').primaryPalette('red');
6706  function registerTheme(name, inheritFrom) {
6707    if (THEMES[name]) return THEMES[name];
6708
6709    inheritFrom = inheritFrom || 'default';
6710
6711    var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom;
6712    var theme = new Theme(name);
6713
6714    if (parentTheme) {
6715      angular.forEach(parentTheme.colors, function(color, colorType) {
6716        theme.colors[colorType] = {
6717          name: color.name,
6718          // Make sure a COPY of the hues is given to the child color,
6719          // not the same reference.
6720          hues: angular.extend({}, color.hues)
6721        };
6722      });
6723    }
6724    THEMES[name] = theme;
6725
6726    return theme;
6727  }
6728
6729  function Theme(name) {
6730    var self = this;
6731    self.name = name;
6732    self.colors = {};
6733
6734    self.dark = setDark;
6735    setDark(false);
6736
6737    function setDark(isDark) {
6738      isDark = arguments.length === 0 ? true : !!isDark;
6739
6740      // If no change, abort
6741      if (isDark === self.isDark) return;
6742
6743      self.isDark = isDark;
6744
6745      self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND;
6746      self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW;
6747
6748      // Light and dark themes have different default hues.
6749      // Go through each existing color type for this theme, and for every
6750      // hue value that is still the default hue value from the previous light/dark setting,
6751      // set it to the default hue value from the new light/dark setting.
6752      var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES;
6753      var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES;
6754      angular.forEach(newDefaultHues, function(newDefaults, colorType) {
6755        var color = self.colors[colorType];
6756        var oldDefaults = oldDefaultHues[colorType];
6757        if (color) {
6758          for (var hueName in color.hues) {
6759            if (color.hues[hueName] === oldDefaults[hueName]) {
6760              color.hues[hueName] = newDefaults[hueName];
6761            }
6762          }
6763        }
6764      });
6765
6766      return self;
6767    }
6768
6769    THEME_COLOR_TYPES.forEach(function(colorType) {
6770      var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType];
6771      self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) {
6772        var color = self.colors[colorType] = {
6773          name: paletteName,
6774          hues: angular.extend({}, defaultHues, hues)
6775        };
6776
6777        Object.keys(color.hues).forEach(function(name) {
6778          if (!defaultHues[name]) {
6779            throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4"
6780              .replace('%1', name)
6781              .replace('%2', self.name)
6782              .replace('%3', paletteName)
6783              .replace('%4', Object.keys(defaultHues).join(', '))
6784            );
6785          }
6786        });
6787        Object.keys(color.hues).map(function(key) {
6788          return color.hues[key];
6789        }).forEach(function(hueValue) {
6790          if (VALID_HUE_VALUES.indexOf(hueValue) == -1) {
6791            throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5"
6792              .replace('%1', hueValue)
6793              .replace('%2', self.name)
6794              .replace('%3', colorType)
6795              .replace('%4', paletteName)
6796              .replace('%5', VALID_HUE_VALUES.join(', '))
6797            );
6798          }
6799        });
6800        return self;
6801      };
6802
6803      self[colorType + 'Color'] = function() {
6804        var args = Array.prototype.slice.call(arguments);
6805        console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' +
6806                     'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.');
6807        return self[colorType + 'Palette'].apply(self, args);
6808      };
6809    });
6810  }
6811
6812  /**
6813   * @ngdoc service
6814   * @name $mdTheming
6815   * @module material.core.theming
6816   *
6817   * @description
6818   *
6819   * Service that makes an element apply theming related <b>classes</b> to itself.
6820   *
6821   * <hljs lang="js">
6822   * app.directive('myFancyDirective', function($mdTheming) {
6823   *   return {
6824   *     restrict: 'e',
6825   *     link: function(scope, el, attrs) {
6826   *       $mdTheming(el);
6827   *
6828   *       $mdTheming.defineTheme('myTheme', {
6829   *         primary: 'blue',
6830   *         accent: 'pink',
6831   *         dark: true
6832   *       })
6833   *     }
6834   *   };
6835   * });
6836   * </hljs>
6837   * @param {element=} element to apply theming to
6838   */
6839
6840  /**
6841   * @ngdoc property
6842   * @name $mdTheming#THEMES
6843   * @description
6844   * Property to get all the themes defined
6845   * @returns {Object} All the themes defined with their properties
6846   */
6847
6848  /**
6849   * @ngdoc property
6850   * @name $mdTheming#PALETTES
6851   * @description
6852   * Property to get all the palettes defined
6853   * @returns {Object} All the palettes defined with their colors
6854   */
6855
6856  /**
6857   * @ngdoc method
6858   * @name $mdTheming#registered
6859   * @description
6860   * Determine is specified theme name is a valid, registered theme
6861   * @param {string} themeName the theme to check if registered
6862   * @returns {boolean} whether the theme is registered or not
6863   */
6864
6865  /**
6866   * @ngdoc method
6867   * @name $mdTheming#defaultTheme
6868   * @description
6869   * Returns the default theme
6870   * @returns {string} The default theme
6871   */
6872
6873  /**
6874   * @ngdoc method
6875   * @name $mdTheming#generateTheme
6876   * @description
6877   * Lazy generate themes - by default, every theme is generated 
6877when defined.
6878   * You can disable this in the configuration section using the
6879   * `$mdThemingProvider.generateThemesOnDemand(true);`
6880   *
6881   * The theme name that is passed in must match the name of the theme that was defined as part of the configuration block.
6882   *
6883   * @param name {string} theme name to generate
6884   */
6885
6886  /**
6887   * @ngdoc method
6888   * @name $mdTheming#setBrowserColor
6889   * @description
6890   * Sets browser header coloring
6891   * for more info please visit:
6892   * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
6893   *
6894   * The default color is `800` from `primary` palette of the `default` theme
6895   *
6896   * options are:<br/>
6897   * `theme`   - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme.<br/>
6898   * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
6899   *             'accent', 'background' and 'warn'. Default is `primary`<br/>
6900   * `hue`     - The hue from the selected palette. Default is `800`
6901   *
6902   * @param {Object} options Options object for the browser color
6903   * @returns {Function} remove function of the browser color
6904   */
6905
6906  /**
6907   * @ngdoc method
6908   * @name $mdTheming#defineTheme
6909   * @description
6910   * Dynamically define a theme by an options object
6911   *
6912   * options are:<br/>
6913   * `primary`    - The primary palette of the theme.<br/>
6914   * `accent`     - The accent palette of the theme.<br/>
6915   * `warn`       - The warn palette of the theme.<br/>
6916   * `background` - The background palette of the theme.<br/>
6917   * `dark`       - Indicates if it's a dark theme.<br/>
6918   *
6919   * @param {String} name Theme name to define
6920   * @param {Object} options Theme definition options
6921   * @returns {Promise<string>} A resolved promise with the theme name
6922   */
6923
6924  /* @ngInject */
6925  function ThemingService($rootScope, $mdUtil, $q, $log) {
6926        // Allow us to be invoked via a linking function signature.
6927    var applyTheme = function (scope, el) {
6928          if (el === undefined) { el = scope; scope = undefined; }
6929          if (scope === undefined) { scope = $rootScope; }
6930          applyTheme.inherit(el, el);
6931        };
6932
6933    Object.defineProperty(applyTheme, 'THEMES', {
6934      get: function () {
6935        return angular.extend({}, THEMES);
6936      }
6937    });
6938    Object.defineProperty(applyTheme, 'PALETTES', {
6939      get: function () {
6940        return angular.extend({}, PALETTES);
6941      }
6942    });
6943    Object.defineProperty(applyTheme, 'ALWAYS_WATCH', {
6944      get: function () {
6945        return alwaysWatchTheme;
6946      }
6947    });
6948    applyTheme.inherit = inheritTheme;
6949    applyTheme.registered = registered;
6950    applyTheme.defaultTheme = function() { return defaultTheme; };
6951    applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };
6952    applyTheme.defineTheme = function(name, options) {
6953      options = options || {};
6954
6955      var theme = registerTheme(name);
6956
6957      if (options.primary) {
6958        theme.primaryPalette(options.primary);
6959      }
6960      if (options.accent) {
6961        theme.accentPalette(options.accent);
6962      }
6963      if (options.warn) {
6964        theme.warnPalette(options.warn);
6965      }
6966      if (options.background) {
6967        theme.backgroundPalette(options.background);
6968      }
6969      if (options.dark){
6970        theme.dark();
6971      }
6972
6973      this.generateTheme(name);
6974
6975      return $q.resolve(name);
6976    };
6977    applyTheme.setBrowserColor = enableBrowserColor;
6978
6979    return applyTheme;
6980
6981    /**
6982     * Determine is specified theme name is a valid, registered theme
6983     */
6984    function registered(themeName) {
6985      if (themeName === undefined || themeName === '') return true;
6986      return applyTheme.THEMES[themeName] !== undefined;
6987    }
6988
6989    /**
6990     * Get theme name for the element, then update with Theme CSS class
6991     */
6992    function inheritTheme (el, parent) {
6993      var ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');
6994
6995      updateThemeClass(lookupThemeName());
6996
6997      if (ctrl) {
6998        var watchTheme = alwaysWatchTheme ||
6999                         ctrl.$shouldWatch ||
7000                         $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'));
7001
7002        var unwatch = ctrl.registerChanges(function (name) {
7003          updateThemeClass(name);
7004
7005          if (!watchTheme) {
7006            unwatch();
7007          }
7008          else {
7009            el.on('$destroy', unwatch);
7010          }
7011        });
7012      }
7013
7014      /**
7015       * Find the theme name from the parent controller or element data
7016       */
7017      function lookupThemeName() {
7018        // As a few components (dialog) add their controllers later, we should also watch for a controller init.
7019        return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme);
7020      }
7021
7022      /**
7023       * Remove old theme class and apply a new one
7024       * NOTE: if not a valid theme name, then the current name is not changed
7025       */
7026      function updateThemeClass(theme) {
7027        if (!theme) return;
7028        if (!registered(theme)) {
7029          $log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' +
7030                    'Register it with $mdThemingProvider.theme().');
7031        }
7032
7033        var oldTheme = el.data('$mdThemeName');
7034        if (oldTheme) el.removeClass('md-' + oldTheme +'-theme');
7035        el.addClass('md-' + theme + '-theme');
7036        el.data('$mdThemeName', theme);
7037        if (ctrl) {
7038          el.data('$mdThemeController', ctrl);
7039        }
7040      }
7041    }
7042
7043  }
7044}
7045
7046function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
7047  return {
7048    priority: 101, // has to be more than 100 to be before interpolation (issue on IE)
7049    link: {
7050      pre: function(scope, el, attrs) {
7051        var registeredCallbacks = [];
7052
7053        var startSymbol = $interpolate.startSymbol();
7054        var endSymbol = $interpolate.endSymbol();
7055
7056        var theme = attrs.mdTheme.trim();
7057
7058        var hasInterpolation =
7059          theme.substr(0, startSymbol.length) === startSymbol &&
7060          theme.lastIndexOf(endSymbol) === theme.length - endSymbol.length;
7061
7062        var oneTimeOperator = '::';
7063        var oneTimeBind = attrs.mdTheme
7064            .split(startSymbol).join('')
7065            .split(endSymbol).join('')
7066            .trim()
7067            .substr(0, oneTimeOperator.length) === oneTimeOperator;
7068
7069        var ctrl = {
7070          registerChanges: function (cb, context) {
7071            if (context) {
7072              cb = angular.bind(context, cb);
7073            }
7074
7075            registeredCallbacks.push(cb);
7076
7077            return function () {
7078              var index = registeredCallbacks.indexOf(cb);
7079
7080              if (index > -1) {
7081                registeredCallbacks.splice(index, 1);
7082              }
7083            };
7084          },
7085          $setTheme: function (theme) {
7086            if (!$mdTheming.registered(theme)) {
7087              $log.warn('attempted to use unregistered theme \'' + theme + '\'');
7088            }
7089
7090            ctrl.$mdTheme = theme;
7091
7092            // Iterating backwards to support unregistering during iteration
7093            // http://stackoverflow.com/a/9882349/890293
7094            // we don't use `reverse()` of array because it mutates the array and we don't want it to get re-indexed
7095            for (var i = registeredCallbacks.length; i--;) {
7096              registeredCallbacks[i](theme);
7097            }
7098          },
7099          $shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')) ||
7100                        $mdTheming.ALWAYS_WATCH ||
7101                        (hasInterpolation && !oneTimeBind)
7102        };
7103
7104        el.data('$mdThemeController', ctrl);
7105
7106        var getTheme = function () {
7107          var interpolation = $interpolate(attrs.mdTheme)(scope);
7108          return $parse(interpolation)(scope) || interpolation;
7109        };
7110
7111        var setParsedTheme = function (theme) {
7112          if (typeof theme === 'string') {
7113            return ctrl.$setTheme(theme);
7114          }
7115
7116          $q.when( angular.isFunction(theme) ?  theme() : theme )
7117            .then(function(name){
7118              ctrl.$setTheme(name);
7119            });
7120        };
7121
7122        setParsedTheme(getTheme());
7123
7124        var unwatch = scope.$watch(getTheme, function(theme) {
7125          if (theme) {
7126            setParsedTheme(theme);
7127
7128            if (!ctrl.$shouldWatch) {
7129              unwatch();
7130            }
7131          }
7132        });
7133      }
7134    }
7135  };
7136}
7137
7138/**
7139 * Special directive that will disable ALL runtime Theme style generation and DOM injection
7140 *
7141 * <link rel="stylesheet" href="angular-material.min.css">
7142 * <link rel="stylesheet" href="angular-material.themes.css">
7143 *
7144 * <body md-themes-disabled>
7145 *  ...
7146 * </body>
7147 *
7148 * Note: Using md-themes-css directive requires the developer to load external
7149 * theme stylesheets; e.g. custom themes from Material-Tools:
7150 *
7151 *       `angular-material.themes.css`
7152 *
7153 * Another option is to use the ThemingProvider to configure and disable the attribute
7154 * conversions; this would obviate the use of the `md-themes-css` directive
7155 *
7156 */
7157function disableThemesDirective() {
7158  themeConfig.disableTheming = true;
7159
7160  // Return a 1x-only, first-match attribute directive
7161  return {
7162    restrict : 'A',
7163    priority : '900'
7164  };
7165}
7166
7167function ThemableDirective($mdTheming) {
7168  return $mdTheming;
7169}
7170
7171function parseRules(theme, colorType, rules) {
7172  checkValidPalette(theme, colorType);
7173
7174  rules = rules.replace(/THEME_NAME/g, theme.name);
7175  var generatedRules = [];
7176  var color = theme.colors[colorType];
7177
7178  var themeNameRegex = new RegExp('\\.md-' + theme.name + '-theme', 'g');
7179  // Matches '{{ primary-color }}', etc
7180  var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g');
7181  var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow|default)-?(\d\.?\d*)?(contrast)?\s*\}\}'?"?/g;
7182  var palette = PALETTES[color.name];
7183
7184  // find and replace simple variables where we use a specific hue, not an entire palette
7185  // eg. "{{primary-100}}"
7186  //\(' + THEME_COLOR_TYPES.join('\|') + '\)'
7187  rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity, contrast) {
7188    if (colorType === 'foreground') {
7189      if (hue == 'shadow') {
7190        return theme.foregroundShadow;
7191      } else {
7192        return theme.foregroundPalette[hue] || theme.foregroundPalette['1'];
7193      }
7194    }
7195
7196    // `default` is also accepted as a hue-value, because the background palettes are
7197    // using it as a name for the default hue.
7198    if (hue.indexOf('hue') === 0 || hue === 'default') {
7199      hue = theme.colors[colorType].hues[hue];
7200    }
7201
7202    return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '')[contrast ? 'contrast' : 'value'], opacity );
7203  });
7204
7205  // For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3)
7206  angular.forEach(color.hues, function(hueValue, hueName) {
7207    var newRule = rules
7208      .replace(hueRegex, function(match, _, colorType, hueType, opacity) {
7209        return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity);
7210      });
7211    if (hueName !== 'default') {
7212      newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName);
7213    }
7214
7215    // Don't apply a selector rule to the default theme, making it easier to override
7216    // styles of the base-component
7217    if (theme.name == 'default') {
7218      var themeRuleRegex = /((?:\s|>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)\.md-default-theme((?:\s|>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)/g;
7219
7220      newRule = newRule.replace(themeRuleRegex, function(match, start, end) {
7221        return match + ', ' + start + end;
7222      });
7223    }
7224    generatedRules.push(newRule);
7225  });
7226
7227  return generatedRules;
7228}
7229
7230var rulesByType = {};
7231
7232// Generate our themes at run time given the state of THEMES and PALETTES
7233function generateAllThemes($injector, $mdTheming) {
7234  var head = document.head;
7235  var firstChild = head ? head.firstElementChild : null;
7236  var themeCss = !themeConfig.disableTheming && $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : '';
7237
7238  // Append our custom registered styles to the theme stylesheet.
7239  themeCss += themeConfig.registeredStyles.join('');
7240
7241  if ( !firstChild ) return;
7242  if (themeCss.length === 0) return; // no rules, so no point in running this expensive task
7243
7244  // Expose contrast colors for palettes to ensure that text is always readable
7245  angular.forEach(PALETTES, sanitizePalette);
7246
7247  // MD_THEME_CSS is a string generated by the build process that includes all the themable
7248  // components as templates
7249
7250  // Break the CSS into individual rules
7251  var rules = themeCss
7252                  .split(/\}(?!(\}|'|"|;))/)
7253                  .filter(function(rule) { return rule && rule.trim().length; })
7254                  .map(function(rule) { return rule.trim() + '}'; });
7255
7256
7257  var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g');
7258
7259  THEME_COLOR_TYPES.forEach(function(type) {
7260    rulesByType[type] = '';
7261  });
7262
7263
7264  // Sort the rules based on type, allowing us to do color substitution on a per-type basis
7265  rules.forEach(function(rule) {
7266    var match = rule.match(ruleMatchRegex);
7267    // First: test that if the rule has '.md-accent', it goes into the accent set of rules
7268    for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) {
7269      if (rule.indexOf('.md-' + type) > -1) {
7270        return rulesByType[type] += rule;
7271      }
7272    }
7273
7274    // If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from
7275    // there
7276    for (i = 0; type = THEME_COLOR_TYPES[i]; i++) {
7277      if (rule.indexOf(type) > -1) {
7278        return rulesByType[type] += rule;
7279      }
7280    }
7281
7282    // Default to the primary array
7283    return rulesByType[DEFAULT_COLOR_TYPE] += rule;
7284  });
7285
7286  // If themes are being generated on-demand, quit here. The user will later manually
7287  // call generateTheme to do this on a theme-by-theme basis.
7288  if (themeConfig.generateOnDemand) return;
7289
7290  angular.forEach($mdTheming.THEMES, function(theme) {
7291    if (!GENERATED[theme.name] && !($mdTheming.defaultTheme() !== 'default' && theme.name === 'default')) {
7292      generateTheme(theme, theme.name, themeConfig.nonce);
7293    }
7294  });
7295
7296
7297  // *************************
7298  // Internal functions
7299  // *************************
7300
7301  // The user specifies a 'default' contrast color as either light or dark,
7302  // then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light)
7303  function sanitizePalette(palette, name) {
7304    var defaultContrast = palette.contrastDefaultColor;
7305    var lightColors = palette.contrastLightColors || [];
7306    var strongLightColors = palette.contrastStrongLightColors || [];
7307    var darkColors = palette.contrastDarkColors || [];
7308
7309    // These colors are provided as space-separated lists
7310    if (typeof lightColors === 'string') lightColors = lightColors.split(' ');
7311    if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' ');
7312    if (typeof darkColors === 'string') darkColors = darkColors.split(' ');
7313
7314    // Cleanup after ourselves
7315    delete palette.contrastDefaultColor;
7316    delete palette.contrastLightColors;
7317    delete palette.contrastStrongLightColors;
7318    delete palette.contrastDarkColors;
7319
7320    // Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR }
7321    angular.forEach(palette, function(hueValue, hueName) {
7322      if (angular.isObject(hueValue)) return; // Already converted
7323      // Map everything to rgb colors
7324      var rgbValue = colorToRgbaArray(hueValue);
7325      if (!rgbValue) {
7326        throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected."
7327                        .replace('%1', hueValue)
7328                        .replace('%2', palette.name)
7329                        .replace('%3', hueName));
7330      }
7331
7332      palette[hueName] = {
7333        hex: palette[hueName],
7334        value: rgbValue,
7335        contrast: getContrastColor()
7336      };
7337      function getContrastColor() {
7338        if (defaultContrast === 'light') {
7339          if (darkColors.indexOf(hueName) > -1) {
7340            return DARK_CONTRAST_COLOR;
7341          } else {
7342            return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
7343              : LIGHT_CONTRAST_COLOR;
7344          }
7345        } else {
7346          if (lightColors.indexOf(hueName) > -1) {
7347            return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
7348              : LIGHT_CONTRAST_COLOR;
7349          } else {
7350            return DARK_CONTRAST_COLOR;
7351          }
7352        }
7353      }
7354    });
7355  }
7356}
7357
7358function generateTheme(theme, name, nonce) {
7359  var head = document.head;
7360  var firstChild = head ? head.firstElementChild : null;
7361
7362  if (!GENERATED[name]) {
7363    // For each theme, use the color palettes specified for
7364    // `primary`, `warn` and `accent` to generate CSS rules.
7365    THEME_COLOR_TYPES.forEach(function(colorType) {
7366      var styleStrings = parseRules(theme, colorType, rulesByType[colorType]);
7367      while (styleStrings.length) {
7368        var styleContent = styleStrings.shift();
7369        if (styleContent) {
7370          var style = document.createElement('style');
7371          style.setAttribute('md-theme-style', '');
7372          if (nonce) {
7373            style.setAttribute('nonce', nonce);
7374          }
7375          style.appendChild(document.createTextNode(styleContent));
7376          head.insertBefore(style, firstChild);
7377        }
7378      }
7379    });
7380
7381    GENERATED[theme.name] = true;
7382  }
7383
7384}
7385
7386
7387function checkValidPalette(theme, colorType) {
7388  // If theme attempts to use a palette that doesnt exist, throw error
7389  if (!PALETTES[ (theme.colors[colorType] || {}).name ]) {
7390    throw new Error(
vendor: 27,409 bytes, lines 7391-8289
7391      "You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3"
7392                    .replace('%1', theme.name)
7393                    .replace('%2', colorType)
7394                    .replace('%3', Object.keys(PALETTES).join(', '))
7395    );
7396  }
7397}
7398
7399function colorToRgbaArray(clr) {
7400  if (angular.isArray(clr) && clr.length == 3) return clr;
7401  if (/^rgb/.test(clr)) {
7402    return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) {
7403      return i == 3 ? parseFloat(value, 10) : parseInt(value, 10);
7404    });
7405  }
7406  if (clr.charAt(0) == '#') clr = clr.substring(1);
7407  if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return;
7408
7409  var dig = clr.length / 3;
7410  var red = clr.substr(0, dig);
7411  var grn = clr.substr(dig, dig);
7412  var blu = clr.substr(dig * 2);
7413  if (dig === 1) {
7414    red += red;
7415    grn += grn;
7416    blu += blu;
7417  }
7418  return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)];
7419}
7420
7421function rgba(rgbArray, opacity) {
7422  if ( !rgbArray ) return "rgb('0,0,0')";
7423
7424  if (rgbArray.length == 4) {
7425    rgbArray = angular.copy(rgbArray);
7426    opacity ? rgbArray.pop() : opacity = rgbArray.pop();
7427  }
7428  return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ?
7429    'rgba(' + rgbArray.join(',') + ',' + opacity + ')' :
7430    'rgb(' + rgbArray.join(',') + ')';
7431}
7432
7433
7434})(window.angular);
7435
7436})();
7437(function(){
7438"use strict";
7439
7440// Polyfill angular < 1.4 (provide $animateCss)
7441angular
7442  .module('material.core')
7443  .factory('$$mdAnimate', ["$q", "$timeout", "$mdConstant", "$animateCss", function($q, $timeout, $mdConstant, $animateCss){
7444
7445     // Since $$mdAnimate is injected into $mdUtil... use a wrapper function
7446     // to subsequently inject $mdUtil as an argument to the AnimateDomUtils
7447
7448     return function($mdUtil) {
7449       return AnimateDomUtils( $mdUtil, $q, $timeout, $mdConstant, $animateCss);
7450     };
7451   }]);
7452
7453/**
7454 * Factory function that requires special injections
7455 */
7456function AnimateDomUtils($mdUtil, $q, $timeout, $mdConstant, $animateCss) {
7457  var self;
7458  return self = {
7459    /**
7460     *
7461     */
7462    translate3d : function( target, from, to, options ) {
7463      return $animateCss(target, {
7464        from: from,
7465        to: to,
7466        addClass: options.transitionInClass,
7467        removeClass: options.transitionOutClass,
7468        duration: options.duration
7469      })
7470      .start()
7471      .then(function(){
7472          // Resolve with reverser function...
7473          return reverseTranslate;
7474      });
7475
7476      /**
7477       * Specific reversal of the request translate animation above...
7478       */
7479      function reverseTranslate (newFrom) {
7480        return $animateCss(target, {
7481           to: newFrom || from,
7482           addClass: options.transitionOutClass,
7483           removeClass: options.transitionInClass,
7484           duration: options.duration
7485        }).start();
7486
7487      }
7488    },
7489
7490    /**
7491     * Listen for transitionEnd event (with optional timeout)
7492     * Announce completion or failure via promise handlers
7493     */
7494    waitTransitionEnd: function (element, opts) {
7495      var TIMEOUT = 3000; // fallback is 3 secs
7496
7497      return $q(function(resolve, reject){
7498        opts = opts || { };
7499
7500        // If there is no transition is found, resolve immediately
7501        //
7502        // NOTE: using $mdUtil.nextTick() causes delays/issues
7503        if (noTransitionFound(opts.cachedTransitionStyles)) {
7504          TIMEOUT = 0;
7505        }
7506
7507        var timer = $timeout(finished, opts.timeout || TIMEOUT);
7508        element.on($mdConstant.CSS.TRANSITIONEND, finished);
7509
7510        /**
7511         * Upon timeout or transitionEnd, reject or resolve (respectively) this promise.
7512         * NOTE: Make sure this transitionEnd didn't bubble up from a child
7513         */
7514        function finished(ev) {
7515          if ( ev && ev.target !== element[0]) return;
7516
7517          if ( ev  ) $timeout.cancel(timer);
7518          element.off($mdConstant.CSS.TRANSITIONEND, finished);
7519
7520          // Never reject since ngAnimate may cause timeouts due missed transitionEnd events
7521          resolve();
7522
7523        }
7524
7525        /**
7526         * Checks whether or not there is a transition.
7527         *
7528         * @param styles The cached styles to use for the calculation. If null, getComputedStyle()
7529         * will be used.
7530         *
7531         * @returns {boolean} True if there is no transition/duration; false otherwise.
7532         */
7533        function noTransitionFound(styles) {
7534          styles = styles || window.getComputedStyle(element[0]);
7535
7536          return styles.transitionDuration == '0s' || (!styles.transition && !styles.transitionProperty);
7537        }
7538
7539      });
7540    },
7541
7542    calculateTransformValues: function (element, originator) {
7543      var origin = originator.element;
7544      var bounds = originator.bounds;
7545
7546      if (origin || bounds) {
7547        var originBnds = origin ? self.clientRect(origin) || currentBounds() : self.copyRect(bounds);
7548        var dialogRect = self.copyRect(element[0].getBoundingClientRect());
7549        var dialogCenterPt = self.centerPointFor(dialogRect);
7550        var originCenterPt = self.centerPointFor(originBnds);
7551
7552        return {
7553          centerX: originCenterPt.x - dialogCenterPt.x,
7554          centerY: originCenterPt.y - dialogCenterPt.y,
7555          scaleX: Math.round(100 * Math.min(0.5, originBnds.width / dialogRect.width)) / 100,
7556          scaleY: Math.round(100 * Math.min(0.5, originBnds.height / dialogRect.height)) / 100
7557        };
7558      }
7559      return {centerX: 0, centerY: 0, scaleX: 0.5, scaleY: 0.5};
7560
7561      /**
7562       * This is a fallback if the origin information is no longer valid, then the
7563       * origin bounds simply becomes the current bounds for the dialogContainer's parent
7564       */
7565      function currentBounds() {
7566        var cntr = element ? element.parent() : null;
7567        var parent = cntr ? cntr.parent() : null;
7568
7569        return parent ? self.clientRect(parent) : null;
7570      }
7571    },
7572
7573    /**
7574     * Calculate the zoom transform from dialog to origin.
7575     *
7576     * We use this to set the dialog position immediately;
7577     * then the md-transition-in actually translates back to
7578     * `translate3d(0,0,0) scale(1.0)`...
7579     *
7580     * NOTE: all values are rounded to the nearest integer
7581     */
7582    calculateZoomToOrigin: function (element, originator) {
7583      var zoomTemplate = "translate3d( {centerX}px, {centerY}px, 0 ) scale( {scaleX}, {scaleY} )";
7584      var buildZoom = angular.bind(null, $mdUtil.supplant, zoomTemplate);
7585
7586      return buildZoom(self.calculateTransformValues(element, originator));
7587    },
7588
7589    /**
7590     * Calculate the slide transform from panel to origin.
7591     * NOTE: all values are rounded to the nearest integer
7592     */
7593    calculateSlideToOrigin: function (element, originator) {
7594      var slideTemplate = "translate3d( {centerX}px, {centerY}px, 0 )";
7595      var buildSlide = angular.bind(null, $mdUtil.supplant, slideTemplate);
7596
7597      return buildSlide(self.calculateTransformValues(element, originator));
7598    },
7599
7600    /**
7601     * Enhance raw values to represent valid css stylings...
7602     */
7603    toCss : function( raw ) {
7604      var css = { };
7605      var lookups = 'left top right bottom width height x y min-width min-height max-width max-height';
7606
7607      angular.forEach(raw, function(value,key) {
7608        if ( angular.isUndefined(value) ) return;
7609
7610        if ( lookups.indexOf(key) >= 0 ) {
7611          css[key] = value + 'px';
7612        } else {
7613          switch (key) {
7614            case 'transition':
7615              convertToVendor(key, $mdConstant.CSS.TRANSITION, value);
7616              break;
7617            case 'transform':
7618              convertToVendor(key, $mdConstant.CSS.TRANSFORM, value);
7619              break;
7620            case 'transformOrigin':
7621              convertToVendor(key, $mdConstant.CSS.TRANSFORM_ORIGIN, value);
7622              break;
7623            case 'font-size':
7624              css['font-size'] = value; // font sizes aren't always in px
7625              break;
7626          }
7627        }
7628      });
7629
7630      return css;
7631
7632      function convertToVendor(key, vendor, value) {
7633        angular.forEach(vendor.split(' '), function (key) {
7634          css[key] = value;
7635        });
7636      }
7637    },
7638
7639    /**
7640     * Convert the translate CSS value to key/value pair(s).
7641     */
7642    toTransformCss: function (transform, addTransition, transition) {
7643      var css = {};
7644      angular.forEach($mdConstant.CSS.TRANSFORM.split(' '), function (key) {
7645        css[key] = transform;
7646      });
7647
7648      if (addTransition) {
7649        transition = transition || "all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) !important";
7650        css.transition = transition;
7651      }
7652
7653      return css;
7654    },
7655
7656    /**
7657     *  Clone the Rect and calculate the height/width if needed
7658     */
7659    copyRect: function (source, destination) {
7660      if (!source) return null;
7661
7662      destination = destination || {};
7663
7664      angular.forEach('left top right bottom width height'.split(' '), function (key) {
7665        destination[key] = Math.round(source[key]);
7666      });
7667
7668      destination.width = destination.width || (destination.right - destination.left);
7669      destination.height = destination.height || (destination.bottom - destination.top);
7670
7671      return destination;
7672    },
7673
7674    /**
7675     * Calculate ClientRect of element; return null if hidden or zero size
7676     */
7677    clientRect: function (element) {
7678      var bounds = angular.element(element)[0].getBoundingClientRect();
7679      var isPositiveSizeClientRect = function (rect) {
7680        return rect && (rect.width > 0) && (rect.height > 0);
7681      };
7682
7683      // If the event origin element has zero size, it has probably been hidden.
7684      return isPositiveSizeClientRect(bounds) ? self.copyRect(bounds) : null;
7685    },
7686
7687    /**
7688     *  Calculate 'rounded' center point of Rect
7689     */
7690    centerPointFor: function (targetRect) {
7691      return targetRect ? {
7692        x: Math.round(targetRect.left + (targetRect.width / 2)),
7693        y: Math.round(targetRect.top + (targetRect.height / 2))
7694      } : { x : 0, y : 0 };
7695    }
7696
7697  };
7698}
7699
7700
7701})();
7702(function(){
7703"use strict";
7704
7705if (angular.version.minor >= 4) {
7706  angular.module('material.core.animate', []);
7707} else {
7708(function() {
7709  "use strict";
7710
7711  var forEach = angular.forEach;
7712
7713  var WEBKIT = angular.isDefined(document.documentElement.style.WebkitAppearance);
7714  var TRANSITION_PROP = WEBKIT ? 'WebkitTransition' : 'transition';
7715  var ANIMATION_PROP = WEBKIT ? 'WebkitAnimation' : 'animation';
7716  var PREFIX = WEBKIT ? '-webkit-' : '';
7717
7718  var TRANSITION_EVENTS = (WEBKIT ? 'webkitTransitionEnd ' : '') + 'transitionend';
7719  var ANIMATION_EVENTS = (WEBKIT ? 'webkitAnimationEnd ' : '') + 'animationend';
7720
7721  var $$ForceReflowFactory = ['$document', function($document) {
7722    return function() {
7723      return $document[0].body.clientWidth + 1;
7724    };
7725  }];
7726
7727  var $$rAFMutexFactory = ['$$rAF', function($$rAF) {
7728    return function() {
7729      var passed = false;
7730      $$rAF(function() {
7731        passed = true;
7732      });
7733      return function(fn) {
7734        passed ? fn() : $$rAF(fn);
7735      };
7736    };
7737  }];
7738
7739  var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
7740    var INITIAL_STATE = 0;
7741    var DONE_PENDING_STATE = 1;
7742    var DONE_COMPLETE_STATE = 2;
7743
7744    function AnimateRunner(host) {
7745      this.setHost(host);
7746
7747      this._doneCallbacks = [];
7748      this._runInAnimationFrame = $$rAFMutex();
7749      this._state = 0;
7750    }
7751
7752    AnimateRunner.prototype = {
7753      setHost: function(host) {
7754        this.host = host || {};
7755      },
7756
7757      done: function(fn) {
7758        if (this._state === DONE_COMPLETE_STATE) {
7759          fn();
7760        } else {
7761          this._doneCallbacks.push(fn);
7762        }
7763      },
7764
7765      progress: angular.noop,
7766
7767      getPromise: function() {
7768        if (!this.promise) {
7769          var self = this;
7770          this.promise = $q(function(resolve, reject) {
7771            self.done(function(status) {
7772              status === false ? reject() : resolve();
7773            });
7774          });
7775        }
7776        return this.promise;
7777      },
7778
7779      then: function(resolveHandler, rejectHandler) {
7780        return this.getPromise().then(resolveHandler, rejectHandler);
7781      },
7782
7783      'catch': function(handler) {
7784        return this.getPromise()['catch'](handler);
7785      },
7786
7787      'finally': function(handler) {
7788        return this.getPromise()['finally'](handler);
7789      },
7790
7791      pause: function() {
7792        if (this.host.pause) {
7793          this.host.pause();
7794        }
7795      },
7796
7797      resume: function() {
7798        if (this.host.resume) {
7799          this.host.resume();
7800        }
7801      },
7802
7803      end: function() {
7804        if (this.host.end) {
7805          this.host.end();
7806        }
7807        this._resolve(true);
7808      },
7809
7810      cancel: function() {
7811        if (this.host.cancel) {
7812          this.host.cancel();
7813        }
7814        this._resolve(false);
7815      },
7816
7817      complete: function(response) {
7818        var self = this;
7819        if (self._state === INITIAL_STATE) {
7820          self._state = DONE_PENDING_STATE;
7821          self._runInAnimationFrame(function() {
7822            self._resolve(response);
7823          });
7824        }
7825      },
7826
7827      _resolve: function(response) {
7828        if (this._state !== DONE_COMPLETE_STATE) {
7829          forEach(this._doneCallbacks, function(fn) {
7830            fn(response);
7831          });
7832          this._doneCallbacks.length = 0;
7833          this._state = DONE_COMPLETE_STATE;
7834        }
7835      }
7836    };
7837
7838    // Polyfill AnimateRunner.all which is used by input animations
7839    AnimateRunner.all = function(runners, callback) {
7840      var count = 0;
7841      var status = true;
7842      forEach(runners, function(runner) {
7843        runner.done(onProgress);
7844      });
7845
7846      function onProgress(response) {
7847        status = status && response;
7848        if (++count === runners.length) {
7849          callback(status);
7850        }
7851      }
7852    };
7853
7854    return AnimateRunner;
7855  }];
7856
7857  angular
7858    .module('material.core.animate', [])
7859    .factory('$$forceReflow', $$ForceReflowFactory)
7860    .factory('$$AnimateRunner', $$AnimateRunnerFactory)
7861    .factory('$$rAFMutex', $$rAFMutexFactory)
7862    .factory('$animateCss', ['$window', '$$rAF', '$$AnimateRunner', '$$forceReflow', '$$jqLite', '$timeout', '$animate',
7863                     function($window,   $$rAF,   $$AnimateRunner,   $$forceReflow,   $$jqLite,   $timeout, $animate) {
7864
7865      function init(element, options) {
7866
7867        var temporaryStyles = [];
7868        var node = getDomNode(element);
7869        var areAnimationsAllowed = node && $animate.enabled();
7870
7871        var hasCompleteStyles = false;
7872        var hasCompleteClasses = false;
7873
7874        if (areAnimationsAllowed) {
7875          if (options.transitionStyle) {
7876            temporaryStyles.push([PREFIX + 'transition', options.transitionStyle]);
7877          }
7878
7879          if (options.keyframeStyle) {
7880            temporaryStyles.push([PREFIX + 'animation', options.keyframeStyle]);
7881          }
7882
7883          if (options.delay) {
7884            temporaryStyles.push([PREFIX + 'transition-delay', options.delay + 's']);
7885          }
7886
7887          if (options.duration) {
7888            temporaryStyles.push([PREFIX + 'transition-duration', options.duration + 's']);
7889          }
7890
7891          hasCompleteStyles = options.keyframeStyle ||
7892              (options.to && (options.duration > 0 || options.transitionStyle));
7893          hasCompleteClasses = !!options.addClass || !!options.removeClass;
7894
7895          blockTransition(element, true);
7896        }
7897
7898        var hasCompleteAnimation = areAnimationsAllowed && (hasCompleteStyles || hasCompleteClasses);
7899
7900        applyAnimationFromStyles(element, options);
7901
7902        var animationClosed = false;
7903        var events, eventFn;
7904
7905        return {
7906          close: $window.close,
7907          start: function() {
7908            var runner = new $$AnimateRunner();
7909            waitUntilQuiet(function() {
7910              blockTransition(element, false);
7911              if (!hasCompleteAnimation) {
7912                return close();
7913              }
7914
7915              forEach(temporaryStyles, function(entry) {
7916                var key = entry[0];
7917                var value = entry[1];
7918                node.style[camelCase(key)] = value;
7919              });
7920
7921              applyClasses(element, options);
7922
7923              var timings = computeTimings(element);
7924              if (timings.duration === 0) {
7925                return close();
7926              }
7927
7928              var moreStyles = [];
7929
7930              if (options.easing) {
7931                if (timings.transitionDuration) {
7932                  moreStyles.push([PREFIX + 'transition-timing-function', options.easing]);
7933                }
7934                if (timings.animationDuration) {
7935                  moreStyles.push([PREFIX + 'animation-timing-function', options.easing]);
7936                }
7937              }
7938
7939              if (options.delay && timings.animationDelay) {
7940                moreStyles.push([PREFIX + 'animation-delay', options.delay + 's']);
7941              }
7942
7943              if (options.duration && timings.animationDuration) {
7944                moreStyles.push([PREFIX + 'animation-duration', options.duration + 's']);
7945              }
7946
7947              forEach(moreStyles, function(entry) {
7948                var key = entry[0];
7949                var value = entry[1];
7950                node.style[camelCase(key)] = value;
7951                temporaryStyles.push(entry);
7952              });
7953
7954              var maxDelay = timings.delay;
7955              var maxDelayTime = maxDelay * 1000;
7956              var maxDuration = timings.duration;
7957              var maxDurationTime = maxDuration * 1000;
7958              var startTime = Date.now();
7959
7960              events = [];
7961              if (timings.transitionDuration) {
7962                events.push(TRANSITION_EVENTS);
7963              }
7964              if (timings.animationDuration) {
7965                events.push(ANIMATION_EVENTS);
7966              }
7967              events = events.join(' ');
7968              eventFn = function(event) {
7969                event.stopPropagation();
7970                var ev = event.originalEvent || event;
7971                var timeStamp = ev.timeStamp || Date.now();
7972                var elapsedTime = parseFloat(ev.elapsedTime.toFixed(3));
7973                if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
7974                  close();
7975                }
7976              };
7977              element.on(events, eventFn);
7978
7979              applyAnimationToStyles(element, options);
7980
7981              $timeout(close, maxDelayTime + maxDurationTime * 1.5, false);
7982            });
7983
7984            return runner;
7985
7986            function close() {
7987              if (animationClosed) return;
7988              animationClosed = true;
7989
7990              if (events && eventFn) {
7991                element.off(events, eventFn);
7992              }
7993              applyClasses(element, options);
7994              applyAnimationStyles(element, options);
7995              forEach(temporaryStyles, function(entry) {
7996                node.style[camelCase(entry[0])] = '';
7997              });
7998              runner.complete(true);
7999              return runner;
8000            }
8001          }
8002        };
8003      }
8004
8005      function applyClasses(element, options) {
8006        if (options.addClass) {
8007          $$jqLite.addClass(element, options.addClass);
8008          options.addClass = null;
8009        }
8010        if (options.removeClass) {
8011          $$jqLite.removeClass(element, options.removeClass);
8012          options.removeClass = null;
8013        }
8014      }
8015
8016      function computeTimings(element) {
8017        var node = getDomNode(element);
8018        var cs = $window.getComputedStyle(node);
8019        var tdr = parseMaxTime(cs[prop('transitionDuration')]);
8020        var adr = parseMaxTime(cs[prop('animationDuration')]);
8021        var tdy = parseMaxTime(cs[prop('transitionDelay')]);
8022        var ady = parseMaxTime(cs[prop('animationDelay')]);
8023
8024        adr *= (parseInt(cs[prop('animationIterationCount')], 10) || 1);
8025        var duration = Math.max(adr, tdr);
8026        var delay = Math.max(ady, tdy);
8027
8028        return {
8029          duration: duration,
8030          delay: delay,
8031          animationDuration: adr,
8032          transitionDuration: tdr,
8033          animationDelay: ady,
8034          transitionDelay: tdy
8035        };
8036
8037        function prop(key) {
8038          return WEBKIT ? 'Webkit' + key.charAt(0).toUpperCase() + key.substr(1)
8039                        : key;
8040        }
8041      }
8042
8043      function parseMaxTime(str) {
8044        var maxValue = 0;
8045        var values = (str || "").split(/\s*,\s*/);
8046        forEach(values, function(value) {
8047          // it's always safe to consider only second values and omit `ms` values since
8048          // getComputedStyle will always handle the conversion for us
8049          if (value.charAt(value.length - 1) == 's') {
8050            value = value.substring(0, value.length - 1);
8051          }
8052          value = parseFloat(value) || 0;
8053          maxValue = maxValue ? Math.max(value, maxValue) : value;
8054        });
8055        return maxValue;
8056      }
8057
8058      var cancelLastRAFRequest;
8059      var rafWaitQueue = [];
8060      function waitUntilQuiet(callback) {
8061        if (cancelLastRAFRequest) {
8062          cancelLastRAFRequest(); //cancels the request
8063        }
8064        rafWaitQueue.push(callback);
8065        cancelLastRAFRequest = $$rAF(function() {
8066          cancelLastRAFRequest = null;
8067
8068          // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
8069          // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
8070          var pageWidth = $$forceReflow();
8071
8072          // we use a for loop to ensure that if the queue is changed
8073          // during this looping then it will consider new requests
8074          for (var i = 0; i < rafWaitQueue.length; i++) {
8075            rafWaitQueue[i](pageWidth);
8076          }
8077          rafWaitQueue.length = 0;
8078        });
8079      }
8080
8081      function applyAnimationStyles(element, options) {
8082        applyAnimationFromStyles(element, options);
8083        applyAnimationToStyles(element, options);
8084      }
8085
8086      function applyAnimationFromStyles(element, options) {
8087        if (options.from) {
8088          element.css(options.from);
8089          options.from = null;
8090        }
8091      }
8092
8093      function applyAnimationToStyles(element, options) {
8094        if (options.to) {
8095          element.css(options.to);
8096          options.to = null;
8097        }
8098      }
8099
8100      function getDomNode(element) {
8101        for (var i = 0; i < element.length; i++) {
8102          if (element[i].nodeType === 1) return element[i];
8103        }
8104      }
8105
8106      function blockTransition(element, bool) {
8107        var node = getDomNode(element);
8108        var key = camelCase(PREFIX + 'transition-delay');
8109        node.style[key] = bool ? '-9999s' : '';
8110      }
8111
8112      return init;
8113    }]);
8114
8115  /**
8116   * Older browsers [FF31] expect camelCase
8117   * property keys.
8118   * e.g.
8119   *  animation-duration --> animationDuration
8120   */
8121  function camelCase(str) {
8122    return str.replace(/-[a-z]/g, function(str) {
8123      return str.charAt(1).toUpperCase();
8124    });
8125  }
8126
8127})();
8128
8129}
8130
8131})();
8132(function(){
8133"use strict";
8134
8135/**
8136 * @ngdoc module
8137 * @name material.components.autocomplete
8138 */
8139/*
8140 * @see js folder for autocomplete implementation
8141 */
8142angular.module('material.components.autocomplete', [
8143  'material.core',
8144  'material.components.icon',
8145  'material.components.virtualRepeat'
8146]);
8147
8148})();
8149(function(){
8150"use strict";
8151
8152/*
8153 * @ngdoc module
8154 * @name material.components.backdrop
8155 * @description Backdrop
8156 */
8157
8158/**
8159 * @ngdoc directive
8160 * @name mdBackdrop
8161 * @module material.components.backdrop
8162 *
8163 * @restrict E
8164 *
8165 * @description
8166 * `<md-backdrop>` is a backdrop element used by other components, such as dialog and bottom sheet.
8167 * Apply class `opaque` to make the backdrop use the theme backdrop color.
8168 *
8169 */
8170
8171angular
8172  .module('material.components.backdrop', ['material.core'])
8173  .directive('mdBackdrop', ["$mdTheming", "$mdUtil", "$animate", "$rootElement", "$window", "$log", "$$rAF", "$document", function BackdropDirective($mdTheming, $mdUtil, $animate, $rootElement, $window, $log, $$rAF, $document) {
8174    var ERROR_CSS_POSITION = '<md-backdrop> may not work properly in a scrolled, static-positioned parent container.';
8175
8176    return {
8177      restrict: 'E',
8178      link: postLink
8179    };
8180
8181    function postLink(scope, element, attrs) {
8182      // backdrop may be outside the $rootElement, tell ngAnimate to animate regardless
8183      if ($animate.pin) $animate.pin(element, $rootElement);
8184
8185      var bodyStyles;
8186
8187      $$rAF(function() {
8188        // If body scrolling has been disabled using mdUtil.disableBodyScroll(),
8189        // adjust the 'backdrop' height to account for the fixed 'body' top offset.
8190        // Note that this can be pretty expensive and is better done inside the $$rAF.
8191        bodyStyles = $window.getComputedStyle($document[0].body);
8192
8193        if (bodyStyles.position === 'fixed') {
8194          var resizeHandler = $mdUtil.debounce(function(){
8195            bodyStyles = $window.getComputedStyle($document[0].body);
8196            resize();
8197          }, 60, null, false);
8198
8199          resize();
8200          angular.element($window).on('resize', resizeHandler);
8201
8202          scope.$on('$destroy', function() {
8203            angular.element($window).off('resize', resizeHandler);
8204          });
8205        }
8206
8207        // Often $animate.enter() is used to append the backDrop element
8208        // so let's wait until $animate is done...
8209        var parent = element.parent();
8210
8211        if (parent.length) {
8212          if (parent[0].nodeName === 'BODY') {
8213            element.css('position', 'fixed');
8214          }
8215
8216          var styles = $window.getComputedStyle(parent[0]);
8217
8218          if (styles.position === 'static') {
8219            // backdrop uses position:absolute and will not work properly with parent position:static (default)
8220            $log.warn(ERROR_CSS_POSITION);
8221          }
8222
8223          // Only inherit the parent if the backdrop has a parent.
8224          $mdTheming.inherit(element, parent);
8225        }
8226      });
8227
8228      function resize() {
8229        var viewportHeight = parseInt(bodyStyles.height, 10) + Math.abs(parseInt(bodyStyles.top, 10));
8230        element.css('height', viewportHeight + 'px');
8231      }
8232    }
8233
8234  }]);
8235
8236})();
8237(function(){
8238"use strict";
8239
8240/**
8241 * @ngdoc module
8242 * @name material.components.bottomSheet
8243 * @description
8244 * BottomSheet
8245 */
8246MdBottomSheetDirective.$inject = ["$mdBottomSheet"];
8247MdBottomSheetProvider.$inject = ["$$interimElementProvider"];
8248angular
8249  .module('material.components.bottomSheet', [
8250    'material.core',
8251    'material.components.backdrop'
8252  ])
8253  .directive('mdBottomSheet', MdBottomSheetDirective)
8254  .provider('$mdBottomSheet', MdBottomSheetProvider);
8255
8256/* @ngInject */
8257function MdBottomSheetDirective($mdBottomSheet) {
8258  return {
8259    restrict: 'E',
8260    link : function postLink(scope, element) {
8261      element.addClass('_md');     // private md component indicator for styling
8262
8263      // When navigation force destroys an interimElement, then
8264      // listen and $destroy() that interim instance...
8265      scope.$on('$destroy', function() {
8266        $mdBottomSheet.destroy();
8267      });
8268    }
8269  };
8270}
8271
8272
8273/**
8274 * @ngdoc service
8275 * @name $mdBottomSheet
8276 * @module material.components.bottomSheet
8277 *
8278 * @description
8279 * `$mdBottomSheet` opens a bottom sheet over the app and provides a simple promise API.
8280 *
8281 * ## Restrictions
8282 *
8283 * - The bottom sheet's template must have an outer `<md-bottom-sheet>` element.
8284 * - Add the `md-grid` class to the bottom sheet for a grid layout.
8285 * - Add the `md-list` class to the bottom sheet for a list layout.
8286 *
8287 * @usage
8288 * <hljs lang="html">
8289 * <div ng-controller="MyController">
8290 *   <md-button ng-click="openBottomSheet()">
8291 *     Open a Bottom Sheet!
8292 *   </md-button>
8293 * </div>
8294 * </hljs>
8295 * <hljs lang="js">
8296 * var app = angular.module('app', ['ngMaterial']);
8297 * app.controller('MyController', function($scope, $mdBottomSheet) {
8298 *   $scope.openBottomSheet = function() {
8299 *     $mdBottomSheet.show({
8300 *       template: '<md-bottom-sheet>' +
8301 *       'Hello! <md-button ng-click="closeBottomSheet()">Close</md-button>' +
8302 *       '</md-bottom-sheet>'
8303 *     })
8304 *
8305 *     // Fires when the hide() method is used
8306 *     .then(function() {
8307 *       console.log('You clicked the button to close the bottom sheet!');
8308 *     })
8309 *
8310 *     // Fires when the cancel() method is used
8311 *     .catch(function() {
8312 *       console.log('You hit escape or clicked the backdrop to close.');
8313 *     });
8314 *   };
8315 *
8316 *   $scope.closeBottomSheet = function($scope, $mdBottomSheet) {
8317 *     $mdBottomSheet.hide();
8318 *   }
8319 *
8320 * });
8321 * </hljs>
8322 */
8323
8324 /**
8325 * @ngdoc method
8326 * @name $mdBottomSheet#show
8327 *
8328 * @description
8329 * Show a bottom sheet with the specified options.
8330 *
8331 * <em><b>Note:</b> You should <b>always</b> provide a `.catch()` method in case the user hits the
8332 * `esc` key or clicks the background to close. In this case, the `cancel()` method will
8333 * automatically be called on the bottom sheet which will `reject()` the promise. See the @usage
8334 * section above for an example.
8335 *
8336 * Newer versions of Angular will throw a `Possibly unhandled rejection` exception if you forget
8337 * this.</em>
8338 *
8339 * @param {object} options An options object, with the following properties:
8340 *
8341 *   - `templateUrl` - `{string=}`: The url of an html template file that will
8342 *   be used as the content of the bottom sheet. Restrictions: the template must
8343 *   have an outer `md-bottom-sheet` element.
8344 *   - `template` - `{string=}`: Same as templateUrl, except this is an actual
8345 *   template string.
8346 *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
8347 *     This scope will be destroyed when the bottom sheet is removed unless `preserveScope` is set to true.
8348 *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
8349 *   - `controller` - `{string=}`: The controller to associate with this bottom sheet.
8350 *   - `locals` - `{string=}`: An object containing key/value pairs. The keys will
8351 *   be used as names of values to inject into the controller. For example,
8352 *   `locals: {three: 3}` would inject `three` into the controller with the value
8353 *   of 3.
8354 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the bottom sheet to
8355 *     close it. Default true.
8356 *   - `bindToController` - `{boolean=}`: When set to true, the locals will be bound to the controller instance.
8357 *   - `disableBackdrop` - `{boolean=}`: When set to true, the bottomsheet will not show a backdrop.
8358 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the bottom sheet.
8359 *     Default true.
8360 *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
8361 *   and the bottom sheet will not open until the promises resolve.
8362 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
8363 *   - `parent` - `{element=}`: The element to append the bottom sheet to. The `parent` may be a `function`, `string`,
8364 *   `object`, or null. Defaults to appending to the body of the root element (or the root element) of the application.
8365 *   e.g. angular.element(document.getElementById('content')) or "#content"
8366 *   - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the bottom sheet is open.
8367 *     Default true.
8368 *
8369 * @returns {promise} A promise that can be resolved with `$mdBottomSheet.hide()` or
8370 * rejected with `$mdBottomSheet.cancel()`.
8371 */
8372
8373/**
8374 * @ngdoc method
8375 * @name $mdBottomSheet#hide
8376 *
8377 * @description
8378 * Hide the existing bottom sheet and resolve the promise returned from
8379 * `$mdBottomSheet.show()`. This call will close the most recently opened/current bottomsheet (if
8380 * any).
8381 *
8382 * <em><b>Note:</b> Use a `.then()` on your `.show()` to handle this callback.</em>
8383 *
8384 * @param {*=} response An argument for the resolved promise.
8385 *
8386 */
8387
8388/**
8389 * @ngdoc method
8390 * @name $mdBottomSheet#cancel
8391 *
8392 * @description
8393 * Hide the existing bottom sheet and reject the promise returned from
8394 * `$mdBottomSheet.show()`.
8395 *
8396 * <em><b>Note:</b> Use a `.catch()` on your `.show()` to handle this callback.</em>
8397 *
8398 * @param {*=} response An argument for the rejected promise.
8399 *
8400 */
8401
8402function MdBottomSheetProvider($$interimElementProvider) {
8403  // how fast we need to flick down to close the sheet, pixels/ms
8404  bottomSheetDefaults.$inject = ["$animate", "$mdConstant", "$mdUtil", "$mdTheming", "$mdBottomSheet", "$rootElement", "$mdGesture", "$log"];
8405  var CLOSING_VELOCITY = 0.5;
8406  var PADDING = 80; // same as css
8407
8408  return $$interimElementProvider('$mdBottomSheet')
8409    .setDefaults({
8410      methods: ['disableParentScroll', 'escapeToClose', 'clickOutsideToClose'],
8411      options: bottomSheetDefaults
8412    });
8413
8414  /* @ngInject */
8415  function bottomSheetDefaults($animate, $mdConstant, $mdUtil, $mdTheming, $mdBottomSheet, $rootElement,
8416                               $mdGesture, $log) {
8417    var backdrop;
8418
8419    return {
8420      themable: true,
8421      onShow: onShow,
8422      onRemove: onRemove,
8423      disableBackdrop: false,
8424      escapeToClose: true,
8425      clickOutsideToClose: true,
8426      disableParentScroll: true
8427    };
8428
8429
8430    function onShow(scope, element, options, controller) {
8431
8432      element = $mdUtil.extractElementByName(element, 'md-bottom-sheet');
8433
8434      // prevent tab focus or click focus on the bottom-sheet container
8435      element.attr('tabindex',"-1");
8436
8437      // Once the md-bottom-sheet has `ng-cloak` applied on his template the opening animation will not work properly.
8438      // This is a very common problem, so we have to notify the developer about this.
8439      if (element.hasClass('ng-cloak')) {
8440        var message = '$mdBottomSheet: using `<md-bottom-sheet ng-cloak >` will affect the bottom-sheet opening animations.';
8441        $log.warn( message, element[0] );
8442      }
8443
8444      if (!options.disableBackdrop) {
8445        // Add a backdrop that will close on click
8446        backdrop = $mdUtil.createBackdrop(scope, "md-bottom-sheet-backdrop md-opaque");
8447
8448        // Prevent mouse focus on backdrop; ONLY programatic focus allowed.
8449        // This allows clicks on backdrop to propogate to the $rootElement and
8450        // ESC key events to be detected properly.
8451        
8452        backdrop[0].tabIndex = -1;
8453
8454        if (options.clickOutsideToClose) {
8455          backdrop.on('click', function() {
8456            $mdUtil.nextTick($mdBottomSheet.cancel,true);
8457          });
8458        }
8459
8460        $mdTheming.inherit(backdrop, options.parent);
8461
8462        $animate.enter(backdrop, options.parent, null);
8463      }
8464
8465      var bottomSheet = new BottomSheet(element, options.parent);
8466      options.bottomSheet = bottomSheet;
8467
8468      $mdTheming.inherit(bottomSheet.element, options.parent);
8469
8470      if (options.disableParentScroll) {
8471        options.restoreScroll = $mdUtil.disableScrollAround(bottomSheet.element, options.parent);
8472      }
8473
8474      return $animate.enter(bottomSheet.element, options.parent, backdrop)
8475        .then(function() {
8476          var focusable = $mdUtil.findFocusTarget(element) || angular.element(
8477            element[0].querySelector('button') ||
8478            element[0].querySelector('a') ||
8479            element[0].querySelector($mdUtil.prefixer('ng-click', true))
8480          ) || backdrop;
8481
8482          if (options.escapeToClose) {
8483            options.rootElementKeyupCallback = function(e) {
8484              if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
8485                $mdUtil.nextTick($mdBottomSheet.cancel,true);
8486              }
8487            };
8488
8489            $rootElement.on('keyup', options.rootElementKeyupCallback);
8490            focusable && focusable.focus();
8491          }
8492        });
8493
8494    }
8495
8496    function onRemove(scope, element, options) {
8497
8498      var bottomSheet = options.bottomSheet;
8499
8500      if (!options.disableBackdrop) $animate.leave(backdrop);
8501      return $animate.leave(bottomSheet.element).then(function() {
8502        if (options.disableParentScroll) {
8503          options.restoreScroll();
8504          delete options.restoreScroll;
8505        }
8506
8507        bottomSheet.cleanup();
8508      });
8509    }
8510
8511    /**
8512     * BottomSheet class to apply bottom-sheet behavior to an element
8513     */
8514    function BottomSheet(element, parent) {
8515      var deregister = $mdGesture.register(parent, 'drag', { horizontal: false });
8516      parent.on('$md.dragstart', onDragStart)
8517        .on('$md.drag', onDrag)
8518        .on('$md.dragend', onDragEnd);
8519
8520      return {
8521        element: element,
8522        cleanup: function cleanup() {
8523          deregister();
8524          parent.off('$md.dragstart', onDragStart);
8525          parent.off('$md.drag', onDrag);
8526          parent.off('$md.dragend', onDragEnd);
8527        }
8528      };
8529
8530      function onDragStart(ev) {
8531        // Disable transitions on transform so that it feels fast
8532        element.css($mdConstant.CSS.TRANSITION_DURATION, '0ms');
8533      }
8534
8535      function onDrag(ev) {
8536        var transform = ev.pointer.distanceY;
8537        if (transform < 5) {
8538          // Slow down drag when trying to drag up, and stop after PADDING
8539          transform = Math.max(-PADDING, transform / 2);
8540        }
8541        element.css($mdConstant.CSS.TRANSFORM, 'translate3d(0,' + (PADDING + transform) + 'px,0)');
8542      }
8543
8544      function onDragEnd(ev) {
8545        if (ev.pointer.distanceY > 0 &&
8546            (ev.pointer.distanceY > 20 || Math.abs(ev.pointer.velocityY) > CLOSING_VELOCITY)) {
8547          var distanceRemaining = element.prop('offsetHeight') - ev.pointer.distanceY;
8548          var transitionDuration = Math.min(distanceRemaining / ev.pointer.velocityY * 0.75, 500);
8549          element.css($mdConstant.CSS.TRANSITION_DURATION, transitionDuration + 'ms');
8550          $mdUtil.nextTick($mdBottomSheet.cancel,true);
8551        } else {
8552          element.css($mdConstant.CSS.TRANSITION_DURATION, '');
8553          element.css($mdConstant.CSS.TRANSFORM, '');
8554        }
8555      }
8556    }
8557
8558  }
8559
8560}
8561
8562})();
8563(function(){
8564"use strict";
8565
8566/**
8567 * @ngdoc module
8568 * @name material.components.button
8569 * @description
8570 *
8571 * Button
8572 */
8573MdButtonDirective.$inject = ["$mdButtonInkRipple", "$mdTheming", "$mdAria", "$mdInteraction"];
8574MdAnchorDirective.$inject = ["$mdTheming"];
8575angular
8576    .module('material.components.button', [ 'material.core' ])
8577    .directive('mdButton', MdButtonDirective)
8578    .directive('a', MdAnchorDirective);
8579
8580
8581/**
8582 * @private
8583 * @restrict E
8584 *
8585 * @description
8586 * `a` is an anchor directive used to inherit theme colors for md-primary, md-accent, etc.
8587 *
8588 * @usage
8589 *
8590 * <hljs lang="html">
8591 *  <md-content md-theme="myTheme">
8592 *    <a href="#chapter1" class="md-accent"></a>
8593 *  </md-content>
8594 * </hljs>
8595 */
8596function MdAnchorDirective($mdTheming) {
8597  return {
8598    restrict : 'E',
8599    link : function postLink(scope, element) {
8600      // Make sure to inherit theme so stand-alone anchors
8601      // support theme colors for md-primary, md-accent, etc.
8602      $mdTheming(element);
8603    }
8604  };
8605}
8606
8607
8608/**
8609 * @ngdoc directive
8610 * @name mdButton
8611 * @module material.components.button
8612 *
8613 * @restrict E
8614 *
8615 * @description
8616 * `<md-button>` is a button directive with optional ink ripples (default enabled).
8617 *
8618 * If you supply a `href` or `ng-href` attribute, it will become an `<a>` element. Otherwise, it
8619 * will become a `<button>` element. As per the
8620 * [Material Design specifications](https://material.google.com/style/color.html#color-color-palette)
8621 * the FAB button background is filled with the accent color [by default]. The primary color palette
8622 * may be used with the `md-primary` class.
8623 *
8624 * Developers can also change the color palette of the button, by using the following classes
8625 * - `md-primary`
8626 * - `md-accent`
8627 * - `md-warn`
8628 *
8629 * See for example
8630 *
8631 * <hljs lang="html">
8632 *   <md-button class="md-primary">Primary Button</md-button>
8633 * </hljs>
8634 *
8635 * Button can be also raised, which means that they will use the current color palette to fill the button.
8636 *
8637 * <hljs lang="html">
8638 *   <md-button class="md-accent md-raised">Raised and Accent Button</md-button>
8639 * </hljs>
8640 *
8641 * It is also possible to disable the focus effect on the button, by using the following markup.
8642 *
8643 * <hljs lang="html">
8644 *   <md-button class="md-no-focus">No Focus Style</md-button>
8645 * </hljs>
8646 *
8647 * @param {boolean=} md-no-ink If present, disable ripple ink effects.
8648 * @param {expression=} ng-disabled En/Disable based on the expression
8649 * @param {string=} md-ripple-size Overrides the default ripple size logic. Options: `full`, `partial`, `auto`
8650 * @param {string=} aria-label Adds alternative text to button for accessibility, useful for icon buttons.
8651 * If no default text is found, a warning will be logged.
8652 *
8653 * @usage
8654 *
8655 * Regular buttons:
8656 *
8657 * <hljs lang="html">
8658 *  <md-button> Flat Button </md-button>
8659 *  <md-button href="http://google.com"> Flat link </md-button>
8660 *  <md-button class="md-raised"> Raised Button </md-button>
8661 *  <md-button ng-disabled="true"> Disabled Button </md-button>
8662 *  <md-button>
8663 *    <md-icon md-svg-src="your/icon.svg"></md-icon>
8664 *    Register Now
8665 *  </md-button>
8666 * </hljs>
8667 *
8668 * FAB buttons:
8669 *
8670 * <hljs lang="html">
8671 *  <md-button class="md-fab" aria-label="FAB">
8672 *    <md-icon md-svg-src="your/icon.svg"></md-icon>
8673 *  </md-button>
8674 *  <!-- mini-FAB -->
8675 *  <md-button class="md-fab md-mini" aria-label="Mini FAB">
8676 *    <md-icon md-svg-src="your/icon.svg"></md-icon>
8677 *  </md-button>
8678 *  <!-- Button with SVG Icon -->
8679 *  <md-button class="md-icon-button" aria-label="Custom Icon Button">
8680 *    <md-icon md-svg-icon="path/to/your.svg"></md-icon>
8681 *  </md-button>
8682 * </hljs>
8683 */
8684function MdButtonDirective($mdButtonInkRipple, $mdTheming, $mdAria, $mdInteraction) {
8685
8686  return {
8687    restrict: 'EA',
8688    replace: true,
8689    transclude: true,
8690    template: getTemplate,
8691    link: postLink
8692  };
8693
8694  function isAnchor(attr) {
8695    return angular.isDefined(attr.href) || angular.isDefined(attr.ngHref) || angular.isDefined(attr.ngLink) || angular.isDefined(attr.uiSref);
8696  }
8697
8698  function getTemplate(element, attr) {
8699    if (isAnchor(attr)) {
8700      return '<a class="md-button" ng-transclude></a>';
8701    } else {
8702      //If buttons don't have type="button", they will submit forms automatically.
8703      var btnType = (typeof attr.type === 'undefined') ? 'button' : attr.type;
8704      return '<button class="md-button" type="' + btnType + '" ng-transclude></button>';
8705    }
8706  }
8707
8708  function postLink(scope, element, attr) {
8709    $mdTheming(element);
8710    $mdButtonInkRipple.attach(scope, element);
8711
8712    // Use async expect to support possible bindings in the button label
8713    $mdAria.expectWithoutText(element, 'aria-label');
8714
8715    // For anchor elements, we have to set tabindex manually when the
8716    // element is disabled
8717    if (isAnchor(attr) && angular.isDefined(attr.ngDisabled) ) {
8718      scope.$watch(attr.ngDisabled, function(isDisabled) {
8719        element.attr('tabindex', isDisabled ? -1 : 0);
8720      });
8721    }
8722
8723    // disabling click event when disabled is true
8724    element.on('click', function(e){
8725      if (attr.disabled === true) {
8726        e.preventDefault();
8727        e.stopImmediatePropagation();
8728      }
8729    });
8730
8731    if (!element.hasClass('md-no-focus')) {
8732
8733      element.on('focus', function() {
8734
8735        // Only show the focus effect when being focused through keyboard interaction or programmatically
8736        if (!$mdInteraction.isUserInvoked() || $mdInteraction.getLastInteractionType() === 'keyboard') {
8737          element.addClass('md-focused');
8738        }
8739
8740      });
8741
8742      element.on('blur', function() {
8743        element.removeClass('md-focused');
8744      });
8745    }
8746
8747  }
8748
8749}
8750
8751})();
8752(function(){
8753"use strict";
8754
8755/**
8756 * @ngdoc module
8757 * @name material.components.card
8758 *
8759 * @description
8760 * Card components.
8761 */
8762mdCardDirective.$inject = ["$mdTheming"];
8763angular.module('material.components.card', [
8764    'material.core'
8765  ])
8766  .directive('mdCard', mdCardDirective);
8767
8768
8769/**
8770 * @ngdoc directive
8771 * @name mdCard
8772 * @module material.components.card
8773 *
8774 * @restrict E
8775 *
8776 * @description
8777 * The `<md-card>` directive is a container element used within `<md-content>` containers.
8778 *
8779 * An image included as a direct descendant will fill the card's width. If you want to avoid this,
8780 * you can add the `md-image-no-fill` class to the parent element. The `<md-card-content>`
8781 * container will wrap text content and provide padding. An `<md-card-footer>` element can be
8782 * optionally included to put content flush against the bottom edge of the card.
8783 *
8784 * Action buttons can be included in an `<md-card-actions>` element, similar to `<md-dialog-actions>`.
8785 * You can then position buttons using layout attributes.
8786 *
8787 * Card is built with:
8788 * * `<md-card-header>` - Header for the card, holds avatar, text and squared image
8789 *  - `<md-card-avatar>` - Card avatar
8790 *    - `md-user-avatar` - Class for user image
8791 *    - `<md-icon>`
8792 *  - `<md-card-header-text>` - Contains elements for the card description
8793 *    - `md-title` - Class for the card title
8794 *    - `md-subhead` - Class for the card sub header
8795 * * `<img>` - Image for the card
8796 * * `<md-card-title>` - Card content title
8797 *  - `<md-card-title-text>`
8798 *    - `md-headline` - Class for the card content title
8799 *    - `md-subhead` - Class for the card content sub header
8800 *  - `<md-card-title-media>` - Squared image within the title
8801 *    - `md-media-sm` - Class for small image
8802 *    - `md-media-md` - Class for medium image
8803 *    - `md-media-lg` - Class for large image
8804 *    - `md-media-xl` - Class for extra large image
8805 * * `<md-card-content>` - Card content
8806 * * `<md-card-actions>` - Card actions
8807 *  - `<md-card-icon-actions>` - Icon actions
8808 *
8809 * Cards have constant width and variable heights; where the maximum height is limited to what can
8810 * fit within a single view on a platform, but it can temporarily expand as needed.
8811 *
8812 * @usage
8813 * ### Card with optional footer
8814 * <hljs lang="html">
8815 * <md-card>
8816 *  <img src="card-image.png" class="md-card-image" alt="image caption">
8817 *  <md-card-content>
8818 *    <h2>Card headline</h2>
8819 *    <p>Card content</p>
8820 *  </md-card-content>
8821 *  <md-card-footer>
8822 *    Card footer
8823 *  </md-card-footer>
8824 * </md-card>
8825 * </hljs>
8826 *
8827 * ### Card with actions
8828 * <hljs lang="html">
8829 * <md-card>
8830 *  <img src="card-image.png" class="md-card-image" alt="image caption">
8831 *  <md-card-content>
8832 *    <h2>Card headline</h2>
8833 *    <p>Card content</p>
8834 *  </md-card-content>
8835 *  <md-card-actions layout="row" layout-align="end center">
8836 *    <md-button>Action 1</md-button>
8837 *    <md-button>Action 2</md-button>
8838 *  </md-card-actions>
8839 * </md-card>
8840 * </hljs>
8841 *
8842 * ### Card with header, image, title actions and content
8843 * <hljs lang="html">
8844 * <md-card>
8845 *   <md-card-header>
8846 *     <md-card-avatar>
8847 *       <img class="md-user-avatar" src="avatar.png"/>
8848 *     </md-card-avatar>
8849 *     <md-card-header-text>
8850 *       <span class="md-title">Title</span>
8851 *       <span class="md-subhead">Sub header</span>
8852 *     </md-card-header-text>
8853 *   </md-card-header>
8854 *   <img ng-src="card-image.png" class="md-card-image" alt="image caption">
8855 *   <md-card-title>
8856 *     <md-card-title-text>
8857 *       <span class="md-headline">Card headline</span>
8858 *       <span class="md-subhead">Card subheader</span>
8859 *     </md-card-title-text>
8860 *   </md-card-title>
8861 *   <md-card-actions layout="row" layout-align="start center">
8862 *     <md-button>Action 1</md-button>
8863 *     <md-button>Action 2</md-button>
8864 *     <md-card-icon-actions>
8865 *       <md-button class="md-icon-button" aria-label="icon">
8866 *         <md-icon md-svg-icon="icon"></md-icon>
8867 *       </md-button>
8868 *     </md-card-icon-actions>
8869 *   </md-card-actions>
8870 *   <md-card-content>
8871 *     <p>
8872 *      Card content
8873 *     </p>
8874 *   </md-card-content>
8875 * </md-card>
8876 * </hljs>
8877 */
8878function mdCardDirective($mdTheming) {
8879  return {
8880    restrict: 'E',
8881    link: function ($scope, $element, attr) {
8882      $element.addClass('_md');     // private md component indicator for styling
8883      $mdTheming($element);
8884    }
8885  };
8886}
8887
8888})();
8889(function(){
8890"use strict";
8891
8892/**
8893 * @ngdoc module
8894 * @name material.components.checkbox
8895 * @description Checkbox module!
8896 */
8897MdCheckboxDirective.$inject = ["inputDirective", "$mdAria", "$mdConstant", "$mdTheming", "$mdUtil", "$mdInteraction"];
8898angular
8899  .module('material.components.checkbox', ['material.core'])
8900  .directive('mdCheckbox', MdCheckboxDirective);
8901
8902/**
8903 * @ngdoc directive
8904 * @name mdCheckbox
8905 * @module material.components.checkbox
8906 * @restrict E
8907 *
8908 * @description
8909 * The checkbox directive is used like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
8910 *
8911 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-color-schemes)
8912 * the checkbox is in the accent color by default. The primary color palette may be used with
8913 * the `md-primary` class.
8914 *
8915 * @param {string} ng-model Assignable angular expression to data-bind to.
8916 * @param {string=} name Property name of the form under which the control is published.
8917 * @param {expression=} ng-true-value The value to which the expression should be set when selected.
8918 * @param {expression=} ng-false-value The value to which the expression should be set when not selected.
8919 * @param {string=} ng-change AngularJS expression to be executed when input changes due to user interaction with the input element.
8920 * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects
8921 * @param {string=} aria-label Adds label to checkbox for accessibility.
8922 *     Defaults to checkbox's text. If no default text is found, a warning will be logged.
8923 * @param {expression=} md-indeterminate This determines when the checkbox should be rendered as 'indeterminate'.
8924 *     If a truthy expression or no value is passed in the checkbox renders in the md-indeterminate state.
8925 *     If falsy expression is passed in it just looks like a normal unchecked checkbox.
8926 *     The indeterminate, checked, and unchecked states are mutually exclusive. A box cannot be in any two states at the same time.
8927 *     Adding the 'md-indeterminate' attribute overrides any checked/unchecked rendering logic.
8928 *     When using the 'md-indeterminate' attribute use 'ng-checked' to define rendering logic instead of using 'ng-model'.
8929 * @param {expression=} ng-checked If this expression evaluates as truthy, the 'md-checked' css class is added to the checkbox and it
8930 *     will appear checked.
8931 *
8932 * @usage
8933 * <hljs lang="html">
8934 * <md-checkbox ng-model="isChecked" aria-label="Finished?">
8935 *   Finished ?
8936 * </md-checkbox>
8937 *
8938 * <md-checkbox md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
8939 *   No Ink Effects
8940 * </md-checkbox>
8941 *
8942 * <md-checkbox ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
8943 *   Disabled
8944 * </md-checkbox>
8945 *
8946 * </hljs>
8947 *
8948 */
8949function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $mdUtil, $mdInteraction) {
8950  inputDirective = inputDirective[0];
8951
8952  return {
8953    restrict: 'E',
8954    transclude: true,
8955    require: ['^?mdInputContainer', '?ngModel', '?^form'],
8956    priority: $mdConstant.BEFORE_NG_ARIA,
8957    template:
8958      '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
8959        '<div class="md-icon"></div>' +
8960      '</div>' +
8961      '<div ng-transclude class="md-label"></div>',
8962    compile: compile
8963  };
8964
8965  // **********************************************************
8966  // Private Methods
8967  // **********************************************************
8968
vendor: 17,759 bytes, lines 8969-9542
8969  function compile (tElement, tAttrs) {
8970    tAttrs.$set('tabindex', tAttrs.tabindex || '0');
8971    tAttrs.$set('type', 'checkbox');
8972    tAttrs.$set('role', tAttrs.type);
8973
8974    return  {
8975      pre: function(scope, element) {
8976        // Attach a click handler during preLink, in order to immediately stop propagation
8977        // (especially for ng-click) when the checkbox is disabled.
8978        element.on('click', function(e) {
8979          if (this.hasAttribute('disabled')) {
8980            e.stopImmediatePropagation();
8981          }
8982        });
8983      },
8984      post: postLink
8985    };
8986
8987    function postLink(scope, element, attr, ctrls) {
8988      var isIndeterminate;
8989      var containerCtrl = ctrls[0];
8990      var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
8991      var formCtrl = ctrls[2];
8992
8993      if (containerCtrl) {
8994        var isErrorGetter = containerCtrl.isErrorGetter || function() {
8995          return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (formCtrl && formCtrl.$submitted));
8996        };
8997
8998        containerCtrl.input = element;
8999
9000        scope.$watch(isErrorGetter, containerCtrl.setInvalid);
9001      }
9002
9003      $mdTheming(element);
9004
9005      // Redirect focus events to the root element, because IE11 is always focusing the container element instead
9006      // of the md-checkbox element. This causes issues when using ngModelOptions: `updateOnBlur`
9007      element.children().on('focus', function() {
9008        element.focus();
9009      });
9010
9011      if ($mdUtil.parseAttributeBoolean(attr.mdIndeterminate)) {
9012        setIndeterminateState();
9013        scope.$watch(attr.mdIndeterminate, setIndeterminateState);
9014      }
9015
9016      if (attr.ngChecked) {
9017        scope.$watch(scope.$eval.bind(scope, attr.ngChecked), function(value) {
9018          ngModelCtrl.$setViewValue(value);
9019          ngModelCtrl.$render();
9020        });
9021      }
9022
9023      $$watchExpr('ngDisabled', 'tabindex', {
9024        true: '-1',
9025        false: attr.tabindex
9026      });
9027
9028      $mdAria.expectWithText(element, 'aria-label');
9029
9030      // Reuse the original input[type=checkbox] directive from AngularJS core.
9031      // This is a bit hacky as we need our own event listener and own render
9032      // function.
9033      inputDirective.link.pre(scope, {
9034        on: angular.noop,
9035        0: {}
9036      }, attr, [ngModelCtrl]);
9037
9038      element.on('click', listener)
9039        .on('keypress', keypressHandler)
9040        .on('focus', function() {
9041          if ($mdInteraction.getLastInteractionType() === 'keyboard') {
9042            element.addClass('md-focused');
9043          }
9044        })
9045        .on('blur', function() {
9046          element.removeClass('md-focused');
9047        });
9048
9049      ngModelCtrl.$render = render;
9050
9051      function $$watchExpr(expr, htmlAttr, valueOpts) {
9052        if (attr[expr]) {
9053          scope.$watch(attr[expr], function(val) {
9054            if (valueOpts[val]) {
9055              element.attr(htmlAttr, valueOpts[val]);
9056            }
9057          });
9058        }
9059      }
9060
9061      function keypressHandler(ev) {
9062        var keyCode = ev.which || ev.keyCode;
9063        if (keyCode === $mdConstant.KEY_CODE.SPACE || keyCode === $mdConstant.KEY_CODE.ENTER) {
9064          ev.preventDefault();
9065          element.addClass('md-focused');
9066          listener(ev);
9067        }
9068      }
9069
9070      function listener(ev) {
9071        // skipToggle boolean is used by the switch directive to prevent the click event
9072        // when releasing the drag. There will be always a click if releasing the drag over the checkbox
9073        if (element[0].hasAttribute('disabled') || scope.skipToggle) {
9074          return;
9075        }
9076
9077        scope.$apply(function() {
9078          // Toggle the checkbox value...
9079          var viewValue = attr.ngChecked && attr.ngClick ? attr.checked : !ngModelCtrl.$viewValue;
9080
9081          ngModelCtrl.$setViewValue(viewValue, ev && ev.type);
9082          ngModelCtrl.$render();
9083        });
9084      }
9085
9086      function render() {
9087        // Cast the $viewValue to a boolean since it could be undefined
9088        element.toggleClass('md-checked', !!ngModelCtrl.$viewValue && !isIndeterminate);
9089      }
9090
9091      function setIndeterminateState(newValue) {
9092        isIndeterminate = newValue !== false;
9093        if (isIndeterminate) {
9094          element.attr('aria-checked', 'mixed');
9095        }
9096        element.toggleClass('md-indeterminate', isIndeterminate);
9097      }
9098    }
9099  }
9100}
9101
9102})();
9103(function(){
9104"use strict";
9105
9106/**
9107 * @ngdoc module
9108 * @name material.components.chips
9109 */
9110/*
9111 * @see js folder for chips implementation
9112 */
9113angular.module('material.components.chips', [
9114  'material.core',
9115  'material.components.autocomplete'
9116]);
9117
9118})();
9119(function(){
9120"use strict";
9121
9122(function () {
9123  "use strict";
9124
9125  /**
9126   *  Use a RegExp to check if the `md-colors="<expression>"` is static string
9127   *  or one that should be observed and dynamically interpolated.
9128   */
9129  MdColorsDirective.$inject = ["$mdColors", "$mdUtil", "$log", "$parse"];
9130  MdColorsService.$inject = ["$mdTheming", "$mdUtil", "$log"];
9131  var STATIC_COLOR_EXPRESSION = /^{((\s|,)*?["'a-zA-Z-]+?\s*?:\s*?('|")[a-zA-Z0-9-.]*('|"))+\s*}$/;
9132  var colorPalettes = null;
9133
9134  /**
9135   * @ngdoc module
9136   * @name material.components.colors
9137   *
9138   * @description
9139   * Define $mdColors service and a `md-colors=""` attribute directive
9140   */
9141  angular
9142    .module('material.components.colors', ['material.core'])
9143    .directive('mdColors', MdColorsDirective)
9144    .service('$mdColors', MdColorsService);
9145
9146  /**
9147   * @ngdoc service
9148   * @name $mdColors
9149   * @module material.components.colors
9150   *
9151   * @description
9152   * With only defining themes, one couldn't get non AngularJS Material elements colored with Material colors,
9153   * `$mdColors` service is used by the md-color directive to convert the 1..n color expressions to RGBA values and will apply
9154   * those values to element as CSS property values.
9155   *
9156   *  @usage
9157   *  <hljs lang="js">
9158   *    angular.controller('myCtrl', function ($mdColors) {
9159   *      var color = $mdColors.getThemeColor('myTheme-red-200-0.5');
9160   *      ...
9161   *    });
9162   *  </hljs>
9163   *
9164   */
9165  function MdColorsService($mdTheming, $mdUtil, $log) {
9166    colorPalettes = colorPalettes || Object.keys($mdTheming.PALETTES);
9167
9168    // Publish service instance
9169    return {
9170      applyThemeColors: applyThemeColors,
9171      getThemeColor: getThemeColor,
9172      hasTheme: hasTheme
9173    };
9174
9175    // ********************************************
9176    // Internal Methods
9177    // ********************************************
9178
9179    /**
9180     * @ngdoc method
9181     * @name $mdColors#applyThemeColors
9182     *
9183     * @description
9184     * Gets a color json object, keys are css properties and values are string of the wanted color
9185     * Then calculate the rgba() values based on the theme color parts
9186     *
9187     * @param {DOMElement} element the element to apply the styles on.
9188     * @param {object} colorExpression json object, keys are css properties and values are string of the wanted color,
9189     * for example: `{color: 'red-A200-0.3'}`.
9190     *
9191     * @usage
9192     * <hljs lang="js">
9193     *   app.directive('myDirective', function($mdColors) {
9194     *     return {
9195     *       ...
9196     *       link: function (scope, elem) {
9197     *         $mdColors.applyThemeColors(elem, {color: 'red'});
9198     *       }
9199     *    }
9200     *   });
9201     * </hljs>
9202     */
9203    function applyThemeColors(element, colorExpression) {
9204      try {
9205        if (colorExpression) {
9206          // Assign the calculate RGBA color values directly as inline CSS
9207          element.css(interpolateColors(colorExpression));
9208        }
9209      } catch (e) {
9210        $log.error(e.message);
9211      }
9212
9213    }
9214
9215    /**
9216     * @ngdoc method
9217     * @name $mdColors#getThemeColor
9218     *
9219     * @description
9220     * Get parsed color from expression
9221     *
9222     * @param {string} expression string of a color expression (for instance `'red-700-0.8'`)
9223     *
9224     * @returns {string} a css color expression (for instance `rgba(211, 47, 47, 0.8)`)
9225     *
9226     * @usage
9227     *  <hljs lang="js">
9228     *    angular.controller('myCtrl', function ($mdColors) {
9229     *      var color = $mdColors.getThemeColor('myTheme-red-200-0.5');
9230     *      ...
9231     *    });
9232     *  </hljs>
9233     */
9234    function getThemeColor(expression) {
9235      var color = extractColorOptions(expression);
9236
9237      return parseColor(color);
9238    }
9239
9240    /**
9241     * Return the parsed color
9242     * @param color hashmap of color definitions
9243     * @param contrast whether use contrast color for foreground
9244     * @returns rgba color string
9245     */
9246    function parseColor(color, contrast) {
9247      contrast = contrast || false;
9248      var rgbValues = $mdTheming.PALETTES[color.palette][color.hue];
9249
9250      rgbValues = contrast ? rgbValues.contrast : rgbValues.value;
9251
9252      return $mdUtil.supplant('rgba({0}, {1}, {2}, {3})',
9253        [rgbValues[0], rgbValues[1], rgbValues[2], rgbValues[3] || color.opacity]
9254      );
9255    }
9256
9257    /**
9258     * Convert the color expression into an object with scope-interpolated values
9259     * Then calculate the rgba() values based on the theme color parts
9260     *
9261     * @results Hashmap of CSS properties with associated `rgba( )` string vales
9262     *
9263     *
9264     */
9265    function interpolateColors(themeColors) {
9266      var rgbColors = {};
9267
9268      var hasColorProperty = themeColors.hasOwnProperty('color');
9269
9270      angular.forEach(themeColors, function (value, key) {
9271        var color = extractColorOptions(value);
9272        var hasBackground = key.indexOf('background') > -1;
9273
9274        rgbColors[key] = parseColor(color);
9275        if (hasBackground && !hasColorProperty) {
9276          rgbColors.color = parseColor(color, true);
9277        }
9278      });
9279
9280      return rgbColors;
9281    }
9282
9283    /**
9284     * Check if expression has defined theme
9285     * e.g.
9286     * 'myTheme-primary' => true
9287     * 'red-800' => false
9288     */
9289    function hasTheme(expression) {
9290      return angular.isDefined($mdTheming.THEMES[expression.split('-')[0]]);
9291    }
9292
9293    /**
9294     * For the evaluated expression, extract the color parts into a hash map
9295     */
9296    function extractColorOptions(expression) {
9297      var parts = expression.split('-');
9298      var hasTheme = angular.isDefined($mdTheming.THEMES[parts[0]]);
9299      var theme = hasTheme ? parts.splice(0, 1)[0] : $mdTheming.defaultTheme();
9300
9301      return {
9302        theme: theme,
9303        palette: extractPalette(parts, theme),
9304        hue: extractHue(parts, theme),
9305        opacity: parts[2] || 1
9306      };
9307    }
9308
9309    /**
9310     * Calculate the theme palette name
9311     */
9312    function extractPalette(parts, theme) {
9313      // If the next section is one of the palettes we assume it's a two word palette
9314      // Two word palette can be also written in camelCase, forming camelCase to dash-case
9315
9316      var isTwoWord = parts.length > 1 && colorPalettes.indexOf(parts[1]) !== -1;
9317      var palette = parts[0].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
9318
9319      if (isTwoWord)  palette = parts[0] + '-' + parts.splice(1, 1);
9320
9321      if (colorPalettes.indexOf(palette) === -1) {
9322        // If the palette is not in the palette list it's one of primary/accent/warn/background
9323        var scheme = $mdTheming.THEMES[theme].colors[palette];
9324        if (!scheme) {
9325          throw new Error($mdUtil.supplant('mdColors: couldn\'t find \'{palette}\' in the palettes.', {palette: palette}));
9326        }
9327        palette = scheme.name;
9328      }
9329
9330      return palette;
9331    }
9332
9333    function extractHue(parts, theme) {
9334      var themeColors = $mdTheming.THEMES[theme].colors;
9335
9336      if (parts[1] === 'hue') {
9337        var hueNumber = parseInt(parts.splice(2, 1)[0], 10);
9338
9339        if (hueNumber < 1 || hueNumber > 3) {
9340          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}));
9341        }
9342        parts[1] = 'hue-' + hueNumber;
9343
9344        if (!(parts[0] in themeColors)) {
9345          throw new Error($mdUtil.supplant('mdColors: \'hue-x\' can only be used with [{availableThemes}], but was used with \'{usedTheme}\'', {
9346            availableThemes: Object.keys(themeColors).join(', '),
9347            usedTheme: parts[0]
9348          }));
9349        }
9350
9351        return themeColors[parts[0]].hues[parts[1]];
9352      }
9353
9354      return parts[1] || themeColors[parts[0] in themeColors ? parts[0] : 'primary'].hues['default'];
9355    }
9356  }
9357
9358  /**
9359   * @ngdoc directive
9360   * @name mdColors
9361   * @module material.components.colors
9362   *
9363   * @restrict A
9364   *
9365   * @description
9366   * `mdColors` directive will apply the theme-based color expression as RGBA CSS style values.
9367   *
9368   *   The format will be similar to our color defining in the scss files:
9369   *
9370   *   ## `[?theme]-[palette]-[?hue]-[?opacity]`
9371   *   - [theme]    - default value is the default theme
9372   *   - [palette]  - can be either palette name or primary/accent/warn/background
9373   *   - [hue]      - default is 500 (hue-x can be used with primary/accent/warn/background)
9374   *   - [opacity]  - default is 1
9375   *
9376   *   > `?` indicates optional parameter
9377   *
9378   * @usage
9379   * <hljs lang="html">
9380   *   <div md-colors="{background: 'myTheme-accent-900-0.43'}">
9381   *     <div md-colors="{color: 'red-A100', 'border-color': 'primary-600'}">
9382   *       <span>Color demo</span>
9383   *     </div>
9384   *   </div>
9385   * </hljs>
9386   *
9387   * `mdColors` directive will automatically watch for changes in the expression if it recognizes an interpolation
9388   * expression or a function. For performance options, you can use `::` prefix to the `md-colors` expression
9389   * to indicate a one-time data binding.
9390   * <hljs lang="html">
9391   *   <md-card md-colors="::{background: '{{theme}}-primary-700'}">
9392   *   </md-card>
9393   * </hljs>
9394   *
9395   */
9396  function MdColorsDirective($mdColors, $mdUtil, $log, $parse) {
9397    return {
9398      restrict: 'A',
9399      require: ['^?mdTheme'],
9400      compile: function (tElem, tAttrs) {
9401        var shouldWatch = shouldColorsWatch();
9402
9403        return function (scope, element, attrs, ctrl) {
9404          var mdThemeController = ctrl[0];
9405
9406          var lastColors = {};
9407
9408          var parseColors = function (theme) {
9409            if (typeof theme !== 'string') {
9410              theme = '';
9411            }
9412
9413            if (!attrs.mdColors) {
9414              attrs.mdColors = '{}';
9415            }
9416
9417            /**
9418             * Json.parse() does not work because the keys are not quoted;
9419             * use $parse to convert to a hash map
9420             */
9421            var colors = $parse(attrs.mdColors)(scope);
9422
9423            /**
9424             * If mdTheme is defined up the DOM tree
9425             * we add mdTheme theme to colors who doesn't specified a theme
9426             *
9427             * # example
9428             * <hljs lang="html">
9429             *   <div md-theme="myTheme">
9430             *     <div md-colors="{background: 'primary-600'}">
9431             *       <span md-colors="{background: 'mySecondTheme-accent-200'}">Color demo</span>
9432             *     </div>
9433             *   </div>
9434             * </hljs>
9435             *
9436             * 'primary-600' will be 'myTheme-primary-600',
9437             * but 'mySecondTheme-accent-200' will stay the same cause it has a theme prefix
9438             */
9439            if (mdThemeController) {
9440              Object.keys(colors).forEach(function (prop) {
9441                var color = colors[prop];
9442                if (!$mdColors.hasTheme(color)) {
9443                  colors[prop] = (theme || mdThemeController.$mdTheme) + '-' + color;
9444                }
9445              });
9446            }
9447
9448            cleanElement(colors);
9449
9450            return colors;
9451          };
9452
9453          var cleanElement = function (colors) {
9454            if (!angular.equals(colors, lastColors)) {
9455              var keys = Object.keys(lastColors);
9456
9457              if (lastColors.background && !keys.color) {
9458                keys.push('color');
9459              }
9460
9461              keys.forEach(function (key) {
9462                element.css(key, '');
9463              });
9464            }
9465
9466            lastColors = colors;
9467          };
9468
9469          /**
9470           * Registering for mgTheme changes and asking mdTheme controller run our callback whenever a theme changes
9471           */
9472          var unregisterChanges = angular.noop;
9473
9474          if (mdThemeController) {
9475            unregisterChanges = mdThemeController.registerChanges(function (theme) {
9476              $mdColors.applyThemeColors(element, parseColors(theme));
9477            });
9478          }
9479
9480          scope.$on('$destroy', function () {
9481            unregisterChanges();
9482          });
9483
9484          try {
9485            if (shouldWatch) {
9486              scope.$watch(parseColors, angular.bind(this,
9487                $mdColors.applyThemeColors, element
9488              ), true);
9489            }
9490            else {
9491              $mdColors.applyThemeColors(element, parseColors());
9492            }
9493
9494          }
9495          catch (e) {
9496            $log.error(e.message);
9497          }
9498
9499        };
9500
9501        function shouldColorsWatch() {
9502          // Simulate 1x binding and mark mdColorsWatch == false
9503          var rawColorExpression = tAttrs.mdColors;
9504          var bindOnce = rawColorExpression.indexOf('::') > -1;
9505          var isStatic = bindOnce ? true : STATIC_COLOR_EXPRESSION.test(tAttrs.mdColors);
9506
9507          // Remove it for the postLink...
9508          tAttrs.mdColors = rawColorExpression.replace('::', '');
9509
9510          var hasWatchAttr = angular.isDefined(tAttrs.mdColorsWatch);
9511
9512          return (bindOnce || isStatic) ? false :
9513            hasWatchAttr ? $mdUtil.parseAttributeBoolean(tAttrs.mdColorsWatch) : true;
9514        }
9515      }
9516    };
9517
9518  }
9519
9520
9521})();
9522
9523})();
9524(function(){
9525"use strict";
9526
9527/**
9528 * @ngdoc module
9529 * @name material.components.content
9530 *
9531 * @description
9532 * Scrollable content
9533 */
9534mdContentDirective.$inject = ["$mdTheming"];
9535angular.module('material.components.content', [
9536  'material.core'
9537])
9538  .directive('mdContent', mdContentDirective);
9539
9540/**
9541 * @ngdoc directive
9542 * @name mdContent
9543 * @module material.components.content
9544 *
9545 * @restrict E
9546 *
9547 * @description
9548 *
9549 * The `<md-content>` directive is a container element useful for scrollable content. It achieves
9550 * this by setting the CSS `overflow` property to `auto` so that content can properly scroll.
9551 *
9552 * In general, `<md-content>` components are not designed to be nested inside one another. If
9553 * possible, it is better to make them siblings. This often results in a better user experience as
9554 * having nested scrollbars may confuse the user.
9555 *
9556 * ## Troubleshooting
9557 *
9558 * In some cases, you may wish to apply the `md-no-momentum` class to ensure that Safari's
9559 * momentum scrolling is disabled. Momentum scrolling can cause flickering issues while scrolling
9560 * SVG icons and some other components.
9561 *
9562 * Additionally, we now also offer the `md-no-flicker` class which can be applied to any element
9563 * and uses a Webkit-specific filter of `blur(0px)` that forces GPU rendering of all elements
9564 * inside (which eliminates the flicker on iOS devices).
9565 *
9566 * _<b>Note:</b> Forcing an element to render on the GPU can have unintended side-effects, especially
9567 * related to the z-index of elements. Please use with caution and only on the elements needed._
9568 *
9569 * @usage
9570 *
9571 * Add the `[layout-padding]` attribute to make the content padded.
9572 *
9573 * <hljs lang="html">
9574 *  <md-content layout-padding>
9575 *      Lorem ipsum dolor sit amet, ne quod novum mei.
9576 *  </md-content>
9577 * </hljs>
9578 */
9579
9580function mdContentDirective($mdTheming) {
9581  return {
9582    restrict: 'E',
9583    controller: ['$scope', '$element', ContentController],
9584    link: function(scope, element) {
9585      element.addClass('_md');     // private md component indicator for styling
9586
9587      $mdTheming(element);
9588      scope.$broadcast('$mdContentLoaded', element);
9589
9590      iosScrollFix(element[0]);
9591    }
9592  };
9593
9594  function ContentController($scope, $element) {
9595    this.$scope = $scope;
9596    this.$element = $element;
9597  }
9598}
9599
9600function iosScrollFix(node) {
9601  // IOS FIX:
9602  // If we scroll where there is no more room for the webview to scroll,
9603  // by default the webview itself will scroll up and down, this looks really
9604  // bad.  So if we are scrolling to the very top or bottom, add/subtract one
9605  angular.element(node).on('$md.pressdown', function(ev) {
9606    // Only touch events
9607    if (ev.pointer.type !== 't') return;
9608    // Don't let a child content's touchstart ruin it for us.
9609    if (ev.$materialScrollFixed) return;
9610    ev.$materialScrollFixed = true;
9611
9612    if (node.scrollTop === 0) {
9613      node.scrollTop = 1;
9614    } else if (node.scrollHeight === node.scrollTop + node.offsetHeight) {
9615      node.scrollTop -= 1;
9616    }
9617  });
9618}
9619
9620})();
9621(function(){
9622"use strict";
9623
9624/**
9625 * @ngdoc module
9626 * @name material.components.datepicker
9627 * @description Module for the datepicker component.
9628 */
9629
9630angular.module('material.components.datepicker', [
9631  'material.core',
9632  'material.components.icon',
9633  'material.components.virtualRepeat'
9634]);
9635
9636})();
9637(function(){
9638"use strict";
9639
9640/**
9641 * @ngdoc module
9642 * @name material.components.dialog
9643 */
9644MdDialogDirective.$inject = ["$$rAF", "$mdTheming", "$mdDialog"];
9645MdDialogProvider.$inject = ["$$interimElementProvider"];
9646angular
9647  .module('material.components.dialog', [
9648    'material.core',
9649    'material.components.backdrop'
9650  ])
9651  .directive('mdDialog', MdDialogDirective)
9652  .provider('$mdDialog', MdDialogProvider);
9653
9654/**
9655 * @ngdoc directive
9656 * @name mdDialog
9657 * @module material.components.dialog
9658 *
9659 * @restrict E
9660 *
9661 * @description
9662 * `<md-dialog>` - The dialog's template must be inside this element.
9663 *
9664 * Inside, use an `<md-dialog-content>` element for the dialog's content, and use
9665 * an `<md-dialog-actions>` element for the dialog's actions.
9666 *
9667 * ## CSS
9668 * - `.md-dialog-content` - class that sets the padding on the content as the spec file
9669 *
9670 * ## Notes
9671 * - If you specify an `id` for the `<md-dialog>`, the `<md-dialog-content>` will have the same `id`
9672 * prefixed with `dialogContent_`.
9673 *
9674 * @usage
9675 * ### Dialog template
9676 * <hljs lang="html">
9677 * <md-dialog aria-label="List dialog">
9678 *   <md-dialog-content>
9679 *     <md-list>
9680 *       <md-list-item ng-repeat="item in items">
9681 *         <p>Number {{item}}</p>
9682 *       </md-list-item>
9683 *     </md-list>
9684 *   </md-dialog-content>
9685 *   <md-dialog-actions>
9686 *     <md-button ng-click="closeDialog()" class="md-primary">Close Dialog</md-button>
9687 *   </md-dialog-actions>
9688 * </md-dialog>
9689 * </hljs>
9690 */
9691function MdDialogDirective($$rAF, $mdTheming, $mdDialog) {
9692  return {
9693    restrict: 'E',
9694    link: function(scope, element) {
9695      element.addClass('_md');     // private md component indicator for styling
9696
9697      $mdTheming(element);
9698      $$rAF(function() {
9699        var images;
9700        var content = element[0].querySelector('md-dialog-content');
9701
9702        if (content) {
9703          images = content.getElementsByTagName('img');
9704          addOverflowClass();
9705          //-- delayed image loading may impact scroll height, check after images are loaded
9706          angular.element(images).on('load', addOverflowClass);
9707        }
9708
9709        scope.$on('$destroy', function() {
9710          $mdDialog.destroy(element);
9711        });
9712
9713        /**
9714         *
9715         */
9716        function addOverflowClass() {
9717          element.toggleClass('md-content-overflow', content.scrollHeight > content.clientHeight);
9718        }
9719
9720
9721      });
9722    }
9723  };
9724}
9725
9726/**
9727 * @ngdoc service
9728 * @name $mdDialog
9729 * @module material.components.dialog
9730 *
9731 * @description
9732 * `$mdDialog` opens a dialog over the app to inform users about critical information or require
9733 *  them to make decisions. There are two approaches for setup: a simple promise API
9734 *  and regular object syntax.
9735 *
9736 * ## Restrictions
9737 *
9738 * - The dialog is always given an isolate scope.
9739 * - The dialog's template must have an outer `<md-dialog>` element.
9740 *   Inside, use an `<md-dialog-content>` element for the dialog's content, and use
9741 *   an `<md-dialog-actions>` element for the dialog's actions.
9742 * - Dialogs must cover the entire application to keep interactions inside of them.
9743 * Use the `parent` option to change where dialogs are appended.
9744 *
9745 * ## Sizing
9746 * - Complex dialogs can be sized with `flex="percentage"`, i.e. `flex="66"`.
9747 * - Default max-width is 80% of the `rootElement` or `parent`.
9748 *
9749 * ## CSS
9750 * - `.md-dialog-content` - class that sets the padding on the content as the spec file
9751 *
9752 * @usage
9753 * <hljs lang="html">
9754 * <div  ng-app="demoApp" ng-controller="EmployeeController">
9755 *   <div>
9756 *     <md-button ng-click="showAlert()" class="md-raised md-warn">
9757 *       Employee Alert!
9758 *       </md-button>
9759 *   </div>
9760 *   <div>
9761 *     <md-button ng-click="showDialog($event)" class="md-raised">
9762 *       Custom Dialog
9763 *       </md-button>
9764 *   </div>
9765 *   <div>
9766 *     <md-button ng-click="closeAlert()" ng-disabled="!hasAlert()" class="md-raised">
9767 *       Close Alert
9768 *     </md-button>
9769 *   </div>
9770 *   <div>
9771 *     <md-button ng-click="showGreeting($event)" class="md-raised md-primary" >
9772 *       Greet Employee
9773 *       </md-button>
9774 *   </div>
9775 * </div>
9776 * </hljs>
9777 *
9778 * ### JavaScript: object syntax
9779 * <hljs lang="js">
9780 * (function(angular, undefined){
9781 *   "use strict";
9782 *
9783 *   angular
9784 *    .module('demoApp', ['ngMaterial'])
9785 *    .controller('AppCtrl', AppController);
9786 *
9787 *   function AppController($scope, $mdDialog) {
9788 *     var alert;
9789 *     $scope.showAlert = showAlert;
9790 *     $scope.showDialog = showDialog;
9791 *     $scope.items = [1, 2, 3];
9792 *
9793 *     // Internal method
9794 *     function showAlert() {
9795 *       alert = $mdDialog.alert({
9796 *         title: 'Attention',
9797 *         textContent: 'This is an example of how easy dialogs can be!',
9798 *         ok: 'Close'
9799 *       });
9800 *
9801 *       $mdDialog
9802 *         .show( alert )
9803 *         .finally(function() {
9804 *           alert = undefined;
9805 *         });
9806 *     }
9807 *
9808 *     function showDialog($event) {
9809 *        var parentEl = angular.element(document.body);
9810 *        $mdDialog.show({
9811 *          parent: parentEl,
9812 *          targetEvent: $event,
9813 *          template:
9814 *            '<md-dialog aria-label="List dialog">' +
9815 *            '  <md-dialog-content>'+
9816 *            '    <md-list>'+
9817 *            '      <md-list-item ng-repeat="item in items">'+
9818 *            '       <p>Number {{item}}</p>' +
9819 *            '      </md-item>'+
9820 *            '    </md-list>'+
9821 *            '  </md-dialog-content>' +
9822 *            '  <md-dialog-actions>' +
9823 *            '    <md-button ng-click="closeDialog()" class="md-primary">' +
9824 *            '      Close Dialog' +
9825 *            '    </md-button>' +
9826 *            '  </md-dialog-actions>' +
9827 *            '</md-dialog>',
9828 *          locals: {
9829 *            items: $scope.items
9830 *          },
9831 *          controller: DialogController
9832 *       });
9833 *       function DialogController($scope, $mdDialog, items) {
9834 *         $scope.items = items;
9835 *         $scope.closeDialog = function() {
9836 *           $mdDialog.hide();
9837 *         }
9838 *       }
9839 *     }
9840 *   }
9841 * })(angular);
9842 * </hljs>
9843 *
9844 * ### Multiple Dialogs
9845 * Using the `multiple` option for the `$mdDialog` service allows developers to show multiple dialogs
9846 * at the same time.
9847 *
9848 * <hljs lang="js">
9849 *   // From plain options
9850 *   $mdDialog.show({
9851 *     multiple: true
9852 *   });
9853 *
9854 *   // From a dialog preset
9855 *   $mdDialog.show(
9856 *     $mdDialog
9857 *       .alert()
9858 *       .multiple(true)
9859 *   );
9860 *
9861 * </hljs>
9862 *
9863 * ### Pre-Rendered Dialogs
9864 * By using the `contentElement` option, it is possible to use an already existing element in the DOM.
9865 *
9866 * > Pre-rendered dialogs will be not linked to any scope and will not instantiate any new controller.<br/>
9867 * > You can manually link the elements to a scope or instantiate a controller from the template (`ng-controller`)
9868 *
9869 * <hljs lang="js">
9870 *   $scope.showPrerenderedDialog = function() {
9871 *     $mdDialog.show({
9872 *       contentElement: '#myStaticDialog',
9873 *       parent: angular.element(document.body)
9874 *     });
9875 *   };
9876 * </hljs>
9877 *
9878 * When using a string as value, `$mdDialog` will automatically query the DOM for the specified CSS selector.
9879 *
9880 * <hljs lang="html">
9881 *   <div style="visibility: hidden">
9882 *     <div class="md-dialog-container" id="myStaticDialog">
9883 *       <md-dialog>
9884 *         This is a pre-rendered dialog.
9885 *       </md-dialog>
9886 *     </div>
9887 *   </div>
9888 * </hljs>
9889 *
9890 * **Notice**: It is important, to use the `.md-dialog-container` as the content element, otherwise the dialog
9891 * will not show up.
9892 *
9893 * It also possible to use a DOM element for the `contentElement` option.
9894 * - `contentElement: document.querySelector('#myStaticDialog')`
9895 * - `contentElement: angular.element(TEMPLATE)`
9896 *
9897 * When using a `template` as content element, it will be not compiled upon open.
9898 * This allows you to compile the element yourself and use it each time the dialog opens.
9899 *
9900 * ### Custom Presets
9901 * Developers are also able to create their own preset, which can be easily used without repeating
9902 * their options each time.
9903 *
9904 * <hljs lang="js">
9905 *   $mdDialogProvider.addPreset('testPreset', {
9906 *     options: function() {
9907 *       return {
9908 *         template:
9909 *           '<md-dialog>' +
9910 *             'This is a custom preset' +
9911 *           '</md-dialog>',
9912 *         controllerAs: 'dialog',
9913 *         bindToController: true,
9914 *         clickOutsideToClose: true,
9915 *         escapeToClose: true
9916 *       };
9917 *     }
9918 *   });
9919 * </hljs>
9920 *
9921 * After you created your preset at config phase, you can easily access it.
9922 *
9923 * <hljs lang="js">
9924 *   $mdDialog.show(
9925 *     $mdDialog.testPreset()
9926 *   );
9927 * </hljs>
9928 *
9929 * ### JavaScript: promise API syntax, custom dialog template
9930 * <hljs lang="js">
9931 * (function(angular, undefined){
9932 *   "use strict";
9933 *
9934 *   angular
9935 *     .module('demoApp', ['ngMaterial'])
9936 *     .controller('EmployeeController', EmployeeEditor)
9937 *     .controller('GreetingController', GreetingController);
9938 *
9939 *   // Fictitious Employee Editor to show how to use simple and complex dialogs.
9940 *
9941 *   function EmployeeEditor($scope, $mdDialog) {
9942 *     var alert;
9943 *
9944 *     $scope.showAlert = showAlert;
9945 *     $scope.closeAlert = closeAlert;
9946 *     $scope.showGreeting = showCustomGreeting;
9947 *
9948 *     $scope.hasAlert = function() { return !!alert };
9949 *     $scope.userName = $scope.userName || 'Bobby';
9950 *
9951 *     // Dialog #1 - Show simple alert dialog and cache
9952 *     // reference to dialog instance
9953 *
9954 *     function showAlert() {
9955 *       alert = $mdDialog.alert()
9956 *         .title('Attention, ' + $scope.userName)
9957 *         .textContent('This is an example of how easy dialogs can be!')
9958 *         .ok('Close');
9959 *
9960 *       $mdDialog
9961 *           .show( alert )
9962 *           .finally(function() {
9963 *             alert = undefined;
9964 *           });
9965 *     }
9966 *
9967 *     // Close the specified dialog instance and resolve with 'finished' flag
9968 *     // Normally this is not needed, just use '$mdDialog.hide()' to close
9969 *     // the most recent dialog popup.
9970 *
9971 *     function closeAlert() {
9972 *       $mdDialog.hide( alert, "finished" );
9973 *       alert = undefined;
9974 *     }
9975 *
9976 *     // Dialog #2 - Demonstrate more complex dialogs construction and popup.
9977 *
9978 *     function showCustomGreeting($event) {
9979 *         $mdDialog.show({
9980 *           targetEvent: $event,
9981 *           template:
9982 *             '<md-dialog>' +
9983 *
9984 *             '  <md-dialog-content>Hello {{ employee }}!</md-dialog-content>' +
9985 *
9986 *             '  <md-dialog-actions>' +
9987 *             '    <md-button ng-click="closeDialog()" class="md-primary">' +
9988 *             '      Close Greeting' +
9989 *             '    </md-button>' +
9990 *             '  </md-dialog-actions>' +
9991 *             '</md-dialog>',
9992 *           controller: 'GreetingController',
9993 *           onComplete: afterShowAnimation,
9994 *           locals: { employee: $scope.userName }
9995 *         });
9996 *
9997 *         // When the 'enter' animation finishes...
9998 *
9999 *         function afterShowAnimation(scope, element, options) {
10000 *            // post-show code here: DOM element focus, etc.
10001 *         }
10002 *     }
10003 *
10004 *     // Dialog #3 - Demonstrate use of ControllerAs and passing $scope to dialog
10005 *     //             Here we used ng-controller="GreetingController as vm" and
10006 *     //             $scope.vm === <controller instance>
10007 *
10008 *     function showCustomGreeting() {
10009 *
10010 *        $mdDialog.show({
10011 *           clickOutsideToClose: true,
10012 *
10013 *           scope: $scope,        // use parent scope in template
10014 *           preserveScope: true,  // do not forget this if use parent scope
10015
10016 *           // Since GreetingController is instantiated with ControllerAs syntax
10017 *           // AND we are passing the parent '$scope' to the dialog, we MUST
10018 *           // use 'vm.<xxx>' in the template markup
10019 *
10020 *           template: '<md-dialog>' +
10021 *                     '  <md-dialog-content>' +
10022 *                     '     Hi There {{vm.employee}}' +
10023 *                     '  </md-dialog-content>' +
10024 *                     '</md-dialog>',
10025 *
10026 *           controller: function DialogController($scope, $mdDialog) {
10027 *             $scope.closeDialog = function() {
10028 *               $mdDialog.hide();
10029 *             }
10030 *           }
10031 *        });
10032 *     }
10033 *
10034 *   }
10035 *
10036 *   // Greeting controller used with the more complex 'showCustomGreeting()' custom dialog
10037 *
10038 *   function GreetingController($scope, $mdDialog, employee) {
10039 *     // Assigned from construction <code>locals</code> options...
10040 *     $scope.employee = employee;
10041 *
10042 *     $scope.closeDialog = function() {
10043 *       // Easily hides most recent dialog shown...
10044 *       // no specific instance reference is needed.
10045 *       $mdDialog.hide();
10046 *     };
10047 *   }
10048 *
10049 * })(angular);
10050 * </hljs>
10051 */
10052
10053/**
10054 * @ngdoc method
10055 * @name $mdDialog#alert
10056 *
10057 * @description
10058 * Builds a preconfigured dialog with the specified message.
10059 *
10060 * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
10061 *
10062 * - $mdDialogPreset#title(string) - Sets the alert title.
10063 * - $mdDialogPreset#textContent(string) - Sets the alert message.
10064 * - $mdDialogPreset#htmlContent(string) - Sets the alert message as HTML. Requires ngSanitize
10065 *     module to be loaded. HTML is not run through Angular's compiler.
10066 * - $mdDialogPreset#ok(string) - Sets the alert "Okay" button text.
10067 * - $mdDialogPreset#theme(string) - Sets the theme of the alert dialog.
10068 * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option,
10069 *     the location of the click will be used as the starting point for the opening animation
10070 *     of the the dialog.
10071 *
10072 */
10073
10074/**
10075 * @ngdoc method
10076 * @name $mdDialog#confirm
10077 *
10078 * @description
10079 * Builds a preconfigured dialog with the specified message. You can call show and the promise returned
10080 * will be resolved only if the user clicks the confirm action on the dialog.
10081 *
10082 * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
10083 *
10084 * Additionally, it supports the following methods:
10085 *
10086 * - $mdDialogPreset#title(string) - Sets the confirm title.
10087 * - $mdDialogPreset#textContent(string) - Sets the confirm message.
10088 * - $mdDialogPreset#htmlContent(string) - Sets the confirm message as HTML. Requires ngSanitize
10089 *     module to be loaded. HTML is not run through Angular's compiler.
10090 * - $mdDialogPreset#ok(string) - Sets the confirm "Okay" button text.
10091 * - $mdDialogPreset#cancel(string) - Sets the confirm "Cancel" button text.
10092 * - $mdDialogPreset#theme(string) - Sets the theme of the confirm dialog.
10093 * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option,
10094 *     the location of the click will be used as the starting point for the opening animation
10095 *     of the the dialog.
10096 *
10097 */
10098
10099/**
10100 * @ngdoc method
10101 * @name $mdDialog#prompt
10102 *
10103 * @description
10104 * Builds a preconfigured dialog with the specified message and input box. You can call show and the promise returned
10105 * will be resolved only if the user clicks the prompt action on the dialog, passing the input value as the first argument.
10106 *
10107 * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
10108 *
10109 * Additionally, it supports the following methods:
10110 *
10111 * - $mdDialogPreset#title(string) - Sets the prompt title.
10112 * - $mdDialogPreset#textContent(string) - Sets the prompt message.
10113 * - $mdDialogPreset#htmlContent(string) - Sets the prompt message as HTML. Requires ngSanitize
10114 *     module to be loaded. HTML is not run through Angular's compiler.
10115 * - $mdDialogPreset#placeholder(string) - Sets the placeholder text for the input.
10116 * - $mdDialogPreset#initialValue(string) - Sets the initial value for the prompt input.
10117 * - $mdDialogPreset#ok(string) - Sets the prompt "Okay" button text.
10118 * - $mdDialogPreset#cancel(string) - Sets the prompt "Cancel" button text.
10119 * - $mdDialogPreset#theme(string) - Sets the theme of the prompt dialog.
10120 * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option,
10121 *     the location of the click will be used as the starting point for the opening animation
10122 *     of the the dialog.
10123 *
10124 */
10125
10126/**
10127 * @ngdoc method
10128 * @name $mdDialog#show
10129 *
10130 * @description
10131 * Show a dialog with the specified options.
10132 *
10133 * @param {object} optionsOrPreset Either provide an `$mdDialogPreset` returned from `alert()`, and
10134 * `confirm()`, or an options object with the following properties:
10135 *   - `templateUrl` - `{string=}`: The url of a template that will be used as the content
10136 *   of the dialog.
10137 *   - `template` - `{string=}`: HTML template to show in the dialog. This **must** be trusted HTML
10138 *      with respect to Angular's [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
10139 *      This template should **never** be constructed with any kind of user input or user data.
10140 *   - `contentElement` - `{string|Element}`: Instead of using a template, which will be compiled each time a
10141 *     dialog opens, you can also use a DOM element.<br/>
10142 *     * When specifying an element, which is present on the DOM, `$mdDialog` will temporary fetch the element into
10143 *       the dialog and restores it at the old DOM position upon close.
10144 *     * When specifying a string, the string be used as a CSS selector, to lookup for the element in the DOM.
10145 *   - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template with a
10146 *     `<md-dialog>` tag if one is not provided. Defaults to true. Can be disabled if you provide a
10147 *     custom dialog directive.
10148 *   - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option,
10149 *     the location of the click will be used as the starting point for the opening animation
10150 *     of the the dialog.
10151 *   - `openFrom` - `{string|Element|object}`: The query selector, DOM element or the Rect object
10152 *     that is used to determine the bounds (top, left, height, width) from which the Dialog will
10153 *     originate.
10154 *   - `closeTo` - `{string|Element|object}`: The query selector, DOM element or the Rect object
10155 *     that is used to determine the bounds (top, left, height, width) to which the Dialog will
10156 *     target.
10157 *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified,
10158 *     it will create a new isolate scope.
10159 *     This scope will be destroyed when the dialog is removed unless `preserveScope` is set to true.
10160 *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
10161 *   - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the dialog is open.
10162 *     Default true.
10163 *   - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog.
10164 *     Default true.
10165 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the dialog to
10166 *     close it. Default false.
10167 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the dialog.
10168 *     Default true.
10169 *   - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on open. Only disable if
10170 *     focusing some other way, as focus management is required for dialogs to be accessible.
10171 *     Defaults to true.
10172 *   - `controller` - `{function|string=}`: The controller to associate with the dialog. The controller
10173 *     will be injected with the local `$mdDialog`, which passes along a scope for the dialog.
10174 *   - `locals` - `{object=}
10174`: An object containing key/value pairs. The keys will be used as names
10175 *     of values to inject into the controller. For example, `locals: {three: 3}` would inject
10176 *     `three` into the controller, with the value 3. If `bindToController` is true, they will be
10177 *     copied to the controller instead.
10178 *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
10179 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
10180 *   - `parent` - `{element=}`: The element to append the dialog to. Defaults to appending
10181 *     to the root element of the application.
10182 *   - `onShowing` - `function(scope, element)`: Callback function used to announce the show() action is
10183 *     starting.
10184 *   - `onComplete` - `function(scope, element)`: Callback function used to announce when the show() action is
10185 *     finished.
10186 *   - `onRemoving` - `function(element, removePromise)`: Callback function used to announce the
10187 *      close/hide() action is starting. This allows developers to run custom animations
10188 *      in parallel the close animations.
10189 *   - `fullscreen` `{boolean=}`: An option to toggle whether the dialog should show in fullscreen
10190 *      or not. Defaults to `false`.
10191 *   - `multiple` `{boolean=}`: An option to allow this dialog to display over one that's currently open.
10192 * @returns {promise} A promise that can be resolved with `$mdDialog.hide()` or
10193 * rejected with `$mdDialog.cancel()`.
10194 */
10195
10196/**
10197 * @ngdoc method
10198 * @name $mdDialog#hide
10199 *
10200 * @description
10201 * Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`.
10202 *
10203 * @param {*=} response An argument for the resolved promise.
10204 *
10205 * @returns {promise} A promise that is resolved when the dialog has been closed.
10206 */
10207
10208/**
10209 * @ngdoc method
10210 * @name $mdDialog#cancel
10211 *
10212 * @description
10213 * Hide an existing dialog and reject the promise returned from `$mdDialog.show()`.
10214 *
10215 * @param {*=} response An argument for the rejected promise.
10216 *
10217 * @returns {promise} A promise that is resolved when the dialog has been closed.
10218 */
10219
10220function MdDialogProvider($$interimElementProvider) {
10221  // Elements to capture and redirect focus when the user presses tab at the dialog boundary.
10222  MdDialogController.$inject = ["$mdDialog", "$mdConstant"];
10223  dialogDefaultOptions.$inject = ["$mdDialog", "$mdAria", "$mdUtil", "$mdConstant", "$animate", "$document", "$window", "$rootElement", "$log", "$injector", "$mdTheming", "$interpolate", "$mdInteraction"];
10224  var topFocusTrap, bottomFocusTrap;
10225
10226  return $$interimElementProvider('$mdDialog')
10227    .setDefaults({
10228      methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose',
10229          'targetEvent', 'closeTo', 'openFrom', 'parent', 'fullscreen', 'multiple'],
10230      options: dialogDefaultOptions
10231    })
10232    .addPreset('alert', {
10233      methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'theme',
10234          'css'],
10235      options: advancedDialogOptions
10236    })
10237    .addPreset('confirm', {
10238      methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'cancel',
10239          'theme', 'css'],
10240      options: advancedDialogOptions
10241    })
10242    .addPreset('prompt', {
10243      methods: ['title', 'htmlContent', 'textContent', 'initialValue', 'content', 'placeholder', 'ariaLabel',
10244          'ok', 'cancel', 'theme', 'css', 'required'],
10245      options: advancedDialogOptions
10246    });
10247
10248  /* @ngInject */
10249  function advancedDialogOptions() {
10250    return {
10251      template: [
10252        '<md-dialog md-theme="{{ dialog.theme || dialog.defaultTheme }}" aria-label="{{ dialog.ariaLabel }}" ng-class="dialog.css">',
10253        '  <md-dialog-content class="md-dialog-content" role="document" tabIndex="-1">',
10254        '    <h2 class="md-title">{{ dialog.title }}</h2>',
10255        '    <div ng-if="::dialog.mdHtmlContent" class="md-dialog-content-body" ',
10256        '        ng-bind-html="::dialog.mdHtmlContent"></div>',
10257        '    <div ng-if="::!dialog.mdHtmlContent" class="md-dialog-content-body">',
10258        '      <p>{{::dialog.mdTextContent}}</p>',
10259        '    </div>',
10260        '    <md-input-container md-no-float ng-if="::dialog.$type == \'prompt\'" class="md-prompt-input-container">',
10261        '      <input ng-keypress="dialog.keypress($event)" md-autofocus ng-model="dialog.result" ' +
10262        '             placeholder="{{::dialog.placeholder}}" ng-required="dialog.required">',
10263        '    </md-input-container>',
10264        '  </md-dialog-content>',
10265        '  <md-dialog-actions>',
vendor: 45,284 bytes, lines 10266-11641
10266        '    <md-button ng-if="dialog.$type === \'confirm\' || dialog.$type === \'prompt\'"' +
10267        '               ng-click="dialog.abort()" class="md-primary md-cancel-button">',
10268        '      {{ dialog.cancel }}',
10269        '    </md-button>',
10270        '    <md-button ng-click="dialog.hide()" class="md-primary md-confirm-button" md-autofocus="dialog.$type===\'alert\'"' +
10271        '               ng-disabled="dialog.required && !dialog.result">',
10272        '      {{ dialog.ok }}',
10273        '    </md-button>',
10274        '  </md-dialog-actions>',
10275        '</md-dialog>'
10276      ].join('').replace(/\s\s+/g, ''),
10277      controller: MdDialogController,
10278      controllerAs: 'dialog',
10279      bindToController: true,
10280    };
10281  }
10282
10283  /**
10284   * Controller for the md-dialog interim elements
10285   * @ngInject
10286   */
10287  function MdDialogController($mdDialog, $mdConstant) {
10288    // For compatibility with AngularJS 1.6+, we should always use the $onInit hook in
10289    // interimElements. The $mdCompiler simulates the $onInit hook for all versions.
10290    this.$onInit = function() {
10291      var isPrompt = this.$type == 'prompt';
10292
10293      if (isPrompt && this.initialValue) {
10294        this.result = this.initialValue;
10295      }
10296
10297      this.hide = function() {
10298        $mdDialog.hide(isPrompt ? this.result : true);
10299      };
10300      this.abort = function() {
10301        $mdDialog.cancel();
10302      };
10303      this.keypress = function($event) {
10304        if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) {
10305          $mdDialog.hide(this.result);
10306        }
10307      };
10308    };
10309  }
10310
10311  /* @ngInject */
10312  function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document, $window, $rootElement,
10313                                $log, $injector, $mdTheming, $interpolate, $mdInteraction) {
10314
10315    return {
10316      hasBackdrop: true,
10317      isolateScope: true,
10318      onCompiling: beforeCompile,
10319      onShow: onShow,
10320      onShowing: beforeShow,
10321      onRemove: onRemove,
10322      clickOutsideToClose: false,
10323      escapeToClose: true,
10324      targetEvent: null,
10325      closeTo: null,
10326      openFrom: null,
10327      focusOnOpen: true,
10328      disableParentScroll: true,
10329      autoWrap: true,
10330      fullscreen: false,
10331      transformTemplate: function(template, options) {
10332        // Make the dialog container focusable, because otherwise the focus will be always redirected to
10333        // an element outside of the container, and the focus trap won't work probably..
10334        // Also the tabindex is needed for the `escapeToClose` functionality, because
10335        // the keyDown event can't be triggered when the focus is outside of the container.
10336        var startSymbol = $interpolate.startSymbol();
10337        var endSymbol = $interpolate.endSymbol();
10338        var theme = startSymbol + (options.themeWatch ? '' : '::') + 'theme' + endSymbol;
10339        return '<div class="md-dialog-container" tabindex="-1" md-theme="' + theme + '">' + validatedTemplate(template) + '</div>';
10340
10341        /**
10342         * The specified template should contain a <md-dialog> wrapper element....
10343         */
10344        function validatedTemplate(template) {
10345          if (options.autoWrap && !/<\/md-dialog>/g.test(template)) {
10346            return '<md-dialog>' + (template || '') + '</md-dialog>';
10347          } else {
10348            return template || '';
10349          }
10350        }
10351      }
10352    };
10353
10354    function beforeCompile(options) {
10355      // Automatically apply the theme, if the user didn't specify a theme explicitly.
10356      // Those option changes need to be done, before the compilation has started, because otherwise
10357      // the option changes will be not available in the $mdCompilers locales.
10358      options.defaultTheme = $mdTheming.defaultTheme();
10359
10360      detectTheming(options);
10361    }
10362
10363    function beforeShow(scope, element, options, controller) {
10364
10365      if (controller) {
10366        var mdHtmlContent = controller.htmlContent || options.htmlContent || '';
10367        var mdTextContent = controller.textContent || options.textContent ||
10368            controller.content || options.content || '';
10369
10370        if (mdHtmlContent && !$injector.has('$sanitize')) {
10371          throw Error('The ngSanitize module must be loaded in order to use htmlContent.');
10372        }
10373
10374        if (mdHtmlContent && mdTextContent) {
10375          throw Error('md-dialog cannot have both `htmlContent` and `textContent`');
10376        }
10377
10378        // Only assign the content if nothing throws, otherwise it'll still be compiled.
10379        controller.mdHtmlContent = mdHtmlContent;
10380        controller.mdTextContent = mdTextContent;
10381      }
10382    }
10383
10384    /** Show method for dialogs */
10385    function onShow(scope, element, options, controller) {
10386      angular.element($document[0].body).addClass('md-dialog-is-showing');
10387
10388      var dialogElement = element.find('md-dialog');
10389
10390      // Once a dialog has `ng-cloak` applied on his template the dialog animation will not work properly.
10391      // This is a very common problem, so we have to notify the developer about this.
10392      if (dialogElement.hasClass('ng-cloak')) {
10393        var message = '$mdDialog: using `<md-dialog ng-cloak>` will affect the dialog opening animations.';
10394        $log.warn( message, element[0] );
10395      }
10396
10397      captureParentAndFromToElements(options);
10398      configureAria(dialogElement, options);
10399      showBackdrop(scope, element, options);
10400      activateListeners(element, options);
10401
10402      return dialogPopIn(element, options)
10403        .then(function() {
10404          lockScreenReader(element, options);
10405          warnDeprecatedActions();
10406          focusOnOpen();
10407        });
10408
10409      /**
10410       * Check to see if they used the deprecated .md-actions class and log a warning
10411       */
10412      function warnDeprecatedActions() {
10413        if (element[0].querySelector('.md-actions')) {
10414          $log.warn('Using a class of md-actions is deprecated, please use <md-dialog-actions>.');
10415        }
10416      }
10417
10418      /**
10419       * For alerts, focus on content... otherwise focus on
10420       * the close button (or equivalent)
10421       */
10422      function focusOnOpen() {
10423        if (options.focusOnOpen) {
10424          var target = $mdUtil.findFocusTarget(element) || findCloseButton() || dialogElement;
10425          target.focus();
10426        }
10427
10428        /**
10429         * If no element with class dialog-close, try to find the last
10430         * button child in md-actions and assume it is a close button.
10431         *
10432         * If we find no actions at all, log a warning to the console.
10433         */
10434        function findCloseButton() {
10435          return element[0].querySelector('.dialog-close, md-dialog-actions button:last-child');
10436        }
10437      }
10438    }
10439
10440    /**
10441     * Remove function for all dialogs
10442     */
10443    function onRemove(scope, element, options) {
10444      options.deactivateListeners();
10445      options.unlockScreenReader();
10446      options.hideBackdrop(options.$destroy);
10447
10448      // Remove the focus traps that we added earlier for keeping focus within the dialog.
10449      if (topFocusTrap && topFocusTrap.parentNode) {
10450        topFocusTrap.parentNode.removeChild(topFocusTrap);
10451      }
10452
10453      if (bottomFocusTrap && bottomFocusTrap.parentNode) {
10454        bottomFocusTrap.parentNode.removeChild(bottomFocusTrap);
10455      }
10456
10457      // For navigation $destroy events, do a quick, non-animated removal,
10458      // but for normal closes (from clicks, etc) animate the removal
10459      return !!options.$destroy ? detachAndClean() : animateRemoval().then( detachAndClean );
10460
10461      /**
10462       * For normal closes, animate the removal.
10463       * For forced closes (like $destroy events), skip the animations
10464       */
10465      function animateRemoval() {
10466        return dialogPopOut(element, options);
10467      }
10468
10469      /**
10470       * Detach the element
10471       */
10472      function detachAndClean() {
10473        angular.element($document[0].body).removeClass('md-dialog-is-showing');
10474
10475        // Reverse the container stretch if using a content element.
10476        if (options.contentElement) {
10477          options.reverseContainerStretch();
10478        }
10479
10480        // Exposed cleanup function from the $mdCompiler.
10481        options.cleanupElement();
10482
10483        // Restores the focus to the origin element if the last interaction upon opening was a keyboard.
10484        if (!options.$destroy && options.originInteraction === 'keyboard') {
10485          options.origin.focus();
10486        }
10487      }
10488    }
10489
10490    function detectTheming(options) {
10491      // Once the user specifies a targetEvent, we will automatically try to find the correct
10492      // nested theme.
10493      var targetEl;
10494      if (options.targetEvent && options.targetEvent.target) {
10495        targetEl = angular.element(options.targetEvent.target);
10496      }
10497
10498      var themeCtrl = targetEl && targetEl.controller('mdTheme');
10499
10500      if (!themeCtrl) {
10501        return;
10502      }
10503
10504      options.themeWatch = themeCtrl.$shouldWatch;
10505
10506      var theme = options.theme || themeCtrl.$mdTheme;
10507
10508      if (theme) {
10509        options.scope.theme = theme;
10510      }
10511
10512      var unwatch = themeCtrl.registerChanges(function (newTheme) {
10513        options.scope.theme = newTheme;
10514
10515        if (!options.themeWatch) {
10516          unwatch();
10517        }
10518      });
10519    }
10520
10521    /**
10522     * Capture originator/trigger/from/to element information (if available)
10523     * and the parent container for the dialog; defaults to the $rootElement
10524     * unless overridden in the options.parent
10525     */
10526    function captureParentAndFromToElements(options) {
10527          options.origin = angular.extend({
10528            element: null,
10529            bounds: null,
10530            focus: angular.noop
10531          }, options.origin || {});
10532
10533          options.parent   = getDomElement(options.parent, $rootElement);
10534          options.closeTo  = getBoundingClientRect(getDomElement(options.closeTo));
10535          options.openFrom = getBoundingClientRect(getDomElement(options.openFrom));
10536
10537          if ( options.targetEvent ) {
10538            options.origin = getBoundingClientRect(options.targetEvent.target, options.origin);
10539            options.originInteraction = $mdInteraction.getLastInteractionType();
10540          }
10541
10542
10543          /**
10544           * Identify the bounding RECT for the target element
10545           *
10546           */
10547          function getBoundingClientRect (element, orig) {
10548            var source = angular.element((element || {}));
10549            if (source && source.length) {
10550              // Compute and save the target element's bounding rect, so that if the
10551              // element is hidden when the dialog closes, we can shrink the dialog
10552              // back to the same position it expanded from.
10553              //
10554              // Checking if the source is a rect object or a DOM element
10555              var bounds = {top:0,left:0,height:0,width:0};
10556              var hasFn = angular.isFunction(source[0].getBoundingClientRect);
10557
10558              return angular.extend(orig || {}, {
10559                  element : hasFn ? source : undefined,
10560                  bounds  : hasFn ? source[0].getBoundingClientRect() : angular.extend({}, bounds, source[0]),
10561                  focus   : angular.bind(source, source.focus),
10562              });
10563            }
10564          }
10565
10566          /**
10567           * If the specifier is a simple string selector, then query for
10568           * the DOM element.
10569           */
10570          function getDomElement(element, defaultElement) {
10571            if (angular.isString(element)) {
10572              element = $document[0].querySelector(element);
10573            }
10574
10575            // If we have a reference to a raw dom element, always wrap it in jqLite
10576            return angular.element(element || defaultElement);
10577          }
10578
10579        }
10580
10581    /**
10582     * Listen for escape keys and outside clicks to auto close
10583     */
10584    function activateListeners(element, options) {
10585      var window = angular.element($window);
10586      var onWindowResize = $mdUtil.debounce(function() {
10587        stretchDialogContainerToViewport(element, options);
10588      }, 60);
10589
10590      var removeListeners = [];
10591      var smartClose = function() {
10592        // Only 'confirm' dialogs have a cancel button... escape/clickOutside will
10593        // cancel or fallback to hide.
10594        var closeFn = ( options.$type == 'alert' ) ? $mdDialog.hide : $mdDialog.cancel;
10595        $mdUtil.nextTick(closeFn, true);
10596      };
10597
10598      if (options.escapeToClose) {
10599        var parentTarget = options.parent;
10600        var keyHandlerFn = function(ev) {
10601          if (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
10602            ev.stopPropagation();
10603            ev.preventDefault();
10604
10605            smartClose();
10606          }
10607        };
10608
10609        // Add keydown listeners
10610        element.on('keydown', keyHandlerFn);
10611        parentTarget.on('keydown', keyHandlerFn);
10612
10613        // Queue remove listeners function
10614        removeListeners.push(function() {
10615
10616          element.off('keydown', keyHandlerFn);
10617          parentTarget.off('keydown', keyHandlerFn);
10618
10619        });
10620      }
10621
10622      // Register listener to update dialog on window resize
10623      window.on('resize', onWindowResize);
10624
10625      removeListeners.push(function() {
10626        window.off('resize', onWindowResize);
10627      });
10628
10629      if (options.clickOutsideToClose) {
10630        var target = element;
10631        var sourceElem;
10632
10633        // Keep track of the element on which the mouse originally went down
10634        // so that we can only close the backdrop when the 'click' started on it.
10635        // A simple 'click' handler does not work,
10636        // it sets the target object as the element the mouse went down on.
10637        var mousedownHandler = function(ev) {
10638          sourceElem = ev.target;
10639        };
10640
10641        // We check if our original element and the target is the backdrop
10642        // because if the original was the backdrop and the target was inside the dialog
10643        // we don't want to dialog to close.
10644        var mouseupHandler = function(ev) {
10645          if (sourceElem === target[0] && ev.target === target[0]) {
10646            ev.stopPropagation();
10647            ev.preventDefault();
10648
10649            smartClose();
10650          }
10651        };
10652
10653        // Add listeners
10654        target.on('mousedown', mousedownHandler);
10655        target.on('mouseup', mouseupHandler);
10656
10657        // Queue remove listeners function
10658        removeListeners.push(function() {
10659          target.off('mousedown', mousedownHandler);
10660          target.off('mouseup', mouseupHandler);
10661        });
10662      }
10663
10664      // Attach specific `remove` listener handler
10665      options.deactivateListeners = function() {
10666        removeListeners.forEach(function(removeFn) {
10667          removeFn();
10668        });
10669        options.deactivateListeners = null;
10670      };
10671    }
10672
10673    /**
10674     * Show modal backdrop element...
10675     */
10676    function showBackdrop(scope, element, options) {
10677
10678      if (options.disableParentScroll) {
10679        // !! DO this before creating the backdrop; since disableScrollAround()
10680        //    configures the scroll offset; which is used by mdBackDrop postLink()
10681        options.restoreScroll = $mdUtil.disableScrollAround(element, options.parent);
10682      }
10683
10684      if (options.hasBackdrop) {
10685        options.backdrop = $mdUtil.createBackdrop(scope, "md-dialog-backdrop md-opaque");
10686        $animate.enter(options.backdrop, options.parent);
10687      }
10688
10689      /**
10690       * Hide modal backdrop element...
10691       */
10692      options.hideBackdrop = function hideBackdrop($destroy) {
10693        if (options.backdrop) {
10694          if ( !!$destroy ) options.backdrop.remove();
10695          else              $animate.leave(options.backdrop);
10696        }
10697
10698
10699        if (options.disableParentScroll) {
10700          options.restoreScroll && options.restoreScroll();
10701          delete options.restoreScroll;
10702        }
10703
10704        options.hideBackdrop = null;
10705      };
10706    }
10707
10708    /**
10709     * Inject ARIA-specific attributes appropriate for Dialogs
10710     */
10711    function configureAria(element, options) {
10712
10713      var role = (options.$type === 'alert') ? 'alertdialog' : 'dialog';
10714      var dialogContent = element.find('md-dialog-content');
10715      var existingDialogId = element.attr('id');
10716      var dialogContentId = 'dialogContent_' + (existingDialogId || $mdUtil.nextUid());
10717
10718      element.attr({
10719        'role': role,
10720        'tabIndex': '-1'
10721      });
10722
10723      if (dialogContent.length === 0) {
10724        dialogContent = element;
10725        // If the dialog element already had an ID, don't clobber it.
10726        if (existingDialogId) {
10727          dialogContentId = existingDialogId;
10728        }
10729      }
10730
10731      dialogContent.attr('id', dialogContentId);
10732      element.attr('aria-describedby', dialogContentId);
10733
10734      if (options.ariaLabel) {
10735        $mdAria.expect(element, 'aria-label', options.ariaLabel);
10736      }
10737      else {
10738        $mdAria.expectAsync(element, 'aria-label', function() {
10739          // If dialog title is specified, set aria-label with it
10740          // See https://github.com/angular/material/issues/10582
10741          if (options.title) {
10742            return options.title;
10743          } else {
10744            var words = dialogContent.text().split(/\s+/);
10745            if (words.length > 3) words = words.slice(0, 3).concat('...');
10746            return words.join(' ');
10747          }
10748        });
10749      }
10750
10751      // Set up elements before and after the dialog content to capture focus and
10752      // redirect back into the dialog.
10753      topFocusTrap = document.createElement('div');
10754      topFocusTrap.classList.add('md-dialog-focus-trap');
10755      topFocusTrap.tabIndex = 0;
10756
10757      bottomFocusTrap = topFocusTrap.cloneNode(false);
10758
10759      // When focus is about to move out of the dialog, we want to intercept it and redirect it
10760      // back to the dialog element.
10761      var focusHandler = function() {
10762        element.focus();
10763      };
10764      topFocusTrap.addEventListener('focus', focusHandler);
10765      bottomFocusTrap.addEventListener('focus', focusHandler);
10766
10767      // The top focus trap inserted immeidately before the md-dialog element (as a sibling).
10768      // The bottom focus trap is inserted at the very end of the md-dialog element (as a child).
10769      element[0].parentNode.insertBefore(topFocusTrap, element[0]);
10770      element.after(bottomFocusTrap);
10771    }
10772
10773    /**
10774     * Prevents screen reader interaction behind modal window
10775     * on swipe interfaces
10776     */
10777    function lockScreenReader(element, options) {
10778      var isHidden = true;
10779
10780      // get raw DOM node
10781      walkDOM(element[0]);
10782
10783      options.unlockScreenReader = function() {
10784        isHidden = false;
10785        walkDOM(element[0]);
10786
10787        options.unlockScreenReader = null;
10788      };
10789
10790      /**
10791       * Walk DOM to apply or remove aria-hidden on sibling nodes
10792       * and parent sibling nodes
10793       *
10794       */
10795      function walkDOM(element) {
10796        while (element.parentNode) {
10797          if (element === document.body) {
10798            return;
10799          }
10800          var children = element.parentNode.children;
10801          for (var i = 0; i < children.length; i++) {
10802            // skip over child if it is an ascendant of the dialog
10803            // or a script or style tag
10804            if (element !== children[i] &&
10805             !isNodeOneOf(children[i], ['SCRIPT', 'STYLE']) &&
10806             !children[i].hasAttribute('aria-live')) {
10807              children[i].setAttribute('aria-hidden', isHidden);
10808            }
10809          }
10810
10811          walkDOM(element = element.parentNode);
10812        }
10813      }
10814    }
10815
10816    /**
10817     * Ensure the dialog container fill-stretches to the viewport
10818     */
10819    function stretchDialogContainerToViewport(container, options) {
10820      var isFixed = $window.getComputedStyle($document[0].body).position == 'fixed';
10821      var backdrop = options.backdrop ? $window.getComputedStyle(options.backdrop[0]) : null;
10822      var height = backdrop ? Math.min($document[0].body.clientHeight, Math.ceil(Math.abs(parseInt(backdrop.height, 10)))) : 0;
10823
10824      var previousStyles = {
10825        top: container.css('top'),
10826        height: container.css('height')
10827      };
10828
10829      // If the body is fixed, determine the distance to the viewport in relative from the parent.
10830      var parentTop = Math.abs(options.parent[0].getBoundingClientRect().top);
10831
10832      container.css({
10833        top: (isFixed ? parentTop : 0) + 'px',
10834        height: height ? height + 'px' : '100%'
10835      });
10836
10837      return function() {
10838        // Reverts the modified styles back to the previous values.
10839        // This is needed for contentElements, which should have the same styles after close
10840        // as before.
10841        container.css(previousStyles);
10842      };
10843    }
10844
10845    /**
10846     *  Dialog open and pop-in animation
10847     */
10848    function dialogPopIn(container, options) {
10849      // Add the `md-dialog-container` to the DOM
10850      options.parent.append(container);
10851      options.reverseContainerStretch = stretchDialogContainerToViewport(container, options);
10852
10853      var dialogEl = container.find('md-dialog');
10854      var animator = $mdUtil.dom.animator;
10855      var buildTranslateToOrigin = animator.calculateZoomToOrigin;
10856      var translateOptions = {transitionInClass: 'md-transition-in', transitionOutClass: 'md-transition-out'};
10857      var from = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.openFrom || options.origin));
10858      var to = animator.toTransformCss("");  // defaults to center display (or parent or $rootElement)
10859
10860      dialogEl.toggleClass('md-dialog-fullscreen', !!options.fullscreen);
10861
10862      return animator
10863        .translate3d(dialogEl, from, to, translateOptions)
10864        .then(function(animateReversal) {
10865
10866          // Build a reversal translate function synced to this translation...
10867          options.reverseAnimate = function() {
10868            delete options.reverseAnimate;
10869
10870            if (options.closeTo) {
10871              // Using the opposite classes to create a close animation to the closeTo element
10872              translateOptions = {transitionInClass: 'md-transition-out', transitionOutClass: 'md-transition-in'};
10873              from = to;
10874              to = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.closeTo));
10875
10876              return animator
10877                .translate3d(dialogEl, from, to,translateOptions);
10878            }
10879
10880            return animateReversal(
10881              to = animator.toTransformCss(
10882                // in case the origin element has moved or is hidden,
10883                // let's recalculate the translateCSS
10884                buildTranslateToOrigin(dialogEl, options.origin)
10885              )
10886            );
10887
10888          };
10889
10890          // Function to revert the generated animation styles on the dialog element.
10891          // Useful when using a contentElement instead of a template.
10892          options.clearAnimate = function() {
10893            delete options.clearAnimate;
10894
10895            // Remove the transition classes, added from $animateCSS, since those can't be removed
10896            // by reversely running the animator.
10897            dialogEl.removeClass([
10898              translateOptions.transitionOutClass,
10899              translateOptions.transitionInClass
10900            ].join(' '));
10901
10902            // Run the animation reversely to remove the previous added animation styles.
10903            return animator.translate3d(dialogEl, to, animator.toTransformCss(''), {});
10904          };
10905
10906          return true;
10907        });
10908    }
10909
10910    /**
10911     * Dialog close and pop-out animation
10912     */
10913    function dialogPopOut(container, options) {
10914      return options.reverseAnimate().then(function() {
10915        if (options.contentElement) {
10916          // When we use a contentElement, we want the element to be the same as before.
10917          // That means, that we have to clear all the animation properties, like transform.
10918          options.clearAnimate();
10919        }
10920      });
10921    }
10922
10923    /**
10924     * Utility function to filter out raw DOM nodes
10925     */
10926    function isNodeOneOf(elem, nodeTypeArray) {
10927      if (nodeTypeArray.indexOf(elem.nodeName) !== -1) {
10928        return true;
10929      }
10930    }
10931
10932  }
10933}
10934
10935})();
10936(function(){
10937"use strict";
10938
10939/**
10940 * @ngdoc module
10941 * @name material.components.divider
10942 * @description Divider module!
10943 */
10944MdDividerDirective.$inject = ["$mdTheming"];
10945angular.module('material.components.divider', [
10946  'material.core'
10947])
10948  .directive('mdDivider', MdDividerDirective);
10949
10950/**
10951 * @ngdoc directive
10952 * @name mdDivider
10953 * @module material.components.divider
10954 * @restrict E
10955 *
10956 * @description
10957 * 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.
10958 *
10959 * @param {boolean=} md-inset Add this attribute to activate the inset divider style.
10960 * @usage
10961 * <hljs lang="html">
10962 * <md-divider></md-divider>
10963 *
10964 * <md-divider md-inset></md-divider>
10965 * </hljs>
10966 *
10967 */
10968function MdDividerDirective($mdTheming) {
10969  return {
10970    restrict: 'E',
10971    link: $mdTheming
10972  };
10973}
10974
10975})();
10976(function(){
10977"use strict";
10978
10979(function() {
10980  'use strict';
10981
10982  /**
10983   * @ngdoc module
10984   * @name material.components.fabActions
10985   */
10986  MdFabActionsDirective.$inject = ["$mdUtil"];
10987  angular
10988    .module('material.components.fabActions', ['material.core'])
10989    .directive('mdFabActions', MdFabActionsDirective);
10990
10991  /**
10992   * @ngdoc directive
10993   * @name mdFabActions
10994   * @module material.components.fabActions
10995   *
10996   * @restrict E
10997   *
10998   * @description
10999   * The `<md-fab-actions>` directive is used inside of a `<md-fab-speed-dial>` or
11000   * `<md-fab-toolbar>` directive to mark an element (or elements) as the actions and setup the
11001   * proper event listeners.
11002   *
11003   * @usage
11004   * See the `<md-fab-speed-dial>` or `<md-fab-toolbar>` directives for example usage.
11005   */
11006  function MdFabActionsDirective($mdUtil) {
11007    return {
11008      restrict: 'E',
11009
11010      require: ['^?mdFabSpeedDial', '^?mdFabToolbar'],
11011
11012      compile: function(element, attributes) {
11013        var children = element.children();
11014
11015        var hasNgRepeat = $mdUtil.prefixer().hasAttribute(children, 'ng-repeat');
11016
11017        // Support both ng-repeat and static content
11018        if (hasNgRepeat) {
11019          children.addClass('md-fab-action-item');
11020        } else {
11021          // Wrap every child in a new div and add a class that we can scale/fling independently
11022          children.wrap('<div class="md-fab-action-item">');
11023        }
11024      }
11025    };
11026  }
11027
11028})();
11029
11030})();
11031(function(){
11032"use strict";
11033
11034(function() {
11035  'use strict';
11036
11037  MdFabController.$inject = ["$scope", "$element", "$animate", "$mdUtil", "$mdConstant", "$timeout"];
11038  angular.module('material.components.fabShared', ['material.core'])
11039    .controller('MdFabController', MdFabController);
11040
11041  function MdFabController($scope, $element, $animate, $mdUtil, $mdConstant, $timeout) {
11042    var vm = this;
11043    var initialAnimationAttempts = 0;
11044
11045    // NOTE: We use async eval(s) below to avoid conflicts with any existing digest loops
11046
11047    vm.open = function() {
11048      $scope.$evalAsync("vm.isOpen = true");
11049    };
11050
11051    vm.close = function() {
11052      // Async eval to avoid conflicts with existing digest loops
11053      $scope.$evalAsync("vm.isOpen = false");
11054
11055      // Focus the trigger when the element closes so users can still tab to the next item
11056      $element.find('md-fab-trigger')[0].focus();
11057    };
11058
11059    // Toggle the open/close state when the trigger is clicked
11060    vm.toggle = function() {
11061      $scope.$evalAsync("vm.isOpen = !vm.isOpen");
11062    };
11063
11064    /*
11065     * AngularJS Lifecycle hook for newer AngularJS versions.
11066     * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
11067     */
11068    vm.$onInit = function() {
11069      setupDefaults();
11070      setupListeners();
11071      setupWatchers();
11072
11073      fireInitialAnimations();
11074    };
11075
11076    // For AngularJS 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
11077    // manually call the $onInit hook.
11078    if (angular.version.major === 1 && angular.version.minor <= 4) {
11079      this.$onInit();
11080    }
11081
11082    function setupDefaults() {
11083      // Set the default direction to 'down' if none is specified
11084      vm.direction = vm.direction || 'down';
11085
11086      // Set the default to be closed
11087      vm.isOpen = vm.isOpen || false;
11088
11089      // Start the keyboard interaction at the first action
11090      resetActionIndex();
11091
11092      // Add an animations waiting class so we know not to run
11093      $element.addClass('md-animations-waiting');
11094    }
11095
11096    function setupListeners() {
11097      var eventTypes = [
11098        'click', 'focusin', 'focusout'
11099      ];
11100
11101      // Add our listeners
11102      angular.forEach(eventTypes, function(eventType) {
11103        $element.on(eventType, parseEvents);
11104      });
11105
11106      // Remove our listeners when destroyed
11107      $scope.$on('$destroy', function() {
11108        angular.forEach(eventTypes, function(eventType) {
11109          $element.off(eventType, parseEvents);
11110        });
11111
11112        // remove any attached keyboard handlers in case element is removed while
11113        // speed dial is open
11114        disableKeyboard();
11115      });
11116    }
11117
11118    var closeTimeout;
11119    function parseEvents(event) {
11120      // If the event is a click, just handle it
11121      if (event.type == 'click') {
11122        handleItemClick(event);
11123      }
11124
11125      // If we focusout, set a timeout to close the element
11126      if (event.type == 'focusout' && !closeTimeout) {
11127        closeTimeout = $timeout(function() {
11128          vm.close();
11129        }, 100, false);
11130      }
11131
11132      // If we see a focusin and there is a timeout about to run, cancel it so we stay open
11133      if (event.type == 'focusin' && closeTimeout) {
11134        $timeout.cancel(closeTimeout);
11135        closeTimeout = null;
11136      }
11137    }
11138
11139    function resetActionIndex() {
11140      vm.currentActionIndex = -1;
11141    }
11142
11143    function setupWatchers() {
11144      // Watch for changes to the direction and update classes/attributes
11145      $scope.$watch('vm.direction', function(newDir, oldDir) {
11146        // Add the appropriate classes so we can target the direction in the CSS
11147        $animate.removeClass($element, 'md-' + oldDir);
11148        $animate.addClass($element, 'md-' + newDir);
11149
11150        // Reset the action index since it may have changed
11151        resetActionIndex();
11152      });
11153
11154      var trigger, actions;
11155
11156      // Watch for changes to md-open
11157      $scope.$watch('vm.isOpen', function(isOpen) {
11158        // Reset the action index since it may have changed
11159        resetActionIndex();
11160
11161        // We can't get the trigger/actions outside of the watch because the component hasn't been
11162        // linked yet, so we wait until the first watch fires to cache them.
11163        if (!trigger || !actions) {
11164          trigger = getTriggerElement();
11165          actions = getActionsElement();
11166        }
11167
11168        if (isOpen) {
11169          enableKeyboard();
11170        } else {
11171          disableKeyboard();
11172        }
11173
11174        var toAdd = isOpen ? 'md-is-open' : '';
11175        var toRemove = isOpen ? '' : 'md-is-open';
11176
11177        // Set the proper ARIA attributes
11178        trigger.attr('aria-haspopup', true);
11179        trigger.attr('aria-expanded', isOpen);
11180        actions.attr('aria-hidden', !isOpen);
11181
11182        // Animate the CSS classes
11183        $animate.setClass($element, toAdd, toRemove);
11184      });
11185    }
11186
11187    function fireInitialAnimations() {
11188      // If the element is actually visible on the screen
11189      if ($element[0].scrollHeight > 0) {
11190        // Fire our animation
11191        $animate.addClass($element, '_md-animations-ready').then(function() {
11192          // Remove the waiting class
11193          $element.removeClass('md-animations-waiting');
11194        });
11195      }
11196
11197      // Otherwise, try for up to 1 second before giving up
11198      else if (initialAnimationAttempts < 10) {
11199        $timeout(fireInitialAnimations, 100);
11200
11201        // Increment our counter
11202        initialAnimationAttempts = initialAnimationAttempts + 1;
11203      }
11204    }
11205
11206    function enableKeyboard() {
11207      $element.on('keydown', keyPressed);
11208
11209      // On the next tick, setup a check for outside clicks; we do this on the next tick to avoid
11210      // clicks/touches that result in the isOpen attribute changing (e.g. a bound radio button)
11211      $mdUtil.nextTick(function() {
11212        angular.element(document).on('click touchend', checkForOutsideClick);
11213      });
11214
11215      // TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but
11216      // this breaks accessibility, especially on mobile, since you have no arrow keys to press
11217      //resetActionTabIndexes();
11218    }
11219
11220    function disableKeyboard() {
11221      $element.off('keydown', keyPressed);
11222      angular.element(document).off('click touchend', checkForOutsideClick);
11223    }
11224
11225    function checkForOutsideClick(event) {
11226      if (event.target) {
11227        var closestTrigger = $mdUtil.getClosest(event.target, 'md-fab-trigger');
11228        var closestActions = $mdUtil.getClosest(event.target, 'md-fab-actions');
11229
11230        if (!closestTrigger && !closestActions) {
11231          vm.close();
11232        }
11233      }
11234    }
11235
11236    function keyPressed(event) {
11237      switch (event.which) {
11238        case $mdConstant.KEY_CODE.ESCAPE: vm.close(); event.preventDefault(); return false;
11239        case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false;
11240        case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false;
11241        case $mdConstant.KEY_CODE.RIGHT_ARROW: doKeyRight(event); return false;
11242        case $mdConstant.KEY_CODE.DOWN_ARROW: doKeyDown(event); return false;
11243      }
11244    }
11245
11246    function doActionPrev(event) {
11247      focusAction(event, -1);
11248    }
11249
11250    function doActionNext(event) {
11251      focusAction(event, 1);
11252    }
11253
11254    function focusAction(event, direction) {
11255      var actions = resetActionTabIndexes();
11256
11257      // Increment/decrement the counter with restrictions
11258      vm.currentActionIndex = vm.currentActionIndex + direction;
11259      vm.currentActionIndex = Math.min(actions.length - 1, vm.currentActionIndex);
11260      vm.currentActionIndex = Math.max(0, vm.currentActionIndex);
11261
11262      // Focus the element
11263      var focusElement =  angular.element(actions[vm.currentActionIndex]).children()[0];
11264      angular.element(focusElement).attr('tabindex', 0);
11265      focusElement.focus();
11266
11267      // Make sure the event doesn't bubble and cause something else
11268      event.preventDefault();
11269      event.stopImmediatePropagation();
11270    }
11271
11272    function resetActionTabIndexes() {
11273      // Grab all of the actions
11274      var actions = getActionsElement()[0].querySelectorAll('.md-fab-action-item');
11275
11276      // Disable all other actions for tabbing
11277      angular.forEach(actions, function(action) {
11278        angular.element(angular.element(action).children()[0]).attr('tabindex', -1);
11279      });
11280
11281      return actions;
11282    }
11283
11284    function doKeyLeft(event) {
11285      if (vm.direction === 'left') {
11286        doActionNext(event);
11287      } else {
11288        doActionPrev(event);
11289      }
11290    }
11291
11292    function doKeyUp(event) {
11293      if (vm.direction === 'down') {
11294        doActionPrev(event);
11295      } else {
11296        doActionNext(event);
11297      }
11298    }
11299
11300    function doKeyRight(event) {
11301      if (vm.direction === 'left') {
11302        doActionPrev(event);
11303      } else {
11304        doActionNext(event);
11305      }
11306    }
11307
11308    function doKeyDown(event) {
11309      if (vm.direction === 'up') {
11310        doActionPrev(event);
11311      } else {
11312        doActionNext(event);
11313      }
11314    }
11315
11316    function isTrigger(element) {
11317      return $mdUtil.getClosest(element, 'md-fab-trigger');
11318    }
11319
11320    function isAction(element) {
11321      return $mdUtil.getClosest(element, 'md-fab-actions');
11322    }
11323
11324    function handleItemClick(event) {
11325      if (isTrigger(event.target)) {
11326        vm.toggle();
11327      }
11328
11329      if (isAction(event.target)) {
11330        vm.close();
11331      }
11332    }
11333
11334    function getTriggerElement() {
11335      return $element.find('md-fab-trigger');
11336    }
11337
11338    function getActionsElement() {
11339      return $element.find('md-fab-actions');
11340    }
11341  }
11342})();
11343
11344})();
11345(function(){
11346"use strict";
11347
11348(function() {
11349  'use strict';
11350
11351  /**
11352   * The duration of the CSS animation in milliseconds.
11353   *
11354   * @type {number}
11355   */
11356  MdFabSpeedDialFlingAnimation.$inject = ["$timeout"];
11357  MdFabSpeedDialScaleAnimation.$inject = ["$timeout"];
11358  var cssAnimationDuration = 300;
11359
11360  /**
11361   * @ngdoc module
11362   * @name material.components.fabSpeedDial
11363   */
11364  angular
11365    // Declare our module
11366    .module('material.components.fabSpeedDial', [
11367      'material.core',
11368      'material.components.fabShared',
11369      'material.components.fabActions'
11370    ])
11371
11372    // Register our directive
11373    .directive('mdFabSpeedDial', MdFabSpeedDialDirective)
11374
11375    // Register our custom animations
11376    .animation('.md-fling', MdFabSpeedDialFlingAnimation)
11377    .animation('.md-scale', MdFabSpeedDialScaleAnimation)
11378
11379    // Register a service for each animation so that we can easily inject them into unit tests
11380    .service('mdFabSpeedDialFlingAnimation', MdFabSpeedDialFlingAnimation)
11381    .service('mdFabSpeedDialScaleAnimation', MdFabSpeedDialScaleAnimation);
11382
11383  /**
11384   * @ngdoc directive
11385   * @name mdFabSpeedDial
11386   * @module material.components.fabSpeedDial
11387   *
11388   * @restrict E
11389   *
11390   * @description
11391   * The `<md-fab-speed-dial>` directive is used to present a series of popup elements (usually
11392   * `<md-button>`s) for quick access to common actions.
11393   *
11394   * There are currently two animations available by applying one of the following classes to
11395   * the component:
11396   *
11397   *  - `md-fling` - The speed dial items appear from underneath the trigger and move into their
11398   *    appropriate positions.
11399   *  - `md-scale` - The speed dial items appear in their proper places by scaling from 0% to 100%.
11400   *
11401   * You may also easily position the trigger by applying one one of the following classes to the
11402   * `<md-fab-speed-dial>` element:
11403   *  - `md-fab-top-left`
11404   *  - `md-fab-top-right`
11405   *  - `md-fab-bottom-left`
11406   *  - `md-fab-bottom-right`
11407   *
11408   * These CSS classes use `position: absolute`, so you need to ensure that the container element
11409   * also uses `position: absolute` or `position: relative` in order for them to work.
11410   *
11411   * Additionally, you may use the standard `ng-mouseenter` and `ng-mouseleave` directives to
11412   * open or close the speed dial. However, if you wish to allow users to hover over the empty
11413   * space where the actions will appear, you must also add the `md-hover-full` class to the speed
11414   * dial element. Without this, the hover effect will only occur on top of the trigger.
11415   *
11416   * See the demos for more information.
11417   *
11418   * ## Troubleshooting
11419   *
11420   * If your speed dial shows the closing animation upon launch, you may need to use `ng-cloak` on
11421   * the parent container to ensure that it is only visible once ready. We have plans to remove this
11422   * necessity in the future.
11423   *
11424   * @usage
11425   * <hljs lang="html">
11426   * <md-fab-speed-dial md-direction="up" class="md-fling">
11427   *   <md-fab-trigger>
11428   *     <md-button aria-label="Add..."><md-icon md-svg-src="/img/icons/plus.svg"></md-icon></md-button>
11429   *   </md-fab-trigger>
11430   *
11431   *   <md-fab-actions>
11432   *     <md-button aria-label="Add User">
11433   *       <md-icon md-svg-src="/img/icons/user.svg"></md-icon>
11434   *     </md-button>
11435   *
11436   *     <md-button aria-label="Add Group">
11437   *       <md-icon md-svg-src="/img/icons/group.svg"></md-icon>
11438   *     </md-button>
11439   *   </md-fab-actions>
11440   * </md-fab-speed-dial>
11441   * </hljs>
11442   *
11443   * @param {string} md-direction From which direction you would like the speed dial to appear
11444   * relative to the trigger element.
11445   * @param {expression=} md-open Programmatically control whether or not the speed-dial is visible.
11446   */
11447  function MdFabSpeedDialDirective() {
11448    return {
11449      restrict: 'E',
11450
11451      scope: {
11452        direction: '@?mdDirection',
11453        isOpen: '=?mdOpen'
11454      },
11455
11456      bindToController: true,
11457      controller: 'MdFabController',
11458      controllerAs: 'vm',
11459
11460      link: FabSpeedDialLink
11461    };
11462
11463    function FabSpeedDialLink(scope, element) {
11464      // Prepend an element to hold our CSS variables so we can use them in the animations below
11465      element.prepend('<div class="_md-css-variables"></div>');
11466    }
11467  }
11468
11469  function MdFabSpeedDialFlingAnimation($timeout) {
11470    function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
11471
11472    function runAnimation(element) {
11473      // Don't run if we are still waiting and we are not ready
11474      if (element.hasClass('md-animations-waiting') && !element.hasClass('_md-animations-ready')) {
11475        return;
11476      }
11477
11478      var el = element[0];
11479      var ctrl = element.controller('mdFabSpeedDial');
11480      var items = el.querySelectorAll('.md-fab-action-item');
11481
11482      // Grab our trigger element
11483      var triggerElement = el.querySelector('md-fab-trigger');
11484
11485      // Grab our element which stores CSS variables
11486      var variablesElement = el.querySelector('._md-css-variables');
11487
11488      // Setup JS variables based on our CSS variables
11489      var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
11490
11491      // Always reset the items to their natural position/state
11492      angular.forEach(items, function(item, index) {
11493        var styles = item.style;
11494
11495        styles.transform = styles.webkitTransform = '';
11496        styles.transitionDelay = '';
11497        styles.opacity = 1;
11498
11499        // Make the items closest to the trigger have the highest z-index
11500        styles.zIndex = (items.length - index) + startZIndex;
11501      });
11502
11503      // Set the trigger to be above all of the actions so they disappear behind it.
11504      triggerElement.style.zIndex = startZIndex + items.length + 1;
11505
11506      // If the control is closed, hide the items behind the trigger
11507      if (!ctrl.isOpen) {
11508        angular.forEach(items, function(item, index) {
11509          var newPosition, axis;
11510          var styles = item.style;
11511
11512          // Make sure to account for differences in the dimensions of the trigger verses the items
11513          // so that we can properly center everything; this helps hide the item's shadows behind
11514          // the trigger.
11515          var triggerItemHeightOffset = (triggerElement.clientHeight - item.clientHeight) / 2;
11516          var triggerItemWidthOffset = (triggerElement.clientWidth - item.clientWidth) / 2;
11517
11518          switch (ctrl.direction) {
11519            case 'up':
11520              newPosition = (item.scrollHeight * (index + 1) + triggerItemHeightOffset);
11521              axis = 'Y';
11522              break;
11523            case 'down':
11524              newPosition = -(item.scrollHeight * (index + 1) + triggerItemHeightOffset);
11525              axis = 'Y';
11526              break;
11527            case 'left':
11528              newPosition = (item.scrollWidth * (index + 1) + triggerItemWidthOffset);
11529              axis = 'X';
11530              break;
11531            case 'right':
11532              newPosition = -(item.scrollWidth * (index + 1) + triggerItemWidthOffset);
11533              axis = 'X';
11534              break;
11535          }
11536
11537          var newTranslate = 'translate' + axis + '(' + newPosition + 'px)';
11538
11539          styles.transform = styles.webkitTransform = newTranslate;
11540        });
11541      }
11542    }
11543
11544    return {
11545      addClass: function(element, className, done) {
11546        if (element.hasClass('md-fling')) {
11547          runAnimation(element);
11548          delayDone(done);
11549        } else {
11550          done();
11551        }
11552      },
11553      removeClass: function(element, className, done) {
11554        runAnimation(element);
11555        delayDone(done);
11556      }
11557    };
11558  }
11559
11560  function MdFabSpeedDialScaleAnimation($timeout) {
11561    function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
11562
11563    var delay = 65;
11564
11565    function runAnimation(element) {
11566      var el = element[0];
11567      var ctrl = element.controller('mdFabSpeedDial');
11568      var items = el.querySelectorAll('.md-fab-action-item');
11569
11570      // Grab our element which stores CSS variables
11571      var variablesElement = el.querySelector('._md-css-variables');
11572
11573      // Setup JS variables based on our CSS variables
11574      var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
11575
11576      // Always reset the items to their natural position/state
11577      angular.forEach(items, function(item, index) {
11578        var styles = item.style,
11579          offsetDelay = index * delay;
11580
11581        styles.opacity = ctrl.isOpen ? 1 : 0;
11582        styles.transform = styles.webkitTransform = ctrl.isOpen ? 'scale(1)' : 'scale(0)';
11583        styles.transitionDelay = (ctrl.isOpen ? offsetDelay : (items.length - offsetDelay)) + 'ms';
11584
11585        // Make the items closest to the trigger have the highest z-index
11586        styles.zIndex = (items.length - index) + startZIndex;
11587      });
11588    }
11589
11590    return {
11591      addClass: function(element, className, done) {
11592        runAnimation(element);
11593        delayDone(done);
11594      },
11595
11596      removeClass: function(element, className, done) {
11597        runAnimation(element);
11598        delayDone(done);
11599      }
11600    };
11601  }
11602})();
11603
11604})();
11605(function(){
11606"use strict";
11607
11608(function() {
11609  'use strict';
11610
11611  /**
11612   * @ngdoc module
11613   * @name material.components.fabToolbar
11614   */
11615  angular
11616    // Declare our module
11617    .module('material.components.fabToolbar', [
11618      'material.core',
11619      'material.components.fabShared',
11620      'material.components.fabActions'
11621    ])
11622
11623    // Register our directive
11624    .directive('mdFabToolbar', MdFabToolbarDirective)
11625
11626    // Register our custom animations
11627    .animation('.md-fab-toolbar', MdFabToolbarAnimation)
11628
11629    // Register a service for the animation so that we can easily inject it into unit tests
11630    .service('mdFabToolbarAnimation', MdFabToolbarAnimation);
11631
11632  /**
11633   * @ngdoc directive
11634   * @name mdFabToolbar
11635   * @module material.components.fabToolbar
11636   *
11637   * @restrict E
11638   *
11639   * @description
11640   *
11641   * The `<md-fab-toolbar>
11641` directive is used to present a toolbar of elements (usually `<md-button>`s)
11642   * for quick access to common actions when a floating action button is activated (via click or
11643   * keyboard navigation).
11644   *
11645   * You may also easily position the trigger by applying one one of the following classes to the
11646   * `<md-fab-toolbar>` element:
11647   *  - `md-fab-top-left`
11648   *  - `md-fab-top-right`
11649   *  - `md-fab-bottom-left`
11650   *  - `md-fab-bottom-right`
11651   *
11652   * These CSS classes use `position: absolute`, so you need to ensure that the container element
11653   * also uses `position: absolute` or `position: relative` in order for them to work.
11654   *
11655   * @usage
11656   *
11657   * <hljs lang="html">
11658   * <md-fab-toolbar md-direction='left'>
11659   *   <md-fab-trigger>
11660   *     <md-button aria-label="Add..."><md-icon md-svg-src="/img/icons/plus.svg"></md-icon></md-button>
11661   *   </md-fab-trigger>
11662   *
11663   *   <md-toolbar>
11664   *    <md-fab-actions>
11665   *      <md-button aria-label="Add User">
11666   *        <md-icon md-svg-src="/img/icons/user.svg"></md-icon>
11667   *      </md-button>
11668   *
11669   *      <md-button aria-label="Add Group">
11670   *        <md-icon md-svg-src="/img/icons/group.svg"></md-icon>
11671   *      </md-button>
11672   *    </md-fab-actions>
11673   *   </md-toolbar>
11674   * </md-fab-toolbar>
11675   * </hljs>
11676   *
11677   * @param {string} md-direction From which direction you would like the toolbar items to appear
11678   * relative to the trigger element. Supports `left` and `right` directions.
11679   * @param {expression=} md-open Programmatically control whether or not the toolbar is visible.
11680   */
11681  function MdFabToolbarDirective() {
11682    return {
11683      restrict: 'E',
11684      transclude: true,
11685      template: '<div class="md-fab-toolbar-wrapper">' +
11686      '  <div class="md-fab-toolbar-content" ng-transclude></div>' +
11687      '</div>',
11688
11689      scope: {
11690        direction: '@?mdDirection',
11691        isOpen: '=?mdOpen'
11692      },
11693
11694      bindToController: true,
11695      controller: 'MdFabController',
11696      controllerAs: 'vm',
11697
11698      link: link
11699    };
11700
11701    function link(scope, element, attributes) {
11702      // Add the base class for animations
11703      element.addClass('md-fab-toolbar');
11704
11705      // Prepend the background element to the trigger's button
11706      element.find('md-fab-trigger').find('button')
11707        .prepend('<div class="md-fab-toolbar-background"></div>');
11708    }
11709  }
11710
11711  function MdFabToolbarAnimation() {
11712
11713    function runAnimation(element, className, done) {
11714      // If no className was specified, don't do anything
11715      if (!className) {
11716        return;
11717      }
11718
11719      var el = element[0];
11720      var ctrl = element.controller('mdFabToolbar');
11721
11722      // Grab the relevant child elements
11723      var backgroundElement = el.querySelector('.md-fab-toolbar-background');
11724      var triggerElement = el.querySelector('md-fab-trigger button');
11725      var toolbarElement = el.querySelector('md-toolbar');
11726      var iconElement = el.querySelector('md-fab-trigger button md-icon');
11727      var actions = element.find('md-fab-actions').children();
11728
11729      // If we have both elements, use them to position the new background
11730      if (triggerElement && backgroundElement) {
11731        // Get our variables
11732        var color = window.getComputedStyle(triggerElement).getPropertyValue('background-color');
11733        var width = el.offsetWidth;
11734        var height = el.offsetHeight;
11735
11736        // Make it twice as big as it should be since we scale from the center
11737        var scale = 2 * (width / triggerElement.offsetWidth);
11738
11739        // Set some basic styles no matter what animation we're doing
11740        backgroundElement.style.backgroundColor = color;
11741        backgroundElement.style.borderRadius = width + 'px';
11742
11743        // If we're open
11744        if (ctrl.isOpen) {
11745          // Turn on toolbar pointer events when closed
11746          toolbarElement.style.pointerEvents = 'inherit';
11747
11748          backgroundElement.style.width = triggerElement.offsetWidth + 'px';
11749          backgroundElement.style.height = triggerElement.offsetHeight + 'px';
11750          backgroundElement.style.transform = 'scale(' + scale + ')';
11751
11752          // Set the next close animation to have the proper delays
11753          backgroundElement.style.transitionDelay = '0ms';
11754          iconElement && (iconElement.style.transitionDelay = '.3s');
11755
11756          // Apply a transition delay to actions
11757          angular.forEach(actions, function(action, index) {
11758            action.style.transitionDelay = (actions.length - index) * 25 + 'ms';
11759          });
11760        } else {
11761          // Turn off toolbar pointer events when closed
11762          toolbarElement.style.pointerEvents = 'none';
11763
11764          // Scale it back down to the trigger's size
11765          backgroundElement.style.transform = 'scale(1)';
11766
11767          // Reset the position
11768          backgroundElement.style.top = '0';
11769
11770          if (element.hasClass('md-right')) {
11771            backgroundElement.style.left = '0';
11772            backgroundElement.style.right = null;
11773          }
11774
11775          if (element.hasClass('md-left')) {
11776            backgroundElement.style.right = '0';
11777            backgroundElement.style.left = null;
11778          }
11779
11780          // Set the next open animation to have the proper delays
11781          backgroundElement.style.transitionDelay = '200ms';
11782          iconElement && (iconElement.style.transitionDelay = '0ms');
11783
11784          // Apply a transition delay to actions
11785          angular.forEach(actions, function(action, index) {
11786            action.style.transitionDelay = 200 + (index * 25) + 'ms';
11787          });
11788        }
11789      }
11790    }
11791
11792    return {
11793      addClass: function(element, className, done) {
11794        runAnimation(element, className, done);
11795        done();
11796      },
11797
11798      removeClass: function(element, className, done) {
11799        runAnimation(element, className, done);
11800        done();
11801      }
11802    };
11803  }
11804})();
11805
11806})();
11807(function(){
11808"use strict";
11809
11810/**
11811 * @ngdoc module
11812 * @name material.components.gridList
11813 */
11814GridListController.$inject = ["$mdUtil"];
11815GridLayoutFactory.$inject = ["$mdUtil"];
11816GridListDirective.$inject = ["$interpolate", "$mdConstant", "$mdGridLayout", "$mdMedia"];
11817GridTileDirective.$inject = ["$mdMedia"];
11818angular.module('material.components.gridList', ['material.core'])
11819       .directive('mdGridList', GridListDirective)
11820       .directive('mdGridTile', GridTileDirective)
11821       .directive('mdGridTileFooter', GridTileCaptionDirective)
11822       .directive('mdGridTileHeader', GridTileCaptionDirective)
11823       .factory('$mdGridLayout', GridLayoutFactory);
11824
11825/**
11826 * @ngdoc directive
11827 * @name mdGridList
11828 * @module material.components.gridList
11829 * @restrict E
11830 * @description
11831 * Grid lists are an alternative to standard list views. Grid lists are distinct
11832 * from grids used for layouts and other visual presentations.
11833 *
11834 * A grid list is best suited to presenting a homogenous data type, typically
11835 * images, and is optimized for visual comprehension and differentiating between
11836 * like data types.
11837 *
11838 * A grid list is a continuous element consisting of tessellated, regular
11839 * subdivisions called cells that contain tiles (`md-grid-tile`).
11840 *
11841 * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7OVlEaXZ5YmU1Xzg/components_grids_usage2.png"
11842 *    style="width: 300px; height: auto; margin-right: 16px;" alt="Concept of grid explained visually">
11843 * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7VGhsOE5idWlJWXM/components_grids_usage3.png"
11844 *    style="width: 300px; height: auto;" alt="Grid concepts legend">
11845 *
11846 * Cells are arrayed vertically and horizontally within the grid.
11847 *
11848 * Tiles hold content and can span one or more cells vertically or horizontally.
11849 *
11850 * ### Responsive Attributes
11851 *
11852 * The `md-grid-list` directive supports "responsive" attributes, which allow
11853 * different `md-cols`, `md-gutter` and `md-row-height` values depending on the
11854 * currently matching media query.
11855 *
11856 * In order to set a responsive attribute, first define the fallback value with
11857 * the standard attribute name, then add additional attributes with the
11858 * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
11859 * (ie. `md-cols-lg="8"`)
11860 *
11861 * @param {number} md-cols Number of columns in the grid.
11862 * @param {string} md-row-height One of
11863 * <ul>
11864 *   <li>CSS length - Fixed height rows (eg. `8px` or `1rem`)</li>
11865 *   <li>`{width}:{height}` - Ratio of width to height (eg.
11866 *   `md-row-height="16:9"`)</li>
11867 *   <li>`"fit"` - Height will be determined by subdividing the available
11868 *   height by the number of rows</li>
11869 * </ul>
11870 * @param {string=} md-gutter The amount of space between tiles in CSS units
11871 *     (default 1px)
11872 * @param {expression=} md-on-layout Expression to evaluate after layout. Event
11873 *     object is available as `$event`, and contains performance information.
11874 *
11875 * @usage
11876 * Basic:
11877 * <hljs lang="html">
11878 * <md-grid-list md-cols="5" md-gutter="1em" md-row-height="4:3">
11879 *   <md-grid-tile></md-grid-tile>
11880 * </md-grid-list>
11881 * </hljs>
11882 *
11883 * Fixed-height rows:
11884 * <hljs lang="html">
11885 * <md-grid-list md-cols="4" md-row-height="200px" ...>
11886 *   <md-grid-tile></md-grid-tile>
11887 * </md-grid-list>
11888 * </hljs>
11889 *
11890 * Fit rows:
11891 * <hljs lang="html">
11892 * <md-grid-list md-cols="4" md-row-height="fit" style="height: 400px;" ...>
11893 *   <md-grid-tile></md-grid-tile>
11894 * </md-grid-list>
11895 * </hljs>
11896 *
11897 * Using responsive attributes:
11898 * <hljs lang="html">
11899 * <md-grid-list
11900 *     md-cols-sm="2"
11901 *     md-cols-md="4"
11902 *     md-cols-lg="8"
11903 *     md-cols-gt-lg="12"
11904 *     ...>
11905 *   <md-grid-tile></md-grid-tile>
11906 * </md-grid-list>
11907 * </hljs>
11908 */
11909function GridListDirective($interpolate, $mdConstant, $mdGridLayout, $mdMedia) {
11910  return {
11911    restrict: 'E',
11912    controller: GridListController,
11913    scope: {
11914      mdOnLayout: '&'
11915    },
11916    link: postLink
11917  };
11918
11919  function postLink(scope, element, attrs, ctrl) {
11920    element.addClass('_md');     // private md component indicator for styling
11921    
11922    // Apply semantics
11923    element.attr('role', 'list');
11924
11925    // Provide the controller with a way to trigger layouts.
11926    ctrl.layoutDelegate = layoutDelegate;
11927
vendor: 4,926 bytes, lines 11928-12056
11928    var invalidateLayout = angular.bind(ctrl, ctrl.invalidateLayout),
11929        unwatchAttrs = watchMedia();
11930      scope.$on('$destroy', unwatchMedia);
11931
11932    /**
11933     * Watches for changes in media, invalidating layout as necessary.
11934     */
11935    function watchMedia() {
11936      for (var mediaName in $mdConstant.MEDIA) {
11937        $mdMedia(mediaName); // initialize
11938        $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
11939            .addListener(invalidateLayout);
11940      }
11941      return $mdMedia.watchResponsiveAttributes(
11942          ['md-cols', 'md-row-height', 'md-gutter'], attrs, layoutIfMediaMatch);
11943    }
11944
11945    function unwatchMedia() {
11946      ctrl.layoutDelegate = angular.noop;
11947
11948      unwatchAttrs();
11949      for (var mediaName in $mdConstant.MEDIA) {
11950        $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
11951            .removeListener(invalidateLayout);
11952      }
11953    }
11954
11955    /**
11956     * Performs grid layout if the provided mediaName matches the currently
11957     * active media type.
11958     */
11959    function layoutIfMediaMatch(mediaName) {
11960      if (mediaName == null) {
11961        // TODO(shyndman): It would be nice to only layout if we have
11962        // instances of attributes using this media type
11963        ctrl.invalidateLayout();
11964      } else if ($mdMedia(mediaName)) {
11965        ctrl.invalidateLayout();
11966      }
11967    }
11968
11969    var lastLayoutProps;
11970
11971    /**
11972     * Invokes the layout engine, and uses its results to lay out our
11973     * tile elements.
11974     *
11975     * @param {boolean} tilesInvalidated Whether tiles have been
11976     *    added/removed/moved since the last layout. This is to avoid situations
11977     *    where tiles are replaced with properties identical to their removed
11978     *    counterparts.
11979     */
11980    function layoutDelegate(tilesInvalidated) {
11981      var tiles = getTileElements();
11982      var props = {
11983        tileSpans: getTileSpans(tiles),
11984        colCount: getColumnCount(),
11985        rowMode: getRowMode(),
11986        rowHeight: getRowHeight(),
11987        gutter: getGutter()
11988      };
11989
11990      if (!tilesInvalidated && angular.equals(props, lastLayoutProps)) {
11991        return;
11992      }
11993
11994      var performance =
11995        $mdGridLayout(props.colCount, props.tileSpans, tiles)
11996          .map(function(tilePositions, rowCount) {
11997            return {
11998              grid: {
11999                element: element,
12000                style: getGridStyle(props.colCount, rowCount,
12001                    props.gutter, props.rowMode, props.rowHeight)
12002              },
12003              tiles: tilePositions.map(function(ps, i) {
12004                return {
12005                  element: angular.element(tiles[i]),
12006                  style: getTileStyle(ps.position, ps.spans,
12007                      props.colCount, rowCount,
12008                      props.gutter, props.rowMode, props.rowHeight)
12009                }
12010              })
12011            }
12012          })
12013          .reflow()
12014          .performance();
12015
12016      // Report layout
12017      scope.mdOnLayout({
12018        $event: {
12019          performance: performance
12020        }
12021      });
12022
12023      lastLayoutProps = props;
12024    }
12025
12026    // Use $interpolate to do some simple string interpolation as a convenience.
12027
12028    var startSymbol = $interpolate.startSymbol();
12029    var endSymbol = $interpolate.endSymbol();
12030
12031    // Returns an expression wrapped in the interpolator's start and end symbols.
12032    function expr(exprStr) {
12033      return startSymbol + exprStr + endSymbol;
12034    }
12035
12036    // The amount of space a single 1x1 tile would take up (either width or height), used as
12037    // a basis for other calculations. This consists of taking the base size percent (as would be
12038    // if evenly dividing the size between cells), and then subtracting the size of one gutter.
12039    // However, since there are no gutters on the edges, each tile only uses a fration
12040    // (gutterShare = numGutters / numCells) of the gutter size. (Imagine having one gutter per
12041    // tile, and then breaking up the extra gutter on the edge evenly among the cells).
12042    var UNIT = $interpolate(expr('share') + '% - (' + expr('gutter') + ' * ' + expr('gutterShare') + ')');
12043
12044    // The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value.
12045    // The position comes the size of a 1x1 tile plus gutter for each previous tile in the
12046    // row/column (offset).
12047    var POSITION  = $interpolate('calc((' + expr('unit') + ' + ' + expr('gutter') + ') * ' + expr('offset') + ')');
12048
12049    // The actual size of a tile, e.g., width or height, taking rowSpan or colSpan into account.
12050    // This is computed by multiplying the base unit by the rowSpan/colSpan, and then adding back
12051    // in the space that the gutter would normally have used (which was already accounted for in
12052    // the base unit calculation).
12053    var DIMENSION = $interpolate('calc((' + expr('unit') + ') * ' + expr('span') + ' + (' + expr('span') + ' - 1) * ' + expr('gutter') + ')');
12054
12055    /**
12056     * Gets the styles applied to a tile element described by the given parameters.
12057     * @param {{row: number, col: number}} position The row and column indices of the tile.
12058     * @param {{row: number, col: number}} spans The rowSpan and colSpan of the tile.
12059     * @param {number} colCount The number of columns.
12060     * @param {number} rowCount The number of rows.
12061     * @param {string} gutter The amount of space between tiles. This will be something like
12062     *     '5px' or '2em'.
12063     * @param {string} rowMode The row height mode. Can be one of:
12064     *     'fixed': all rows have a fixed size, given by rowHeight,
12065     *     'ratio': row height defined as a ratio to width, or
12066     *     'fit': fit to the grid-list element height, divinding evenly among rows.
12067     * @param {string|number} rowHeight The height of a row. This is only used for 'fixed' mode and
12068     *     for 'ratio' mode. For 'ratio' mode, this is the *ratio* of width-to-height (e.g., 0.75).
12069     * @returns {Object} Map of CSS properties to be applied to the style element. Will define
12070     *     values for top, left, width, height, marginTop, and paddingTop.
12071     */
12072    function getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) {
12073      // TODO(shyndman): There are style caching opportunities here.
12074
12075      // Percent of the available horizontal space that one column takes up.
12076      var hShare = (1 / colCount) * 100;
12077
12078      // Fraction of the gutter size that each column takes up.
12079      var hGutterShare = (colCount - 1) / colCount;
12080
12081      // Base horizontal size of a column.
12082      var hUnit = UNIT({share: hShare, gutterShare: hGutterShare, gutter: gutter});
12083
12084      // The width and horizontal position of each tile is always calculated the same way, but the
12085      // height and vertical position depends on the rowMode.
12086      var ltr = document.dir != 'rtl' && document.body.dir != 'rtl';
12087      var style = ltr ? {
12088          left: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }),
12089          width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }),
12090          // resets
12091          paddingTop: '',
12092          marginTop: '',
12093          top: '',
12094          height: ''
12095        } : {
12096        right: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }),
12097        width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }),
12098        // resets
12099        paddingTop: '',
12100        marginTop: '',
12101        top: '',
12102        height: ''
12103      };
12104
12105      switch (rowMode) {
12106        case 'fixed':
12107          // In fixed mode, simply use the given rowHeight.
12108          style.top = POSITION({ unit: rowHeight, offset: position.row, gutter: gutter });
12109          style.height = DIMENSION({ unit: rowHeight, span: spans.row, gutter: gutter });
12110          break;
12111
12112        case 'ratio':
12113          // Percent of the available vertical space that one row takes up. Here, rowHeight holds
12114          // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333.
12115          var vShare = hShare / rowHeight;
12116
12117          // Base veritcal size of a row.
12118          var vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
12119
12120          // padidngTop and marginTop are used to maintain the given aspect ratio, as
12121          // a percentage-based value for these properties is applied to the *width* of the
12122          // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
12123          style.paddingTop = DIMENSION({ unit: vUnit, span: spans.row, gutter: gutter});
12124          style.marginTop = POSITION({ unit: vUnit, offset: position.row, gutter: gutter });
12125          break;
12126
12127        case 'fit':
12128          // Fraction of the gutter size that each column takes up.
12129          var vGutterShare = (rowCount - 1) / rowCount;
12130
12131          // Percent of the available vertical space that one row takes up.
12132          var vShare = (1 / rowCount) * 100;
12133
12134          // Base vertical size of a row.
12135          var vUnit = UNIT({share: vShare, gutterShare: vGutterShare, gutter: gutter});
12136
12137          style.top = POSITION({unit: vUnit, offset: position.row, gutter: gutter});
12138          style.height = DIMENSION({unit: vUnit, span: spans.row, gutter: gutter});
12139          break;
12140      }
12141
12142      return style;
12143    }
12144
12145    function getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) {
12146      var style = {};
12147
12148      switch
vendor: 6,126 bytes, lines 12148-12366
12148(rowMode) {
12149        case 'fixed':
12150          style.height = DIMENSION({ unit: rowHeight, span: rowCount, gutter: gutter });
12151          style.paddingBottom = '';
12152          break;
12153
12154        case 'ratio':
12155          // rowHeight is width / height
12156          var hGutterShare = colCount === 1 ? 0 : (colCount - 1) / colCount,
12157              hShare = (1 / colCount) * 100,
12158              vShare = hShare * (1 / rowHeight),
12159              vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
12160
12161          style.height = '';
12162          style.paddingBottom = DIMENSION({ unit: vUnit, span: rowCount, gutter: gutter});
12163          break;
12164
12165        case 'fit':
12166          // noop, as the height is user set
12167          break;
12168      }
12169
12170      return style;
12171    }
12172
12173    function getTileElements() {
12174      return [].filter.call(element.children(), function(ele) {
12175        return ele.tagName == 'MD-GRID-TILE' && !ele.$$mdDestroyed;
12176      });
12177    }
12178
12179    /**
12180     * Gets an array of objects containing the rowspan and colspan for each tile.
12181     * @returns {Array<{row: number, col: number}>}
12182     */
12183    function getTileSpans(tileElements) {
12184      return [].map.call(tileElements, function(ele) {
12185        var ctrl = angular.element(ele).controller('mdGridTile');
12186        return {
12187          row: parseInt(
12188              $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-rowspan'), 10) || 1,
12189          col: parseInt(
12190              $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-colspan'), 10) || 1
12191        };
12192      });
12193    }
12194
12195    function getColumnCount() {
12196      var colCount = parseInt($mdMedia.getResponsiveAttribute(attrs, 'md-cols'), 10);
12197      if (isNaN(colCount)) {
12198        throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value';
12199      }
12200      return colCount;
12201    }
12202
12203    function getGutter() {
12204      return applyDefaultUnit($mdMedia.getResponsiveAttribute(attrs, 'md-gutter') || 1);
12205    }
12206
12207    function getRowHeight() {
12208      var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
12209      if (!rowHeight) {
12210        throw 'md-grid-list: md-row-height attribute was not found';
12211      }
12212
12213      switch (getRowMode()) {
12214        case 'fixed':
12215          return applyDefaultUnit(rowHeight);
12216        case 'ratio':
12217          var whRatio = rowHeight.split(':');
12218          return parseFloat(whRatio[0]) / parseFloat(whRatio[1]);
12219        case 'fit':
12220          return 0; // N/A
12221      }
12222    }
12223
12224    function getRowMode() {
12225      var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
12226      if (!rowHeight) {
12227        throw 'md-grid-list: md-row-height attribute was not found';
12228      }
12229
12230      if (rowHeight == 'fit') {
12231        return 'fit';
12232      } else if (rowHeight.indexOf(':') !== -1) {
12233        return 'ratio';
12234      } else {
12235        return 'fixed';
12236      }
12237    }
12238
12239    function applyDefaultUnit(val) {
12240      return /\D$/.test(val) ? val : val + 'px';
12241    }
12242  }
12243}
12244
12245/* @ngInject */
12246function GridListController($mdUtil) {
12247  this.layoutInvalidated = false;
12248  this.tilesInvalidated = false;
12249  this.$timeout_ = $mdUtil.nextTick;
12250  this.layoutDelegate = angular.noop;
12251}
12252
12253GridListController.prototype = {
12254  invalidateTiles: function() {
12255    this.tilesInvalidated = true;
12256    this.invalidateLayout();
12257  },
12258
12259  invalidateLayout: function() {
12260    if (this.layoutInvalidated) {
12261      return;
12262    }
12263    this.layoutInvalidated = true;
12264    this.$timeout_(angular.bind(this, this.layout));
12265  },
12266
12267  layout: function() {
12268    try {
12269      this.layoutDelegate(this.tilesInvalidated);
12270    } finally {
12271      this.layoutInvalidated = false;
12272      this.tilesInvalidated = false;
12273    }
12274  }
12275};
12276
12277
12278/* @ngInject */
12279function GridLayoutFactory($mdUtil) {
12280  var defaultAnimator = GridTileAnimator;
12281
12282  /**
12283   * Set the reflow animator callback
12284   */
12285  GridLayout.animateWith = function(customAnimator) {
12286    defaultAnimator = !angular.isFunction(customAnimator) ? GridTileAnimator : customAnimator;
12287  };
12288
12289  return GridLayout;
12290
12291  /**
12292   * Publish layout function
12293   */
12294  function GridLayout(colCount, tileSpans) {
12295      var self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime;
12296
12297      layoutTime = $mdUtil.time(function() {
12298        layoutInfo = calculateGridFor(colCount, tileSpans);
12299      });
12300
12301      return self = {
12302
12303        /**
12304         * An array of objects describing each tile's position in the grid.
12305         */
12306        layoutInfo: function() {
12307          return layoutInfo;
12308        },
12309
12310        /**
12311         * Maps grid positioning to an element and a set of styles using the
12312         * provided updateFn.
12313         */
12314        map: function(updateFn) {
12315          mapTime = $mdUtil.time(function() {
12316            var info = self.layoutInfo();
12317            gridStyles = updateFn(info.positioning, info.rowCount);
12318          });
12319          return self;
12320        },
12321
12322        /**
12323         * Default animator simply sets the element.css( <styles> ). An alternate
12324         * animator can be provided as an argument. The function has the following
12325         * signature:
12326         *
12327         *    function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>)
12328         */
12329        reflow: function(animatorFn) {
12330          reflowTime = $mdUtil.time(function() {
12331            var animator = animatorFn || defaultAnimator;
12332            animator(gridStyles.grid, gridStyles.tiles);
12333          });
12334          return self;
12335        },
12336
12337        /**
12338         * Timing for the most recent layout run.
12339         */
12340        performance: function() {
12341          return {
12342            tileCount: tileSpans.length,
12343            layoutTime: layoutTime,
12344            mapTime: mapTime,
12345            reflowTime: reflowTime,
12346            totalTime: layoutTime + mapTime + reflowTime
12347          };
12348        }
12349      };
12350    }
12351
12352  /**
12353   * Default Gridlist animator simple sets the css for each element;
12354   * NOTE: any transitions effects must be manually set in the CSS.
12355   * e.g.
12356   *
12357   *  md-grid-tile {
12358   *    transition: all 700ms ease-out 50ms;
12359   *  }
12360   *
12361   */
12362  function GridTileAnimator(grid, tiles) {
12363    grid.element.css(grid.style);
12364    tiles.forEach(function(t) {
12365      t.element.css(t.style);
12366    })
vendor: 4,262 bytes, lines 12366-12510
12366
12367  }
12368
12369  /**
12370   * Calculates the positions of tiles.
12371   *
12372   * The algorithm works as follows:
12373   *    An Array<Number> with length colCount (spaceTracker) keeps track of
12374   *    available tiling positions, where elements of value 0 represents an
12375   *    empty position. Space for a tile is reserved by finding a sequence of
12376   *    0s with length <= than the tile's colspan. When such a space has been
12377   *    found, the occupied tile positions are incremented by the tile's
12378   *    rowspan value, as these positions have become unavailable for that
12379   *    many rows.
12380   *
12381   *    If the end of a row has been reached without finding space for the
12382   *    tile, spaceTracker's elements are each decremented by 1 to a minimum
12383   *    of 0. Rows are searched in this fashion until space is found.
12384   */
12385  function calculateGridFor(colCount, tileSpans) {
12386    var curCol = 0,
12387        curRow = 0,
12388        spaceTracker = newSpaceTracker();
12389
12390    return {
12391      positioning: tileSpans.map(function(spans, i) {
12392        return {
12393          spans: spans,
12394          position: reserveSpace(spans, i)
12395        };
12396      }),
12397      rowCount: curRow + Math.max.apply(Math, spaceTracker)
12398    };
12399
12400    function reserveSpace(spans, i) {
12401      if (spans.col > colCount) {
12402        throw 'md-grid-list: Tile at position ' + i + ' has a colspan ' +
12403            '(' + spans.col + ') that exceeds the column count ' +
12404            '(' + colCount + ')';
12405      }
12406
12407      var start = 0,
12408          end = 0;
12409
12410      // TODO(shyndman): This loop isn't strictly necessary if you can
12411      // determine the minimum number of rows before a space opens up. To do
12412      // this, recognize that you've iterated across an entire row looking for
12413      // space, and if so fast-forward by the minimum rowSpan count. Repeat
12414      // until the required space opens up.
12415      while (end - start < spans.col) {
12416        if (curCol >= colCount) {
12417          nextRow();
12418          continue;
12419        }
12420
12421        start = spaceTracker.indexOf(0, curCol);
12422        if (start === -1 || (end = findEnd(start + 1)) === -1) {
12423          start = end = 0;
12424          nextRow();
12425          continue;
12426        }
12427
12428        curCol = end + 1;
12429      }
12430
12431      adjustRow(start, spans.col, spans.row);
12432      curCol = start + spans.col;
12433
12434      return {
12435        col: start,
12436        row: curRow
12437      };
12438    }
12439
12440    function nextRow() {
12441      curCol = 0;
12442      curRow++;
12443      adjustRow(0, colCount, -1); // Decrement row spans by one
12444    }
12445
12446    function adjustRow(from, cols, by) {
12447      for (var i = from; i < from + cols; i++) {
12448        spaceTracker[i] = Math.max(spaceTracker[i] + by, 0);
12449      }
12450    }
12451
12452    function findEnd(start) {
12453      var i;
12454      for (i = start; i < spaceTracker.length; i++) {
12455        if (spaceTracker[i] !== 0) {
12456          return i;
12457        }
12458      }
12459
12460      if (i === spaceTracker.length) {
12461        return i;
12462      }
12463    }
12464
12465    function newSpaceTracker() {
12466      var tracker = [];
12467      for (var i = 0; i < colCount; i++) {
12468        tracker.push(0);
12469      }
12470      return tracker;
12471    }
12472  }
12473}
12474
12475/**
12476 * @ngdoc directive
12477 * @name mdGridTile
12478 * @module material.components.gridList
12479 * @restrict E
12480 * @description
12481 * Tiles contain the content of an `md-grid-list`. They span one or more grid
12482 * cells vertically or horizontally, and use `md-grid-tile-{footer,header}` to
12483 * display secondary content.
12484 *
12485 * ### Responsive Attributes
12486 *
12487 * The `md-grid-tile` directive supports "responsive" attributes, which allow
12488 * different `md-rowspan` and `md-colspan` values depending on the currently
12489 * matching media query.
12490 *
12491 * In order to set a responsive attribute, first define the fallback value with
12492 * the standard attribute name, then add additional attributes with the
12493 * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
12494 * (ie. `md-colspan-sm="4"`)
12495 *
12496 * @param {number=} md-colspan The number of columns to span (default 1). Cannot
12497 *    exceed the number of columns in the grid. Supports interpolation.
12498 * @param {number=} md-rowspan The number of rows to span (default 1). Supports
12499 *     interpolation.
12500 *
12501 * @usage
12502 * With header:
12503 * <hljs lang="html">
12504 * <md-grid-tile>
12505 *   <md-grid-tile-header>
12506 *     <h3>This is a header</h3>
12507 *   </md-grid-tile-header>
12508 * </md-grid-tile>
12509 * </hljs>
12510 *
12511 * With footer:
12512 * <hljs lang="html">
12513 * <md-grid-tile>
12514 *   <md-grid-tile-footer>
12515 *     <h3>This is a footer</h3>
12516 *   </md-grid-tile-footer>
12517 * </md-grid-tile>
12518 * </hljs>
12519 *
12520 * Spanning multiple rows/columns:
12521 * <hljs lang="html">
12522 * <md-grid-tile md-colspan="2" md-rowspan="3">
12523 * </md-grid-tile>
12524 * </hljs>
12525 *
12526 * Responsive attributes:
12527 * <hljs lang="html">
12528 * <md-grid-tile md-colspan="1" md-colspan-sm="3" md-colspan-md="5">
12529 * </md-grid-tile>
12530 * </hljs>
12531 */
12532function GridTileDirective($mdMedia) {
12533  return {
12534    restrict: 'E',
12535    require: '^mdGridList',
12536    template: '<figure ng-transclude></figure>',
12537    transclude: true,
12538    scope: {},
12539    // Simple controller that exposes attributes to the grid directive
12540    controller: ["$attrs", function($attrs) {
12541      this.$attrs = $attrs;
12542    }],
12543    link: postLink
12544  };
12545
12546  function postLink(scope, element, attrs, gridCtrl) {
12547    // Apply semantics
12548    element.attr('role', 'listitem');
12549
12550    // If our colspan or rowspan changes, trigger a layout
12551    var unwatchAttrs = $mdMedia.watchResponsiveAttributes(['md-colspan', 'md-rowspan'],
12552        attrs, angular.bind(gridCtrl, gridCtrl.invalidateLayout));
12553
12554    // Tile registration/deregistration
12555    gridCtrl.invalidateTiles();
12556    scope.$on('$destroy', function() {
12557      // Mark the tile as destroyed so it is no longer considered in layout,
12558      // even if the DOM element sticks around (like during a leave animation)
12559      element[0].$$mdDestroyed = true;
12560      unwatchAttrs();
12561      gridCtrl.invalidateLayout();
12562    });
12563
12564    if (angular.isDefined(scope.$parent.$index)) {
12565      scope.$watch(function() { return scope.$parent.$index; },
12566        function indexChanged(newIdx, oldIdx) {
12567          if (newIdx === oldIdx) {
12568            return;
12569          }
12570          gridCtrl.invalidateTiles();
12571        });
12572    }
12573  }
12574}
12575
12576
12577function GridTileCaptionDirective() {
12578  return {
12579    template: '<figcaption ng-transclude></figcaption>',
12580    transclude: true
12581  };
12582}
12583
12584})();
12585(function(){
12586"use strict";
12587
12588/**
12589 * @ngdoc module
12590 * @name material.components.icon
12591 * @description
12592 * Icon
12593 */
12594angular.module('material.components.icon', ['material.core']);
12595
12596})();
12597(function(){
12598"use strict";
12599
12600/**
12601 * @ngdoc module
12602 * @name material.components.input
12603 */
12604mdInputContainerDirective.$inject = ["$mdTheming", "$parse"];
12605inputTextareaDirective.$inject = ["$mdUtil", "$window", "$mdAria", "$timeout", "$mdGesture"];
12606mdMaxlengthDirective.$inject = ["$animate", "$mdUtil"];
12607placeholderDirective.$inject = ["$compile"];
12608ngMessageDirective.$inject = ["$mdUtil"];
12609mdSelectOnFocusDirective.$inject = ["$timeout"];
12610mdInputInvalidMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
12611ngMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
12612ngMessageAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
12613var inputModule = angular.module('material.components.input', [
12614    'material.core'
12615  ])
12616  .directive('mdInputContainer', mdInputContainerDirective)
12617  .directive('label', labelDirective)
12618  .directive('input', inputTextareaDirective)
12619  .directive('textarea', inputTextareaDirective)
12620  .directive('mdMaxlength', mdMaxlengthDirective)
12621  .directive('placeholder', placeholderDirective)
12622  .directive('ngMessages', ngMessagesDirective)
12623  .directive('ngMessage', ngMessageDirective)
12624  .directive('ngMessageExp', ngMessageDirective)
12625  .directive('mdSelectOnFocus', mdSelectOnFocusDirective)
12626
12627  .animation('.md-input-invalid', mdInputInvalidMessagesAnimation)
12628  .animation('.md-input-messages-animation', ngMessagesAnimation)
12629  .animation('.md-input-message-animation', ngMessageAnimation);
12630
12631// If we are running inside of tests; expose some extra services so that we can test them
12632if (window._mdMocksIncluded) {
12633  inputModule.service('$$mdInput', function() {
12634    return {
12635      // special accessor to internals... useful for testing
12636      messages: {
12637        show        : showInputMessages,
12638        hide        : hideInputMessages,
12639        getElement  : getMessagesElement
12640      }
12641    }
12642  })
12643
12644  // Register a service for each animation so that we can easily inject them into unit tests
12645  .service('mdInputInvalidAnimation', mdInputInvalidMessagesAnimation)
12646  .service('mdInputMessagesAnimation', ngMessagesAnimation)
12647  .service('mdInputMessageAnimation', ngMessageAnimation);
12648}
12649
12650/**
12651 * @ngdoc directive
12652 * @name mdInputContainer
12653 * @module material.components.input
12654 *
12655 * @restrict E
12656 *
12657 * @description
12658 * `<md-input-container>` is the parent of any input or textarea element.
12659 *
12660 * Input and textarea elements will not behave properly unless the md-input-container
12661 * parent is provided.
12662 *
12663 * A single `<md-input-container>` should contain only one `<input>` element, otherwise it will throw an error.
12664 *
12665 * <b>Exception:</b>
12665 Hidden inputs (`<input type="hidden" />`) are ignored and will not throw an error, so
12666 * you may combine these with other inputs.
12667 *
12668 * <b>Note:</b> When using `ngMessages` with your input element, make sure the message and container elements
12669 * are *block* elements, otherwise animations applied to the messages will not look as intended. Either use a `div` and
12670 * apply the `ng-message` and `ng-messages` classes respectively, or use the `md-block` class on your element.
12671 *
12672 * @param md-is-error {expression=} When the given expression evaluates to true, the input container
12673 *   will go into error state. Defaults to erroring if the input has been touched and is invalid.
12674 * @param md-no-float {boolean=} When present, `placeholder` attributes on the input will not be converted to floating
12675 *   labels.
12676 *
12677 * @usage
12678 * <hljs lang="html">
12679 *
12680 * <md-input-container>
12681 *   <label>Username</label>
12682 *   <input type="text" ng-model="user.name">
12683 * </md-input-container>
12684 *
12685 * <md-input-container>
12686 *   <label>Description</label>
12687 *   <textarea ng-model="user.description"></textarea>
12688 * </md-input-container>
12689 *
12690 * </hljs>
12691 *
12692 * <h3>When disabling floating labels</h3>
12693 * <hljs lang="html">
12694 *
12695 * <md-input-container md-no-float>
12696 *   <input type="text" placeholder="Non-Floating Label">
12697 * </md-input-container>
12698 *
12699 * </hljs>
12700 */
12701function mdInputContainerDirective($mdTheming, $parse) {
12702
12703  ContainerCtrl.$inject = ["$scope", "$element", "$attrs", "$animate"];
12704  var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT', 'MD-SELECT'];
12705
12706  var LEFT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
12707    return selectors.concat(['md-icon ~ ' + isel, '.md-icon ~ ' + isel]);
12708  }, []).join(",");
12709
12710  var RIGHT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
12711    return selectors.concat([isel + ' ~ md-icon', isel + ' ~ .md-icon']);
12712  }, []).join(",");
12713
12714  return {
12715    restrict: 'E',
12716    compile: compile,
12717    controller: ContainerCtrl
12718  };
12719
12720  function compile(tElement) {
12721    // Check for both a left & right icon
12722    var leftIcon = tElement[0].querySelector(LEFT_SELECTORS);
12723    var rightIcon = tElement[0].querySelector(RIGHT_SELECTORS);
12724
12725    if (leftIcon) { tElement.addClass('md-icon-left'); }
12726    if (rightIcon) { tElement.addClass('md-icon-right'); }
12727
12728    return function postLink(scope, element) {
12729      $mdTheming(element);
12730    };
12731  }
12732
12733  function ContainerCtrl($scope, $element, $attrs, $animate) {
12734    var self = this;
12735
12736    self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);
12737
12738    self.delegateClick = function() {
12739      self.input.focus();
12740    };
12741    self.element = $element;
12742    self.setFocused = function(isFocused) {
12743      $element.toggleClass('md-input-focused', !!isFocused);
12744    };
12745    self.setHasValue = function(hasValue) {
12746      $element.toggleClass('md-input-has-value', !!hasValue);
12747    };
12748    self.setHasPlaceholder = function(hasPlaceholder) {
12749      $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder);
12750    };
12751    self.setInvalid = function(isInvalid) {
12752      if (isInvalid) {
12753        $animate.addClass($element, 'md-input-invalid');
12754      } else {
12755        $animate.removeClass($element, 'md-input-invalid');
12756      }
12757    };
12758    $scope.$watch(function() {
12759      return self.label && self.input;
12760    }, function(hasLabelAndInput) {
12761      if (hasLabelAndInput && !self.label.attr('for')) {
12762        self.label.attr('for', self.input.attr('id'));
12763      }
12764    });
12765  }
12766}
12767
12768function labelDirective() {
12769  return {
12770    restrict: 'E',
12771    require: '^?mdInputContainer',
12772    link: function(scope, element, attr, containerCtrl) {
12773      if (!containerCtrl || attr.mdNoFloat || element.hasClass('md-container-ignore')) return;
12774
12775      containerCtrl.label = element;
12776      scope.$on('$destroy', function() {
12777        containerCtrl.label = null;
12778      });
12779    }
12780  };
12781}
12782
12783/**
12784 * @ngdoc directive
12785 * @name mdInput
12786 * @restrict E
12787 * @module material.components.input
12788 *
12789 * @description
12790 * You can use any `<input>` or `<textarea>` element as a child of an `<md-input-container>`. This
12791 * allows you to build complex forms for data entry.
12792 *
12793 * When the input is required and uses a floating label, then the label will automatically contain
12794 * an asterisk (`*`).<br/>
12795 * This behavior can be disabled by using the `md-no-asterisk` attribute.
12796 *
12797 * @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is
12798 *   specified, a character counter will be shown underneath the input.<br/><br/>
12799 *   The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
12800 *   want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
12801 *   or maxlength attributes.<br/><br/>
12802 *   **Note:** Only valid for text/string inputs (not numeric).
12803 *
12804 * @param {string=} aria-label Aria-label is required when no label is present.  A warning message
12805 *   will be logged in the console if not present.
12806 * @param {string=} placeholder An alternative approach to using aria-label when the label is not
12807 *   PRESENT. The placeholder text is copied to the aria-label attribute.
12808 * @param md-no-autogrow {boolean=} When present, textareas will not grow automatically.
12809 * @param md-no-asterisk {boolean=} When present, an asterisk will not be appended to the inputs floating label
12810 * @param md-no-resize {boolean=} Disables the textarea resize handle.
12811 * @param {number=} max-rows The maximum amount of rows for a textarea.
12812 * @param md-detect-hidden {boolean=} When present, textareas will be sized properly when they are
12813 *   revealed after being hidden. This is off by default for performance reasons because it
12814 *   guarantees a reflow every digest cycle.
12815 *
12816 * @usage
12817 * <hljs lang="html">
12818 * <md-input-container>
12819 *   <label>Color</label>
12820 *   <input type="text" ng-model="color" required md-maxlength="10">
12821 * </md-input-container>
12822 * </hljs>
12823 *
12824 * <h3>With Errors</h3>
12825 *
12826 * `md-input-container` also supports errors using the standard `ng-messages` directives and
12827 * animates the messages when they become visible using from the `ngEnter`/`ngLeave` events or
12828 * the `ngShow`/`ngHide` events.
12829 *
12830 * By default, the messages will be hidden until the input is in an error state. This is based off
12831 * of the `md-is-error` expression of the `md-input-container`. This gives the user a chance to
12832 * fill out the form before the errors become visible.
12833 *
12834 * <hljs lang="html">
12835 * <form name="colorForm">
12836 *   <md-input-container>
12837 *     <label>Favorite Color</label>
12838 *     <input name="favoriteColor" ng-model="favoriteColor" required>
12839 *     <div ng-messages="colorForm.favoriteColor.$error">
12840 *       <div ng-message="required">This is required!</div>
12841 *     </div>
12842 *   </md-input-container>
12843 * </form>
12844 * </hljs>
12845 *
12846 * We automatically disable this auto-hiding functionality if you provide any of the following
12847 * visibility directives on the `ng-messages` container:
12848 *
12849 *  - `ng-if`
12850 *  - `ng-show`/`ng-hide`
12851 *  - `ng-switch-when`/`ng-switch-default`
12852 *
12853 * You can also disable this functionality manually by adding the `md-auto-hide="false"` expression
12854 * to the `ng-messages` container. This may be helpful if you always want to see the error messages
12855 * or if you are building your own visibilty directive.
12856 *
12857 * _<b>Note:</b> The `md-auto-hide` attribute is a static string that is  only checked upon
12858 * initialization of the `ng-messages` directive to see if it equals the string `false`._
12859 *
12860 * <hljs lang="html">
12861 * <form name="userForm">
12862 *   <md-input-container>
12863 *     <label>Last Name</label>
12864 *     <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
12865 *     <div ng-messages="userForm.lastName.$error" ng-show="userForm.lastName.$dirty">
12866 *       <div ng-message="required">This is required!</div>
12867 *       <div ng-message="md-maxlength">That's too long!</div>
12868 *       <div ng-message="minlength">That's too short!</div>
12869 *     </div>
12870 *   </md-input-container>
12871 *   <md-input-container>
12872 *     <label>Biography</label>
12873 *     <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
12874 *     <div ng-messages="userForm.bio.$error" ng-show="userForm.bio.$dirty">
12875 *       <div ng-message="required">This is required!</div>
12876 *       <div ng-message="md-maxlength">That's too long!</div>
12877 *     </div>
12878 *   </md-input-container>
12879 *   <md-input-container>
12880 *     <input aria-label='title' ng-model='title'>
12881 *   </md-input-container>
12882 *   <md-input-container>
12883 *     <input placeholder='title' ng-model='title'>
12884 *   </md-input-container>
12885 * </form>
12886 * </hljs>
12887 *
12888 * <h3>Notes</h3>
12889 *
12890 * - Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
12891 * - Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
12892 *
12893 * The `md-input` and `md-input-container` directives use very specific positioning to achieve the
12894 * error animation effects. Therefore, it is *not* advised to use the Layout system inside of the
12895 * `<md-input-container>` tags. Instead, use relative or absolute positioning.
12896 *
12897 *
12898 * <h3>Textarea directive</h3>
12899 * The `textarea` element within a `md-input-container` has the following specific behavior:
12900 * - By default the `textarea` grows as the user types. This can be disabled via the `md-no-autogrow`
12901 * attribute.
12902 * - If a `textarea` has the `rows` attribute, it will treat the `rows` as the minimum height and will
12903 * continue growing as the user types. For example a textarea with `rows="3"` will be 3 lines of text
12904 * high initially. If no rows are specified, the directive defaults to 1.
12905 * - The textarea's height gets set on initialization, as well as while the user is typing. In certain situations
12906 * (e.g. while animating) the directive might have been initialized, before the element got it's final height. In
12907 * those cases, you can trigger a resize manually by broadcasting a `md-resize-textarea` event on the scope.
12908 * - If you want a `textarea` to stop growing at a certain point, you can specify the `max-rows` attribute.
12909 * - The textarea's bottom border acts as a handle which users can drag, in order to resize the element vertically.
12910 * Once the user has resized a `textarea`, the autogrowing functionality becomes disabled. If you don't want a
12911 * `textarea` to be resizeable by the user, you can add the `md-no-resize` attribute.
12912 */
12913
12914function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) {
12915  return {
12916    restrict: 'E',
12917    require: ['^?mdInputContainer', '?ngModel', '?^form'],
12918    link: postLink
12919  };
12920
12921  function postLink(scope, element, attr, ctrls) {
12922
12923    var containerCtrl = ctrls[0];
12924    var hasNgModel = !!ctrls[1];
12925    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
12926    var parentForm = ctrls[2];
12927    var isReadonly = angular.isDefined(attr.readonly);
12928    var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
12929    var tagName = element[0].tagName.toLowerCase();
12930
12931
12932    if (!containerCtrl) return;
12933    if (attr.type === 'hidden') {
12934      element.attr('aria-hidden', 'true');
12935      return;
12936    } else if (containerCtrl.input) {
12937      if (containerCtrl.input[0].contains(element[0])) {
12938        return;
12939      } else {
12940        throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
12941      }
12942    }
12943    containerCtrl.input = element;
12944
12945    setupAttributeWatchers();
12946
12947    // Add an error spacer div after our input to provide space for the char counter and any ng-messages
vendor: 10,434 bytes, lines 12948-13267
12948    var errorsSpacer = angular.element('<div class="md-errors-spacer">');
12949    element.after(errorsSpacer);
12950
12951    var placeholderText = angular.isString(attr.placeholder) ? attr.placeholder.trim() : '';
12952    if (!containerCtrl.label && !placeholderText.length) {
12953      $mdAria.expect(element, 'aria-label');
12954    }
12955
12956    element.addClass('md-input');
12957    if (!element.attr('id')) {
12958      element.attr('id', 'input_' + $mdUtil.nextUid());
12959    }
12960
12961    // This works around a Webkit issue where number inputs, placed in a flexbox, that have
12962    // a `min` and `max` will collapse to about 1/3 of their proper width. Please check #7349
12963    // for more info. Also note that we don't override the `step` if the user has specified it,
12964    // in order to prevent some unexpected behaviour.
12965    if (tagName === 'input' && attr.type === 'number' && attr.min && attr.max && !attr.step) {
12966      element.attr('step', 'any');
12967    } else if (tagName === 'textarea') {
12968      setupTextarea();
12969    }
12970
12971    // If the input doesn't have an ngModel, it may have a static value. For that case,
12972    // we have to do one initial check to determine if the container should be in the
12973    // "has a value" state.
12974    if (!hasNgModel) {
12975      inputCheckValue();
12976    }
12977
12978    var isErrorGetter = containerCtrl.isErrorGetter || function() {
12979      return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
12980    };
12981
12982    scope.$watch(isErrorGetter, containerCtrl.setInvalid);
12983
12984    // When the developer uses the ngValue directive for the input, we have to observe the attribute, because
12985    // AngularJS's ngValue directive is just setting the `value` attribute.
12986    if (attr.ngValue) {
12987      attr.$observe('value', inputCheckValue);
12988    }
12989
12990    ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
12991    ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);
12992
12993    element.on('input', inputCheckValue);
12994
12995    if (!isReadonly) {
12996      element
12997        .on('focus', function(ev) {
12998          $mdUtil.nextTick(function() {
12999            containerCtrl.setFocused(true);
13000          });
13001        })
13002        .on('blur', function(ev) {
13003          $mdUtil.nextTick(function() {
13004            containerCtrl.setFocused(false);
13005            inputCheckValue();
13006          });
13007        });
13008    }
13009
13010    scope.$on('$destroy', function() {
13011      containerCtrl.setFocused(false);
13012      containerCtrl.setHasValue(false);
13013      containerCtrl.input = null;
13014    });
13015
13016    /** Gets run through ngModel's pipeline and set the `has-value` class on the container. */
13017    function ngModelPipelineCheckValue(arg) {
13018      containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
13019      return arg;
13020    }
13021
13022    function setupAttributeWatchers() {
13023      if (containerCtrl.label) {
13024        attr.$observe('required', function (value) {
13025          // We don't need to parse the required value, it's always a boolean because of angular's
13026          // required directive.
13027          containerCtrl.label.toggleClass('md-required', value && !mdNoAsterisk);
13028        });
13029      }
13030    }
13031
13032    function inputCheckValue() {
13033      // An input's value counts if its length > 0,
13034      // or if the input's validity state says it has bad input (eg string in a number input)
13035      containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity || {}).badInput);
13036    }
13037
13038    function setupTextarea() {
13039      var isAutogrowing = !attr.hasOwnProperty('mdNoAutogrow');
13040
13041      attachResizeHandle();
13042
13043      if (!isAutogrowing) return;
13044
13045      // Can't check if height was or not explicity set,
13046      // so rows attribute will take precedence if present
13047      var minRows = attr.hasOwnProperty('rows') ? parseInt(attr.rows) : NaN;
13048      var maxRows = attr.hasOwnProperty('maxRows') ? parseInt(attr.maxRows) : NaN;
13049      var scopeResizeListener = scope.$on('md-resize-textarea', growTextarea);
13050      var lineHeight = null;
13051      var node = element[0];
13052
13053      // This timeout is necessary, because the browser needs a little bit
13054      // of time to calculate the `clientHeight` and `scrollHeight`.
13055      $timeout(function() {
13056        $mdUtil.nextTick(growTextarea);
13057      }, 10, false);
13058
13059      // We could leverage ngModel's $parsers here, however it
13060      // isn't reliable, because AngularJS trims the input by default,
13061      // which means that growTextarea won't fire when newlines and
13062      // spaces are added.
13063      element.on('input', growTextarea);
13064
13065      // We should still use the $formatters, because they fire when
13066      // the value was changed from outside the textarea.
13067      if (hasNgModel) {
13068        ngModelCtrl.$formatters.push(formattersListener);
13069      }
13070
13071      if (!minRows) {
13072        element.attr('rows', 1);
13073      }
13074
13075      angular.element($window).on('resize', growTextarea);
13076      scope.$on('$destroy', disableAutogrow);
13077
13078      function growTextarea() {
13079        // temporarily disables element's flex so its height 'runs free'
13080        element
13081          .attr('rows', 1)
13082          .css('height', 'auto')
13083          .addClass('md-no-flex');
13084
13085        var height = getHeight();
13086
13087        if (!lineHeight) {
13088          // offsetHeight includes padding which can throw off our value
13089          var originalPadding = element[0].style.padding || '';
13090          lineHeight = element.css('padding', 0).prop('offsetHeight');
13091          element[0].style.padding = originalPadding;
13092        }
13093
13094        if (minRows && lineHeight) {
13095          height = Math.max(height, lineHeight * minRows);
13096        }
13097
13098        if (maxRows && lineHeight) {
13099          var maxHeight = lineHeight * maxRows;
13100
13101          if (maxHeight < height) {
13102            element.attr('md-no-autogrow', '');
13103            height = maxHeight;
13104          } else {
13105            element.removeAttr('md-no-autogrow');
13106          }
13107        }
13108
13109        if (lineHeight) {
13110          element.attr('rows', Math.round(height / lineHeight));
13111        }
13112
13113        element
13114          .css('height', height + 'px')
13115          .removeClass('md-no-flex');
13116      }
13117
13118      function getHeight() {
13119        var offsetHeight = node.offsetHeight;
13120        var line = node.scrollHeight - offsetHeight;
13121        return offsetHeight + Math.max(line, 0);
13122      }
13123
13124      function formattersListener(value) {
13125        $mdUtil.nextTick(growTextarea);
13126        return value;
13127      }
13128
13129      function disableAutogrow() {
13130        if (!isAutogrowing) return;
13131
13132        isAutogrowing = false;
13133        angular.element($window).off('resize', growTextarea);
13134        scopeResizeListener && scopeResizeListener();
13135        element
13136          .attr('md-no-autogrow', '')
13137          .off('input', growTextarea);
13138
13139        if (hasNgModel) {
13140          var listenerIndex = ngModelCtrl.$formatters.indexOf(formattersListener);
13141
13142          if (listenerIndex > -1) {
13143            ngModelCtrl.$formatters.splice(listenerIndex, 1);
13144          }
13145        }
13146      }
13147
13148      function attachResizeHandle() {
13149        if (attr.hasOwnProperty('mdNoResize')) return;
13150
13151        var handle = angular.element('<div class="md-resize-handle"></div>');
13152        var isDragging = false;
13153        var dragStart = null;
13154        var startHeight = 0;
13155        var container = containerCtrl.element;
13156        var dragGestureHandler = $mdGesture.register(handle, 'drag', { horizontal: false });
13157
13158
13159        element.wrap('<div class="md-resize-wrapper">').after(handle);
13160        handle.on('mousedown', onMouseDown);
13161
13162        container
13163          .on('$md.dragstart', onDragStart)
13164          .on('$md.drag', onDrag)
13165          .on('$md.dragend', onDragEnd);
13166
13167        scope.$on('$destroy', function() {
13168          handle
13169            .off('mousedown', onMouseDown)
13170            .remove();
13171
13172          container
13173            .off('$md.dragstart', onDragStart)
13174            .off('$md.drag', onDrag)
13175            .off('$md.dragend', onDragEnd);
13176
13177          dragGestureHandler();
13178          handle = null;
13179          container = null;
13180          dragGestureHandler = null;
13181        });
13182
13183        function onMouseDown(ev) {
13184          ev.preventDefault();
13185          isDragging = true;
13186          dragStart = ev.clientY;
13187          startHeight = parseFloat(element.css('height')) || element.prop('offsetHeight');
13188        }
13189
13190        function onDragStart(ev) {
13191          if (!isDragging) return;
13192          ev.preventDefault();
13193          disableAutogrow();
13194          container.addClass('md-input-resized');
13195        }
13196
13197        function onDrag(ev) {
13198          if (!isDragging) return;
13199
13200          element.css('height', (startHeight + ev.pointer.distanceY) + 'px');
13201        }
13202
13203        function onDragEnd(ev) {
13204          if (!isDragging) return;
13205          isDragging = false;
13206          container.removeClass('md-input-resized');
13207        }
13208      }
13209
13210      // Attach a watcher to detect when the textarea gets shown.
13211      if (attr.hasOwnProperty('mdDetectHidden')) {
13212
13213        var handleHiddenChange = function() {
13214          var wasHidden = false;
13215
13216          return function() {
13217            var isHidden = node.offsetHeight === 0;
13218
13219            if (isHidden === false && wasHidden === true) {
13220              growTextarea();
13221            }
13222
13223            wasHidden = isHidden;
13224          };
13225        }();
13226
13227        // Check every digest cycle whether the visibility of the textarea has changed.
13228        // Queue up to run after the digest cycle is complete.
13229        scope.$watch(function() {
13230          $mdUtil.nextTick(handleHiddenChange, false);
13231          return true;
13232        });
13233      }
13234    }
13235  }
13236}
13237
13238function mdMaxlengthDirective($animate, $mdUtil) {
13239  return {
13240    restrict: 'A',
13241    require: ['ngModel', '^mdInputContainer'],
13242    link: postLink
13243  };
13244
13245  function postLink(scope, element, attr, ctrls) {
13246    var maxlength;
13247    var ngModelCtrl = ctrls[0];
13248    var containerCtrl = ctrls[1];
13249    var charCountEl, errorsSpacer;
13250
13251    // Wait until the next tick to ensure that the input has setup the errors spacer where we will
13252    // append our counter
13253    $mdUtil.nextTick(function() {
13254      errorsSpacer = angular.element(containerCtrl.element[0].querySelector('.md-errors-spacer'));
13255      charCountEl = angular.element('<div class="md-char-counter">');
13256
13257      // Append our character counter inside the errors spacer
13258      errorsSpacer.append(charCountEl);
13259
13260      // Stop model from trimming. This makes it so whitespace
13261      // over the maxlength still counts as invalid.
13262      attr.$set('ngTrim', 'false');
13263
13264      scope.$watch(attr.mdMaxlength, function(value) {
13265        maxlength = value;
13266        if (angular.isNumber(value) && value > 0) {
13267          if (!charCountEl.parent().length) {
13268            $animate.enter(charCountEl, errorsSpacer);
13269          }
13270          renderCharCount();
13271        } else {
13272          $animate.leave(charCountEl);
13273        }
13274      });
13275
13276      ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
13277        if (!angular.isNumber(maxlength) || maxlength < 0) {
13278          return true;
13279        }
13280
13281        // We always update the char count, when the modelValue has changed.
13282        // Using the $validators for triggering the update works very well.
13283        renderCharCount();
13284
13285        return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
13286      };
13287    });
13288
13289    function renderCharCount(value) {
13290      // If we have not been appended to the body yet; do not render
13291      if (!charCountEl.parent) {
13292        return value;
13293      }
13294
13295      // Force the value into a string since it may be a number,
13296      // which does not have a length property.
13297      charCountEl.text(String(element.val() || value || '').length + ' / ' + maxlength);
13298      return value;
13299    }
13300  }
13301}
13302
13303function placeholderDirective($compile) {
13304  return {
13305    restrict: 'A',
13306    require: '^^?mdInputContainer',
13307    priority: 200,
13308    link: {
13309      // Note that we need to do this in the pre-link, as opposed to the post link, if we want to
13310      // support data bindings in the placeholder. This is necessary, because we have a case where
13311      // we transfer the placeholder value to the `<label>` and we remove it from the original `<input>`.
13312      // If we did this in the post-link, AngularJS would have set up the observers already and would be
13313      // re-adding the attribute, even though we removed it from the element.
13314      pre: preLink
13315    }
13316  };
13317
13318  function preLink(scope, element, attr, inputContainer) {
13319    // If there is no input container, just return
13320    if (!inputContainer) return;
13321
13322    var label = inputContainer.element.find('label');
13323    var noFloat = inputContainer.element.attr('md-no-float');
13324
13325    // If we have a label, or they specify the md-no-float attribute, just return
13326    if ((label && label.length) || noFloat === '' || scope.$eval(noFloat)) {
13327      // Add a placeholder class so we can target it in the CSS
13328      inputContainer.setHasPlaceholder(true);
13329      return;
13330    }
13331
13332    // md-select handles placeholders on it's own
13333    if (element[0].nodeName != 'MD-SELECT') {
13334      // Move the placeholder expression to the label
13335      var newLabel = angular.element('<label ng-click="delegateClick()" tabindex="-1">' + attr.placeholder + '</label>');
13336
13337      // Note that we unset it via `attr`, in order to get AngularJS
13338      // to remove any observers that it might have set up. Otherwise
13339      // the attribute will be added on the next digest.
13340      attr.$set('placeholder', null);
13341
13342      // We need to compile the label manually in case it has any bindings.
13343      // A gotcha here is that we first add the element to the DOM and we compile
13344      // it later. This is necessary, because if we compile the element beforehand,
13345      // it won't be able to find the `mdInputContainer` controller.
13346      inputContainer.element
13347        .addClass('md-icon-float')
13348        .prepend(newLabel);
13349
13350      $compile(newLabel)(scope);
13351    }
13352  }
13353}
13354
13355/**
13356 * @ngdoc directive
13357 * @name mdSelectOnFocus
13358 * @module material.components.input
13359 *
13360 * @restrict A
13361 *
13362 * @description
13363 * The `md-select-on-focus` directive allows you to automatically select the element's input text on focus.
13364 *
13365 * <h3>Notes</h3>
13366 * - The use of `md-select-on-focus` is restricted to `<input>` and `<textarea>` elements.
13367 *
13368 * @usage
13369 * <h3>Using with an Input</h3>
13370 * <hljs lang="html">
13371 *
13372 * <md-input-container>
13373 *   <label>Auto Select</label>
13374 *   <input type="text" md-select-on-focus>
13375 * </md-input-container>
13376 * </hljs>
13377 *
13378 * <h3>Using with a Textarea</h3>
13379 * <hljs lang="html">
13380 *
13381 * <md-input-container>
13382 *   <label>Auto Select</label>
13383 *   <textarea md-select-on-focus>This text will be selected on focus.</textarea>
13384 * </md-input-container>
13385 *
13386 * </hljs>
13387 */
13388function mdSelectOnFocusDirective($timeout) {
13389
13390  return {
13391    restrict: 'A',
13392    link: postLink
13393  };
13394
13395  function postLink(scope, element, attr) {
13396    if (element[0].nodeName !== 'INPUT' && element[0].nodeName !== "TEXTAREA") return;
13397
13398    var preventMouseUp = false;
vendor: 10,130 bytes, lines 13399-13763
13399
13400    element
13401      .on('focus', onFocus)
13402      .on('mouseup', onMouseUp);
13403
13404    scope.$on('$destroy', function() {
13405      element
13406        .off('focus', onFocus)
13407        .off('mouseup', onMouseUp);
13408    });
13409
13410    function onFocus() {
13411      preventMouseUp = true;
13412
13413      $timeout(function() {
13414        // Use HTMLInputElement#select to fix firefox select issues.
13415        // The debounce is here for Edge's sake, otherwise the selection doesn't work.
13416        element[0].select();
13417
13418        // This should be reset from inside the `focus`, because the event might
13419        // have originated from something different than a click, e.g. a keyboard event.
13420        preventMouseUp = false;
13421      }, 1, false);
13422    }
13423
13424    // Prevents the default action of the first `mouseup` after a focus.
13425    // This is necessary, because browsers fire a `mouseup` right after the element
13426    // has been focused. In some browsers (Firefox in particular) this can clear the
13427    // selection. There are examples of the problem in issue #7487.
13428    function onMouseUp(event) {
13429      if (preventMouseUp) {
13430        event.preventDefault();
13431      }
13432    }
13433  }
13434}
13435
13436var visibilityDirectives = ['ngIf', 'ngShow', 'ngHide', 'ngSwitchWhen', 'ngSwitchDefault'];
13437function ngMessagesDirective() {
13438  return {
13439    restrict: 'EA',
13440    link: postLink,
13441
13442    // This is optional because we don't want target *all* ngMessage instances, just those inside of
13443    // mdInputContainer.
13444    require: '^^?mdInputContainer'
13445  };
13446
13447  function postLink(scope, element, attrs, inputContainer) {
13448    // If we are not a child of an input container, don't do anything
13449    if (!inputContainer) return;
13450
13451    // Add our animation class
13452    element.toggleClass('md-input-messages-animation', true);
13453
13454    // Add our md-auto-hide class to automatically hide/show messages when container is invalid
13455    element.toggleClass('md-auto-hide', true);
13456
13457    // If we see some known visibility directives, remove the md-auto-hide class
13458    if (attrs.mdAutoHide == 'false' || hasVisibiltyDirective(attrs)) {
13459      element.toggleClass('md-auto-hide', false);
13460    }
13461  }
13462
13463  function hasVisibiltyDirective(attrs) {
13464    return visibilityDirectives.some(function(attr) {
13465      return attrs[attr];
13466    });
13467  }
13468}
13469
13470function ngMessageDirective($mdUtil) {
13471  return {
13472    restrict: 'EA',
13473    compile: compile,
13474    priority: 100
13475  };
13476
13477  function compile(tElement) {
13478    if (!isInsideInputContainer(tElement)) {
13479
13480      // When the current element is inside of a document fragment, then we need to check for an input-container
13481      // in the postLink, because the element will be later added to the DOM and is currently just in a temporary
13482      // fragment, which causes the input-container check to fail.
13483      if (isInsideFragment()) {
13484        return function (scope, element) {
13485          if (isInsideInputContainer(element)) {
13486            // Inside of the postLink function, a ngMessage directive will be a comment element, because it's
13487            // currently hidden. To access the shown element, we need to use the element from the compile function.
13488            initMessageElement(tElement);
13489          }
13490        };
13491      }
13492    } else {
13493      initMessageElement(tElement);
13494    }
13495
13496    function isInsideFragment() {
13497      var nextNode = tElement[0];
13498      while (nextNode = nextNode.parentNode) {
13499        if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
13500          return true;
13501        }
13502      }
13503      return false;
13504    }
13505
13506    function isInsideInputContainer(element) {
13507      return !!$mdUtil.getClosest(element, "md-input-container");
13508    }
13509
13510    function initMessageElement(element) {
13511      // Add our animation class
13512      element.toggleClass('md-input-message-animation', true);
13513    }
13514  }
13515}
13516
13517var $$AnimateRunner, $animateCss, $mdUtil, $log;
13518
13519function mdInputInvalidMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
13520  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
13521
13522  return {
13523    addClass: function(element, className, done) {
13524      showInputMessages(element, done);
13525    }
13526
13527    // NOTE: We do not need the removeClass method, because the message ng-leave animation will fire
13528  };
13529}
13530
13531function ngMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
13532  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
13533
13534  return {
13535    enter: function(element, done) {
13536      showInputMessages(element, done);
13537    },
13538
13539    leave: function(element, done) {
13540      hideInputMessages(element, done);
13541    },
13542
13543    addClass: function(element, className, done) {
13544      if (className == "ng-hide") {
13545        hideInputMessages(element, done);
13546      } else {
13547        done();
13548      }
13549    },
13550
13551    removeClass: function(element, className, done) {
13552      if (className == "ng-hide") {
13553        showInputMessages(element, done);
13554      } else {
13555        done();
13556      }
13557    }
13558  };
13559}
13560
13561function ngMessageAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
13562  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
13563
13564  return {
13565    enter: function(element, done) {
13566      var animator = showMessage(element);
13567
13568      animator.start().done(done);
13569    },
13570
13571    leave: function(element, done) {
13572      var animator = hideMessage(element);
13573
13574      animator.start().done(done);
13575    }
13576  };
13577}
13578
13579function showInputMessages(element, done) {
13580  var animators = [], animator;
13581  var messages = getMessagesElement(element);
13582  var children = messages.children();
13583
13584  if (messages.length == 0 || children.length == 0) {
13585    $log.warn('mdInput messages show animation called on invalid messages element: ', element);
13586    done();
13587    return;
13588  }
13589
13590  angular.forEach(children, function(child) {
13591    animator = showMessage(angular.element(child));
13592
13593    animators.push(animator.start());
13594  });
13595
13596  $$AnimateRunner.all(animators, done);
13597}
13598
13599function hideInputMessages(element, done) {
13600  var animators = [], animator;
13601  var messages = getMessagesElement(element);
13602  var children = messages.children();
13603
13604  if (messages.length == 0 || children.length == 0) {
13605    $log.warn('mdInput messages hide animation called on invalid messages element: ', element);
13606    done();
13607    return;
13608  }
13609
13610  angular.forEach(children, function(child) {
13611    animator = hideMessage(angular.element(child));
13612
13613    animators.push(animator.start());
13614  });
13615
13616  $$AnimateRunner.all(animators, done);
13617}
13618
13619function showMessage(element) {
13620  var height = parseInt(window.getComputedStyle(element[0]).height);
13621  var topMargin = parseInt(window.getComputedStyle(element[0]).marginTop);
13622
13623  var messages = getMessagesElement(element);
13624  var container = getInputElement(element);
13625
13626  // Check to see if the message is already visible so we can skip
13627  var alreadyVisible = (topMargin > -height);
13628
13629  // If we have the md-auto-hide class, the md-input-invalid animation will fire, so we can skip
13630  if (alreadyVisible || (messages.hasClass('md-auto-hide') && !container.hasClass('md-input-invalid'))) {
13631    return $animateCss(element, {});
13632  }
13633
13634  return $animateCss(element, {
13635    event: 'enter',
13636    structural: true,
13637    from: {"opacity": 0, "margin-top": -height + "px"},
13638    to: {"opacity": 1, "margin-top": "0"},
13639    duration: 0.3
13640  });
13641}
13642
13643function hideMessage(element) {
13644  var height = element[0].offsetHeight;
13645  var styles = window.getComputedStyle(element[0]);
13646
13647  // If we are already hidden, just return an empty animation
13648  if (parseInt(styles.opacity) === 0) {
13649    return $animateCss(element, {});
13650  }
13651
13652  // Otherwise, animate
13653  return $animateCss(element, {
13654    event: 'leave',
13655    structural: true,
13656    from: {"opacity": 1, "margin-top": 0},
13657    to: {"opacity": 0, "margin-top": -height + "px"},
13658    duration: 0.3
13659  });
13660}
13661
13662function getInputElement(element) {
13663  var inputContainer = element.controller('mdInputContainer');
13664
13665  return inputContainer.element;
13666}
13667
13668function getMessagesElement(element) {
13669  // If we ARE the messages element, just return ourself
13670  if (element.hasClass('md-input-messages-animation')) {
13671    return element;
13672  }
13673
13674  // If we are a ng-message element, we need to traverse up the DOM tree
13675  if (element.hasClass('md-input-message-animation')) {
13676    return angular.element($mdUtil.getClosest(element, function(node) {
13677      return node.classList.contains('md-input-messages-animation');
13678    }));
13679  }
13680
13681  // Otherwise, we can traverse down
13682  return angular.element(element[0].querySelector('.md-input-messages-animation'));
13683}
13684
13685function saveSharedServices(_$$AnimateRunner_, _$animateCss_, _$mdUtil_, _$log_) {
13686  $$AnimateRunner = _$$AnimateRunner_;
13687  $animateCss = _$animateCss_;
13688  $mdUtil = _$mdUtil_;
13689  $log = _$log_;
13690}
13691
13692})();
13693(function(){
13694"use strict";
13695
13696/**
13697 * @ngdoc module
13698 * @name material.components.list
13699 * @description
13700 * List module
13701 */
13702MdListController.$inject = ["$scope", "$element", "$mdListInkRipple"];
13703mdListDirective.$inject = ["$mdTheming"];
13704mdListItemDirective.$inject = ["$mdAria", "$mdConstant", "$mdUtil", "$timeout"];
13705angular.module('material.components.list', [
13706  'material.core'
13707])
13708  .controller('MdListController', MdListController)
13709  .directive('mdList', mdListDirective)
13710  .directive('mdListItem', mdListItemDirective);
13711
13712/**
13713 * @ngdoc directive
13714 * @name mdList
13715 * @module material.components.list
13716 *
13717 * @restrict E
13718 *
13719 * @description
13720 * The `<md-list>` directive is a list container for 1..n `<md-list-item>` tags.
13721 *
13722 * @usage
13723 * <hljs lang="html">
13724 * <md-list>
13725 *   <md-list-item class="md-2-line" ng-repeat="item in todos">
13726 *     <md-checkbox ng-model="item.done"></md-checkbox>
13727 *     <div class="md-list-item-text">
13728 *       <h3>{{item.title}}</h3>
13729 *       <p>{{item.description}}</p>
13730 *     </div>
13731 *   </md-list-item>
13732 * </md-list>
13733 * </hljs>
13734 */
13735
13736function mdListDirective($mdTheming) {
13737  return {
13738    restrict: 'E',
13739    compile: function(tEl) {
13740      tEl[0].setAttribute('role', 'list');
13741      return $mdTheming;
13742    }
13743  };
13744}
13745/**
13746 * @ngdoc directive
13747 * @name mdListItem
13748 * @module material.components.list
13749 *
13750 * @restrict E
13751 *
13752 * @description
13753 * A `md-list-item` element can be used to represent some information in a row.<br/>
13754 *
13755 * @usage
13756 * ### Single Row Item
13757 * <hljs lang="html">
13758 *   <md-list-item>
13759 *     <span>Single Row Item</span>
13760 *   </md-list-item>
13761 * </hljs>
13762 *
13763 * ### Multiple Lines
13764 * By using the following markup, you will be able to have two lines inside of one `md-list-item`.
13765 *
13766 * <hljs lang="html">
13767 *   <md-list-item class="md-2-line">
13768 *     <div class="md-list-item-text" layout="column">
13769 *       <p>First Line</p>
13770 *       <p>Second Line</p>
13771 *     </div>
13772 *   </md-list-item>
13773 * </hljs>
13774 *
13775 * It is also possible to have three lines inside of one list item.
13776 *
13777 * <hljs lang="html">
13778 *   <md-list-item class="md-3-line">
13779 *     <div class="md-list-item-text" layout="column">
13780 *       <p>First Line</p>
13781 *       <p>Second Line</p>
13782 *       <p>Third Line</p>
13783 *     </div>
13784 *   </md-list-item>
13785 * </hljs>
13786 *
13787 * ### Secondary Items
13788 * Secondary items are elements which will be aligned at the end of the `md-list-item`.
13789 *
13790 * <hljs lang="html">
13791 *   <md-list-item>
13792 *     <span>Single Row Item</span>
13793 *     <md-button class="md-secondary">
13794 *       Secondary Button
13795 *     </md-button>
13796 *   </md-list-item>
13797 * </hljs>
13798 *
13799 * It also possible to have multiple secondary items inside of one `md-list-item`.
13800 *
13801 * <hljs lang="html">
13802 *   <md-list-item>
13803 *     <span>Single Row Item</span>
13804 *     <md-button class="md-secondary">First Button</md-button>
13805 *     <md-button class="md-secondary">Second Button</md-button>
13806 *   </md-list-item>
13807 * </hljs>
13808 *
13809 * ### Proxy Item
13810 * Proxies are elements, which will execute their specific action on click<br/>
13811 * Currently supported proxy items are
13812 * - `md-checkbox` (Toggle)
13813 * - `md-switch` (Toggle)
13814 * - `md-menu` (Open)
13815 *
13816 * This means, when using a supported proxy item inside of `md-list-item`, the list item will
13817 * automatically become clickable and executes the associated action of the proxy element on click.
13818 *
13819 * It is possible to disable this behavior by applying the `md-no-proxy` class to the list item.
13820 *
13821 * <hljs lang="html">
13822 *   <md-list-item class="md-no-proxy">
13823 *     <span>No Proxy List</span>
13824 *     <md-checkbox class="md-secondary"></md-checkbox>
13825 *   </md-list-item>
13826 * </hljs>
13827 *
13828 * Here are a few examples of proxy elements inside of a list item.
13829 *
13830 * <hljs lang="html">
13831 *   <md-list-item>
13832 *     <span>First Line</span>
13833 *     <md-checkbox class="md-secondary"></md-checkbox>
13834 *   </md-list-item>
13835 * </hljs>
13836 *
13837 * The `md-checkbox` element will be automatically detected as a proxy element and will toggle on click.
13838 *
13839 * <hljs lang="html">
13840 *   <md-list-item>
13841 *     <span>First Line</span>
13842 *     <md-switch class="md-secondary"></md-switch>
13843 *   </md-list-item>
13844 * </hljs>
13845 *
13846 * The recognized `md-switch` will toggle its state, when the user clicks on the `md-list-item`.
13847 *
13848 * It is also possible to have a `md-menu` inside of a `md-list-item`.
13849 * <hljs lang="html">
13850 *   <md-list-item>
13851 *     <p>Click anywhere to fire the secondary action</p>
13852 *     <md-menu class="md-secondary">
13853 *       <md-button class="md-icon-button">
13854 *         <md-icon md-svg-icon="communication:message"></md-icon>
13855 *       </md-button>
13856 *       <md-menu-content width="4">
13857 *         <md-menu-item>
13858 *           <md-button>
13859 *             Redial
13860 *           </md-button>
13861 *         </md-menu-item>
13862 *         <md-menu-item>
13863 *           <md-button>
13864 *             Check voicemail
13865 *           </md-button>
13866 *         </md-menu-item>
13867 *         <md-menu-divider></md-menu-divider>
13868 *         <md-menu-item>
13869 *           <md-button>
13870 *             Notifications
13871 *           </md-button>
13872 *         </md-menu-item>
13873 *       </md-menu-content>
13874 *     </md-menu>
13875 *   </md-list-item>
13876 * </hljs>
13877 *
13878 * The menu will automatically open, when the users clicks on the `md-list-item`.<br/>
13879 *
13880 * If the developer didn't specify any position mode on the menu, the `md-list-item` will automatically detect the
13881 * position mode and applies it to the `md-menu`.
13882 *
13883 * ### Avatars
13884 * Sometimes you may want to have some avatars inside of the `md-list-item `.<br/>
13885 * You are able to create a optimized icon for the list item, by applying the `.md-avatar` class on the `<img>` element.
13886 *
13887 * <hljs lang="html">
13888 *   <md-list-item>
13889 *     <img src="my-avatar.png" class="md-avatar">
13890 *     <span>Alan Turing</span>
13891 * </hljs>
13892 *
13893 * When using `<md-icon>` for an avatar, you have to use the `.md-avatar-icon` class.
13894 * <hljs lang="html">
13895 *   <md-list-item>
13896 *     <md-icon class="md-avatar-icon" md-svg-icon="avatars:timothy"></md-icon>
13897 *     <span>Timothy Kopra</span>
13898 *   </md-list-item>
13899 * </hljs>
13900 *
13901 * In cases, you have a `md-list-item`, which doesn't have any avatar,
13902 * but you want to align it with the other avatar items, you have to use the `.md-offset` class.
13903 *
13904 * <hljs lang="html">
13905 *   <md-list-item class="md-offset">
13906 *     <span>Jon Doe</span>
13907 *   </md-list-item>
13908 * </hljs>
13909 *
13910 * ### DOM modification
13911 * The `md-list-item` component automatically detects if the list item should be clickable.
13912 *
13913 * ---
13914 * If the `md-list-item` is clickable, we wrap all content inside of a `<div>` and create
13915 * an overlaying button, which will will execute the given actions (like `ng-href`, `ng-click`)
13916 *
13917 * We create an overlaying button, instead of wrapping all content inside of the button,
13918 * because otherwise some elements may not be clickable inside of the button.
13919 *
13920 * ---
13921 * When using a secondary item inside of your list item, the `md-list-item` component will automatically create
13922 * a secondary container at the end of the `md-list-item`, which contains all secondary items.
13923 *
13924 * The secondary item container is not static, because otherwise the overflow will not work properly on the
13925 * list item.
13926 *
13927 */
13928function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
13929  var proxiedTypes = ['md-checkbox', 'md-switch', 'md-menu'];
13930  return {
13931    restrict: 'E',
13932    controller: 'MdListController',
vendor: 15,580 bytes, lines 13933-14391
13933    compile: function(tEl, tAttrs) {
13934
13935      // Check for proxy controls (no ng-click on parent, and a control inside)
13936      var secondaryItems = tEl[0].querySelectorAll('.md-secondary');
13937      var hasProxiedElement;
13938      var proxyElement;
13939      var itemContainer = tEl;
13940
13941      tEl[0].setAttribute('role', 'listitem');
13942
13943      if (tAttrs.ngClick || tAttrs.ngDblclick ||  tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref) {
13944        wrapIn('button');
13945      } else if (!tEl.hasClass('md-no-proxy')) {
13946
13947        for (var i = 0, type; type = proxiedTypes[i]; ++i) {
13948          if (proxyElement = tEl[0].querySelector(type)) {
13949            hasProxiedElement = true;
13950            break;
13951          }
13952        }
13953
13954        if (hasProxiedElement) {
13955          wrapIn('div');
13956        } else {
13957          tEl.addClass('md-no-proxy');
13958        }
13959
13960      }
13961
13962      wrapSecondaryItems();
13963      setupToggleAria();
13964
13965      if (hasProxiedElement && proxyElement.nodeName === "MD-MENU") {
13966        setupProxiedMenu();
13967      }
13968
13969      function setupToggleAria() {
13970        var toggleTypes = ['md-switch', 'md-checkbox'];
13971        var toggle;
13972
13973        for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) {
13974          if (toggle = tEl.find(toggleType)[0]) {
13975            if (!toggle.hasAttribute('aria-label')) {
13976              var p = tEl.find('p')[0];
13977              if (!p) return;
13978              toggle.setAttribute('aria-label', 'Toggle ' + p.textContent);
13979            }
13980          }
13981        }
13982      }
13983
13984      function setupProxiedMenu() {
13985        var menuEl = angular.element(proxyElement);
13986
13987        var isEndAligned = menuEl.parent().hasClass('md-secondary-container') ||
13988                           proxyElement.parentNode.firstElementChild !== proxyElement;
13989
13990        var xAxisPosition = 'left';
13991
13992        if (isEndAligned) {
13993          // When the proxy item is aligned at the end of the list, we have to set the origin to the end.
13994          xAxisPosition = 'right';
13995        }
13996
13997        // Set the position mode / origin of the proxied menu.
13998        if (!menuEl.attr('md-position-mode')) {
13999          menuEl.attr('md-position-mode', xAxisPosition + ' target');
14000        }
14001
14002        // Apply menu open binding to menu button
14003        var menuOpenButton = menuEl.children().eq(0);
14004        if (!hasClickEvent(menuOpenButton[0])) {
14005          menuOpenButton.attr('ng-click', '$mdMenu.open($event)');
14006        }
14007
14008        if (!menuOpenButton.attr('aria-label')) {
14009          menuOpenButton.attr('aria-label', 'Open List Menu');
14010        }
14011      }
14012
14013      function wrapIn(type) {
14014        if (type == 'div') {
14015          itemContainer = angular.element('<div class="md-no-style md-list-item-inner">');
14016          itemContainer.append(tEl.contents());
14017          tEl.addClass('md-proxy-focus');
14018        } else {
14019          // Element which holds the default list-item content.
14020          itemContainer = angular.element(
14021            '<div class="md-button md-no-style">'+
14022            '   <div class="md-list-item-inner"></div>'+
14023            '</div>'
14024          );
14025
14026          // Button which shows ripple and executes primary action.
14027          var buttonWrap = angular.element(
14028            '<md-button class="md-no-style"></md-button>'
14029          );
14030
14031          copyAttributes(tEl[0], buttonWrap[0]);
14032
14033          // If there is no aria-label set on the button (previously copied over if present)
14034          // we determine the label from the content and copy it to the button.
14035          if (!buttonWrap.attr('aria-label')) {
14036            buttonWrap.attr('aria-label', $mdAria.getText(tEl));
14037          }
14038
14039          // We allow developers to specify the `md-no-focus` class, to disable the focus style
14040          // on the button executor. Once more classes should be forwarded, we should probably make the
14041          // class forward more generic.
14042          if (tEl.hasClass('md-no-focus')) {
14043            buttonWrap.addClass('md-no-focus');
14044          }
14045
14046          // Append the button wrap before our list-item content, because it will overlay in relative.
14047          itemContainer.prepend(buttonWrap);
14048          itemContainer.children().eq(1).append(tEl.contents());
14049
14050          tEl.addClass('_md-button-wrap');
14051        }
14052
14053        tEl[0].setAttribute('tabindex', '-1');
14054        tEl.append(itemContainer);
14055      }
14056
14057      function wrapSecondaryItems() {
14058        var secondaryItemsWrapper = angular.element('<div class="md-secondary-container">');
14059
14060        angular.forEach(secondaryItems, function(secondaryItem) {
14061          wrapSecondaryItem(secondaryItem, secondaryItemsWrapper);
14062        });
14063
14064        itemContainer.append(secondaryItemsWrapper);
14065      }
14066
14067      function wrapSecondaryItem(secondaryItem, container) {
14068        // If the current secondary item is not a button, but contains a ng-click attribute,
14069        // the secondary item will be automatically wrapped inside of a button.
14070        if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) {
14071
14072          $mdAria.expect(secondaryItem, 'aria-label');
14073          var buttonWrapper = angular.element('<md-button class="md-secondary md-icon-button">');
14074
14075          // Copy the attributes from the secondary item to the generated button.
14076          // We also support some additional attributes from the secondary item,
14077          // because some developers may use a ngIf, ngHide, ngShow on their item.
14078          copyAttributes(secondaryItem, buttonWrapper[0], ['ng-if', 'ng-hide', 'ng-show']);
14079
14080          secondaryItem.setAttribute('tabindex', '-1');
14081          buttonWrapper.append(secondaryItem);
14082
14083          secondaryItem = buttonWrapper[0];
14084        }
14085
14086        if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) {
14087          // In this case we remove the secondary class, so we can identify it later, when we searching for the
14088          // proxy items.
14089          angular.element(secondaryItem).removeClass('md-secondary');
14090        }
14091
14092        tEl.addClass('md-with-secondary');
14093        container.append(secondaryItem);
14094      }
14095
14096      /**
14097       * Copies attributes from a source element to the destination element
14098       * By default the function will copy the most necessary attributes, supported
14099       * by the button executor for clickable list items.
14100       * @param source Element with the specified attributes
14101       * @param destination Element which will retrieve the attributes
14102       * @param extraAttrs Additional attributes, which will be copied over.
14103       */
14104      function copyAttributes(source, destination, extraAttrs) {
14105        var copiedAttrs = $mdUtil.prefixer([
14106          'ng-if', 'ng-click', 'ng-dblclick', 'aria-label', 'ng-disabled', 'ui-sref',
14107          'href', 'ng-href', 'rel', 'target', 'ng-attr-ui-sref', 'ui-sref-opts'
14108        ]);
14109
14110        if (extraAttrs) {
14111          copiedAttrs = copiedAttrs.concat($mdUtil.prefixer(extraAttrs));
14112        }
14113
14114        angular.forEach(copiedAttrs, function(attr) {
14115          if (source.hasAttribute(attr)) {
14116            destination.setAttribute(attr, source.getAttribute(attr));
14117            source.removeAttribute(attr);
14118          }
14119        });
14120      }
14121
14122      function isProxiedElement(el) {
14123        return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1;
14124      }
14125
14126      function isButton(el) {
14127        var nodeName = el.nodeName.toUpperCase();
14128
14129        return nodeName == "MD-BUTTON" || nodeName == "BUTTON";
14130      }
14131
14132      function hasClickEvent (element) {
14133        var attr = element.attributes;
14134        for (var i = 0; i < attr.length; i++) {
14135          if (tAttrs.$normalize(attr[i].name) === 'ngClick') return true;
14136        }
14137        return false;
14138      }
14139
14140      return postLink;
14141
14142      function postLink($scope, $element, $attr, ctrl) {
14143        $element.addClass('_md');     // private md component indicator for styling
14144
14145        var proxies       = [],
14146            firstElement  = $element[0].firstElementChild,
14147            isButtonWrap  = $element.hasClass('_md-button-wrap'),
14148            clickChild    = isButtonWrap ? firstElement.firstElementChild : firstElement,
14149            hasClick      = clickChild && hasClickEvent(clickChild),
14150            noProxies     = $element.hasClass('md-no-proxy');
14151
14152        computeProxies();
14153        computeClickable();
14154
14155        if (proxies.length) {
14156          angular.forEach(proxies, function(proxy) {
14157            proxy = angular.element(proxy);
14158
14159            $scope.mouseActive = false;
14160            proxy.on('mousedown', function() {
14161              $scope.mouseActive = true;
14162              $timeout(function(){
14163                $scope.mouseActive = false;
14164              }, 100);
14165            })
14166            .on('focus', function() {
14167              if ($scope.mouseActive === false) { $element.addClass('md-focused'); }
14168              proxy.on('blur', function proxyOnBlur() {
14169                $element.removeClass('md-focused');
14170                proxy.off('blur', proxyOnBlur);
14171              });
14172            });
14173          });
14174        }
14175
14176
14177        function computeProxies() {
14178
14179          if (firstElement && firstElement.children && !hasClick && !noProxies) {
14180
14181            angular.forEach(proxiedTypes, function(type) {
14182
14183              // All elements which are not capable for being used a proxy have the .md-secondary class
14184              // applied. These items had been sorted out in the secondary wrap function.
14185              angular.forEach(firstElement.querySelectorAll(type + ':not(.md-secondary)'), function(child) {
14186                proxies.push(child);
14187              });
14188            });
14189
14190          }
14191        }
14192
14193        function computeClickable() {
14194          if (proxies.length == 1 || hasClick) {
14195            $element.addClass('md-clickable');
14196
14197            if (!hasClick) {
14198              ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style')));
14199            }
14200          }
14201        }
14202
14203        function isEventFromControl(event) {
14204          var forbiddenControls = ['md-slider'];
14205
14206          // If there is no path property in the event, then we can assume that the event was not bubbled.
14207          if (!event.path) {
14208            return forbiddenControls.indexOf(event.target.tagName.toLowerCase()) !== -1;
14209          }
14210
14211          // We iterate the event path up and check for a possible component.
14212          // Our maximum index to search, is the list item root.
14213          var maxPath = event.path.indexOf($element.children()[0]);
14214
14215          for (var i = 0; i < maxPath; i++) {
14216            if (forbiddenControls.indexOf(event.path[i].tagName.toLowerCase()) !== -1) {
14217              return true;
14218            }
14219          }
14220        }
14221
14222        var clickChildKeypressListener = function(e) {
14223          if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) {
14224            var keyCode = e.which || e.keyCode;
14225            if (keyCode == $mdConstant.KEY_CODE.SPACE) {
14226              if (clickChild) {
14227                clickChild.click();
14228                e.preventDefault();
14229                e.stopPropagation();
14230              }
14231            }
14232          }
14233        };
14234
14235        if (!hasClick && !proxies.length) {
14236          clickChild && clickChild.addEventListener('keypress', clickChildKeypressListener);
14237        }
14238
14239        $element.off('click');
14240        $element.off('keypress');
14241
14242        if (proxies.length == 1 && clickChild) {
14243          $element.children().eq(0).on('click', function(e) {
14244            // When the event is coming from an control and it should not trigger the proxied element
14245            // then we are skipping.
14246            if (isEventFromControl(e)) return;
14247
14248            var parentButton = $mdUtil.getClosest(e.target, 'BUTTON');
14249            if (!parentButton && clickChild.contains(e.target)) {
14250              angular.forEach(proxies, function(proxy) {
14251                if (e.target !== proxy && !proxy.contains(e.target)) {
14252                  if (proxy.nodeName === 'MD-MENU') {
14253                    proxy = proxy.children[0];
14254                  }
14255                  angular.element(proxy).triggerHandler('click');
14256                }
14257              });
14258            }
14259          });
14260        }
14261
14262        $scope.$on('$destroy', function () {
14263          clickChild && clickChild.removeEventListener('keypress', clickChildKeypressListener);
14264        });
14265      }
14266    }
14267  };
14268}
14269
14270/*
14271 * @private
14272 * @ngdoc controller
14273 * @name MdListController
14274 * @module material.components.list
14275 *
14276 */
14277function MdListController($scope, $element, $mdListInkRipple) {
14278  var ctrl = this;
14279  ctrl.attachRipple = attachRipple;
14280
14281  function attachRipple (scope, element) {
14282    var options = {};
14283    $mdListInkRipple.attach(scope, element, options);
14284  }
14285}
14286
14287})();
14288(function(){
14289"use strict";
14290
14291/**
14292 * @ngdoc module
14293 * @name material.components.menu
14294 */
14295
14296angular.module('material.components.menu', [
14297  'material.core',
14298  'material.components.backdrop'
14299]);
14300
14301})();
14302(function(){
14303"use strict";
14304
14305/**
14306 * @ngdoc module
14307 * @name material.components.menuBar
14308 */
14309
14310angular.module('material.components.menuBar', [
14311  'material.core',
14312  'material.components.icon',
14313  'material.components.menu'
14314]);
14315
14316})();
14317(function(){
14318"use strict";
14319
14320/**
14321 * @ngdoc module
14322 * @name material.components.navBar
14323 */
14324
14325
14326MdNavBarController.$inject = ["$element", "$scope", "$timeout", "$mdConstant"];
14327MdNavItem.$inject = ["$mdAria", "$$rAF"];
14328MdNavItemController.$inject = ["$element"];
14329MdNavBar.$inject = ["$mdAria", "$mdTheming"];
14330angular.module('material.components.navBar', ['material.core'])
14331    .controller('MdNavBarController', MdNavBarController)
14332    .directive('mdNavBar', MdNavBar)
14333    .controller('MdNavItemController', MdNavItemController)
14334    .directive('mdNavItem', MdNavItem);
14335
14336
14337/*****************************************************************************
14338 *                            PUBLIC DOCUMENTATION                           *
14339 *****************************************************************************/
14340/**
14341 * @ngdoc directive
14342 * @name mdNavBar
14343 * @module material.components.navBar
14344 *
14345 * @restrict E
14346 *
14347 * @description
14348 * The `<md-nav-bar>` directive renders a list of material tabs that can be used
14349 * for top-level page navigation. Unlike `<md-tabs>`, it has no concept of a tab
14350 * body and no bar pagination.
14351 *
14352 * Because it deals with page navigation, certain routing concepts are built-in.
14353 * Route changes via via ng-href, ui-sref, or ng-click events are supported.
14354 * Alternatively, the user could simply watch currentNavItem for changes.
14355 *
14356 * Accessibility functionality is implemented as a site navigator with a
14357 * listbox, according to
14358 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style
14359 *
14360 * @param {string=} mdSelectedNavItem The name of the current tab; this must
14361 *     match the name attribute of `<md-nav-item>`
14362 * @param {boolean=} mdNoInkBar If set to true, the ink bar will be hidden.
14363 * @param {string=} navBarAriaLabel An aria-label for the nav-bar
14364 *
14365 * @usage
14366 * <hljs lang="html">
14367 *  <md-nav-bar md-selected-nav-item="currentNavItem">
14368 *    <md-nav-item md-nav-click="goto('page1')" name="page1">
14369 *      Page One
14370 *    </md-nav-item>
14371 *    <md-nav-item md-nav-href="#page2" name="page3">Page Two</md-nav-item>
14372 *    <md-nav-item md-nav-sref="page3" name="page2">Page Three</md-nav-item>
14373 *    <md-nav-item
14374 *      md-nav-sref="app.page4"
14375 *      sref-opts="{reload: true, notify: true}"
14376 *      name="page4">
14377 *      Page Four
14378 *    </md-nav-item>
14379 *  </md-nav-bar>
14380 *</hljs>
14381 * <hljs lang="js">
14382 * (function() {
14383 *   'use strict';
14384 *
14385 *    $rootScope.$on('$routeChangeSuccess', function(event, current) {
14386 *      $scope.currentLink = getCurrentLinkFromRoute(current);
14387 *    });
14388 * });
14389 * </hljs>
14390 */
14391
14392/*****************************************************************************
14393 *                            mdNavItem
14394 *****************************************************************************/
14395/**
14396 * @ngdoc directive
14397 * @name mdNavItem
14398 * @module material.components.navBar
14399 *
14400 * @restrict E
14401 *
14402 * @description
14403 * `<md-nav-item>` describes a page navigation link within the `<md-nav-bar>`
14404 * component. It renders an md-button as the actual link.
14405 *
14406 * Exactly one of the mdNavClick, mdNavHref, mdNavSref attributes are required
14407 * to be specified.
14408 *
14409 * @param {Function=} mdNavClick Function which will be called when the
14410 *     link is clicked to change the page. Renders as an `ng-click`.
14411 * @param {string=} mdNavHref url to transition to when this link is clicked.
14412 *     Renders as an `ng-href`.
14413 * @param {string=} mdNavSref Ui-router state to transition to when this link is
14414 *     clicked. Renders as a `ui-sref`.
14415 * @param {!Object=} srefOpts Ui-router options that are passed to the
14416 *     `$state.go()` function. See the [Ui-router documentation for details]
14417 *     (https://ui-router.github.io/docs/latest/interfaces/transition.transitionoptions.html).
14418 * @param {string=} name The name of this link. Used by the nav bar to know
14419 *     which link is currently selected.
14420 * @param {string=} aria-label Adds alternative text for accessibility
14421 *
14422 * @usage
14423 * See `<md-nav-bar>` for usage.
14424 */
14425
14426
14427/*****************************************************************************
14428 *                                IMPLEMENTATION                             *
14429 *****************************************************************************/
14430
14431function MdNavBar($mdAria, $mdTheming) {
14432  return {
14433    restrict: 'E',
14434    transclude: true,
14435    controller: MdNavBarController,
14436    controllerAs: 'ctrl',
14437    bindToController: true,
14438    scope: {
14439      'mdSelectedNavItem': '=?',
14440      'mdNoInkBar': '=?',
14441      'navBarAriaLabel': '@?',
14442    },
14443    template:
14444      '<div class="md-nav-bar">' +
14445        '<nav role="navigation">' +
14446          '<ul class="_md-nav-bar-list" ng-transclude role="listbox"' +
14447            'tabindex="0"' +
14448            'ng-focus="ctrl.onFocus()"' +
14449            'ng-keydown="ctrl.onKeydown($event)"' +
14450            'aria-label="{{ctrl.navBarAriaLabel}}">' +
14451          '</ul>' +
14452        '</nav>' +
14453        '<md-nav-ink-bar ng-hide="ctrl.mdNoInkBar"></md-nav-ink-bar>' +
14454      '</div>',
14455    link: function(scope, element, attrs, ctrl) {
14456      $mdTheming(element);
14457      if (!ctrl.navBarAriaLabel) {
14458        $mdAria.expectAsync(element, 'aria-label', angular.noop);
14459      }
14460    },
14461  };
14462}
14463
14464/**
14465 * Controller for the nav-bar component.
14466 *
14467 * Accessibility functionality is implemented as a site navigator with a
14468 * listbox, according to
14469 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style
14470 * @param {!angular.JQLite} $element
14471 * @param {!angular.Scope} $scope
14472 * @param {!angular.Timeout} $timeout
14473 * @param {!Object} $mdConstant
14474 * @constructor
14475 * @final
14476 * @ngInject
14477 */
14478function MdNavBarController($element, $scope, $timeout, $mdConstant) {
14479  // Injected variables
14480  /** @private @const {!angular.Timeout} */
14481  this._$timeout = $timeout;
14482
14483  /** @private @const {!angular.Scope} */
14484  this._$scope = $scope;
14485
14486  /** @private @const {!Object} */
14487  this._$mdConstant = $mdConstant;
14488
14489  // Data-bound variables.
14490  /** @type {string} */
14491  this.mdSelectedNavItem;
14492
14493  /** @type {string} */
14494  this.navBarAriaLabel;
14495
14496  // State variables.
14497
14498  /** @type {?angular.JQLite} */
14499  this._navBarEl = $element[0];
14500
14501  /** @type {?angular.JQLite} */
14502  this._inkbar;
14503
14504  var self = this;
14505  // need to wait for transcluded content to be available
14506  var deregisterTabWatch = this._$scope.$watch(function() {
14507    return self._navBarEl.querySelectorAll('._md-nav-button').length;
14508  },
14509  function(newLength) {
14510    if (newLength > 0) {
14511      self._initTabs();
14512      deregisterTabWatch();
14513    }
14514  });
14515}
14516
14517
14518
14519/**
14520 * Initializes the tab components once they exist.
14521 * @private
14522 */
14523MdNavBarController.prototype._initTabs = function() {
14524  this._inkbar = angular.element(this._navBarEl.querySelector('md-nav-ink-bar'));
14525
14526  var self = this;
14527  this._$timeout(function() {
14528    self._updateTabs(self.mdSelectedNavItem, undefined);
14529  });
14530
14531  this._$scope.$watch('ctrl.mdSelectedNavItem', function(newValue, oldValue) {
14532    // Wait a digest before update tabs for products doing
14533    // anything dynamic in the template.
14534    self._$timeout(function() {
14535      self._updateTabs(newValue, oldValue);
14536    });
14537  });
14538};
14539
14540/**
14541 * Set the current tab to be selected.
14542 * @param {string|undefined} newValue New current tab name.
14543 * @param {string|undefined} oldValue Previous tab name.
14544 * @private
14545 */
14546MdNavBarController.prototype._updateTabs = function(newValue, oldValue) {
14547  var self = this;
14548  var tabs = this._getTabs();
14549
14550  // this._getTabs can return null if nav-bar has not yet been initialized
14551  if(!tabs)
14552    return;
14553
14554  var oldIndex = -1;
14555  var newIndex = -1;
14556  var newTab = this._getTabByName(newValue);
14557  var oldTab = this._getTabByName(oldValue);
14558
14559  if (oldTab) {
14560    oldTab.setSelected(false);
14561    oldIndex = tabs.indexOf(oldTab);
14562  }
14563
14564  if (newTab) {
14565    newTab.setSelected(true);
14566    newIndex = tabs.indexOf(newTab);
14567  }
14568
14569  this._$timeout(function() {
14570    self._updateInkBarStyles(newTab, newIndex, oldIndex);
14571  });
14572};
14573
14574/**
14575 * Repositions the ink bar to the selected tab.
14576 * @private
14577 */
14578MdNavBarController.prototype._updateInkBarStyles = function(tab, newIndex, oldIndex) {
14579  this._inkbar.toggleClass('_md-left', newIndex < oldIndex)
14580      .toggleClass('_md-right', newIndex > oldIndex);
14581
14582  this._inkbar.css({display: newIndex < 0 ? 'none' : ''});
14583
14584  if (tab) {
14585    var tabEl = tab.getButtonEl();
14586    var left = tabEl.offsetLeft;
14587
14588    this._inkbar.css({left: left + 'px', width: tabEl.offsetWidth + 'px'});
14589  }
14590};
14591
14592/**
14593 * Returns an array of the current tabs.
14594 * @return {!Array<!NavItemController>}
14595 * @private
14596 */
14597MdNavBarController.prototype._getTabs = function() {
14598  var controllers = Array.prototype.slice.call(
14599    this._navBarEl.querySelectorAll('.md-nav-item'))
14600    .map(function(el) {
14601      return angular.element(el).controller('mdNavItem')
14602    });
14603  return controllers.indexOf(undefined) ? controllers : null;
14604};
14605
14606/**
14607 * Returns the tab with the specified name.
14608 * @param {string} name The name of the tab, found in its name attribute.
14609 * @return {!NavItemController|undefined}
14610 * @private
14611 */
14612MdNavBarController.prototype._getTabByName = function(name) {
14613  return this._findTab(function(tab) {
14614    return tab.getName() == name;
14615  });
14616};
14617
14618/**
14619 * Returns the selected tab.
14620 * @return {!NavItemController|undefined}
14621 * @private
14622 */
14623MdNavBarController.prototype._getSelectedTab = function() {
14624  return this._findTab(function(tab) {
14625    return tab.isSelected();
14626  });
14627};
14628
14629/**
14630 * Returns the focused tab.
14631 * @return {!NavItemController|undefined}
14632 */
14633MdNavBarController.prototype.getFocusedTab = function() {
14634  return this._findTab(function(tab) {
14635    return tab.hasFocus();
14636  });
14637};
14638
14639/**
14640 * Find a tab that matches the specified function.
14641 * @private
14642 */
14643MdNavBarController.prototype._findTab = function(fn) {
14644  var tabs = this._getTabs();
14645  for (var i = 0; i < tabs.length; i++) {
14646    if (fn(tabs[i])) {
14647      return tabs[i];
14648    }
14649  }
14650
14651  return null;
14652};
14653
14654/**
14655 * Direct focus to the selected tab when focus enters the nav bar.
14656 */
14657MdNavBarController.prototype.onFocus = function() {
14658  var tab = this._getSelectedTab();
14659  if (tab) {
14660    tab.setFocused(true);
14661  }
14662};
14663
14664/**
14665 * Move focus from oldTab to newTab.
14666 * @param {!NavItemController} oldTab
14667 * @param {!NavItemController} newTab
14668 * @private
14669 */
14670MdNavBarController.prototype._moveFocus = function(oldTab, newTab) {
14671  oldTab.setFocused(false);
14672  newTab.setFocused(true);
14673};
14674
14675/**
14676 * Responds to keypress events.
14677 * @param {!Event} e
14678 */
14679MdNavBarController.prototype.onKeydown = function(e) {
14680  var keyCodes = this._$mdConstant.KEY_CODE;
14681  var tabs = this._getTabs();
14682  var focusedTab = this.getFocusedTab();
14683  if (!focusedTab) return;
14684
14685  var focusedTabIndex = tabs.indexOf(focusedTab);
14686
14687  // use arrow keys to navigate between tabs
14688  switch (e.keyCode) {
14689    case keyCodes.UP_ARROW:
14690    case keyCodes.LEFT_ARROW:
14691      if (focusedTabIndex > 0) {
14692        this._moveFocus(focusedTab, tabs[focusedTabIndex - 1]);
14693      }
14694      break;
14695    case keyCodes.DOWN_ARROW:
14696    case keyCodes.RIGHT_ARROW:
14697      if (focusedTabIndex < tabs.length - 1) {
14698        this._moveFocus(focusedTab, tabs[focusedTabIndex + 1]);
14699      }
14700      break;
14701    case keyCodes.SPACE:
14702    case keyCodes.ENTER:
14703      // timeout to avoid a "digest already in progress" console error
14704      this._$timeout(function() {
14705        focusedTab.getButtonEl().click();
14706      });
14707      break;
14708  }
14709};
14710
14711/**
14712 * @ngInject
14713 */
14714function MdNavItem($mdAria, $$rAF) {
14715  return {
14716    restrict: 'E',
14717    require: ['mdNavItem', '^mdNavBar'],
14718    controller: MdNavItemController,
14719    bindToController: true,
14720    controllerAs: 'ctrl',
14721    replace: true,
14722    transclude: true,
vendor: 6,215 bytes, lines 14723-14952
14723    template: function(tElement, tAttrs) {
14724      var hasNavClick = tAttrs.mdNavClick;
14725      var hasNavHref = tAttrs.mdNavHref;
14726      var hasNavSref = tAttrs.mdNavSref;
14727      var hasSrefOpts = tAttrs.srefOpts;
14728      var navigationAttribute;
14729      var navigationOptions;
14730      var buttonTemplate;
14731
14732      // Cannot specify more than one nav attribute
14733      if ((hasNavClick ? 1:0) + (hasNavHref ? 1:0) + (hasNavSref ? 1:0) > 1) {
14734        throw Error(
14735          'Must not specify more than one of the md-nav-click, md-nav-href, ' +
14736          'or md-nav-sref attributes per nav-item directive.'
14737        );
14738      }
14739
14740      if (hasNavClick) {
14741        navigationAttribute = 'ng-click="ctrl.mdNavClick()"';
14742      } else if (hasNavHref) {
14743        navigationAttribute = 'ng-href="{{ctrl.mdNavHref}}"';
14744      } else if (hasNavSref) {
14745        navigationAttribute = 'ui-sref="{{ctrl.mdNavSref}}"';
14746      }
14747
14748      navigationOptions = hasSrefOpts ? 'ui-sref-opts="{{ctrl.srefOpts}}" ' : '';
14749
14750      if (navigationAttribute) {
14751        buttonTemplate = '' +
14752          '<md-button class="_md-nav-button md-accent" ' +
14753            'ng-class="ctrl.getNgClassMap()" ' +
14754            'ng-blur="ctrl.setFocused(false)" ' +
14755            'tabindex="-1" ' +
14756            navigationOptions +
14757            navigationAttribute + '>' +
14758            '<span ng-transclude class="_md-nav-button-text"></span>' +
14759          '</md-button>';
14760      }
14761
14762      return '' +
14763        '<li class="md-nav-item" ' +
14764          'role="option" ' +
14765          'aria-selected="{{ctrl.isSelected()}}">' +
14766          (buttonTemplate || '') +
14767        '</li>';
14768    },
14769    scope: {
14770      'mdNavClick': '&?',
14771      'mdNavHref': '@?',
14772      'mdNavSref': '@?',
14773      'srefOpts': '=?',
14774      'name': '@',
14775    },
14776    link: function(scope, element, attrs, controllers) {
14777      // When accessing the element's contents synchronously, they
14778      // may not be defined yet because of transclusion. There is a higher
14779      // chance that it will be accessible if we wait one frame.
14780      $$rAF(function() {
14781        var mdNavItem = controllers[0];
14782        var mdNavBar = controllers[1];
14783        var navButton = angular.element(element[0].querySelector('._md-nav-button'));
14784
14785        if (!mdNavItem.name) {
14786          mdNavItem.name = angular.element(element[0]
14787              .querySelector('._md-nav-button-text')).text().trim();
14788        }
14789
14790        navButton.on('click', function() {
14791          mdNavBar.mdSelectedNavItem = mdNavItem.name;
14792          scope.$apply();
14793        });
14794
14795        $mdAria.expectWithText(element, 'aria-label');
14796      });
14797    }
14798  };
14799}
14800
14801/**
14802 * Controller for the nav-item component.
14803 * @param {!angular.JQLite} $element
14804 * @constructor
14805 * @final
14806 * @ngInject
14807 */
14808function MdNavItemController($element) {
14809
14810  /** @private @const {!angular.JQLite} */
14811  this._$element = $element;
14812
14813  // Data-bound variables
14814
14815  /** @const {?Function} */
14816  this.mdNavClick;
14817
14818  /** @const {?string} */
14819  this.mdNavHref;
14820
14821  /** @const {?string} */
14822  this.mdNavSref;
14823  /** @const {?Object} */
14824  this.srefOpts;
14825  /** @const {?string} */
14826  this.name;
14827
14828  // State variables
14829  /** @private {boolean} */
14830  this._selected = false;
14831
14832  /** @private {boolean} */
14833  this._focused = false;
14834}
14835
14836/**
14837 * Returns a map of class names and values for use by ng-class.
14838 * @return {!Object<string,boolean>}
14839 */
14840MdNavItemController.prototype.getNgClassMap = function() {
14841  return {
14842    'md-active': this._selected,
14843    'md-primary': this._selected,
14844    'md-unselected': !this._selected,
14845    'md-focused': this._focused,
14846  };
14847};
14848
14849/**
14850 * Get the name attribute of the tab.
14851 * @return {string}
14852 */
14853MdNavItemController.prototype.getName = function() {
14854  return this.name;
14855};
14856
14857/**
14858 * Get the button element associated with the tab.
14859 * @return {!Element}
14860 */
14861MdNavItemController.prototype.getButtonEl = function() {
14862  return this._$element[0].querySelector('._md-nav-button');
14863};
14864
14865/**
14866 * Set the selected state of the tab.
14867 * @param {boolean} isSelected
14868 */
14869MdNavItemController.prototype.setSelected = function(isSelected) {
14870  this._selected = isSelected;
14871};
14872
14873/**
14874 * @return {boolean}
14875 */
14876MdNavItemController.prototype.isSelected = function() {
14877  return this._selected;
14878};
14879
14880/**
14881 * Set the focused state of the tab.
14882 * @param {boolean} isFocused
14883 */
14884MdNavItemController.prototype.setFocused = function(isFocused) {
14885  this._focused = isFocused;
14886
14887  if (isFocused) {
14888    this.getButtonEl().focus();
14889  }
14890};
14891
14892/**
14893 * @return {boolean}
14894 */
14895MdNavItemController.prototype.hasFocus = function() {
14896  return this._focused;
14897};
14898
14899})();
14900(function(){
14901"use strict";
14902
14903/**
14904 * @ngdoc module
14905 * @name material.components.panel
14906 */
14907MdPanelService.$inject = ["presets", "$rootElement", "$rootScope", "$injector", "$window"];
14908angular
14909  .module('material.components.panel', [
14910    'material.core',
14911    'material.components.backdrop'
14912  ])
14913  .provider('$mdPanel', MdPanelProvider);
14914
14915
14916/*****************************************************************************
14917 *                            PUBLIC DOCUMENTATION                           *
14918 *****************************************************************************/
14919
14920
14921/**
14922 * @ngdoc service
14923 * @name $mdPanelProvider
14924 * @module material.components.panel
14925 *
14926 * @description
14927 * `$mdPanelProvider` allows users to create configuration presets that will be
14928 * stored within a cached presets object. When the configuration is needed, the
14929 * user can request the preset by passing it as the first parameter in the
14930 * `$mdPanel.create` or `$mdPanel.open` methods.
14931 *
14932 * @usage
14933 * <hljs lang="js">
14934 * (function(angular, undefined) {
14935 *   'use strict';
14936 *
14937 *   angular
14938 *       .module('demoApp', ['ngMaterial'])
14939 *       .config(DemoConfig)
14940 *       .controller('DemoCtrl', DemoCtrl)
14941 *       .controller('DemoMenuCtrl', DemoMenuCtrl);
14942 *
14943 *   function DemoConfig($mdPanelProvider) {
14944 *     $mdPanelProvider.definePreset('demoPreset', {
14945 *       attachTo: angular.element(document.body),
14946 *       controller: DemoMenuCtrl,
14947 *       controllerAs: 'ctrl',
14948 *       template: '' +
14949 *           '<div class="menu-panel" md-whiteframe="4">' +
14950 *           '  <div class="menu-content">' +
14951 *           '    <div class="menu-item" ng-repeat="item in ctrl.items">' +
14952 *           '      <button class="md-button">' +
14953 *           '        <span>{{item}}</span>' +
14954 *           '      </button>' +
14955 *           '    </div>' +
14956 *           '    <md-divider></md-divider>' +
14957 *           '    <div class="menu-item">' +
14958 *           '      <button class="md-button" ng-click="ctrl.closeMenu()">' +
14959 *           '        <span>Close Menu</span>' +
14960 *           '      </button>' +
14961 *           '    </div>' +
14962 *           '  </div>' +
14963 *           '</div>',
14964 *       panelClass: 'menu-panel-container',
14965 *       focusOnOpen: false,
14966 *       zIndex: 100,
14967 *       propagateContainerEvents: true,
14968 *       groupName: 'menus'
14969 *     });
14970 *   }
14971 *
14972 *   function PanelProviderCtrl($mdPanel) {
14973 *     this.navigation = {
14974 *       name: 'navigation',
14975 *       items: [
14976 *         'Home',
14977 *         'About',
14978 *         'Contact'
14979 *       ]
14980 *     };
14981 *     this.favorites = {
14982 *       name: 'favorites',
14983 *       items: [
14984 *         'Add to Favorites'
14985 *       ]
14986 *     };
14987 *     this.more = {
14988 *       name: 'more',
14989 *       items: [
14990 *         'Account',
14991 *         'Sign Out'
14992 *       ]
14993 *     };
14994 *
14995 *     $mdPanel.newPanelGroup('menus', {
14996 *       maxOpen: 2
14997 *     });
14998 *
14999 *     this.showMenu = function($event, menu) {
15000 *       $mdPanel.open('demoPreset', {
15001 *         id: 'menu_' + menu.name,
15002 *         position: $mdPanel.newPanelPosition()
15003 *             .relativeTo($event.srcElement)
15004 *             .addPanelPosition(
15005 *               $mdPanel.xPosition.ALIGN_START,
15006 *               $mdPanel.yPosition.BELOW
15007 *             ),
15008 *         locals: {
15009 *           items: menu.items
15010 *         },
15011 *         openFrom: $event
15012 *       });
15013 *     };
15014 *   }
15015 *
15016 *   function PanelMenuCtrl(mdPanelRef) {
15017 *     // The controller is provided with an import named 'mdPanelRef'
15018 *     this.closeMenu = function() {
15019 *       mdPanelRef && mdPanelRef.close();
15020 *     };
15021 *   }
15022 * })(angular);
15023 * </hljs>
15024 */
15025
15026/**
15027 * @ngdoc method
15028 * @name $mdPanelProvider#definePreset
15029 * @description
15030 * Takes the passed in preset name and preset configuration object and adds it
15031 * to the `_presets` object of the provider. This `_presets` object is then
15032 * passed along to the `$mdPanel` service.
15033 *
15034 * @param {string} name Preset name.
15035 * @param {!Object} preset Specific configuration object that can contain any
15036 *     and all of the parameters avaialble within the `$mdPanel.create` method.
15037 *     However, parameters that pertain to id, position, animation, and user
15038 *     interaction are not allowed and will be removed from the preset
15039 *     configuration.
15040 */
15041
15042
15043/*****************************************************************************
15044 *                               MdPanel Service                             *
15045 *****************************************************************************/
15046
15047
15048/**
15049 * @ngdoc service
15050 * @name $mdPanel
15051 * @module material.components.panel
15052 *
15053 * @description
15054 * `$mdPanel` is a robust, low-level service for creating floating panels on
15055 * the screen. It can be used to implement tooltips, dialogs, pop-ups, etc.
15056 *
15057 * @usage
15058 * <hljs lang="js">
15059 * (function(angular, undefined) {
15060 *   'use strict';
15061 *
15062 *   angular
15063 *       .module('demoApp', ['ngMaterial'])
15064 *       .controller('DemoDialogController', DialogController);
15065 *
15066 *   var panelRef;
15067 *
15068 *   function showPanel($event) {
15069 *     var panelPosition = $mdPanel.newPanelPosition()
15070 *         .absolute()
15071 *         .top('50%')
15072 *         .left('50%');
15073 *
15074 *     var panelAnimation = $mdPanel.newPanelAnimation()
15075 *         .targetEvent($event)
15076 *         .defaultAnimation('md-panel-animate-fly')
15077 *         .closeTo('.show-button');
15078 *
15079 *     var config = {
15080 *       attachTo: angular.element(document.body),
15081 *       controller: DialogController,
15082 *       controllerAs: 'ctrl',
15083 *       position: panelPosition,
15084 *       animation: panelAnimation,
15085 *       targetEvent: $event,
15086 *       templateUrl: 'dialog-template.html',
15087 *       clickOutsideToClose: true,
15088 *       escapeToClose: true,
15089 *       focusOnOpen: true
15090 *     };
15091 *
15092 *     $mdPanel.open(config)
15093 *         .then(function(result) {
15094 *           panelRef = result;
15095 *         });
15096 *   }
15097 *
15098 *   function DialogController(MdPanelRef) {
15099 *     function closeDialog() {
15100 *       if (MdPanelRef) MdPanelRef.close();
15101 *     }
15102 *   }
15103 * })(angular);
15104 * </hljs>
15105 */
15106
15107/**
15108 * @ngdoc method
15109 * @name $mdPanel#create
15110 * @description
15111 * Creates a panel with the specified options.
15112 *
15113 * @param config {!Object=} Specific configuration object that may contain the
15114 *     following properties:
15115 *
15116 *   - `id` - `{string=}`: An ID to track the panel by. When an ID is provided,
15117 *     the created panel is added to a tracked panels object. Any subsequent
15118 *     requests made to create a panel with that ID are ignored. This is useful
15119 *     in having the panel service not open multiple panels from the same user
15120 *     interaction when there is no backdrop and events are propagated. Defaults
15121 *     to an arbitrary string that is not tracked.
15122 *   - `template` - `{string=}`: HTML template to show in the panel. This
15123 *     **must** be trusted HTML with respect to AngularJS’s
15124 *     [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
15125 *   - `templateUrl` - `{string=}`: The URL that will be used as the content of
15126 *     the panel.
15127 *   - `contentElement` - `{(string|!angular.JQLite|!Element)=}`: Pre-compiled
15128 *     element to be used as the panel's content.
15129 *   - `controller` - `{(function|string)=}`: The controller to associate with
15130 *     the panel. The controller can inject a reference to the returned
15131 *     panelRef, which allows the panel to be closed, hidden, and shown. Any
15132 *     fields passed in through locals or resolve will be bound to the
15133 *     controller.
15134 *   - `controllerAs` - `{string=}`: An alias to assign the controller to on
15135 *     the scope.
15136 *   - `bindToController` - `{boolean=}`: Binds locals to the controller
15137 *     instead of passing them in. Defaults to true, as this is a best
15138 *     practice.
15139 *   - `locals` - `{Object=}`: An object containing key/value pairs. The keys
15140 *     will be used as names of values to inject into the controller. For
15141 *     example, `locals: {three: 3}` would inject `three` into the controller,
15142 *     with the value 3. 'mdPanelRef' is a reserved key, and will always 
15143 *     be set to the created MdPanelRef instance.
15144 *   - `resolve` - `{Object=}`: Similar to locals, except it takes promises as
15145 *     values. The panel will not open until all of the promises resolve.
15146 *   - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to
15147 *     attach the panel to. Defaults to appending to the root element of the
15148 *     application.
15149 *   - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch
15150 *     events should be allowed to propagate 'go through' the container, aka the
15151 *     wrapper, of the panel. Defaults to false.
15152 *   - `panelClass` - `{string=}`: A css class to apply to the panel element.
15153 *     This class should define any borders, box-shadow, etc. for the panel.
15154 *   - `zIndex` - `{number=}`: The z-index to place the panel at.
15155 *     Defaults to 80.
15156 *   - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that
15157 *     specifies the alignment of the panel. For more information, see
15158 *     `MdPanelPosition`.
15159 *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click
15160 *     outside the panel to close it. Defaults to false.
15161 *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to
15162 *     close the panel. Defaults to false.
15163 *   - `onCloseSuccess` - `{function(!panelRef, string)=}`: Function that is
15164 *     called after the close successfully finishes. The first parameter passed
15165 *     into this function is the current panelRef and the 2nd is an optional
15166 *     string explaining the close reason. The currently supported closeReasons
15167 *     can be found in the MdPanelRef.closeReasons enum. These are by default
15168 *     passed along by the panel.
15169 *   - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the
15170 *     panel. If `trapFocus` is true, the user will not be able to interact
15171 *     with the rest of the page until the panel is dismissed. Defaults to
15172 *     false.
15173 *   - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on
15174 *     open. Only disable if focusing some other way, as focus management is
15175 *     required for panels to be accessible. Defaults to true.
15176 *   - `fullscreen` - `{boolean=}`: Whether the panel should be full screen.
15177 *     Applies the class `._md-panel-fullscreen` to the panel on open. Defaults
15178 *     to false.
15179 *   - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that
15180 *     specifies the animation of the panel. For more information, see
15181 *     `MdPanelAnimation`.
15182 *   - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop
15183 *     behind the panel. Defaults to false.
15184 *   - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the
15185 *     page behind the panel. Defaults to false.
15186 *   - `onDomAdded` - `{function=}`: Callback function used to announce when
15187 *     the panel is added to the DOM.
15188 *   - `onOpenComplete` - `{function=}`: Callback function used to announce
15189 *     when the open() action is finished.
15190 *   - `onRemoving` - `{function=}`: Callback function used to announce the
15191 *     close/hide() action is starting.
15192 *   - `onDomRemoved` - `{function=}`: Callback function used to announce when
15193 *     the panel is removed from the DOM.
15194 *   - `origin` - `{(string|!angular.JQLite|!Element)=}`: The element to focus
15195 *     on when the panel closes. This is commonly the element which triggered
15196 *     the opening of the panel. If you do not use `origin`, you need to control
15197 *     the focus manually.
15198 *   - `groupName` - `{(string|!Array<string>)=}`: A group name or an array of
15199 *     group names. The group name is used for creating a group of panels. The
15200 *     group is used for configuring the number of open panels and identifying
15201 *     specific behaviors for groups. For instance, all tooltips could be
15202 *     identified using the same groupName.
15203 *
15204 * @returns {!MdPanelRef} panelRef
15205 */
15206
15207/**
15208 * @ngdoc method
15209 * @name $mdPanel#open
15210 * @description
15211 * Calls the create method above, then opens the panel. This is a shortcut for
15212 * creating and then calling open manually. If custom methods need to be
15213 * called when the panel is added to the DOM or opened, do not use this method.
15214 * Instead create the panel, chain promises on the domAdded and openComplete
15215 * methods, and call open from the returned panelRef.
15216 *
15217 * @param {!Object=} config Specific configuration object that may contain
15218 *     the properties defined in `$mdPanel.create`.
15219 * @returns {!angular.$q.Promise<!MdPanelRef>} panelRef A promise that resolves
15220 *     to an instance of the panel.
15221 */
15222
15223/**
15224 * @ngdoc method
15225 * @name $mdPanel#newPanelPosition
15226 * @description
15227 * Returns a new instance of the MdPanelPosition object. Use this to create
15228 * the position config object.
15229 *
15230 * @returns {!MdPanelPosition} panelPosition
15231 */
15232
15233/**
15234 * @ngdoc method
15235 * @name $mdPanel#newPanelAnimation
15236 * @description
15237 * Returns a new instance of the MdPanelAnimation object. Use this to create
15238 * the animation config object.
15239 *
15240 * @returns {!MdPanelAnimation} panelAnimation
15241 */
15242
15243/**
15244 * @ngdoc method
15245 * @name $mdPanel#newPanelGroup
15246 * @description
15247 * Creates a panel group and adds it to a tracked list of panel groups.
15248 *
15249 * @param {string} groupName Name of the group to create.
15250 * @param {!Object=} config Specific configuration object that may contain the
15251 *     following properties:
15252 *
15253 *   - `maxOpen` - `{number=}`: The maximum number of panels that are allowed to
15254 *     be open within a defined panel group.
15255 *
15256 * @returns {!Object<string,
15257 *     {panels: !Array<!MdPanelRef>,
15258 *     openPanels: !Array<!MdPanelRef>,
15259 *     maxOpen: number}>} panelGroup
15260 */
15261
15262/**
15263 * @ngdoc method
15264 * @name $mdPanel#setGroupMaxOpen
15265 * @description
15266 * Sets the maximum number of panels in a group that can be opened at a given
15267 * time.
15268 *
15269 * @param {string} groupName The name of the group to configure.
15270 * @param {number} maxOpen The maximum number of panels that can be
15271 *     opened. Infinity can be passed in to remove the maxOpen limit.
15272 */
15273
15274
15275/*****************************************************************************
15276 *                                 MdPanelRef                                *
15277 *****************************************************************************/
15278
15279
15280/**
15281 * @ngdoc type
15282 * @name MdPanelRef
15283 * @module material.components.panel
15284 * @description
15285 * A reference to a created panel. This reference contains a unique id for the
15286 * panel, along with the following properties:
15287 *
15288 *   - `id` - `{string}`: The unique id for the panel. This id is used to track
15289 *     when a panel was interacted with.
15290 *   - `config` - `{!Object=}`: The entire config object that was used in
15291 *     create.
15292 *   - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM.
15293 *     Visibility to the user does not factor into isAttached.
15294 *   - `panelContainer` - `{angular.JQLite}`: The wrapper element containing the
15295 *     panel. This property is added in order to have access to the `addClass`,
15296 *     `removeClass`, `toggleClass`, etc methods.
15297 *   - `panelEl` - `{angular.JQLite}`: The panel element. This property is added
15298 *     in order to have access to the `addClass`, `removeClass`, `toggleClass`,
15299 *     etc methods.
15300 */
15301
15302/**
15303 * @ngdoc method
15304 * @name MdPanelRef#open
15305 * @description
15306 * Attaches and shows the panel.
15307 *
15308 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
15309 *     opened.
15310 */
15311
15312/**
15313 * @ngdoc method
15314 * @name MdPanelRef#close
15315 * @description
15316 * Hides and detaches the panel. Note that this will **not** destroy the panel.
15317 * If you don't intend on using the panel again, call the {@link #destroy
15318 * destroy} method afterwards.
15319 *
15320 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
15321 *     closed.
15322 */
15323
15324/**
15325 * @ngdoc method
15326 * @name MdPanelRef#attach
15327 * @description
15328 * Create the panel elements and attach them to the DOM. The panel will be
15329 * hidden by default.
15330 *
15331 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
15332 *     attached.
15333 */
15334
15335/**
15336 * @ngdoc method
15337 * @name MdPanelRef#detach
15338 * @description
15339 * Removes the panel from the DOM. This will NOT hide the panel before removing
15340 * it.
15341 *
15342 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
15343 *     detached.
15344 */
15345
15346/**
15347 * @ngdoc method
15348 * @name MdPanelRef#show
15349 * @description
15350 * Shows the panel.
15351 *
15352 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
15353 *     shown and animations are completed.
15354 */
15355
15356/**
15357 * @ngdoc method
15358 * @name MdPanelRef#hide
15359 * @description
15360 * Hides the panel.
15361 *
15362 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
15363 *     hidden and animations are completed.
15364 */
15365
15366/**
15367 * @ngdoc method
15368 * @name MdPanelRef#destroy
15369 * @description
15370 * Destroys the panel. The panel cannot be opened again after this is called.
15371 */
15372
15373/**
15374 * @ngdoc method
15375 * @name MdPanelRef#addClass
15376 * @deprecated
15377 * This method is in the process of being deprecated in favor of using the panel
15378 * and container JQLite elements that are referenced in the MdPanelRef object.
15379 * Full deprecation is scheduled for material 1.2.
15380 * @description
15381 * Adds a class to the panel. DO NOT use this hide/show the panel.
15382 *
15383 * @param {string} newClass class to be added.
15384 * @param {boolean} toElement Whether or not to add the class to the panel
15385 *     element instead of the container.
15386 */
15387
15388/**
15389 * @ngdoc method
15390 * @name MdPanelRef#removeClass
15391 * @deprecated
15392 * This method is in the process of being deprecated in favor of using the panel
15393 * and container JQLite elements that are referenced in the MdPanelRef object.
15394 * Full deprecation is scheduled for material 1.2.
15395 * @description
15396 * Removes a class from the panel. DO NOT use this to hide/show the panel.
15397 *
15398 * @param {string} oldClass Class to be removed.
15399 * @param {boolean} fromElement Whether or not to remove the class from the
15400 *     panel element instead of the container.
15401 */
15402
15403/**
15404 * @ngdoc method
15405 * @name MdPanelRef#toggleClass
15406 * @deprecated
15407 * This method is in the process of being deprecated in favor of using the panel
15408 * and container JQLite elements that are referenced in the MdPanelRef object.
15409 * Full deprecation is scheduled for material 1.2.
15410 * @description
15411 * Toggles a class on the panel. DO NOT use this to hide/show the panel.
15412 *
15413 * @param {string} toggleClass Class to be toggled.
15414 * @param {boolean} onElement Whether or not to remove the class from the panel
15415 *     element instead of the container.
15416 */
15417
15418/**
15419 * @ngdoc method
15420 * @name MdPanelRef#updatePosition
15421 * @description
15422 * Updates the position configuration of a panel. Use this to update the
15423 * position of a panel that is open, without having to close and re-open the
15424 * panel.
15425 *
15426 * @param {!MdPanelPosition} position
15427 */
15428
15429/**
15430 * @ngdoc method
15431 * @name MdPanelRef#addToGroup
15432 * @description
15433 * Adds a panel to a group if the panel does not exist within the group already.
15434 * A panel can only exist within a single group.
15435 *
15436 * @param {string} groupName The name of the group to add the panel to.
15437 */
15438
15439/**
15440 * @ngdoc method
15441 * @name MdPanelRef#removeFromGroup
15442 * @description
15443 * Removes a panel from a group if the panel exists within that group. The group
15444 * must be created ahead of time.
15445 *
15446 * @param {string} groupName The name of the group.
15447 */
15448
15449/**
15450 * @ngdoc method
15451 * @name MdPanelRef#registerInterceptor
15452 * @description
15453 * Registers an interceptor with the panel. The callback should return a promise,
15454 * which will allow the action to continue when it gets resolved, or will
15455 * prevent an action if it is rejected. The interceptors are called sequentially
15456 * and it reverse order. `type` must be one of the following
15457 * values available on `$mdPanel.interceptorTypes`:
15458 * * `CLOSE` - Gets called before the panel begins closing.
15459 *
15460 * @param {string} type Type of interceptor.
15461 * @param {!angular.$q.Promise<any>} callback Callback to be registered.
15462 * @returns {!MdPanelRef}
15463 */
15464
15465/**
15466 * @ngdoc method
15467 * @name MdPanelRef#removeInterceptor
15468 * @description
15469 * Removes a registered interceptor.
15470 *
15471 * @param {string} type Type of interceptor to be removed.
15472 * @param {function(): !angular.$q.Promise<any>} callback Interceptor to be removed.
15473 * @returns {!MdPanelRef}
15474 */
15475
15476/**
15477 * @ngdoc method
15478 * @name MdPanelRef#removeAllInterceptors
15479 * @description
15480 * Removes all interceptors. If a type is supplied, only the
15481 * interceptors of that type will be cleared.
15482 *
15483 * @param {string=} type Type of interceptors to be removed.
15484 * @returns {!MdPanelRef}
15485 */
15486
15487/**
15488 * @ngdoc method
15489 * @name MdPanelRef#updateAnimation
15490 * @description
15491 * Updates the animation configuration for a panel. You can use this to change
15492 * the panel's animation without having to re-create it.
15493 *
15494 * @param {!MdPanelAnimation} animation
15495 */
15496
15497
15498/*****************************************************************************
15499 *                               MdPanelPosition                            *
15500 *****************************************************************************/
15501
15502
15503/**
15504 * @ngdoc type
15505 * @name MdPanelPosition
15506 * @module material.components.panel
15507 * @description
15508 *
15509 * Object for configuring the position of the panel.
15510 *
15511 * @usage
15512 *
15513 * #### Centering the panel
15514 *
15515 * <hljs lang="js">
15516 * new MdPanelPosition().absolute().center();
15517 * </hljs>
15518 *
15519 * #### Overlapping the panel with an element
15520 *
15521 * <hljs lang="js">
15522 * new MdPanelPosition()
15523 *     .relativeTo(someElement)
15524 *     .addPanelPosition(
15525 *       $mdPanel.xPosition.ALIGN_START,
15526 *       $mdPanel.yPosition.ALIGN_TOPS
15527 *     );
15528 * </hljs>
15529 *
15530 * #### Aligning the panel with the bottom of an element
15531 *
15532 * <hljs lang="js">
15533 * new MdPanelPosition()
15534 *     .relativeTo(someElement)
15535 *     .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW);
15536 * </hljs>
15537 */
15538
15539/**
15540 * @ngdoc method
15541 * @name MdPanelPosition#absolute
15542 * @description
15543 * Positions the panel absolutely relative to the parent element. If the parent
15544 * is document.body, this is equivalent to positioning the panel absolutely
15545 * within the viewport.
15546 *
15547 * @returns {!MdPanelPosition}
15548 */
15549
15550/**
15551 * @ngdoc method
15552 * @name MdPanelPosition#relativeTo
15553 * @description
15554 * Positions the panel relative to a specific element.
15555 *
15556 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
15557 *     or angular element to position the panel with respect to.
15558 * @returns {!MdPanelPosition}
15559 */
15560
15561/**
15562 * @ngdoc method
15563 * @name MdPanelPosition#top
15564 * @description
15565 * Sets the value of `top` for the panel. Clears any previously set vertical
15566 * position.
15567 *
15568 * @param {string=} top Value of `top`. Defaults to '0'.
15569 * @returns {!MdPanelPosition}
15570 */
15571
15572/**
15573 * @ngdoc method
15574 * @name MdPanelPosition#bottom
15575 * @description
15576 * Sets the value of `bottom` for the panel. Clears any previously set vertical
15577 * position.
15578 *
15579 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
15580 * @returns {!MdPanelPosition}
15581 */
15582
15583/**
15584 * @ngdoc method
15585 * @name MdPanelPosition#start
15586 * @description
15587 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
15588 * `rtl`. Clears any previously set horizontal position.
15589 *
15590 * @param {string=} start Value of position. Defaults to '0'.
15591 * @returns {!MdPanelPosition}
15592 */
15593
15594/**
15595 * @ngdoc method
15596 * @name MdPanelPosition#end
15597 * @description
15598 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
15599 * Clears any previously set horizontal position.
15600 *
15601 * @param {string=} end Value of position. Defaults to '0'.
15602 * @returns {!MdPanelPosition}
15603 */
15604
15605/**
15606 * @ngdoc method
15607 * @name MdPanelPosition#left
15608 * @description
15609 * Sets the value of `left` for the panel. Clears any previously set
15610 * horizontal position.
15611 *
15612 * @param {string=} left Value of `left`. Defaults to '0'.
15613 * @returns {!MdPanelPosition}
15614 */
15615
15616/**
15617 * @ngdoc method
15618 * @name MdPanelPosition#right
15619 * @description
15620 * Sets the value of `right` for the panel. Clears any previously set
15621 * horizontal position.
15622 *
15623 * @param {string=} right Value of `right`. Defaults to '0'.
15624 * @returns {!MdPanelPosition}
15625 */
15626
15627/**
15628 * @ngdoc method
15629 * @name MdPanelPosition#centerHorizontally
15630 * @description
15631 * Centers the panel horizontally in the viewport. Clears any previously set
15632 * horizontal position.
15633 *
15634 * @returns {!MdPanelPosition}
15635 */
15636
15637/**
15638 * @ngdoc method
15639 * @name MdPanelPosition#centerVertically
15640 * @description
15641 * Centers the panel vertically in the viewport. Clears any previously set
15642 * vertical position.
15643 *
15644 * @returns {!MdPanelPosition}
15645 */
15646
15647/**
15648 * @ngdoc method
15649 * @name MdPanelPosition#center
15650 * @description
15651 * Centers the panel horizontally and vertically in the viewport. This is
15652 * equivalent to calling both `centerHorizontally` and `centerVertically`.
15653 * Clears any previously set horizontal and vertical positions.
15654 *
15655 * @returns {!MdPanelPosition}
15656 */
15657
15658/**
15659 * @ngdoc method
15660 * @name MdPanelPosition#addPanelPosition
15661 * @description
15662 * Sets the x and y position for the panel relative to another element. Can be
15663 * called multiple times to specify an ordered list of panel positions. The
15664 * first position which allows the panel to be completely on-screen will be
15665 * chosen; the last position will be chose whether it is on-screen or not.
15666 *
15667 * xPosition must be one of the following values available on
15668 * $mdPanel.xPosition:
15669 *
15670 *
15671 * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END
15672 *
15673 * <pre>
15674 *    *************
15675 *    *           *
15676 *    *   PANEL   *
15677 *    *           *
15678 *    *************
15679 *   A B    C    D E
15680 *
15681 * A: OFFSET_START (for LTR displays)
15682 * B: ALIGN_START (for LTR displays)
15683 * C: CENTER
15684 * D: ALIGN_END (for LTR displays)
15685 * E: OFFSET_END (for LTR displays)
15686 * </pre>
15687 *
15688 * yPosition must be one of the following values available on
15689 * $mdPanel.yPosition:
15690 *
15691 * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW
15692 *
15693 * <pre>
15694 *   F
15695 *   G *************
15696 *     *           *
15697 *   H *   PANEL   *
15698 *     *           *
15699 *   I *************
15700 *   J
15701 *
15702 * F: BELOW
15703 * G: ALIGN_TOPS
15704 * H: CENTER
15705 * I: ALIGN_BOTTOMS
15706 * J: ABOVE
15707 * </pre>
15708 *
15709 * @param {string} xPosition
15710 * @param {string} yPosition
15711 * @returns {!MdPanelPosition}
15712 */
15713
15714/**
15715 * @ngdoc method
15716 * @name MdPanelPosition#withOffsetX
15717 * @description
15718 * Sets the value of the offset in the x-direction.
15719 *
15720 * @param {string} offsetX
15721 * @returns {!MdPanelPosition}
15722 */
15723
15724/**
15725 * @ngdoc method
15726 * @name MdPanelPosition#withOffsetY
15727 * @description
15728 * Sets the value of the offset in the y-direction.
15729 *
15730 * @param {string} offsetY
15731 * @returns {!MdPanelPosition}
15732 */
15733
15734
15735/*****************************************************************************
15736 *                               MdPanelAnimation                            *
15737 *****************************************************************************/
15738
15739
15740/**
15741 * @ngdoc type
15742 * @name MdPanelAnimation
15743 * @module material.components.panel
15744 * @description
15745 * Animation configuration object. To use, create an MdPanelAnimation with the
15746 * desired properties, then pass the object as part of $mdPanel creation.
15747 *
15748 * @usage
15749 *
15750 * <hljs lang="js">
15751 * var panelAnimation = new MdPanelAnimation()
15752 *     .openFrom(myButtonEl)
15753 *     .duration(1337)
15754 *     .closeTo('.my-button')
15755 *     .withAnimation($mdPanel.animation.SCALE);
15756 *
15757 * $mdPanel.create({
15758 *   animation: panelAnimation
15759 * });
15760 * </hljs>
15761 */
15762
15763/**
15764 * @ngdoc method
15765 * @name MdPanelAnimation#openFrom
15766 * @description
15767 * Specifies where to start the open animation. `openFrom` accepts a
15768 * click event object, query selector, DOM element, or a Rect object that
15769 * is used to determine the bounds. When passed a click event, the location
15770 * of the click will be used as the position to start the animation.
15771 *
15772 * @param {string|!Element|!Event|{top: number, left: number}}
15773 * @returns {!MdPanelAnimation}
15774 */
15775
15776/**
15777 * @ngdoc method
15778 * @name MdPanelAnimation#closeTo
15779 * @description
15780 * Specifies where to animate the panel close. `closeTo` accepts a
15781 * query selector, DOM element, or a Rect object that is used to determine
15782 * the bounds.
15783 *
15784 * @param {string|!Element|{top: number, left: number}}
15785 * @returns {!MdPanelAnimation}
15786 */
15787
15788/**
15789 * @ngdoc method
15790 * @name MdPanelAnimation#withAnimation
15791 * @description
15792 * Specifies the animation class.
15793 *
15794 * There are several default animations that can be used:
15795 * ($mdPanel.animation)
15796 *   SLIDE: The panel slides in and out from the specified
15797 *       elements. It will not fade in or out.
15798 *   SCALE: The panel scales in and out. Slide and fade are
15799 *       included in this animation.
15800 *   FADE: The panel fades in and out.
15801 *
15802 * Custom classes will by default fade in and out unless
15803 * "transition: opacity 1ms" is added to the to custom class.
15804 *
15805 * @param {string|{open: string, close: string}} cssClass
15806 * @returns {!MdPanelAnimation}
15807 */
15808
15809/**
15810 * @ngdoc method
15811 * @name MdPanelAnimation#duration
15812 * @description
15813 * Specifies the duration of the animation in milliseconds. The `duration`
15814 * method accepts either a number or an object with separate open and close
15815 * durations.
15816 *
15817 * @param {number|{open: number, close: number}} duration
15818 * @returns {!MdPanelAnimation}
15819 */
15820
15821
15822/*****************************************************************************
15823 *                            PUBLIC DOCUMENTATION                           *
15824 *****************************************************************************/
15825
15826
15827var MD_PANEL_Z_INDEX = 80;
15828var MD_PANEL_HIDDEN = '_md-panel-hidden';
15829var FOCUS_TRAP_TEMPLATE = angular.element(
15830    '<div class="_md-panel-focus-trap" tabindex="0"></div>');
15831
15832var _presets = {};
15833
15834
15835/**
15836 * A provider that is used for creating presets for the panel API.
15837 * @final @constructor @ngInject
15838 */
15839function MdPanelProvider() {
15840  return {
15841    'definePreset': definePreset,
15842    'getAllPresets': getAllPresets,
15843    'clearPresets': clearPresets,
15844    '$get': $getProvider()
15845  };
15846}
15847
15848
15849/**
15850 * Takes the passed in panel configuration object and adds it to the `_presets`
15851 * object at the specified name.
15852 * @param {string} name Name of the preset to set.
15853 * @param {!Object} preset Specific configuration object that can contain any
15854 *     and all of the parameters avaialble within the `$mdPanel.create` method.
15855 *     However, parameters that pertain to id, position, animation, and user
15856 *     interaction are not allowed and will be removed from the preset
15857 *     configuration.
15858 */
15859function definePreset(name, preset) {
15860  if (!name || !preset) {
15861    throw new Error('mdPanelProvider: The panel preset definition is ' +
15862        'malformed. The name and preset object are required.');
15863  } else if (_presets.hasOwnProperty(name)) {
15864    throw new Error('mdPanelProvider: The panel preset you have requested ' +
15865        'has already been defined.');
15866  }
15867
15868  // Delete any property on the preset that is not allowed.
15869  delete preset.id;
15870  delete preset.position;
15871  delete preset.animation;
15872
15873  _presets[name] = preset;
15874}
15875
15876
15877/**
15878 * Gets a clone of the `_presets`.
15879 * @return {!Object}
15880 */
15881function getAllPresets() {
15882  return angular.copy(_presets);
15883}
15884
15885
15886/**
15887 * Clears all of the stored presets.
15888 */
15889function clearPresets() {
15890  _presets = {};
15891}
15892
15893
15894/**
15895 * Represents the `$get` method of the AngularJS provider. From here, a new
15896 * reference to the MdPanelService is returned where the needed arguments are
15897 * passed in including the MdPanelProvider `_presets`.
15898 * @param {!Object} _presets
15899 * @param {!angular.JQLite} $rootElement
15900 * @param {!angular.Scope} $rootScope
15901 * @param {!angular.$injector} $injector
15902 * @param {!angular.$window} $window
15903 */
15904function $getProvider() {
15905  return [
15906    '$rootElement', '$rootScope', '$injector', '$window',
15907    function($rootElement, $rootScope, $injector, $window) {
15908      return new MdPanelService(_presets, $rootElement, $rootScope,
15909          $injector, $window);
15910    }
15911  ];
15912}
15913
15914
15915/*****************************************************************************
15916 *                               MdPanel Service                             *
15917 *****************************************************************************/
15918
15919
15920/**
15921 * A service that is used for controlling/displaying panels on the screen.
15922 * @param {!Object} presets
15923 * @param {!angular.JQLite} $rootElement
15924 * @param {!angular.Scope} $rootScope
15925 * @param {!angular.$injector} $injector
15926 * @param {!angular.$window} $window
15927 * @final @constructor @ngInject
15928 */
15929function MdPanelService(presets, $rootElement, $rootScope, $injector, $window) {
15930  /**
15931   * Default config options for the panel.
15932   * Anything angular related needs to be done later. Therefore
15933   *     scope: $rootScope.$new(true),
15934   *     attachTo: $rootElement,
15935   * are added later.
15936   * @private {!Object}
15937   */
15938  this._defaultConfigOptions = {
15939    bindToController: true,
15940    clickOutsideToClose: false,
15941    disableParentScroll: false,
15942    escapeToClose: false,
15943    focusOnOpen: true,
15944    fullscreen: false,
15945    hasBackdrop: false,
15946    propagateContainerEvents: false,
15947    transformTemplate: angular.bind(this, this._wrapTemplate),
15948    trapFocus: false,
15949    zIndex: MD_PANEL_Z_INDEX
15950  };
15951
15952  /** @private {!Object} */
15953  this._config = {};
15954
15955  /** @private {!Object} */
15956  this._presets = presets;
15957
15958  /** @private @const */
15959  this._$rootElement = $rootElement;
15960
15961  /** @private @const */
15962  this._$rootScope = $rootScope;
15963
15964  /** @private @const */
15965  this._$injector = $injector;
15966
15967  /** @private @const */
15968  this._$window = $window;
15969
15970  /** @private @const */
15971  this._$mdUtil = this._$injector.get('$mdUtil');
15972
15973  /** @private {!Object<string, !MdPanelRef>} */
15974  this._trackedPanels = {};
15975
15976  /**
15977   * @private {!Object<string,
15978   *     {panels: !Array<!MdPanelRef>,
15979   *     openPanels: !Array<!MdPanelRef>,
15980   *     maxOpen: number}>}
15981   */
15982  this._groups = Object.create(null);
15983
15984  /**
15985   * Default animations that can be used within the panel.
15986   * @type {enum}
15987   */
15988  this.animation = MdPanelAnimation.animation;
15989
15990  /**
15991   * Possible values of xPosition for positioning the panel relative to
15992   * another element.
15993   * @type {enum}
15994   */
15995  this.xPosition = MdPanelPosition.xPosition;
15996
15997  /**
15998   * Possible values of yPosition for positioning the panel relative to
15999   * another element.
16000   * @type {enum}
16001   */
16002  this.yPosition = MdPanelPosition.yPosition;
16003
16004  /**
16005   * Possible values for the interceptors that can be registered on a panel.
16006   * @type {enum}
16007   */
16008  this.interceptorTypes = MdPanelRef.interceptorTypes;
16009
16010  /**
16011   * Possible values for closing of a panel.
16012   * @type {enum}
16013   */
16014  this.closeReasons = MdPanelRef.closeReasons;
16015
16016  /**
16017   * Possible values of absolute position.
16018   * @type {enum}
16019   */
16020  this.absPosition = MdPanelPosition.absPosition;
16021}
16022
16023
16024/**
16025 * Creates a panel with the specified options.
16026 * @param {string=} preset Name of a preset configuration that can be used to
16027 *     extend the panel configuration.
16028 * @param {!Object=} config Configuration object for the panel.
16029 * @returns {!MdPanelRef}
16030 */
16031MdPanelService.prototype.create = function(preset, config) {
16032  if (typeof preset === 'string') {
16033    preset = this._getPresetByName(preset);
16034  } else if (typeof preset === 'object' &&
16035      (angular.isUndefined(config) || !config)) {
16036    config = preset;
16037    preset = {};
16038  }
16039
16040  preset = preset || {};
16041  config = config || {};
16042
16043  // If the passed-in config contains an ID and the ID is within _trackedPanels,
16044  // return the tracked panel after updating its config with the passed-in
16045  // config.
16046  if (angular.isDefined(config.id) && this._trackedPanels[config.id]) {
16047    var trackedPanel = this._trackedPanels[config.id];
16048    angular.extend(trackedPanel.config, config);
16049    return trackedPanel;
16050  }
16051
16052  // Combine the passed-in config, the _defaultConfigOptions, and the preset
16053  // configuration into the `_config`.
16054  this._config = angular.extend({
16055    // If no ID is set within the passed-in config, then create an arbitrary ID.
16056    id: config.id || 'panel_' + this._$mdUtil.nextUid(),
16057    scope: this._$rootScope.$new(true),
16058    attachTo: this._$rootElement
16059  }, this._defaultConfigOptions, config, preset);
16060
16061  // Create the panelRef and add it to the `_trackedPanels` object.
16062  var panelRef = new MdPanelRef(this._config, this._$injector);
16063  this._trackedPanels[config.id] = panelRef;
16064
16065  // Add the panel to each of its requested groups.
16066  if (this._config.groupName) {
16067    if (angular.isString(this._config.groupName)) {
16068      this._config.groupName = [this._config.groupName];
16069    }
16070    angular.forEach(this._config.groupName, function(group) {
16071      panelRef.addToGroup(group);
16072    });
16073  }
16074
16075  this._config.scope.$on('$destroy', angular.bind(panelRef, panelRef.detach));
16076
16077  return panelRef;
16078};
16079
16080
16081/**
16082 * Creates and opens a panel with the specified options.
16083 * @param {string=} preset Name of a preset configuration that can be used to
16084 *     extend the panel configuration.
16085 * @param {!Object=} config Configuration object for the panel.
16086 * @returns {!angular.$q.Promise<!MdPanelRef>}
16086 The panel created from create.
16087 */
16088MdPanelService.prototype.open = function(preset, config) {
16089  var panelRef = this.create(preset, config);
16090  return panelRef.open().then(function() {
16091    return panelRef;
16092  });
16093};
16094
16095
16096/**
16097 * Gets a specific preset configuration object saved within `_presets`.
16098 * @param {string} preset Name of the preset to search for.
16099 * @returns {!Object} The preset configuration object.
16100 */
16101MdPanelService.prototype._getPresetByName = function(preset) {
16102  if (!this._presets[preset]) {
16103    throw new Error('mdPanel: The panel preset configuration that you ' +
16104        'requested does not exist. Use the $mdPanelProvider to create a ' +
16105        'preset before requesting one.');
16106  }
16107  return this._presets[preset];
16108};
16109
16110
16111/**
16112 * Returns a new instance of the MdPanelPosition. Use this to create the
16113 * positioning object.
16114 * @returns {!MdPanelPosition}
16115 */
16116MdPanelService.prototype.newPanelPosition = function() {
16117  return new MdPanelPosition(this._$injector);
16118};
16119
16120
16121/**
16122 * Returns a new instance of the MdPanelAnimation. Use this to create the
16123 * animation object.
16124 * @returns {!MdPanelAnimation}
16125 */
16126MdPanelService.prototype.newPanelAnimation = function() {
16127  return new MdPanelAnimation(this._$injector);
16128};
16129
16130
16131/**
16132 * Creates a panel group and adds it to a tracked list of panel groups.
16133 * @param groupName {string} Name of the group to create.
16134 * @param config {!Object=} Specific configuration object that may contain the
16135 *     following properties:
16136 *
16137 *   - `maxOpen` - `{number=}`: The maximum number of panels that are allowed
16138 *     open within a defined panel group.
16139 *
16140 * @returns {!Object<string,
16141 *     {panels: !Array<!MdPanelRef>,
16142 *     openPanels: !Array<!MdPanelRef>,
16143 *     maxOpen: number}>} panelGroup
16144 */
16145MdPanelService.prototype.newPanelGroup = function(groupName, config) {
16146  if (!this._groups[groupName]) {
16147    config = config || {};
16148    var group = {
16149      panels: [],
16150      openPanels: [],
16151      maxOpen: config.maxOpen > 0 ? config.maxOpen : Infinity
16152    };
16153    this._groups[groupName] = group;
16154  }
16155  return this._groups[groupName];
16156};
16157
16158
16159/**
16160 * Sets the maximum number of panels in a group that can be opened at a given
16161 * time.
16162 * @param {string} groupName The name of the group to configure.
16163 * @param {number} maxOpen The maximum number of panels that can be
16164 *     opened. Infinity can be passed in to remove the maxOpen limit.
16165 */
16166MdPanelService.prototype.setGroupMaxOpen = function(groupName, maxOpen) {
16167  if (this._groups[groupName]) {
16168    this._groups[groupName].maxOpen = maxOpen;
16169  } else {
16170    throw new Error('mdPanel: Group does not exist yet. Call newPanelGroup().');
16171  }
16172};
16173
16174
16175/**
16176 * Determines if the current number of open panels within a group exceeds the
16177 * limit of allowed open panels.
16178 * @param {string} groupName The name of the group to check.
16179 * @returns {boolean} true if open count does exceed maxOpen and false if not.
16180 * @private
16181 */
16182MdPanelService.prototype._openCountExceedsMaxOpen = function(groupName) {
16183  if (this._groups[groupName]) {
16184    var group = this._groups[groupName];
16185    return group.maxOpen > 0 && group.openPanels.length > group.maxOpen;
16186  }
16187  return false;
16188};
16189
16190
16191/**
16192 * Closes the first open panel within a specific group.
16193 * @param {string} groupName The name of the group.
16194 * @private
16195 */
16196MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) {
16197  this._groups[groupName].openPanels[0].close();
16198};
16199
16200
16201/**
16202 * Wraps the users template in two elements, md-panel-outer-wrapper, which
16203 * covers the entire attachTo element, and md-panel, which contains only the
16204 * template. This allows the panel control over positioning, animations,
16205 * and similar properties.
16206 * @param {string} origTemplate The original template.
16207 * @returns {string} The wrapped template.
16208 * @private
16209 */
16210MdPanelService.prototype._wrapTemplate = function(origTemplate) {
16211  var template = origTemplate || '';
16212
16213  // The panel should be initially rendered offscreen so we can calculate
16214  // height and width for positioning.
16215  return '' +
16216      '<div class="md-panel-outer-wrapper">' +
16217      '  <div class="md-panel _md-panel-offscreen">' + template + '</div>' +
16218      '</div>';
16219};
16220
16221
16222/**
16223 * Wraps a content element in a md-panel-outer wrapper and
16224 * positions it off-screen. Allows for proper control over positoning
16225 * and animations.
16226 * @param {!angular.JQLite} contentElement Element to be wrapped.
16227 * @return {!angular.JQLite} Wrapper element.
16228 * @private
16229 */
16230MdPanelService.prototype._wrapContentElement = function(contentElement) {
16231  var wrapper = angular.element('<div class="md-panel-outer-wrapper">');
16232
16233  contentElement.addClass('md-panel _md-panel-offscreen');
16234  wrapper.append(contentElement);
16235
16236  return wrapper;
16237};
16238
16239
16240/*****************************************************************************
16241 *                                 MdPanelRef                                *
16242 *****************************************************************************/
16243
16244
16245/**
16246 * A reference to a created panel. This reference contains a unique id for the
16247 * panel, along with properties/functions used to control the panel.
16248 * @param {!Object} config
16249 * @param {!angular.$injector} $injector
16250 * @final @constructor
16251 */
16252function MdPanelRef(config, $injector) {
16253  // Injected variables.
16254  /** @private @const {!angular.$q} */
16255  this._$q = $injector.get('$q');
16256
16257  /** @private @const {!angular.$mdCompiler} */
16258  this._$mdCompiler = $injector.get('$mdCompiler');
16259
16260  /** @private @const {!angular.$mdConstant} */
16261  this._$mdConstant = $injector.get('$mdConstant');
16262
16263  /** @private @const {!angular.$mdUtil} */
16264  this._$mdUtil = $injector.get('$mdUtil');
16265
16266  /** @private @const {!angular.$mdTheming} */
16267  this._$mdTheming = $injector.get('$mdTheming');
16268
16269  /** @private @const {!angular.Scope} */
16270  this._$rootScope = $injector.get('$rootScope');
16271
16272  /** @private @const {!angular.$animate} */
16273  this._$animate = $injector.get('$animate');
16274
16275  /** @private @const {!MdPanelRef} */
16276  this._$mdPanel = $injector.get('$mdPanel');
16277
16278  /** @private @const {!angular.$log} */
16279  this._$log = $injector.get('$log');
16280
16281  /** @private @const {!angular.$window} */
16282  this._$window = $injector.get('$window');
16283
16284  /** @private @const {!Function} */
16285  this._$$rAF = $injector.get('$$rAF');
16286
16287  // Public variables.
16288  /**
16289   * Unique id for the panelRef.
16290   * @type {string}
16291   */
16292  this.id = config.id;
16293
16294  /** @type {!Object} */
16295  this.config = config;
16296
16297  /** @type {!angular.JQLite|undefined} */
16298  this.panelContainer;
16299
16300  /** @type {!angular.JQLite|undefined} */
16301  this.panelEl;
16302
16303  /**
16304   * Whether the panel is attached. This is synchronous. When attach is called,
16305   * isAttached is set to true. When detach is called, isAttached is set to
16306   * false.
16307   * @type {boolean}
16308   */
16309  this.isAttached = false;
vendor: 8,363 bytes, lines 16310-16620
16310
16311  // Private variables.
16312  /** @private {Array<function()>} */
16313  this._removeListeners = [];
16314
16315  /** @private {!angular.JQLite|undefined} */
16316  this._topFocusTrap;
16317
16318  /** @private {!angular.JQLite|undefined} */
16319  this._bottomFocusTrap;
16320
16321  /** @private {!$mdPanel|undefined} */
16322  this._backdropRef;
16323
16324  /** @private {Function?} */
16325  this._restoreScroll = null;
16326
16327  /**
16328   * Keeps track of all the panel interceptors.
16329   * @private {!Object}
16330   */
16331  this._interceptors = Object.create(null);
16332
16333  /**
16334   * Cleanup function, provided by `$mdCompiler` and assigned after the element
16335   * has been compiled. When `contentElement` is used, the function is used to
16336   * restore the element to it's proper place in the DOM.
16337   * @private {!Function}
16338   */
16339  this._compilerCleanup = null;
16340
16341  /**
16342   * Cache for saving and restoring element inline styles, CSS classes etc.
16343   * @type {{styles: string, classes: string}}
16344   */
16345  this._restoreCache = {
16346    styles: '',
16347    classes: ''
16348  };
16349}
16350
16351
16352MdPanelRef.interceptorTypes = {
16353  CLOSE: 'onClose'
16354};
16355
16356
16357/**
16358 * Opens an already created and configured panel. If the panel is already
16359 * visible, does nothing.
16360 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
16361 *     the panel is opened and animations finish.
16362 */
16363MdPanelRef.prototype.open = function() {
16364  var self = this;
16365  return this._$q(function(resolve, reject) {
16366    var done = self._done(resolve, self);
16367    var show = self._simpleBind(self.show, self);
16368    var checkGroupMaxOpen = function() {
16369      if (self.config.groupName) {
16370        angular.forEach(self.config.groupName, function(group) {
16371          if (self._$mdPanel._openCountExceedsMaxOpen(group)) {
16372            self._$mdPanel._closeFirstOpenedPanel(group);
16373          }
16374        });
16375      }
16376    };
16377
16378    self.attach()
16379        .then(show)
16380        .then(checkGroupMaxOpen)
16381        .then(done)
16382        .catch(reject);
16383  });
16384};
16385
16386
16387/**
16388 * Closes the panel.
16389 * @param {string} closeReason The event type that triggered the close.
16390 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
16391 *     the panel is closed and animations finish.
16392 */
16393MdPanelRef.prototype.close = function(closeReason) {
16394  var self = this;
16395
16396  return this._$q(function(resolve, reject) {
16397    self._callInterceptors(MdPanelRef.interceptorTypes.CLOSE).then(function() {
16398      var done = self._done(resolve, self);
16399      var detach = self._simpleBind(self.detach, self);
16400      var onCloseSuccess = self.config['onCloseSuccess'] || angular.noop;
16401      onCloseSuccess = angular.bind(self, onCloseSuccess, self, closeReason);
16402
16403      self.hide()
16404          .then(detach)
16405          .then(done)
16406          .then(onCloseSuccess)
16407          .catch(reject);
16408    }, reject);
16409  });
16410};
16411
16412
16413/**
16414 * Attaches the panel. The panel will be hidden afterwards.
16415 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
16416 *     the panel is attached.
16417 */
16418MdPanelRef.prototype.attach = function() {
16419  if (this.isAttached && this.panelEl) {
16420    return this._$q.when(this);
16421  }
16422
16423  var self = this;
16424  return this._$q(function(resolve, reject) {
16425    var done = self._done(resolve, self);
16426    var onDomAdded = self.config['onDomAdded'] || angular.noop;
16427    var addListeners = function(response) {
16428      self.isAttached = true;
16429      self._addEventListeners();
16430      return response;
16431    };
16432
16433    self._$q.all([
16434        self._createBackdrop(),
16435        self._createPanel()
16436            .then(addListeners)
16437            .catch(reject)
16438    ]).then(onDomAdded)
16439      .then(done)
16440      .catch(reject);
16441  });
16442};
16443
16444
16445/**
16446 * Only detaches the panel. Will NOT hide the panel first.
16447 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
16448 *     the panel is detached.
16449 */
16450MdPanelRef.prototype.detach = function() {
16451  if (!this.isAttached) {
16452    return this._$q.when(this);
16453  }
16454
16455  var self = this;
16456  var onDomRemoved = self.config['onDomRemoved'] || angular.noop;
16457
16458  var detachFn = function() {
16459    self._removeEventListeners();
16460
16461    // Remove the focus traps that we added earlier for keeping focus within
16462    // the panel.
16463    if (self._topFocusTrap && self._topFocusTrap.parentNode) {
16464      self._topFocusTrap.parentNode.removeChild(self._topFocusTrap);
16465    }
16466
16467    if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) {
16468      self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap);
16469    }
16470
16471    if (self._restoreCache.classes) {
16472      self.panelEl[0].className = self._restoreCache.classes;
16473    }
16474
16475    // Either restore the saved styles or clear the ones set by mdPanel.
16476    self.panelEl[0].style.cssText = self._restoreCache.styles || '';
16477
16478    self._compilerCleanup();
16479    self.panelContainer.remove();
16480    self.isAttached = false;
16481    return self._$q.when(self);
16482  };
16483
16484  if (this._restoreScroll) {
16485    this._restoreScroll();
16486    this._restoreScroll = null;
16487  }
16488
16489  return this._$q(function(resolve, reject) {
16490    var done = self._done(resolve, self);
16491
16492    self._$q.all([
16493      detachFn(),
16494      self._backdropRef ? self._backdropRef.detach() : true
16495    ]).then(onDomRemoved)
16496      .then(done)
16497      .catch(reject);
16498  });
16499};
16500
16501
16502/**
16503 * Destroys the panel. The Panel cannot be opened again after this.
16504 */
16505MdPanelRef.prototype.destroy = function() {
16506  var self = this;
16507  if (this.config.groupName) {
16508    angular.forEach(this.config.groupName, function(group) {
16509      self.removeFromGroup(group);
16510    });
16511  }
16512  this.config.scope.$destroy();
16513  this.config.locals = null;
16514  this._interceptors = null;
16515};
16516
16517
16518/**
16519 * Shows the panel.
16520 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
16521 *     the panel has shown and animations finish.
16522 */
16523MdPanelRef.prototype.show = function() {
16524  if (!this.panelContainer) {
16525    return this._$q(function(resolve, reject) {
16526      reject('mdPanel: Panel does not exist yet. Call open() or attach().');
16527    });
16528  }
16529
16530  if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
16531    return this._$q.when(this);
16532  }
16533
16534  var self = this;
16535  var animatePromise = function() {
16536    self.panelContainer.removeClass(MD_PANEL_HIDDEN);
16537    return self._animateOpen();
16538  };
16539
16540  return this._$q(function(resolve, reject) {
16541    var done = self._done(resolve, self);
16542    var onOpenComplete = self.config['onOpenComplete'] || angular.noop;
16543    var addToGroupOpen = function() {
16544      if (self.config.groupName) {
16545        angular.forEach(self.config.groupName, function(group) {
16546          self._$mdPanel._groups[group].openPanels.push(self);
16547        });
16548      }
16549    };
16550
16551    self._$q.all([
16552      self._backdropRef ? self._backdropRef.show() : self,
16553      animatePromise().then(function() { self._focusOnOpen(); }, reject)
16554    ]).then(onOpenComplete)
16555      .then(addToGroupOpen)
16556      .then(done)
16557      .catch(reject);
16558  });
16559};
16560
16561
16562/**
16563 * Hides the panel.
16564 * @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
16565 *     the panel has hidden and animations finish.
16566 */
16567MdPanelRef.prototype.hide = function() {
16568  if (!this.panelContainer) {
16569    return this._$q(function(resolve, reject) {
16570      reject('mdPanel: Panel does not exist yet. Call open() or attach().');
16571    });
16572  }
16573
16574  if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
16575    return this._$q.when(this);
16576  }
16577
16578  var self = this;
16579
16580  return this._$q(function(resolve, reject) {
16581    var done = self._done(resolve, self);
16582    var onRemoving = self.config['onRemoving'] || angular.noop;
16583    var hidePanel = function() {
16584      self.panelContainer.addClass(MD_PANEL_HIDDEN);
16585    };
16586    var removeFromGroupOpen = function() {
16587      if (self.config.groupName) {
16588        var group, index;
16589        angular.forEach(self.config.groupName, function(group) {
16590          group = self._$mdPanel._groups[group];
16591          index = group.openPanels.indexOf(self);
16592          if (index > -1) {
16593            group.openPanels.splice(index, 1);
16594          }
16595        });
16596      }
16597    };
16598    var focusOnOrigin = function() {
16599      var origin = self.config['origin'];
16600      if (origin) {
16601        getElement(origin).focus();
16602      }
16603    };
16604
16605    self._$q.all([
16606      self._backdropRef ? self._backdropRef.hide() : self,
16607      self._animateClose()
16608          .then(onRemoving)
16609          .then(hidePanel)
16610          .then(removeFromGroupOpen)
16611          .then(focusOnOrigin)
16612          .catch(reject)
16613    ]).then(done, reject);
16614  });
16615};
16616
16617
16618/**
16619 * Add a class to the panel. DO NOT use this to hide/show the panel.
16620 * @deprecated
16621 * This method is in the process of being deprecated in favor of using the panel
16622 * and container JQLite elements that are referenced in the MdPanelRef object.
16623 * Full deprecation is scheduled for material 1.2.
16624 *
16625 * @param {string} newClass Class to be added.
16626 * @param {boolean} toElement Whether or not to add the class to the panel
16627 *     element instead of the container.
16628 */
16629MdPanelRef.prototype.addClass = function(newClass, toElement) {
16630  this._$log.warn(
16631      'mdPanel: The addClass method is in the process of being deprecated. ' +
16632      'Full deprecation is scheduled for the AngularJS Material 1.2 release. ' +
16633      'To achieve the same results, use the panelContainer or panelEl ' +
16634      'JQLite elements that are referenced in MdPanelRef.');
16635
16636  if (!this.panelContainer) {
16637    throw new Error(
16638        'mdPanel: Panel does not exist yet. Call open() or attach().');
16639  }
16640
16641  if (!toElement && !this.panelContainer.hasClass(newClass)) {
16642    this.panelContainer.addClass(newClass);
16643  } else if (toElement && !this.panelEl.hasClass(newClass)) {
16644    this.panelEl.addClass(newClass);
16645  }
16646};
16647
16648
16649/**
16650 * Remove a class from the panel. DO NOT use this to hide/show the panel.
16651 * @deprecated
16652 * This method is in the process of being deprecated in favor of using the panel
16653 * and container JQLite elements that are referenced in the MdPanelRef object.
16654 * Full deprecation is scheduled for material 1.2.
16655 *
16656 * @param {string} oldClass Class to be removed.
16657 * @param {boolean} fromElement Whether or not to remove the class from the
16658 *     panel element instead of the container.
16659 */
16660MdPanelRef.prototype.removeClass = function(oldClass, fromElement) {
16661  this._$log.warn(
16662      'mdPanel: The removeClass method is in the process of being deprecated. ' +
16663      'Full deprecation is scheduled for the AngularJS Material 1.2 release. ' +
16664      'To achieve the same results, use the panelContainer or panelEl ' +
16665      'JQLite elements that are referenced in MdPanelRef.');
16666
16667  if (!this.panelContainer) {
16668    throw new Error(
16669        'mdPanel: Panel does not exist yet. Call open() or attach().');
16670  }
16671
16672  if (!fromElement && this.panelContainer.hasClass(oldClass)) {
16673    this.panelContainer.removeClass(oldClass);
16674  } else if (fromElement && this.panelEl.hasClass(oldClass)) {
16675    this.panelEl.removeClass(oldClass);
16676  }
16677};
16678
16679
16680/**
16681 * Toggle a class on the panel. DO NOT use this to hide/show the panel.
16682 * @deprecated
16683 * This method is in the process of being deprecated in favor of using the panel
16684 * and container JQLite elements that are referenced in the MdPanelRef object.
16685 * Full deprecation is scheduled for material 1.2.
16686 *
16687 * @param {string} toggleClass The class to toggle.
16688 * @param {boolean} onElement Whether or not to toggle the class on the panel
16689 *     element instead of the container.
16690 */
16691MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) {
16692  this._$log.warn(
16693      'mdPanel: The toggleClass method is in the process of being deprecated. ' +
16694      'Full deprecation is scheduled for the AngularJS Material 1.2 release. ' +
16695      'To achieve the same results, use the panelContainer or panelEl ' +
16696      'JQLite elements that are referenced in MdPanelRef.');
16697
16698  if (!this.panelContainer) {
16699    throw new Error(
16700        'mdPanel: Panel does not exist yet. Call open() or attach().');
16701  }
16702
16703  if (!onElement) {
16704    this.panelContainer.toggleClass(toggleClass);
16705  } else {
16706    this.panelEl.toggleClass(toggleClass);
16707  }
16708};
16709
16710
16711/**
16712 * Compiles the panel, according to the passed in config and appends it to
16713 * the DOM. Helps normalize differences in the compilation process between
16714 * using a string template and a content element.
16715 * @returns {!angular.$q.Promise<!MdPanelRef>} Promise that is resolved when
16716 *     the element has been compiled and added to the DOM.
16717 * @private
16718 */
16719MdPanelRef.prototype._compile = function() {
16720  var self = this;
16721
16722  // Compile the element via $mdCompiler. Note that when using a
16723  // contentElement, the element isn't actually being compiled, rather the
16724  // compiler saves it's place in the DOM and provides a way of restoring it.
16725  return self._$mdCompiler.compile(self.config).then(function(compileData) {
16726    var config = self.config;
16727
16728    if (config.contentElement) {
16729      var panelEl = compileData.element;
16730
16731      // Since mdPanel modifies the inline styles and CSS classes, we need
16732      // to save them in order to be able to restore on close.
16733      self._restoreCache.styles = panelEl[0].style.cssText;
16734      self._restoreCache.classes = panelEl[0].className;
16735
16736      self.panelContainer = self._$mdPanel._wrapContentElement(panelEl);
16737      self.panelEl = panelEl;
16738    } else {
16739      self.panelContainer = compileData.link(config['scope']);
16740      self.panelEl = angular.element(
16741        self.panelContainer[0].querySelector('.md-panel')
16742      );
16743    }
16744
16745    // Save a reference to the cleanup function from the compiler.
16746    self._compilerCleanup = compileData.cleanup;
16747
16748    // Attach the panel to the proper place in the DOM.
16749    getElement(self.config['attachTo']).append(self.panelContainer);
16750
16751    return self;
16752  });
16753};
16754
16755
16756/**
16757 * Creates a panel and adds it to the dom.
16758 * @returns {!angular.$q.Promise} A promise that is resolved when the panel is
16759 *     created.
16760 * @private
16761 */
16762MdPanelRef.prototype._createPanel = function() {
16763  var self = this;
16764
16765  return this._$q(function(resolve, reject) {
16766    if (!self.config.locals) {
16767      self.config.locals = {};
16768    }
16769
16770    self.config.locals.mdPanelRef = self;
16771
16772    self._compile().then(function() {
16773      if (self.config['disableParentScroll']) {
16774        self._restoreScroll = self._$mdUtil.disableScrollAround(
16775          null,
16776          self.panelContainer,
16777          { disableScrollMask: true }
16778        );
16779      }
16780
16781      // Add a custom CSS class to the panel element.
16782      if (self.config['panelClass']) {
16783        self.panelEl.addClass(self.config['panelClass']);
16784      }
16785
16786      // Handle click and touch events for the panel container.
16787      if (self.config['propagateContainerEvents']) {
16788        self.panelContainer.css('pointer-events', 'none');
16789        self.panelEl.css('pointer-events', 'all');
16790      }
16791
16792      // Panel may be outside the $rootElement, tell ngAnimate to animate
16793      // regardless.
16794      if (self._$animate.pin) {
16795        self._$animate.pin(
16796          self.panelContainer,
16797          getElement(self.config['attachTo'])
16798        );
16799      }
16800
16801      self._configureTrapFocus();
16802      self._addStyles().then(function() {
16803        resolve(self);
16804      }, reject);
16805    }, reject);
16806
16807  });
16808};
16809
16810
16811/**
16812 * Adds the styles for the panel, such as positioning and z-index. Also,
vendor: 10,063 bytes, lines 16813-17165
16813 * themes the panel element and panel container using `$mdTheming`.
16814 * @returns {!angular.$q.Promise<!MdPanelRef>}
16815 * @private
16816 */
16817MdPanelRef.prototype._addStyles = function() {
16818  var self = this;
16819  return this._$q(function(resolve) {
16820    self.panelContainer.css('z-index', self.config['zIndex']);
16821    self.panelEl.css('z-index', self.config['zIndex'] + 1);
16822
16823    var hideAndResolve = function() {
16824      // Theme the element and container.
16825      self._setTheming();
16826
16827      // Remove offscreen class and add hidden class.
16828      self.panelEl.removeClass('_md-panel-offscreen');
16829      self.panelContainer.addClass(MD_PANEL_HIDDEN);
16830
16831      resolve(self);
16832    };
16833
16834    if (self.config['fullscreen']) {
16835      self.panelEl.addClass('_md-panel-fullscreen');
16836      hideAndResolve();
16837      return; // Don't setup positioning.
16838    }
16839
16840    var positionConfig = self.config['position'];
16841    if (!positionConfig) {
16842      hideAndResolve();
16843      return; // Don't setup positioning.
16844    }
16845
16846    // Wait for angular to finish processing the template
16847    self._$rootScope['$$postDigest'](function() {
16848      // Position it correctly. This is necessary so that the panel will have a
16849      // defined height and width.
16850      self._updatePosition(true);
16851
16852      // Theme the element and container.
16853      self._setTheming();
16854
16855      resolve(self);
16856    });
16857  });
16858};
16859
16860
16861/**
16862 * Sets the `$mdTheming` classes on the `panelContainer` and `panelEl`.
16863 * @private
16864 */
16865MdPanelRef.prototype._setTheming = function() {
16866  this._$mdTheming(this.panelEl);
16867  this._$mdTheming(this.panelContainer);
16868};
16869
16870
16871/**
16872 * Updates the position configuration of a panel
16873 * @param {!MdPanelPosition} position
16874 */
16875MdPanelRef.prototype.updatePosition = function(position) {
16876  if (!this.panelContainer) {
16877    throw new Error(
16878        'mdPanel: Panel does not exist yet. Call open() or attach().');
16879  }
16880
16881  this.config['position'] = position;
16882  this._updatePosition();
16883};
16884
16885
16886/**
16887 * Calculates and updates the position of the panel.
16888 * @param {boolean=} init
16889 * @private
16890 */
16891MdPanelRef.prototype._updatePosition = function(init) {
16892  var positionConfig = this.config['position'];
16893
16894  if (positionConfig) {
16895    positionConfig._setPanelPosition(this.panelEl);
16896
16897    // Hide the panel now that position is known.
16898    if (init) {
16899      this.panelEl.removeClass('_md-panel-offscreen');
16900      this.panelContainer.addClass(MD_PANEL_HIDDEN);
16901    }
16902
16903    this.panelEl.css(
16904      MdPanelPosition.absPosition.TOP,
16905      positionConfig.getTop()
16906    );
16907    this.panelEl.css(
16908      MdPanelPosition.absPosition.BOTTOM,
16909      positionConfig.getBottom()
16910    );
16911    this.panelEl.css(
16912      MdPanelPosition.absPosition.LEFT,
16913      positionConfig.getLeft()
16914    );
16915    this.panelEl.css(
16916      MdPanelPosition.absPosition.RIGHT,
16917      positionConfig.getRight()
16918    );
16919  }
16920};
16921
16922
16923/**
16924 * Focuses on the panel or the first focus target.
16925 * @private
16926 */
16927MdPanelRef.prototype._focusOnOpen = function() {
16928  if (this.config['focusOnOpen']) {
16929    // Wait for the template to finish rendering to guarantee md-autofocus has
16930    // finished adding the class md-autofocus, otherwise the focusable element
16931    // isn't available to focus.
16932    var self = this;
16933    this._$rootScope['$$postDigest'](function() {
16934      var target = self._$mdUtil.findFocusTarget(self.panelEl) ||
16935          self.panelEl;
16936      target.focus();
16937    });
16938  }
16939};
16940
16941
16942/**
16943 * Shows the backdrop.
16944 * @returns {!angular.$q.Promise} A promise that is resolved when the backdrop
16945 *     is created and attached.
16946 * @private
16947 */
16948MdPanelRef.prototype._createBackdrop = function() {
16949  if (this.config.hasBackdrop) {
16950    if (!this._backdropRef) {
16951      var backdropAnimation = this._$mdPanel.newPanelAnimation()
16952          .openFrom(this.config.attachTo)
16953          .withAnimation({
16954            open: '_md-opaque-enter',
16955            close: '_md-opaque-leave'
16956          });
16957
16958      if (this.config.animation) {
16959        backdropAnimation.duration(this.config.animation._rawDuration);
16960      }
16961
16962      var backdropConfig = {
16963        animation: backdropAnimation,
16964        attachTo: this.config.attachTo,
16965        focusOnOpen: false,
16966        panelClass: '_md-panel-backdrop',
16967        zIndex: this.config.zIndex - 1
16968      };
16969
16970      this._backdropRef = this._$mdPanel.create(backdropConfig);
16971    }
16972    if (!this._backdropRef.isAttached) {
16973      return this._backdropRef.attach();
16974    }
16975  }
16976};
16977
16978
16979/**
16980 * Listen for escape keys and outside clicks to auto close.
16981 * @private
16982 */
16983MdPanelRef.prototype._addEventListeners = function() {
16984  this._configureEscapeToClose();
16985  this._configureClickOutsideToClose();
16986  this._configureScrollListener();
16987};
16988
16989
16990/**
16991 * Remove event listeners added in _addEventListeners.
16992 * @private
16993 */
16994MdPanelRef.prototype._removeEventListeners = function() {
16995  this._removeListeners && this._removeListeners.forEach(function(removeFn) {
16996    removeFn();
16997  });
16998  this._removeListeners = [];
16999};
17000
17001
17002/**
17003 * Setup the escapeToClose event listeners.
17004 * @private
17005 */
17006MdPanelRef.prototype._configureEscapeToClose = function() {
17007  if (this.config['escapeToClose']) {
17008    var parentTarget = getElement(this.config['attachTo']);
17009    var self = this;
17010
17011    var keyHandlerFn = function(ev) {
17012      if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) {
17013        ev.stopPropagation();
17014        ev.preventDefault();
17015
17016        self.close(MdPanelRef.closeReasons.ESCAPE);
17017      }
17018    };
17019
17020    // Add keydown listeners
17021    this.panelContainer.on('keydown', keyHandlerFn);
17022    parentTarget.on('keydown', keyHandlerFn);
17023
17024    // Queue remove listeners function
17025    this._removeListeners.push(function() {
17026      self.panelContainer.off('keydown', keyHandlerFn);
17027      parentTarget.off('keydown', keyHandlerFn);
17028    });
17029  }
17030};
17031
17032
17033/**
17034 * Setup the clickOutsideToClose event listeners.
17035 * @private
17036 */
17037MdPanelRef.prototype._configureClickOutsideToClose = function() {
17038  if (this.config['clickOutsideToClose']) {
17039    var target = this.config['propagateContainerEvents'] ?
17040        angular.element(document.body) :
17041        this.panelContainer;
17042    var sourceEl;
17043
17044    // Keep track of the element on which the mouse originally went down
17045    // so that we can only close the backdrop when the 'click' started on it.
17046    // A simple 'click' handler does not work, it sets the target object as the
17047    // element the mouse went down on.
17048    var mousedownHandler = function(ev) {
17049      sourceEl = ev.target;
17050    };
17051
17052    // We check if our original element and the target is the backdrop
17053    // because if the original was the backdrop and the target was inside the
17054    // panel we don't want to panel to close.
17055    var self = this;
17056    var mouseupHandler = function(ev) {
17057      if (self.config['propagateContainerEvents']) {
17058
17059        // We check if the sourceEl of the event is the panel element or one
17060        // of it's children. If it is not, then close the panel.
17061        if (sourceEl !== self.panelEl[0] && !self.panelEl[0].contains(sourceEl)) {
17062          self.close();
17063        }
17064
17065      } else if (sourceEl === target[0] && ev.target === target[0]) {
17066        ev.stopPropagation();
17067        ev.preventDefault();
17068
17069        self.close(MdPanelRef.closeReasons.CLICK_OUTSIDE);
17070      }
17071    };
17072
17073    // Add listeners
17074    target.on('mousedown', mousedownHandler);
17075    target.on('mouseup', mouseupHandler);
17076
17077    // Queue remove listeners function
17078    this._removeListeners.push(function() {
17079      target.off('mousedown', mousedownHandler);
17080      target.off('mouseup', mouseupHandler);
17081    });
17082  }
17083};
17084
17085
17086/**
17087 * Configures the listeners for updating the panel position on scroll.
17088 * @private
17089*/
17090MdPanelRef.prototype._configureScrollListener = function() {
17091  // No need to bind the event if scrolling is disabled.
17092  if (!this.config['disableParentScroll']) {
17093    var updatePosition = angular.bind(this, this._updatePosition);
17094    var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition);
17095    var self = this;
17096
17097    var onScroll = function() {
17098      debouncedUpdatePosition();
17099    };
17100
17101    // Add listeners.
17102    this._$window.addEventListener('scroll', onScroll, true);
17103
17104    // Queue remove listeners function.
17105    this._removeListeners.push(function() {
17106      self._$window.removeEventListener('scroll', onScroll, true);
17107    });
17108  }
17109};
17110
17111
17112/**
17113 * Setup the focus traps. These traps will wrap focus when tabbing past the
17114 * panel. When shift-tabbing, the focus will stick in place.
17115 * @private
17116 */
17117MdPanelRef.prototype._configureTrapFocus = function() {
17118  // Focus doesn't remain inside of the panel without this.
17119  this.panelEl.attr('tabIndex', '-1');
17120  if (this.config['trapFocus']) {
17121    var element = this.panelEl;
17122    // Set up elements before and after the panel to capture focus and
17123    // redirect back into the panel.
17124    this._topFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
17125    this._bottomFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0];
17126
17127    // When focus is about to move out of the panel, we want to intercept it
17128    // and redirect it back to the panel element.
17129    var focusHandler = function() {
17130      element.focus();
17131    };
17132    this._topFocusTrap.addEventListener('focus', focusHandler);
17133    this._bottomFocusTrap.addEventListener('focus', focusHandler);
17134
17135    // Queue remove listeners function
17136    this._removeListeners.push(this._simpleBind(function() {
17137      this._topFocusTrap.removeEventListener('focus', focusHandler);
17138      this._bottomFocusTrap.removeEventListener('focus', focusHandler);
17139    }, this));
17140
17141    // The top focus trap inserted immediately before the md-panel element (as
17142    // a sibling). The bottom focus trap inserted immediately after the
17143    // md-panel element (as a sibling).
17144    element[0].parentNode.insertBefore(this._topFocusTrap, element[0]);
17145    element.after(this._bottomFocusTrap);
17146  }
17147};
17148
17149
17150/**
17151 * Updates the animation of a panel.
17152 * @param {!MdPanelAnimation} animation
17153 */
17154MdPanelRef.prototype.updateAnimation = function(animation) {
17155  this.config['animation'] = animation;
17156
17157  if (this._backdropRef) {
17158    this._backdropRef.config.animation.duration(animation._rawDuration);
17159  }
17160};
17161
17162
17163/**
17164 * Animate the panel opening.
17165 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
17166 *     animated open.
17167 * @private
17168 */
17169MdPanelRef.prototype._animateOpen = function() {
17170  this.panelContainer.addClass('md-panel-is-showing');
17171  var animationConfig = this.config['animation'];
17172  if (!animationConfig) {
17173    // Promise is in progress, return it.
17174    this.panelContainer.addClass('_md-panel-shown');
17175    return this._$q.when(this);
17176  }
17177
17178  var self = this;
17179  return this._$q(function(resolve) {
17180    var done = self._done(resolve, self);
17181    var warnAndOpen = function() {
17182      self._$log.warn(
17183          'mdPanel: MdPanel Animations failed. ' +
17184          'Showing panel without animating.');
17185      done();
17186    };
17187
17188    animationConfig.animateOpen(self.panelEl)
17189        .then(done, warnAndOpen);
17190  });
17191};
17192
17193
17194/**
17195 * Animate the panel closing.
17196 * @returns {!angular.$q.Promise} A promise that is resolved when the panel has
17197 *     animated closed.
17198 * @private
17199 */
17200MdPanelRef.prototype._animateClose = function() {
17201  var animationConfig = this.config['animation'];
17202  if (!animationConfig) {
17203    this.panelContainer.removeClass('md-panel-is-showing');
vendor: 35,225 bytes, lines 17204-18496
17204    this.panelContainer.removeClass('_md-panel-shown');
17205    return this._$q.when(this);
17206  }
17207
17208  var self = this;
17209  return this._$q(function(resolve) {
17210    var done = function() {
17211      self.panelContainer.removeClass('md-panel-is-showing');
17212      resolve(self);
17213    };
17214    var warnAndClose = function() {
17215      self._$log.warn(
17216          'mdPanel: MdPanel Animations failed. ' +
17217          'Hiding panel without animating.');
17218      done();
17219    };
17220
17221    animationConfig.animateClose(self.panelEl)
17222        .then(done, warnAndClose);
17223  });
17224};
17225
17226
17227/**
17228 * Registers a interceptor with the panel. The callback should return a promise,
17229 * which will allow the action to continue when it gets resolved, or will
17230 * prevent an action if it is rejected.
17231 * @param {string} type Type of interceptor.
17232 * @param {!angular.$q.Promise<!any>} callback Callback to be registered.
17233 * @returns {!MdPanelRef}
17234 */
17235MdPanelRef.prototype.registerInterceptor = function(type, callback) {
17236  var error = null;
17237
17238  if (!angular.isString(type)) {
17239    error = 'Interceptor type must be a string, instead got ' + typeof type;
17240  } else if (!angular.isFunction(callback)) {
17241    error = 'Interceptor callback must be a function, instead got ' + typeof callback;
17242  }
17243
17244  if (error) {
17245    throw new Error('MdPanel: ' + error);
17246  }
17247
17248  var interceptors = this._interceptors[type] = this._interceptors[type] || [];
17249
17250  if (interceptors.indexOf(callback) === -1) {
17251    interceptors.push(callback);
17252  }
17253
17254  return this;
17255};
17256
17257
17258/**
17259 * Removes a registered interceptor.
17260 * @param {string} type Type of interceptor to be removed.
17261 * @param {Function} callback Interceptor to be removed.
17262 * @returns {!MdPanelRef}
17263 */
17264MdPanelRef.prototype.removeInterceptor = function(type, callback) {
17265  var index = this._interceptors[type] ?
17266    this._interceptors[type].indexOf(callback) : -1;
17267
17268  if (index > -1) {
17269    this._interceptors[type].splice(index, 1);
17270  }
17271
17272  return this;
17273};
17274
17275
17276/**
17277 * Removes all interceptors.
17278 * @param {string=} type Type of interceptors to be removed.
17279 *     If ommited, all interceptors types will be removed.
17280 * @returns {!MdPanelRef}
17281 */
17282MdPanelRef.prototype.removeAllInterceptors = function(type) {
17283  if (type) {
17284    this._interceptors[type] = [];
17285  } else {
17286    this._interceptors = Object.create(null);
17287  }
17288
17289  return this;
17290};
17291
17292
17293/**
17294 * Invokes all the interceptors of a certain type sequantially in
17295 *     reverse order. Works in a similar way to `$q.all`, except it
17296 *     respects the order of the functions.
17297 * @param {string} type Type of interceptors to be invoked.
17298 * @returns {!angular.$q.Promise<!MdPanelRef>}
17299 * @private
17300 */
17301MdPanelRef.prototype._callInterceptors = function(type) {
17302  var self = this;
17303  var $q = self._$q;
17304  var interceptors = self._interceptors && self._interceptors[type] || [];
17305
17306  return interceptors.reduceRight(function(promise, interceptor) {
17307    var isPromiseLike = interceptor && angular.isFunction(interceptor.then);
17308    var response = isPromiseLike ? interceptor : null;
17309
17310    /**
17311    * For interceptors to reject/cancel subsequent portions of the chain, simply
17312    * return a `$q.reject(<value>)`
17313    */
17314    return promise.then(function() {
17315      if (!response) {
17316        try {
17317          response = interceptor(self);
17318        } catch(e) {
17319          response = $q.reject(e);
17320        }
17321      }
17322
17323     return response;
17324    });
17325  }, $q.resolve(self));
17326};
17327
17328
17329/**
17330 * Faster, more basic than angular.bind
17331 * http://jsperf.com/angular-bind-vs-custom-vs-native
17332 * @param {function} callback
17333 * @param {!Object} self
17334 * @return {function} Callback function with a bound self.
17335 */
17336MdPanelRef.prototype._simpleBind = function(callback, self) {
17337  return function(value) {
17338    return callback.apply(self, value);
17339  };
17340};
17341
17342
17343/**
17344 * @param {function} callback
17345 * @param {!Object} self
17346 * @return {function} Callback function with a self param.
17347 */
17348MdPanelRef.prototype._done = function(callback, self) {
17349  return function() {
17350    callback(self);
17351  };
17352};
17353
17354
17355/**
17356 * Adds a panel to a group if the panel does not exist within the group already.
17357 * A panel can only exist within a single group.
17358 * @param {string} groupName The name of the group.
17359 */
17360MdPanelRef.prototype.addToGroup = function(groupName) {
17361  if (!this._$mdPanel._groups[groupName]) {
17362    this._$mdPanel.newPanelGroup(groupName);
17363  }
17364
17365  var group = this._$mdPanel._groups[groupName];
17366  var index = group.panels.indexOf(this);
17367
17368  if (index < 0) {
17369    group.panels.push(this);
17370  }
17371};
17372
17373
17374/**
17375 * Removes a panel from a group if the panel exists within that group. The group
17376 * must be created ahead of time.
17377 * @param {string} groupName The name of the group.
17378 */
17379MdPanelRef.prototype.removeFromGroup = function(groupName) {
17380  if (!this._$mdPanel._groups[groupName]) {
17381    throw new Error('mdPanel: The group ' + groupName + ' does not exist.');
17382  }
17383
17384  var group = this._$mdPanel._groups[groupName];
17385  var index = group.panels.indexOf(this);
17386
17387  if (index > -1) {
17388    group.panels.splice(index, 1);
17389  }
17390};
17391
17392
17393/**
17394 * Possible default closeReasons for the close function.
17395 * @enum {string}
17396 */
17397MdPanelRef.closeReasons = {
17398  CLICK_OUTSIDE: 'clickOutsideToClose',
17399  ESCAPE: 'escapeToClose',
17400};
17401
17402
17403/*****************************************************************************
17404 *                               MdPanelPosition                             *
17405 *****************************************************************************/
17406
17407
17408/**
17409 * Position configuration object. To use, create an MdPanelPosition with the
17410 * desired properties, then pass the object as part of $mdPanel creation.
17411 *
17412 * Example:
17413 *
17414 * var panelPosition = new MdPanelPosition()
17415 *     .relativeTo(myButtonEl)
17416 *     .addPanelPosition(
17417 *       $mdPanel.xPosition.CENTER,
17418 *       $mdPanel.yPosition.ALIGN_TOPS
17419 *     );
17420 *
17421 * $mdPanel.create({
17422 *   position: panelPosition
17423 * });
17424 *
17425 * @param {!angular.$injector} $injector
17426 * @final @constructor
17427 */
17428function MdPanelPosition($injector) {
17429  /** @private @const {!angular.$window} */
17430  this._$window = $injector.get('$window');
17431
17432  /** @private {boolean} */
17433  this._isRTL = $injector.get('$mdUtil').bidi() === 'rtl';
17434
17435  /** @private @const {!angular.$mdConstant} */
17436  this._$mdConstant = $injector.get('$mdConstant');
17437
17438  /** @private {boolean} */
17439  this._absolute = false;
17440
17441  /** @private {!angular.JQLite} */
17442  this._relativeToEl;
17443
17444  /** @private {string} */
17445  this._top = '';
17446
17447  /** @private {string} */
17448  this._bottom = '';
17449
17450  /** @private {string} */
17451  this._left = '';
17452
17453  /** @private {string} */
17454  this._right = '';
17455
17456  /** @private {!Array<string>} */
17457  this._translateX = [];
17458
17459  /** @private {!Array<string>} */
17460  this._translateY = [];
17461
17462  /** @private {!Array<{x:string, y:string}>} */
17463  this._positions = [];
17464
17465  /** @private {?{x:string, y:string}} */
17466  this._actualPosition;
17467}
17468
17469
17470/**
17471 * Possible values of xPosition.
17472 * @enum {string}
17473 */
17474MdPanelPosition.xPosition = {
17475  CENTER: 'center',
17476  ALIGN_START: 'align-start',
17477  ALIGN_END: 'align-end',
17478  OFFSET_START: 'offset-start',
17479  OFFSET_END: 'offset-end'
17480};
17481
17482
17483/**
17484 * Possible values of yPosition.
17485 * @enum {string}
17486 */
17487MdPanelPosition.yPosition = {
17488  CENTER: 'center',
17489  ALIGN_TOPS: 'align-tops',
17490  ALIGN_BOTTOMS: 'align-bottoms',
17491  ABOVE: 'above',
17492  BELOW: 'below'
17493};
17494
17495
17496/**
17497 * Possible values of absolute position.
17498 * @enum {string}
17499 */
17500MdPanelPosition.absPosition = {
17501  TOP: 'top',
17502  RIGHT: 'right',
17503  BOTTOM: 'bottom',
17504  LEFT: 'left'
17505};
17506
17507/**
17508 * Margin between the edges of a panel and the viewport.
17509 * @const {number}
17510 */
17511MdPanelPosition.viewportMargin = 8;
17512
17513
17514/**
17515 * Sets absolute positioning for the panel.
17516 * @return {!MdPanelPosition}
17517 */
17518MdPanelPosition.prototype.absolute = function() {
17519  this._absolute = true;
17520  return this;
17521};
17522
17523
17524/**
17525 * Sets the value of a position for the panel. Clears any previously set
17526 * position.
17527 * @param {string} position Position to set
17528 * @param {string=} value Value of the position. Defaults to '0'.
17529 * @returns {!MdPanelPosition}
17530 * @private
17531 */
17532MdPanelPosition.prototype._setPosition = function(position, value) {
17533  if (position === MdPanelPosition.absPosition.RIGHT ||
17534      position === MdPanelPosition.absPosition.LEFT) {
17535    this._left = this._right = '';
17536  } else if (
17537      position === MdPanelPosition.absPosition.BOTTOM ||
17538      position === MdPanelPosition.absPosition.TOP) {
17539    this._top = this._bottom = '';
17540  } else {
17541    var positions = Object.keys(MdPanelPosition.absPosition).join()
17542        .toLowerCase();
17543
17544    throw new Error('mdPanel: Position must be one of ' + positions + '.');
17545  }
17546
17547  this['_' +  position] = angular.isString(value) ? value : '0';
17548
17549  return this;
17550};
17551
17552
17553/**
17554 * Sets the value of `top` for the panel. Clears any previously set vertical
17555 * position.
17556 * @param {string=} top Value of `top`. Defaults to '0'.
17557 * @returns {!MdPanelPosition}
17558 */
17559MdPanelPosition.prototype.top = function(top) {
17560  return this._setPosition(MdPanelPosition.absPosition.TOP, top);
17561};
17562
17563
17564/**
17565 * Sets the value of `bottom` for the panel. Clears any previously set vertical
17566 * position.
17567 * @param {string=} bottom Value of `bottom`. Defaults to '0'.
17568 * @returns {!MdPanelPosition}
17569 */
17570MdPanelPosition.prototype.bottom = function(bottom) {
17571  return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom);
17572};
17573
17574
17575/**
17576 * Sets the panel to the start of the page - `left` if `ltr` or `right` for
17577 * `rtl`. Clears any previously set horizontal position.
17578 * @param {string=} start Value of position. Defaults to '0'.
17579 * @returns {!MdPanelPosition}
17580 */
17581MdPanelPosition.prototype.start = function(start) {
17582  var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT;
17583  return this._setPosition(position, start);
17584};
17585
17586
17587/**
17588 * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
17589 * Clears any previously set horizontal position.
17590 * @param {string=} end Value of position. Defaults to '0'.
17591 * @returns {!MdPanelPosition}
17592 */
17593MdPanelPosition.prototype.end = function(end) {
17594  var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT;
17595  return this._setPosition(position, end);
17596};
17597
17598
17599/**
17600 * Sets the value of `left` for the panel. Clears any previously set
17601 * horizontal position.
17602 * @param {string=} left Value of `left`. Defaults to '0'.
17603 * @returns {!MdPanelPosition}
17604 */
17605MdPanelPosition.prototype.left = function(left) {
17606  return this._setPosition(MdPanelPosition.absPosition.LEFT, left);
17607};
17608
17609
17610/**
17611 * Sets the value of `right` for the panel. Clears any previously set
17612 * horizontal position.
17613 * @param {string=} right Value of `right`. Defaults to '0'.
17614 * @returns {!MdPanelPosition}
17615*/
17616MdPanelPosition.prototype.right = function(right) {
17617  return this._setPosition(MdPanelPosition.absPosition.RIGHT, right);
17618};
17619
17620
17621/**
17622 * Centers the panel horizontally in the viewport. Clears any previously set
17623 * horizontal position.
17624 * @returns {!MdPanelPosition}
17625 */
17626MdPanelPosition.prototype.centerHorizontally = function() {
17627  this._left = '50%';
17628  this._right = '';
17629  this._translateX = ['-50%'];
17630  return this;
17631};
17632
17633
17634/**
17635 * Centers the panel vertically in the viewport. Clears any previously set
17636 * vertical position.
17637 * @returns {!MdPanelPosition}
17638 */
17639MdPanelPosition.prototype.centerVertically = function() {
17640  this._top = '50%';
17641  this._bottom = '';
17642  this._translateY = ['-50%'];
17643  return this;
17644};
17645
17646
17647/**
17648 * Centers the panel horizontally and vertically in the viewport. This is
17649 * equivalent to calling both `centerHorizontally` and `centerVertically`.
17650 * Clears any previously set horizontal and vertical positions.
17651 * @returns {!MdPanelPosition}
17652 */
17653MdPanelPosition.prototype.center = function() {
17654  return this.centerHorizontally().centerVertically();
17655};
17656
17657
17658/**
17659 * Sets element for relative positioning.
17660 * @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
17661 *     or angular element to set the panel relative to.
17662 * @returns {!MdPanelPosition}
17663 */
17664MdPanelPosition.prototype.relativeTo = function(element) {
17665  this._absolute = false;
17666  this._relativeToEl = getElement(element);
17667  return this;
17668};
17669
17670
17671/**
17672 * Sets the x and y positions for the panel relative to another element.
17673 * @param {string} xPosition must be one of the MdPanelPosition.xPosition
17674 *     values.
17675 * @param {string} yPosition must be one of the MdPanelPosition.yPosition
17676 *     values.
17677 * @returns {!MdPanelPosition}
17678 */
17679MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) {
17680  if (!this._relativeToEl) {
17681    throw new Error('mdPanel: addPanelPosition can only be used with ' +
17682        'relative positioning. Set relativeTo first.');
17683  }
17684
17685  this._validateXPosition(xPosition);
17686  this._validateYPosition(yPosition);
17687
17688  this._positions.push({
17689      x: xPosition,
17690      y: yPosition,
17691  });
17692  return this;
17693};
17694
17695
17696/**
17697 * Ensures that yPosition is a valid position name. Throw an exception if not.
17698 * @param {string} yPosition
17699 */
17700MdPanelPosition.prototype._validateYPosition = function(yPosition) {
17701  // empty is ok
17702  if (yPosition == null) {
17703      return;
17704  }
17705
17706  var positionKeys = Object.keys(MdPanelPosition.yPosition);
17707  var positionValues = [];
17708  for (var key, i = 0; key = positionKeys[i]; i++) {
17709    var position = MdPanelPosition.yPosition[key];
17710    positionValues.push(position);
17711
17712    if (position === yPosition) {
17713      return;
17714    }
17715  }
17716
17717  throw new Error('mdPanel: Panel y position only accepts the following ' +
17718      'values:\n' + positionValues.join(' | '));
17719};
17720
17721
17722/**
17723 * Ensures that xPosition is a valid position name. Throw an exception if not.
17724 * @param {string} xPosition
17725 */
17726MdPanelPosition.prototype._validateXPosition = function(xPosition) {
17727  // empty is ok
17728  if (xPosition == null) {
17729      return;
17730  }
17731
17732  var positionKeys = Object.keys(MdPanelPosition.xPosition);
17733  var positionValues = [];
17734  for (var key, i = 0; key = positionKeys[i]; i++) {
17735    var position = MdPanelPosition.xPosition[key];
17736    positionValues.push(position);
17737    if (position === xPosition) {
17738      return;
17739    }
17740  }
17741
17742  throw new Error('mdPanel: Panel x Position only accepts the following ' +
17743      'values:\n' + positionValues.join(' | '));
17744};
17745
17746
17747/**
17748 * Sets the value of the offset in the x-direction. This will add to any
17749 * previously set offsets.
17750 * @param {string|function(MdPanelPosition): string} offsetX
17751 * @returns {!MdPanelPosition}
17752 */
17753MdPanelPosition.prototype.withOffsetX = function(offsetX) {
17754  this._translateX.push(offsetX);
17755  return this;
17756};
17757
17758
17759/**
17760 * Sets the value of the offset in the y-direction. This will add to any
17761 * previously set offsets.
17762 * @param {string|function(MdPanelPosition): string} offsetY
17763 * @returns {!MdPanelPosition}
17764 */
17765MdPanelPosition.prototype.withOffsetY = function(offsetY) {
17766  this._translateY.push(offsetY);
17767  return this;
17768};
17769
17770
17771/**
17772 * Gets the value of `top` for the panel.
17773 * @returns {string}
17774 */
17775MdPanelPosition.prototype.getTop = function() {
17776  return this._top;
17777};
17778
17779
17780/**
17781 * Gets the value of `bottom` for the panel.
17782 * @returns {string}
17783 */
17784MdPanelPosition.prototype.getBottom = function() {
17785  return this._bottom;
17786};
17787
17788
17789/**
17790 * Gets the value of `left` for the panel.
17791 * @returns {string}
17792 */
17793MdPanelPosition.prototype.getLeft = function() {
17794  return this._left;
17795};
17796
17797
17798/**
17799 * Gets the value of `right` for the panel.
17800 * @returns {string}
17801 */
17802MdPanelPosition.prototype.getRight = function() {
17803  return this._right;
17804};
17805
17806
17807/**
17808 * Gets the value of `transform` for the panel.
17809 * @returns {string}
17810 */
17811MdPanelPosition.prototype.getTransform = function() {
17812  var translateX = this._reduceTranslateValues('translateX', this._translateX);
17813  var translateY = this._reduceTranslateValues('translateY', this._translateY);
17814
17815  // It's important to trim the result, because the browser will ignore the set
17816  // operation if the string contains only whitespace.
17817  return (translateX + ' ' + translateY).trim();
17818};
17819
17820
17821/**
17822 * Sets the `transform` value for a panel element.
17823 * @param {!angular.JQLite} panelEl
17824 * @returns {!angular.JQLite}
17825 * @private
17826 */
17827MdPanelPosition.prototype._setTransform = function(panelEl) {
17828  return panelEl.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
17829};
17830
17831
17832/**
17833 * True if the panel is completely on-screen with this positioning; false
17834 * otherwise.
17835 * @param {!angular.JQLite} panelEl
17836 * @return {boolean}
17837 * @private
17838 */
17839MdPanelPosition.prototype._isOnscreen = function(panelEl) {
17840  // this works because we always use fixed positioning for the panel,
17841  // which is relative to the viewport.
17842  var left = parseInt(this.getLeft());
17843  var top = parseInt(this.getTop());
17844
17845  if (this._translateX.length || this._translateY.length) {
17846    var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
17847    var offsets = getComputedTranslations(panelEl, prefixedTransform);
17848    left += offsets.x;
17849    top += offsets.y;
17850  }
17851
17852  var right = left + panelEl[0].offsetWidth;
17853  var bottom = top + panelEl[0].offsetHeight;
17854
17855  return (left >= 0) &&
17856    (top >= 0) &&
17857    (bottom <= this._$window.innerHeight) &&
17858    (right <= this._$window.innerWidth);
17859};
17860
17861
17862/**
17863 * Gets the first x/y position that can fit on-screen.
17864 * @returns {{x: string, y: string}}
17865 */
17866MdPanelPosition.prototype.getActualPosition = function() {
17867  return this._actualPosition;
17868};
17869
17870
17871/**
17872 * Reduces a list of translate values to a string that can be used within
17873 * transform.
17874 * @param {string} translateFn
17875 * @param {!Array<string>} values
17876 * @returns {string}
17877 * @private
17878 */
17879MdPanelPosition.prototype._reduceTranslateValues =
17880    function(translateFn, values) {
17881      return values.map(function(translation) {
17882        // TODO(crisbeto): this should add the units after #9609 is merged.
17883        var translationValue = angular.isFunction(translation) ?
17884            translation(this) : translation;
17885        return translateFn + '(' + translationValue + ')';
17886      }, this).join(' ');
17887    };
17888
17889
17890/**
17891 * Sets the panel position based on the created panel element and best x/y
17892 * positioning.
17893 * @param {!angular.JQLite} panelEl
17894 * @private
17895 */
17896MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
17897  // Remove the "position adjusted" class in case it has been added before.
17898  panelEl.removeClass('_md-panel-position-adjusted');
17899
17900  // Only calculate the position if necessary.
17901  if (this._absolute) {
17902    this._setTransform(panelEl);
17903    return;
17904  }
17905
17906  if (this._actualPosition) {
17907    this._calculatePanelPosition(panelEl, this._actualPosition);
17908    this._setTransform(panelEl);
17909    this._constrainToViewport(panelEl);
17910    return;
17911  }
17912
17913  for (var i = 0; i < this._positions.length; i++) {
17914    this._actualPosition = this._positions[i];
17915    this._calculatePanelPosition(panelEl, this._actualPosition);
17916    this._setTransform(panelEl);
17917
17918    if (this._isOnscreen(panelEl)) {
17919      return;
17920    }
17921  }
17922
17923  this._constrainToViewport(panelEl);
17924};
17925
17926
17927/**
17928 * Constrains a panel's position to the viewport.
17929 * @param {!angular.JQLite} panelEl
17930 * @private
17931 */
17932MdPanelPosition.prototype._constrainToViewport = function(panelEl) {
17933  var margin = MdPanelPosition.viewportMargin;
17934  var initialTop = this._top;
17935  var initialLeft = this._left;
17936
17937  if (this.getTop()) {
17938    var top = parseInt(this.getTop());
17939    var bottom = panelEl[0].offsetHeight + top;
17940    var viewportHeight = this._$window.innerHeight;
17941
17942    if (top < margin) {
17943      this._top = margin + 'px';
17944    } else if (bottom > viewportHeight) {
17945      this._top = top - (bottom - viewportHeight + margin) + 'px';
17946    }
17947  }
17948
17949  if (this.getLeft()) {
17950    var left = parseInt(this.getLeft());
17951    var right = panelEl[0].offsetWidth + left;
17952    var viewportWidth = this._$window.innerWidth;
17953
17954    if (left < margin) {
17955      this._left = margin + 'px';
17956    } else if (right > viewportWidth) {
17957      this._left = left - (right - viewportWidth + margin) + 'px';
17958    }
17959  }
17960
17961  // Class that can be used to re-style the panel if it was repositioned.
17962  panelEl.toggleClass(
17963    '_md-panel-position-adjusted',
17964    this._top !== initialTop || this._left !== initialLeft
17965  );
17966};
17967
17968
17969/**
17970 * Switches between 'start' and 'end'.
17971 * @param {string} position Horizontal position of the panel
17972 * @returns {string} Reversed position
17973 * @private
17974 */
17975MdPanelPosition.prototype._reverseXPosition = function(position) {
17976  if (position === MdPanelPosition.xPosition.CENTER) {
17977    return position;
17978  }
17979
17980  var start = 'start';
17981  var end = 'end';
17982
17983  return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start);
17984};
17985
17986
17987/**
17988 * Handles horizontal positioning in rtl or ltr environments.
17989 * @param {string} position Horizontal position of the panel
17990 * @returns {string} The correct position according the page direction
17991 * @private
17992 */
17993MdPanelPosition.prototype._bidi = function(position) {
17994  return this._isRTL ? this._reverseXPosition(position) : position;
17995};
17996
17997
17998/**
17999 * Calculates the panel position based on the created panel element and the
18000 * provided positioning.
18001 * @param {!angular.JQLite} panelEl
18002 * @param {!{x:string, y:string}} position
18003 * @private
18004 */
18005MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) {
18006
18007  var panelBounds = panelEl[0].getBoundingClientRect();
18008  var panelWidth = panelBounds.width;
18009  var panelHeight = panelBounds.height;
18010
18011  var targetBounds = this._relativeToEl[0].getBoundingClientRect();
18012
18013  var targetLeft = targetBounds.left;
18014  var targetRight = targetBounds.right;
18015  var targetWidth = targetBounds.width;
18016
18017  switch (this._bidi(position.x)) {
18018    case MdPanelPosition.xPosition.OFFSET_START:
18019      this._left = targetLeft - panelWidth + 'px';
18020      break;
18021    case MdPanelPosition.xPosition.ALIGN_END:
18022      this._left = targetRight - panelWidth + 'px';
18023      break;
18024    case MdPanelPosition.xPosition.CENTER:
18025      var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth);
18026      this._left = left + 'px';
18027      break;
18028    case MdPanelPosition.xPosition.ALIGN_START:
18029      this._left = targetLeft + 'px';
18030      break;
18031    case MdPanelPosition.xPosition.OFFSET_END:
18032      this._left = targetRight + 'px';
18033      break;
18034  }
18035
18036  var targetTop = targetBounds.top;
18037  var targetBottom = targetBounds.bottom;
18038  var targetHeight = targetBounds.height;
18039
18040  switch (position.y) {
18041    case MdPanelPosition.yPosition.ABOVE:
18042      this._top = targetTop - panelHeight + 'px';
18043      break;
18044    case MdPanelPosition.yPosition.ALIGN_BOTTOMS:
18045      this._top = targetBottom - panelHeight + 'px';
18046      break;
18047    case MdPanelPosition.yPosition.CENTER:
18048      var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight);
18049      this._top = top + 'px';
18050      break;
18051    case MdPanelPosition.yPosition.ALIGN_TOPS:
18052      this._top = targetTop + 'px';
18053      break;
18054    case MdPanelPosition.yPosition.BELOW:
18055      this._top = targetBottom + 'px';
18056      break;
18057  }
18058};
18059
18060
18061/*****************************************************************************
18062 *                               MdPanelAnimation                            *
18063 *****************************************************************************/
18064
18065
18066/**
18067 * Animation configuration object. To use, create an MdPanelAnimation with the
18068 * desired properties, then pass the object as part of $mdPanel creation.
18069 *
18070 * Example:
18071 *
18072 * var panelAnimation = new MdPanelAnimation()
18073 *     .openFrom(myButtonEl)
18074 *     .closeTo('.my-button')
18075 *     .withAnimation($mdPanel.animation.SCALE);
18076 *
18077 * $mdPanel.create({
18078 *   animation: panelAnimation
18079 * });
18080 *
18081 * @param {!angular.$injector} $injector
18082 * @final @constructor
18083 */
18084function MdPanelAnimation($injector) {
18085  /** @private @const {!angular.$mdUtil} */
18086  this._$mdUtil = $injector.get('$mdUtil');
18087
18088  /**
18089   * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
18090   *     undefined}
18091   */
18092  this._openFrom;
18093
18094  /**
18095   * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}|
18096   *     undefined}
18097   */
18098  this._closeTo;
18099
18100  /** @private {string|{open: string, close: string}} */
18101  this._animationClass = '';
18102
18103  /** @private {number} */
18104  this._openDuration;
18105
18106  /** @private {number} */
18107  this._closeDuration;
18108
18109  /** @private {number|{open: number, close: number}} */
18110  this._rawDuration;
18111}
18112
18113
18114/**
18115 * Possible default animations.
18116 * @enum {string}
18117 */
18118MdPanelAnimation.animation = {
18119  SLIDE: 'md-panel-animate-slide',
18120  SCALE: 'md-panel-animate-scale',
18121  FADE: 'md-panel-animate-fade'
18122};
18123
18124
18125/**
18126 * Specifies where to start the open animation. `openFrom` accepts a
18127 * click event object, query selector, DOM element, or a Rect object that
18128 * is used to determine the bounds. When passed a click event, the location
18129 * of the click will be used as the position to start the animation.
18130 * @param {string|!Element|!Event|{top: number, left: number}} openFrom
18131 * @returns {!MdPanelAnimation}
18132 */
18133MdPanelAnimation.prototype.openFrom = function(openFrom) {
18134  // Check if 'openFrom' is an Event.
18135  openFrom = openFrom.target ? openFrom.target : openFrom;
18136
18137  this._openFrom = this._getPanelAnimationTarget(openFrom);
18138
18139  if (!this._closeTo) {
18140    this._closeTo = this._openFrom;
18141  }
18142  return this;
18143};
18144
18145
18146/**
18147 * Specifies where to animate the panel close. `closeTo` accepts a
18148 * query selector, DOM element, or a Rect object that is used to determine
18149 * the bounds.
18150 * @param {string|!Element|{top: number, left: number}} closeTo
18151 * @returns {!MdPanelAnimation}
18152 */
18153MdPanelAnimation.prototype.closeTo = function(closeTo) {
18154  this._closeTo = this._getPanelAnimationTarget(closeTo);
18155  return this;
18156};
18157
18158
18159/**
18160 * Specifies the duration of the animation in milliseconds.
18161 * @param {number|{open: number, close: number}} duration
18162 * @returns {!MdPanelAnimation}
18163 */
18164MdPanelAnimation.prototype.duration = function(duration) {
18165  if (duration) {
18166    if (angular.isNumber(duration)) {
18167      this._openDuration = this._closeDuration = toSeconds(duration);
18168    } else if (angular.isObject(duration)) {
18169      this._openDuration = toSeconds(duration.open);
18170      this._closeDuration = toSeconds(duration.close);
18171    }
18172  }
18173
18174  // Save the original value so it can be passed to the backdrop.
18175  this._rawDuration = duration;
18176
18177  return this;
18178
18179  function toSeconds(value) {
18180    if (angular.isNumber(value)) return value / 1000;
18181  }
18182};
18183
18184
18185/**
18186 * Returns the element and bounds for the animation target.
18187 * @param {string|!Element|{top: number, left: number}} location
18188 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
18189 * @private
18190 */
18191MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) {
18192  if (angular.isDefined(location.top) || angular.isDefined(location.left)) {
18193    return {
18194      element: undefined,
18195      bounds: {
18196        top: location.top || 0,
18197        left: location.left || 0
18198      }
18199    };
18200  } else {
18201    return this._getBoundingClientRect(getElement(location));
18202  }
18203};
18204
18205
18206/**
18207 * Specifies the animation class.
18208 *
18209 * There are several default animations that can be used:
18210 * (MdPanelAnimation.animation)
18211 *   SLIDE: The panel slides in and out from the specified
18212 *        elements.
18213 *   SCALE: The panel scales in and out.
18214 *   FADE: The panel fades in and out.
18215 *
18216 * @param {string|{open: string, close: string}} cssClass
18217 * @returns {!MdPanelAnimation}
18218 */
18219MdPanelAnimation.prototype.withAnimation = function(cssClass) {
18220  this._animationClass = cssClass;
18221  return this;
18222};
18223
18224
18225/**
18226 * Animate the panel open.
18227 * @param {!angular.JQLite} panelEl
18228 * @returns {!angular.$q.Promise} A promise that is resolved when the open
18229 *     animation is complete.
18230 */
18231MdPanelAnimation.prototype.animateOpen = function(panelEl) {
18232  var animator = this._$mdUtil.dom.animator;
18233
18234  this._fixBounds(panelEl);
18235  var animationOptions = {};
18236
18237  // Include the panel transformations when calculating the animations.
18238  var panelTransform = panelEl[0].style.transform || '';
18239
18240  var openFrom = animator.toTransformCss(panelTransform);
18241  var openTo = animator.toTransformCss(panelTransform);
18242
18243  switch (this._animationClass) {
18244    case MdPanelAnimation.animation.SLIDE:
18245      // Slide should start with opacity: 1.
18246      panelEl.css('opacity', '1');
18247
18248      animationOptions = {
18249        transitionInClass: '_md-panel-animate-enter'
18250      };
18251
18252      var openSlide = animator.calculateSlideToOrigin(
18253              panelEl, this._openFrom) || '';
18254      openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform);
18255      break;
18256
18257    case MdPanelAnimation.animation.SCALE:
18258      animationOptions = {
18259        transitionInClass: '_md-panel-animate-enter'
18260      };
18261
18262      var openScale = animator.calculateZoomToOrigin(
18263              panelEl, this._openFrom) || '';
18264      openFrom = animator.toTransformCss(openScale + ' ' + panelTransform);
18265      break;
18266
18267    case MdPanelAnimation.animation.FADE:
18268      animationOptions = {
18269        transitionInClass: '_md-panel-animate-enter'
18270      };
18271      break;
18272
18273    default:
18274      if (angular.isString(this._animationClass)) {
18275        animationOptions = {
18276          transitionInClass: this._animationClass
18277        };
18278      } else {
18279        animationOptions = {
18280          transitionInClass: this._animationClass['open'],
18281          transitionOutClass: this._animationClass['close'],
18282        };
18283      }
18284  }
18285
18286  animationOptions.duration = this._openDuration;
18287
18288  return animator
18289      .translate3d(panelEl, openFrom, openTo, animationOptions);
18290};
18291
18292
18293/**
18294 * Animate the panel close.
18295 * @param {!angular.JQLite} panelEl
18296 * @returns {!angular.$q.Promise} A promise that resolves when the close
18297 *     animation is complete.
18298 */
18299MdPanelAnimation.prototype.animateClose = function(panelEl) {
18300  var animator = this._$mdUtil.dom.animator;
18301  var reverseAnimationOptions = {};
18302
18303  // Include the panel transformations when calculating the animations.
18304  var panelTransform = panelEl[0].style.transform || '';
18305
18306  var closeFrom = animator.toTransformCss(panelTransform);
18307  var closeTo = animator.toTransformCss(panelTransform);
18308
18309  switch (this._animationClass) {
18310    case MdPanelAnimation.animation.SLIDE:
18311      // Slide should start with opacity: 1.
18312      panelEl.css('opacity', '1');
18313      reverseAnimationOptions = {
18314        transitionInClass: '_md-panel-animate-leave'
18315      };
18316
18317      var closeSlide = animator.calculateSlideToOrigin(
18318              panelEl, this._closeTo) || '';
18319      closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform);
18320      break;
18321
18322    case MdPanelAnimation.animation.SCALE:
18323      reverseAnimationOptions = {
18324        transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave'
18325      };
18326
18327      var closeScale = animator.calculateZoomToOrigin(
18328              panelEl, this._closeTo) || '';
18329      closeTo = animator.toTransformCss(closeScale + ' ' + panelTransform);
18330      break;
18331
18332    case MdPanelAnimation.animation.FADE:
18333      reverseAnimationOptions = {
18334        transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave'
18335      };
18336      break;
18337
18338    default:
18339      if (angular.isString(this._animationClass)) {
18340        reverseAnimationOptions = {
18341          transitionOutClass: this._animationClass
18342        };
18343      } else {
18344        reverseAnimationOptions = {
18345          transitionInClass: this._animationClass['close'],
18346          transitionOutClass: this._animationClass['open']
18347        };
18348      }
18349  }
18350
18351  reverseAnimationOptions.duration = this._closeDuration;
18352
18353  return animator
18354      .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions);
18355};
18356
18357
18358/**
18359 * Set the height and width to match the panel if not provided.
18360 * @param {!angular.JQLite} panelEl
18361 * @private
18362 */
18363MdPanelAnimation.prototype._fixBounds = function(panelEl) {
18364  var panelWidth = panelEl[0].offsetWidth;
18365  var panelHeight = panelEl[0].offsetHeight;
18366
18367  if (this._openFrom && this._openFrom.bounds.height == null) {
18368    this._openFrom.bounds.height = panelHeight;
18369  }
18370  if (this._openFrom && this._openFrom.bounds.width == null) {
18371    this._openFrom.bounds.width = panelWidth;
18372  }
18373  if (this._closeTo && this._closeTo.bounds.height == null) {
18374    this._closeTo.bounds.height = panelHeight;
18375  }
18376  if (this._closeTo && this._closeTo.bounds.width == null) {
18377    this._closeTo.bounds.width = panelWidth;
18378  }
18379};
18380
18381
18382/**
18383 * Identify the bounding RECT for the target element.
18384 * @param {!angular.JQLite} element
18385 * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}}
18386 * @private
18387 */
18388MdPanelAnimation.prototype._getBoundingClientRect = function(element) {
18389  if (element instanceof angular.element) {
18390    return {
18391      element: element,
18392      bounds: element[0].getBoundingClientRect()
18393    };
18394  }
18395};
18396
18397
18398/*****************************************************************************
18399 *                                Util Methods                               *
18400 *****************************************************************************/
18401
18402
18403/**
18404 * Returns the angular element associated with a css selector or element.
18405 * @param el {string|!angular.JQLite|!Element}
18406 * @returns {!angular.JQLite}
18407 */
18408function getElement(el) {
18409  var queryResult = angular.isString(el) ?
18410      document.querySelector(el) : el;
18411  return angular.element(queryResult);
18412}
18413
18414
18415/**
18416 * Gets the computed values for an element's translateX and translateY in px.
18417 * @param {!angular.JQLite|!Element} el
18418 * @param {string} property
18419 * @return {{x: number, y: number}}
18420 */
18421function getComputedTranslations(el, property) {
18422  // The transform being returned by `getComputedStyle` is in the format:
18423  // `matrix(a, b, c, d, translateX, translateY)` if defined and `none`
18424  // if the element doesn't have a transform.
18425  var transform = getComputedStyle(el[0] || el)[property];
18426  var openIndex = transform.indexOf('(');
18427  var closeIndex = transform.lastIndexOf(')');
18428  var output = { x: 0, y: 0 };
18429
18430  if (openIndex > -1 && closeIndex > -1) {
18431    var parsedValues = transform
18432      .substring(openIndex + 1, closeIndex)
18433      .split(', ')
18434      .slice(-2);
18435
18436    output.x = parseInt(parsedValues[0]);
18437    output.y = parseInt(parsedValues[1]);
18438  }
18439
18440  return output;
18441}
18442
18443})();
18444(function(){
18445"use strict";
18446
18447/**
18448 * @ngdoc module
18449 * @name material.components.progressCircular
18450 * @description Module for a circular progressbar
18451 */
18452
18453angular.module('material.components.progressCircular', ['material.core']);
18454
18455})();
18456(function(){
18457"use strict";
18458
18459/**
18460 * @ngdoc module
18461 * @name material.components.progressLinear
18462 * @description Linear Progress module!
18463 */
18464MdProgressLinearDirective.$inject = ["$mdTheming", "$mdUtil", "$log"];
18465angular.module('material.components.progressLinear', [
18466  'material.core'
18467])
18468  .directive('mdProgressLinear', MdProgressLinearDirective);
18469
18470/**
18471 * @ngdoc directive
18472 * @name mdProgressLinear
18473 * @module material.components.progressLinear
18474 * @restrict E
18475 *
18476 * @description
18477 * The linear progress directive is used to make loading content
18478 * in your app as delightful and painless as possible by minimizing
18479 * the amount of visual change a user sees before they can view
18480 * and interact with content.
18481 *
18482 * Each operation should only be represented by one activity indicator
18483 * For example: one refresh operation should not display both a
18484 * refresh bar and an activity circle.
18485 *
18486 * For operations where the percentage of the operation completed
18487 * can be determined, use a determinate indicator. They give users
18488 * a quick sense of how long an operation will take.
18489 *
18490 * For operations where the user is asked to wait a moment while
18491 * something finishes up, and it’s not necessary to expose what's
18492 * happening behind the scenes and how long it will take, use an
18493 * indeterminate indicator.
18494 *
18495 * @param {string} md-mode Select from one of four modes: determinate, indeterminate, buffer or query.
18496 *
18497 * Note: if the `md-mode` value is set as undefined or specified as 1 of the four (4) valid modes, then `indeterminate`
18498 * will be auto-applied as the mode.
18499 *
18500 * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute. If `value=""` is also specified, however,
18501 * then `md-mode="determinate"` would be auto-injected instead.
18502 * @param {number=} value In determinate and buffer modes, this number represents the percentage of the primary progress bar. Default: 0
18503 * @param {number=} md-buffer-value In the buffer mode, this number represents the percentage of the secondary progress bar. Default: 0
18504 * @param {boolean=} ng-disabled Determines whether to disable the progress element.
18505 *
18506 * @usage
18507 * <hljs lang="html">
18508 * <md-progress-linear md-mode="determinate" value="..."></md-progress-linear>
18509 *
18510 * <md-progress-linear md-mode="determinate" ng-value="..."></md-progress-linear>
18511 *
18512 * <md-progress-linear md-mode="indeterminate"></md-progress-linear>
18513 *
18514 * <md-progress-linear md-mode="buffer" value="..." md-buffer-value="..."></md-progress-linear>
18515 *
18516 * <md-progress-linear md-mode="query"></md-progress-linear>
18517 * </hljs>
18518 */
18519function MdProgressLinearDirective($mdTheming, $mdUtil, $log) {
18520  var MODE_DETERMINATE = "determinate";
18521  var MODE_INDETERMINATE = "indeterminate";
18522  var MODE_BUFFER = "buffer";
18523  var MODE_QUERY = "query";
18524  var DISABLED_CLASS = "_md-progress-linear-disabled";
18525
18526  return {
18527    restrict: 'E',
18528    template: '<div class="md-container">' +
18529      '<div class="md-dashed"></div>' +
18530      '<div class="md-bar md-bar1"></div>' +
18531      '<div class="md-bar md-bar2"></div>' +
18532      '</div>',
18533    compile: compile
18534  };
18535
18536  function compile(tElement, tAttrs, transclude) {
18537    tElement.attr('aria-valuemin', 0);
18538    tElement.attr('aria-valuemax', 100);
18539    tElement.attr('role', 'progressbar');
18540
18541    return postLink;
18542  }
18543  function postLink(scope, element, attr) {
18544    $mdTheming(element);
18545
18546    var lastMode;
18547    var isDisabled = attr.hasOwnProperty('disabled');
18548    var toVendorCSS = $mdUtil.dom.animator.toCss;
18549    var bar1 = angular.element(element[0].querySelector('.md-bar1'));
18550    var bar2 = angular.element(element[0].querySelector('.md-bar2'));
18551    var container = angular.element(element[0].querySelector('.md-container'));
18552
18553    element
18554      .attr('md-mode', mode())
18555      .toggleClass(DISABLED_CLASS, isDisabled);
18556
18557    validateMode();
18558    watchAttributes();
18559
18560    /**
18561     * Watch the value, md-buffer-value, and md-mode attributes
18562     */
18563    function watchAttributes() {
18564      attr.$observe('value', function(value) {
18565        var percentValue = clamp(value);
18566        element.attr('aria-valuenow', percentValue);
18567
18568        if (mode() != MODE_QUERY) animateIndicator(bar2, percentValue);
18569      });
18570
18571      attr.$observe('mdBufferValue', function(value) {
18572        animateIndicator(bar1, clamp(value));
18573      });
18574
18575      attr.$observe('disabled', function(value) {
18576        if (value === true || value === false) {
18577          isDisabled = !!value;
18578        } else {
18579          isDisabled = angular.isDefined(value);
18580        }
18581
18582        element.toggleClass(DISABLED_CLASS, isDisabled);
18583        container.toggleClass(lastMode, !isDisabled);
18584      });
18585
18586      attr.$observe('mdMode', function(mode) {
18587        if (lastMode) container.removeClass( lastMode );
18588
18589        switch( mode ) {
18590          case MODE_QUERY:
18591          case MODE_BUFFER:
18592          case MODE_DETERMINATE:
18593          case MODE_INDETERMINATE:
18594            container.addClass( lastMode = "md-mode-" + mode );
18595            break;
18596          default:
18597            container.addClass( lastMode = "md-mode-" + MODE_INDETERMINATE );
18598            break;
18599        }
18600      });
18601    }
18602
18603    /**
18604     * Auto-defaults the mode to either `determinate` or `indeterminate` mode; if not specified
18605     */
18606    function validateMode() {
18607      if ( angular.isUndefined(attr.mdMode) ) {
18608        var hasValue = angular.isDefined(attr.value);
18609        var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE;
18610        var info = "Auto-adding the missing md-mode='{0}' to the ProgressLinear element";
18611
18612        //$log.debug( $mdUtil.supplant(info, [mode]) );
18613
18614        element.attr("md-mode", mode);
18615        attr.mdMode = mode;
18616      }
18617    }
18618
18619    /**
18620     * Is the md-mode a valid option?
18621     */
18622    function mode() {
18623      var value = (attr.mdMode || "").trim();
18624      if ( value ) {
18625        switch(value) {
18626          case MODE_DETERMINATE:
18627          case MODE_INDETERMINATE:
18628          case MODE_BUFFER:
18629          case MODE_QUERY:
18630            break;
18631          default:
18632            value = MODE_INDETERMINATE;
18633            break;
18634        }
18635      }
18636      return value;
18637    }
18638
18639    /**
18640     * Manually set CSS to animate the Determinate indicator based on the specified
18641     * percentage value (0-100).
18642     */
18643    function animateIndicator(target, value) {
18644      if ( isDisabled || !mode() ) return;
18645
18646      var to = $mdUtil.supplant("translateX({0}%) scale({1},1)", [ (value-100)/2, value/100 ]);
18647      var styles = toVendorCSS({ transform : to });
18648      angular.element(target).css( styles );
18649    }
18650  }
18651
18652  /**
18653   * Clamps the value to be between 0 and 100.
18654   * @param {number} value The value to clamp.
18655   * @returns {number}
18656   */
18657  function clamp(value) {
18658    return Math.max(0, Math.min(value || 0, 100));
18659  }
18660}
18661
18662
18663})();
18664(function(){
18665"use strict";
18666
18667/**
18668 * @ngdoc module
18669 * @name material.components.radioButton
18670 * @description radioButton module!
18671 */
18672mdRadioGroupDirective.$inject = ["$mdUtil", "$mdConstant", "$mdTheming", "$timeout"];
18673mdRadioButtonDirective.$inject = ["$mdAria", "$mdUtil", "$mdTheming"];
18674angular.module('material.components.radioButton', [
18675  'material.core'
18676])
18677  .directive('mdRadioGroup', mdRadioGroupDirective)
18678  .directive('mdRadioButton', mdRadioButtonDirective);
18679
18680/**
18681 * @ngdoc directive
18682 * @module material.components.radioButton
18683 * @name mdRadioGroup
18684 *
18685 * @restrict E
18686 *
18687 * @description
18688 * The `<md-radio-group>` directive identifies a grouping
18689 * container for the 1..n grouped radio buttons; specified using nested
18690 * `<md-radio-button>
18690` tags.
18691 *
18692 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
18693 * the radio button is in the accent color by default. The primary color palette may be used with
18694 * the `md-primary` class.
18695 *
18696 * Note: `<md-radio-group>` and `<md-radio-button>` handle tabindex differently
18697 * than the native `<input type='radio'>` controls. Whereas the native controls
18698 * force the user to tab through all the radio buttons, `<md-radio-group>`
18699 * is focusable, and by default the `<md-radio-button>`s are not.
18700 *
18701 * @param {string} ng-model Assignable angular expression to data-bind to.
18702 * @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects.
18703 * @param {string} ngModel Assignable angular expression to data-bind to.
18704 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
18705 *    interaction with the input element.
18706 *
18707 * @usage
18708 * <hljs lang="html">
18709 * <md-radio-group ng-model="selected">
18710 *
18711 *   <md-radio-button
18712 *        ng-repeat="d in colorOptions"
18713 *        ng-value="d.value" aria-label="{{ d.label }}">
18714 *
18715 *          {{ d.label }}
18716 *
18717 *   </md-radio-button>
18717
18718 *
18719 * </md-radio-group>
18720 * </hljs>
18721 *
18722 */
18723function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) {
18724  RadioGroupController.prototype = createRadioGroupControllerProto();
18725
18726  return {
18727    restrict: 'E',
18728    controller: ['$element', RadioGroupController],
18729    require: ['mdRadioGroup', '?ngModel'],
18730    link: { pre: linkRadioGroup }
18731  };
18732
18733  function linkRadioGroup(scope, element, attr, ctrls) {
18734    element.addClass('_md');     // private md component indicator for styling
18735    $mdTheming(element);
18736
18737    var rgCtrl = ctrls[0];
18738    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
18739
18740    rgCtrl.init(ngModelCtrl);
18741
18742    scope.mouseActive = false;
18743
18744    element
18745      .attr({
18746        'role': 'radiogroup',
18747        'tabIndex': element.attr('tabindex') || '0'
18748      })
18749      .on('keydown', keydownListener)
18750      .on('mousedown', function(event) {
18751        scope.mouseActive = true;
18752        $timeout(function() {
18753          scope.mouseActive = false;
18754        }, 100);
18755      })
18756      .on('focus', function() {
18757        if(scope.mouseActive === false) {
18758          rgCtrl.$element.addClass('md-focused');
18759        }
18760      })
18761      .on('blur', function() {
18762        rgCtrl.$element.removeClass('md-focused');
18763      });
18764
18765    /**
18766     *
18767     */
18768    function setFocus() {
18769      if (!element.hasClass('md-focused')) { element.addClass('md-focused'); }
18770    }
18771
18772    /**
18773     *
18774     */
18775    function keydownListener(ev) {
18776      var keyCode = ev.which || ev.keyCode;
18777
18778      // Only listen to events that we originated ourselves
18779      // so that we don't trigger on things like arrow keys in
18780      // inputs.
18781
18782      if (keyCode != $mdConstant.KEY_CODE.ENTER &&
18783          ev.currentTarget != ev.target) {
18784        return;
18785      }
18786
18787      switch (keyCode) {
18788        case $mdConstant.KEY_CODE.LEFT_ARROW:
18789        case $mdConstant.KEY_CODE.UP_ARROW:
18790          ev.preventDefault();
18791          rgCtrl.selectPrevious();
18792          setFocus();
18793          break;
18794
18795        case $mdConstant.KEY_CODE.RIGHT_ARROW:
18796        case $mdConstant.KEY_CODE.DOWN_ARROW:
18797          ev.preventDefault();
18798          rgCtrl.selectNext();
18799          setFocus();
18800          break;
18801
18802        case $mdConstant.KEY_CODE.ENTER:
18803          var form = angular.element($mdUtil.getClosest(element[0], 'form'));
18804          if (form.length > 0) {
18805            form.triggerHandler('submit');
18806          }
18807          break;
18808      }
18809
18810    }
18811  }
18812
18813  function RadioGroupController($element) {
18814    this._radioButtonRenderFns = [];
18815    this.$element = $element;
18816  }
18817
18818  function createRadioGroupControllerProto() {
18819    return {
18820      init: function(ngModelCtrl) {
18821        this._ngModelCtrl = ngModelCtrl;
18822        this._ngModelCtrl.$render = angular.bind(this, this.render);
18823      },
18824      add: function(rbRender) {
18825        this._radioButtonRenderFns.push(rbRender);
18826      },
18827      remove: function(rbRender) {
18828        var index = this._radioButtonRenderFns.indexOf(rbRender);
18829        if (index !== -1) {
18830          this._radioButtonRenderFns.splice(index, 1);
18831        }
18832      },
18833      render: function() {
18834        this._radioButtonRenderFns.forEach(function(rbRender) {
18835          rbRender();
18836        });
18837      },
18838      setViewValue: function(value, eventType) {
18839        this._ngModelCtrl.$setViewValue(value, eventType);
18840        // update the other radio buttons as well
18841        this.render();
18842      },
18843      getViewValue: function() {
18844        return this._ngModelCtrl.$viewValue;
18845      },
18846      selectNext: function() {
18847        return changeSelectedButton(this.$element, 1);
18848      },
18849      selectPrevious: function() {
18850        return changeSelectedButton(this.$element, -1);
18851      },
18852      setActiveDescendant: function (radioId) {
18853        this.$element.attr('aria-activedescendant', radioId);
18854      },
18855      isDisabled: function() {
18856        return this.$element[0].hasAttribute('disabled');
18857      }
18858    };
18859  }
18860  /**
18861   * Change the radio group's selected button by a given increment.
18862   * If no button is selected, select the first button.
18863   */
18864  function changeSelectedButton(parent, increment) {
18865    // Coerce all child radio buttons into an array, then wrap then in an iterator
18866    var buttons = $mdUtil.iterator(parent[0].querySelectorAll('md-radio-button'), true);
18867
18868    if (buttons.count()) {
18869      var validate = function (button) {
18870        // If disabled, then NOT valid
18871        return !angular.element(button).attr("disabled");
18872      };
18873
18874      var selected = parent[0].querySelector('md-radio-button.md-checked');
18875      var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
18876
18877      // Activate radioButton's click listener (triggerHandler won't create a real click event)
18878      angular.element(target).triggerHandler('click');
18879    }
18880  }
18881
18882}
18883
18884/**
18885 * @ngdoc directive
18886 * @module material.components.radioButton
18887 * @name mdRadioButton
18888 *
18889 * @restrict E
18890 *
18891 * @description
18892 * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
18893 *
18894 * While similar to the `<input type="radio" ng-model="" value="">` directive,
18895 * the `<md-radio-button>` directive provides ink effects, ARIA support, and
18896 * supports use within named radio groups.
18897 *
18898 * @param {string} ngValue AngularJS expression which sets the value to which the expression should
18899 *    be set when selected.
18900 * @param {string} value The value to which the expression should be set when selected.
18901 * @param {string=} name Property name of the form under which the control is published.
18902 * @param {string=} aria-label Adds label to radio button for accessibility.
18903 * Defaults to radio button's text. If no text content is available, a warning will be logged.
18904 *
18905 * @usage
18906 * <hljs lang="html">
18907 *
18908 * <md-radio-button value="1" aria-label="Label 1">
18909 *   Label 1
18910 * </md-radio-button>
vendor: 4,115 bytes, lines 18910-19061
18910
18911 *
18912 * <md-radio-button ng-value="specialValue" aria-label="Green">
18913 *   Green
18914 * </md-radio-button>
18915 *
18916 * </hljs>
18917 *
18918 */
18919function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
18920
18921  var CHECKED_CSS = 'md-checked';
18922
18923  return {
18924    restrict: 'E',
18925    require: '^mdRadioGroup',
18926    transclude: true,
18927    template: '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
18928                '<div class="md-off"></div>' +
18929                '<div class="md-on"></div>' +
18930              '</div>' +
18931              '<div ng-transclude class="md-label"></div>',
18932    link: link
18933  };
18934
18935  function link(scope, element, attr, rgCtrl) {
18936    var lastChecked;
18937
18938    $mdTheming(element);
18939    configureAria(element, scope);
18940
18941    // ngAria overwrites the aria-checked inside a $watch for ngValue.
18942    // We should defer the initialization until all the watches have fired.
18943    // This can also be fixed by removing the `lastChecked` check, but that'll
18944    // cause more DOM manipulation on each digest.
18945    if (attr.ngValue) {
18946      $mdUtil.nextTick(initialize, false);
18947    } else {
18948      initialize();
18949    }
18950
18951    /**
18952     * Initializes the component.
18953     */
18954    function initialize() {
18955      if (!rgCtrl) {
18956        throw 'RadioButton: No RadioGroupController could be found.';
18957      }
18958
18959      rgCtrl.add(render);
18960      attr.$observe('value', render);
18961
18962      element
18963        .on('click', listener)
18964        .on('$destroy', function() {
18965          rgCtrl.remove(render);
18966        });
18967    }
18968
18969    /**
18970     * On click functionality.
18971     */
18972    function listener(ev) {
18973      if (element[0].hasAttribute('disabled') || rgCtrl.isDisabled()) return;
18974
18975      scope.$apply(function() {
18976        rgCtrl.setViewValue(attr.value, ev && ev.type);
18977      });
18978    }
18979
18980    /**
18981     *  Add or remove the `.md-checked` class from the RadioButton (and conditionally its parent).
18982     *  Update the `aria-activedescendant` attribute.
18983     */
18984    function render() {
18985      var checked = rgCtrl.getViewValue() == attr.value;
18986
18987      if (checked === lastChecked) return;
18988
18989      if (element[0].parentNode.nodeName.toLowerCase() !== 'md-radio-group') {
18990        // If the radioButton is inside a div, then add class so highlighting will work
18991        element.parent().toggleClass(CHECKED_CSS, checked);
18992      }
18993
18994      if (checked) {
18995        rgCtrl.setActiveDescendant(element.attr('id'));
18996      }
18997
18998      lastChecked = checked;
18999
19000      element
19001        .attr('aria-checked', checked)
19002        .toggleClass(CHECKED_CSS, checked);
19003    }
19004
19005    /**
19006     * Inject ARIA-specific attributes appropriate for each radio button
19007     */
19008    function configureAria(element, scope){
19009      element.attr({
19010        id: attr.id || 'radio_' + $mdUtil.nextUid(),
19011        role: 'radio',
19012        'aria-checked': 'false'
19013      });
19014
19015      $mdAria.expectWithText(element, 'aria-label');
19016    }
19017  }
19018}
19019
19020})();
19021(function(){
19022"use strict";
19023
19024/**
19025 * @ngdoc module
19026 * @name material.components.select
19027 */
19028
19029/***************************************************
19030
19031 ### TODO - POST RC1 ###
19032 - [ ] Abstract placement logic in $mdSelect service to $mdMenu service
19033
19034 ***************************************************/
19035
19036SelectDirective.$inject = ["$mdSelect", "$mdUtil", "$mdConstant", "$mdTheming", "$mdAria", "$parse", "$sce", "$injector"];
19037SelectMenuDirective.$inject = ["$parse", "$mdUtil", "$mdConstant", "$mdTheming"];
19038OptionDirective.$inject = ["$mdButtonInkRipple", "$mdUtil", "$mdTheming"];
19039SelectProvider.$inject = ["$$interimElementProvider"];
19040var SELECT_EDGE_MARGIN = 8;
19041var selectNextId = 0;
19042var CHECKBOX_SELECTION_INDICATOR =
19043  angular.element('<div class="md-container"><div class="md-icon"></div></div>');
19044
19045angular.module('material.components.select', [
19046    'material.core',
19047    'material.components.backdrop'
19048  ])
19049  .directive('mdSelect', SelectDirective)
19050  .directive('mdSelectMenu', SelectMenuDirective)
19051  .directive('mdOption', OptionDirective)
19052  .directive('mdOptgroup', OptgroupDirective)
19053  .directive('mdSelectHeader', SelectHeaderDirective)
19054  .provider('$mdSelect', SelectProvider);
19055
19056/**
19057 * @ngdoc directive
19058 * @name mdSelect
19059 * @restrict E
19060 * @module material.components.select
19061 *
19062 * @description Displays a select box, bound to an ng-model.
19063 *
19064 * When the select is required and uses a floating label, then the label will automatically contain
19065 * an asterisk (`*`). This behavior can be disabled by using the `md-no-asterisk` attribute.
19066 *
19067 * By default, the select will display with an underline to match other form elements. This can be
19068 * disabled by applying the `md-no-underline` CSS class.
19069 *
19070 * ### Option Params
19071 *
19072 * When applied, `md-option-empty` will mark the option as "empty" allowing the option to  clear the
19073 * select and put it back in it's default state. You may supply this attribute on any option you
19074 * wish, however, it is automatically applied to an option whose `value` or `ng-value` are not
19075 * defined.
19076 *
19077 * **Automatically Applied**
19078 *
19079 *  - `<md-option>`
19080 *  - `<md-option value>`
19081 *  - `<md-option value="">`
19082 *  - `<md-option ng-value>`
19083 *  - `<md-option ng-value="">`
19084 *
19085 * **NOT Automatically Applied**
19086 *
19087 *  - `<md-option ng-value="1">`
19088 *  - `<md-option ng-value="''">`
19089 *  - `<md-option ng-value="undefined">`
19090 *  - `<md-option value="undefined">` (this evaluates to the string `"undefined"`)
19091 *  - <code ng-non-bindable>&lt;md-option ng-value="{{someValueThatMightBeUndefined}}"&gt;</code>
19092 *
19093 * **Note:** A value of `undefined` ***is considered a valid value*** (and does not auto-apply this
19094 * attribute) since you may wish this to be your "Not Available" or "None" option.
19095 *
19096 * **Note:** Using the `value` attribute (as opposed to `ng-value`) always evaluates to a string, so
19097 * `value="null"` will require the test `ng-if="myValue != 'null'"` rather than `ng-if="!myValue"`.
19098 *
19099 * @param {expression} ng-model The model!
19100 * @param {boolean=} multiple When set to true, allows for more than one option to be selected. The model is an array with the selected choices.
19101 * @param {expression=} md-on-close Expression to be evaluated when the select is closed.
19102 * @param {expression=} md-on-open Expression to be evaluated when opening the select.
19103 * Will hide the select options and show a spinner until the evaluated promise resolves.
19104 * @param {expression=} md-selected-text Expression to be evaluated that will return a string
19105 * to be displayed as a placeholder in the select input box when it is closed. The value
19106 * will be treated as *text* (not html).
19107 * @param {expression=} md-selected-html Expression to be evaluated that will return a string
19108 * to be displayed as a placeholder in the select input box when it is closed. The value
19109 * will be treated as *html*. The value must either be explicitly marked as trustedHtml or
19110 * the ngSanitize module must be loaded.
19111 * @param {string=} placeholder Placeholder hint text.
19112 * @param md-no-asterisk {boolean=} When set to true, an asterisk will not be appended to the
19113 * floating label. **Note:** This attribute is only evaluated once; it is not watched.
19114 * @param {string=} aria-label Optional label for accessibility. Only necessary if no placeholder or
19115 * explicit label is present.
19116 * @param {string=} md-container-class Class list to get applied to the `.md-select-menu-container`
19117 * element (for custom styling).
19118 *
19119 * @usage
19120 * With a placeholder (label and aria-label are added dynamically)
19121 * <hljs lang="html">
19122 *   <md-input-container>
19123 *     <md-select
19124 *       ng-model="someModel"
19125 *       placeholder="Select a state">
19126 *       <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
19127 *     </md-select>
19128 *   </md-input-container>
19129 * </hljs>
19130 *
19131 * With an explicit label
19132 * <hljs lang="html">
19133 *   <md-input-container>
19134 *     <label>State</label>
19135 *     <md-select
19136 *       ng-model="someModel">
19137 *       <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
19138 *     </md-select>
19139 *   </md-input-container>
19140 * </hljs>
19141 *
19142 * With a select-header
19143 *
19144 * When a developer needs to put more than just a text label in the
19145 * md-select-menu, they should use the md-select-header.
19146 * The user can put custom HTML inside of the header and style it to their liking.
19147 * One common use case of this would be a sticky search bar.
19148 *
19149 * When using the md-select-header the labels that would previously be added to the
19150 * OptGroupDirective are ignored.
19151 *
19152 * <hljs lang="html">
19153 *   <md-input-container>
19154 *     <md-select ng-model="someModel">
19155 *       <md-select-header>
19156 *         <span> Neighborhoods - </span>
19157 *       </md-select-header>
19158 *       <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
19159 *     </md-select>
19160 *   </md-input-container>
19161 * </hljs>
19162 *
19163 * ## Selects and object equality
19164 * When using a `md-select` to pick from a list of objects, it is important to realize how javascript handles
19165 * equality. Consider the following example:
19166 * <hljs lang="js">
19167 * angular.controller('MyCtrl', function($scope) {
19168 *   $scope.users = [
19169 *     { id: 1, name: 'Bob' },
19170 *     { id: 2, name: 'Alice' },
19171 *     { id: 3, name: 'Steve' }
19172 *   ];
19173 *   $scope.selectedUser = { id: 1, name: 'Bob' };
19174 * });
19175 * </hljs>
19176 * <hljs lang="html">
19177 * <div ng-controller="MyCtrl">
19178 *   <md-select ng-model="selectedUser">
19179 *     <md-option ng-value="user" ng-repeat="user in users">{{ user.name }}</md-option>
19180 *   </md-select>
19181 * </div>
19182 * </hljs>
19183 *
19184 * At first one might expect that the select should be populated with "Bob" as the selected user. However,
19185 * this is not true. To determine whether something is selected,
19186 * `ngModelController` is looking at whether `$scope.selectedUser == (any user in $scope.users);`;
19187 *
19188 * Javascript's `==` operator does not check for deep equality (ie. that all properties
19189 * on the object are the same), but instead whether the objects are *the same object in memory*.
19190 * In this case, we have two instances of identical objects, but they exist in memory as unique
19191 * entities. Because of this, the select will have no value populated for a selected user.
19192 *
19193 * To get around this, `ngModelController` provides a `track by` option that allows us to specify a different
19194 * expression which will be used for the equality operator. As such, we can update our `html` to
19195 * make use of this by specifying the `ng-model-options="{trackBy: '$value.id'}"` on the `md-select`
19196 * element. This converts our equality expression to be
19197 * `$scope.selectedUser.id == (any id in $scope.users.map(function(u) { return u.id; }));`
19198 * which results in Bob being selected as desired.
19199 *
19200 * Working HTML:
19201 * <hljs lang="html">
19202 * <div ng-controller="MyCtrl">
19203 *   <md-select ng-model="selectedUser" ng-model-options="{trackBy: '$value.id'}">
19204 *     <md-option ng-value="user" ng-repeat="user in users">{{ user.name }}</md-option>
19205 *   </md-select>
19206 * </div>
19207 * </hljs>
19208 */
19209function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $parse, $sce,
19210    $injector) {
19211  var keyCodes = $mdConstant.KEY_CODE;
19212  var NAVIGATION_KEYS = [keyCodes.SPACE, keyCodes.ENTER, keyCodes.UP_ARROW, keyCodes.DOWN_ARROW];
19213
19214  return {
19215    restrict: 'E',
19216    require: ['^?mdInputContainer', 'mdSelect', 'ngModel', '?^form'],
19217    compile: compile,
19218    controller: function() {
19219    } // empty placeholder controller to be initialized in link
19220  };
19221
19222  function compile(element, attr) {
19223    // add the select value that will hold our placeholder or selected option value
19224    var valueEl = angular.element('<md-select-value><span></span></md-select-value>');
19225    valueEl.append('<span class="md-select-icon" aria-hidden="true"></span>');
19226    valueEl.addClass('md-select-value');
19227    if (!valueEl[0].hasAttribute('id')) {
19228      valueEl.attr('id', 'select_value_label_' + $mdUtil.nextUid());
19229    }
19230
19231    // There's got to be an md-content inside. If there's not one, let's add it.
19232    var mdContentEl = element.find('md-content');
19233    if (!mdContentEl.length) {
19234      element.append(angular.element('<md-content>').append(element.contents()));
19235    }
19236    mdContentEl.attr('role', 'presentation');
19237
19238
19239    // Add progress spinner for md-options-loading
19240    if (attr.mdOnOpen) {
19241
19242      // Show progress indicator while loading async
19243      // Use ng-hide for `display:none` so the indicator does not interfere with the options list
19244      element
19245        .find('md-content')
19246        .prepend(angular.element(
19247          '<div>' +
19248          ' <md-progress-circular md-mode="indeterminate" ng-if="$$loadingAsyncDone === false" md-diameter="25px"></md-progress-circular>' +
19249          '</div>'
19250        ));
19251
19252      // Hide list [of item options] while loading async
19253      element
19254        .find('md-option')
19255        .attr('ng-show', '$$loadingAsyncDone');
19256    }
19257
19258    if (attr.name) {
19259      var autofillClone = angular.element('<select class="md-visually-hidden">');
19260      autofillClone.attr({
19261        'name': attr.name,
19262        'aria-hidden': 'true',
19263        'tabindex': '-1'
19264      });
19265      var opts = element.find('md-option');
19266      angular.forEach(opts, function(el) {
19267        var newEl = angular.element('<option>' + el.innerHTML + '</option>');
19268        if (el.hasAttribute('ng-value')) newEl.attr('ng-value', el.getAttribute('ng-value'));
19269        else if (el.hasAttribute('value')) newEl.attr('value', el.getAttribute('value'));
19270        autofillClone.append(newEl);
19271      });
19272
19273      // Adds an extra option that will hold the selected value for the
19274      // cases where the select is a part of a non-angular form. This can be done with a ng-model,
19275      // however if the `md-option` is being `ng-repeat`-ed, AngularJS seems to insert a similar
19276      // `option` node, but with a value of `? string: <value> ?` which would then get submitted.
19277      // This also goes around having to prepend a dot to the name attribute.
19278      autofillClone.append(
19279        '<option ng-value="' + attr.ngModel + '" selected></option>'
19280      );
19281
19282      element.parent().append(autofillClone);
19283    }
19284
19285    var isMultiple = $mdUtil.parseAttributeBoolean(attr.multiple);
19286
19287    // Use everything that's left inside element.contents() as the contents of the menu
19288    var multipleContent = isMultiple ? 'multiple' : '';
19289    var selectTemplate = '' +
19290      '<div class="md-select-menu-container" aria-hidden="true" role="presentation">' +
19291      '<md-select-menu role="presentation" {0}>{1}</md-select-menu>' +
19292      '</div>';
19293
19294    selectTemplate = $mdUtil.supplant(selectTemplate, [multipleContent, element.html()]);
19295    element.empty().append(valueEl);
19296    element.append(selectTemplate);
19297
19298    if(!attr.tabindex){
19299      attr.$set('tabindex', 0);
19300    }
19301
19302    return function postLink(scope, element, attr, ctrls) {
19303      var untouched = true;
19304      var isDisabled, ariaLabelBase;
19305
19306      var containerCtrl = ctrls[0];
19307      var mdSelectCtrl = ctrls[1];
19308      var ngModelCtrl = ctrls[2];
19309      var formCtrl = ctrls[3];
19310      // grab a reference to the select menu value label
19311      var valueEl = element.find('md-select-value');
19312      var isReadonly = angular.isDefined(attr.readonly);
19313      var disableAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
19314
19315      if (disableAsterisk) {
19316        element.addClass('md-no-asterisk');
19317      }
19318
19319      if (containerCtrl) {
19320        var isErrorGetter = containerCtrl.isErrorGetter || function() {
19321          return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (formCtrl && formCtrl.$submitted));
19322        };
19323
19324        if (containerCtrl.input) {
19325          // We ignore inputs that are in the md-select-header (one
19326          // case where this might be useful would be adding as searchbox)
19327          if (element.find('md-select-header').find('input')[0] !== containerCtrl.input[0]) {
19328            throw new Error("<md-input-container> can only have *one* child <input>, <textarea> or <select> element!");
19329          }
19330        }
19331
19332        containerCtrl.input = element;
19333        if (!containerCtrl.label) {
19334          $mdAria.expect(element, 'aria-label', element.attr('placeholder'));
19335        }
19336
19337        scope.$watch(isErrorGetter, containerCtrl.setInvalid);
19338      }
19339
19340      var selectContainer, selectScope, selectMenuCtrl;
19341
19342      findSelectContainer();
19343      $mdTheming(element);
19344
19345      if (formCtrl && angular.isDefined(attr.multiple)) {
19346        $mdUtil.nextTick(function() {
19347          var hasModelValue = ngModelCtrl.$modelValue || ngModelCtrl.$viewValue;
19348          if (hasModelValue) {
19349            formCtrl.$setPristine();
19350          }
19351        });
19352      }
19353
19354      var originalRender = ngModelCtrl.$render;
19355      ngModelCtrl.$render = function() {
19356        originalRender();
19357        syncLabelText();
19358        syncAriaLabel();
19359        inputCheckValue();
19360      };
19361
19362      attr.$observe('placeholder', ngModelCtrl.$render);
19363
19364      if (containerCtrl && containerCtrl.label) {
19365        attr.$observe('required', function (value) {
19366          // Toggle the md-required class on the input containers label, because the input container is automatically
19367          // applying the asterisk indicator on the label.
19368          containerCtrl.label.toggleClass('md-required', value && !disableAsterisk);
19369        });
19370      }
19371
19372      mdSelectCtrl.setLabelText = function(text) {
19373        mdSelectCtrl.setIsPlaceholder(!text);
19374
19375        // Whether the select label has been given via user content rather than the internal
19376        // template of <md-option>
19377        var isSelectLabelFromUser = false;
19378
19379        if (attr.mdSelectedText && attr.mdSelectedHtml) {
19380          throw Error('md-select cannot have both `md-selected-text` and `md-selected-html`');
19381        }
19382
19383        if (attr.mdSelectedText || attr.mdSelectedHtml) {
19384          text = $parse(attr.mdSelectedText || attr.mdSelectedHtml)(scope);
19385          isSelectLabelFromUser = true;
19386        } else if (!text) {
19387          // Use placeholder attribute, otherwise fallback to the md-input-container label
19388          var tmpPlaceholder = attr.placeholder ||
19389              (containerCtrl && containerCtrl.label ? containerCtrl.label.text() : '');
19390
19391          text = tmpPlaceholder || '';
19392          isSelectLabelFromUser = true;
19393        }
19394
19395        var target = valueEl.children().eq(0);
19396
19397        if (attr.mdSelectedHtml) {
19398            // Using getTrustedHtml will run the content through $sanitize if it is not already
19399            // explicitly trusted. If the ngSanitize module is not loaded, this will
19400            // *correctly* throw an sce error.
19401            target.html($sce.getTrustedHtml(text));
19402        } else if (isSelectLabelFromUser) {
19403          target.text(text);
19404        } else {
19405          // If we've reached this point, the text is not user-prov
19405ided.
19406          target.html(text);
19407        }
19408      };
19409
19410      mdSelectCtrl.setIsPlaceholder = function(isPlaceholder) {
19411        if (isPlaceholder) {
19412          valueEl.addClass('md-select-placeholder');
19413          if (containerCtrl && containerCtrl.label) {
19414            containerCtrl.label.addClass('md-placeholder');
19415          }
19416        } else {
19417          valueEl.removeClass('md-select-placeholder');
19418          if (containerCtrl && containerCtrl.label) {
19419            containerCtrl.label.removeClass('md-placeholder');
19420          }
19421        }
19422      };
19423
19424      if (!isReadonly) {
19425        element
19426          .on('focus', function(ev) {
19427            // Always focus the container (if we have one) so floating labels and other styles are
19428            // applied properly
19429            containerCtrl && containerCtrl.setFocused(true);
19430          });
19431
19432        // Attach before ngModel's blur listener to stop propagation of blur event
19433        // to prevent from setting $touched.
19434        element.on('blur', function(event) {
19435          if (untouched) {
19436            untouched = false;
19437            if (selectScope._mdSelectIsOpen) {
19438              event.stopImmediatePropagation();
19439            }
19440          }
19441
19442          if (selectScope._mdSelectIsOpen) return;
19443          containerCtrl && containerCtrl.setFocused(false);
19444          inputCheckValue();
19445        });
19446      }
19447
19448      mdSelectCtrl.triggerClose = function() {
19449        $parse(attr.mdOnClose)(scope);
19450      };
19451
19452      scope.$$postDigest(function() {
19453        initAriaLabel();
19454        syncLabelText();
19455        syncAriaLabel();
19456      });
19457
19458      function initAriaLabel() {
19459        var labelText = element.attr('aria-label') || element.attr('placeholder');
19460        if (!labelText && containerCtrl && containerCtrl.label) {
19461          labelText = containerCtrl.label.text();
19462        }
19463        ariaLabelBase = labelText;
19464        $mdAria.expect(element, 'aria-label', labelText);
19465      }
19466
19467      scope.$watch(function() {
19468        return selectMenuCtrl.selectedLabels();
19469      }, syncLabelText);
19470
19471      function syncLabelText() {
19472        if (selectContainer) {
19473          selectMenuCtrl = selectMenuCtrl || selectContainer.find('md-select-menu').controller('mdSelectMenu');
19474          mdSelectCtrl.setLabelText(selectMenuCtrl.selectedLabels());
19475        }
19476      }
19477
19478      function syncAriaLabel() {
19479        if (!ariaLabelBase) return;
19480        var ariaLabels = selectMenuCtrl.selectedLabels({mode: 'aria'});
19481        element.attr('aria-label', ariaLabels.length ? ariaLabelBase + '
19481: ' + ariaLabels : ariaLabelBase);
19482      }
19483
19484      var deregisterWatcher;
19485      attr.$observe('ngMultiple', function(val) {
19486        if (deregisterWatcher) deregisterWatcher();
19487        var parser = $parse(val);
19488        deregisterWatcher = scope.$watch(function() {
19489          return parser(scope);
19490        }, function(multiple, prevVal) {
19491          if (multiple === undefined && prevVal === undefined) return; // assume compiler did a good job
19492          if (multiple) {
19493            element.attr('multiple', 'multiple');
19494          } else {
19495            element.removeAttr('multiple');
19496          }
19497          element.attr('aria-multiselectable', multiple ? 'true' : 'false');
19498          if (selectContainer) {
19499            selectMenuCtrl.setMultiple(multiple);
19500            originalRender = ngModelCtrl.$render;
19501            ngModelCtrl.$render = function() {
19502              originalRender();
19503              syncLabelText();
19504              syncAriaLabel();
19505              inputCheckValue();
19506            };
19507            ngModelCtrl.$render();
19508          }
19509        });
19510      });
19511
19512      attr.$observe('disabled', function(disabled) {
19513        if (angular.isString(disabled)) {
19514          disabled = true;
19515        }
19516        // Prevent click event being registered twice
19517        if (isDisabled !== undefined && isDisabled === disabled) {
19518          return;
19519        }
19520        isDisabled = disabled;
19521        if (disabled) {
19522          element
19523            .attr({'aria-disabled': 'true'})
19524            .removeAttr('tabindex')
19525            .off('click', openSelect)
19526            .off('keydown', handleKeypress);
19527        } else {
19528          element
19529            .attr({'tabindex': attr.tabindex, 'aria-disabled': 'false'})
19530            .on('click', openSelect)
19531            .on('keydown', handleKeypress);
19532        }
19533      });
19534
19535      if (!attr.hasOwnProperty('disabled') && !attr.hasOwnProperty('ngDisabled')) {
19536        element.attr({'aria-disabled': 'false'});
19537        element.on('click', openSelect);
19538        element.on('keydown', handleKeypress);
19539      }
19540
19541      var ariaAttrs = {
19542        role: 'listbox',
19543        'aria-expanded': 'false',
19544        'aria-multiselectable': isMultiple && !attr.ngMultiple ? 'true' : 'false'
19545      };
19546
19547      if (!element[0].hasAttribute('id')) {
19548        ariaAttrs.id = 'select_' + $mdUtil.nextUid();
19549      }
19550
19551      var containerId = 'select_container_' + $mdUtil.nextUid();
19552      selectContainer.attr('id', containerId);
19553      // Only add aria-owns if element ownership is NOT represented in the DOM.
19554      if (!element.find('md-select-menu').length) {
19555        ariaAttrs['aria-owns'] = containerId;
19556      }
19557      element.attr(ariaAttrs);
19558
19559      scope.$on('$destroy', function() {
19560        $mdSelect
19561          .destroy()
19562          .finally(function() {
19563            if (containerCtrl) {
19564              containerCtrl.setFocused(false);
19565              containerCtrl.setHasValue(false);
19566              containerCtrl.input = null;
19567            }
19568            ngModelCtrl.$setTouched();
19569          });
19570      });
19571
19572
19573
19574      function inputCheckValue() {
19575        // The select counts as having a value if one or more options are selected,
19576        // or if the input's validity state says it has bad input (eg string in a number input)
19577        containerCtrl && containerCtrl.setHasValue(selectMenuCtrl.selectedLabels().length > 0 || (element[0].validity || {}).badInput);
19578      }
19579
19580      function findSelectContainer() {
19581        selectContainer = angular.element(
19582          element[0].querySelector('.md-select-menu-container')
19583        );
19584        selectScope = scope;
19585        if (attr.mdContainerClass) {
19586          var value = selectContainer[0].getAttribute('class') + ' ' + attr.mdContainerClass;
19587          selectContainer[0].setAttribute('class', value);
19588        }
19589        selectMenuCtrl = selectContainer.find('md-select-menu').controller('mdSelectMenu');
19590        selectMenuCtrl.init(ngModelCtrl, attr.ngModel);
19591        element.on('$destroy', function() {
19592          selectContainer.remove();
19593        });
19594      }
19595
19596      function handleKeypress(e) {
19597        if ($mdConstant.isNavigationKey(e)) {
19598          // prevent page scrolling on interaction
19599          e.preventDefault();
19600          openSelect(e);
19601        } else {
19602          if (shouldHandleKey(e, $mdConstant)) {
19603            e.preventDefault();
19604
19605            var node = selectMenuCtrl.optNodeForKeyboardSearch(e);
19606            if (!node || node.hasAttribute('disabled')) return;
19607            var optionCtrl = angular.element(node).controller('mdOption');
19608            if (!selectMenuCtrl.isMultiple) {
19609              selectMenuCtrl.deselect(Object.keys(selectMenuCtrl.selected)[0]);
19610            }
19611            selectMenuCtrl.select(optionCtrl.hashKey, optionCtrl.value);
19612            selectMenuCtrl.refreshViewValue();
19613          }
19614        }
19615      }
19616
19617      function openSelect() {
19618        selectScope._mdSelectIsOpen = true;
19619        element.attr('aria-expanded', 'true');
19620
19621        $mdSelect.show({
19622          scope: selectScope,
19623          preserveScope: true,
19624          skipCompile: true,
19625          element: selectContainer,
19626          target: element[0],
19627          selectCtrl: mdSelectCtrl,
19628          preserveElement: true,
19629          hasBackdrop: true,
19630          loadingAsync: attr.mdOnOpen ? scope.$eval(attr.mdOnOpen) || true : false
19631        }).finally(function() {
19632          selectScope._mdSelectIsOpen = false;
19633          element.focus();
19634          element.attr('aria-expanded', 'false');
19635          ngModelCtrl.$setTouched();
19636        });
19637      }
19638
19639    };
19640  }
19641}
19642
19643function SelectMenuDirective($parse, $mdUtil, $mdConstant, $mdTheming) {
19644  // We want the scope to be set to 'false' so an isolated scope is not created
19645  // which would interfere with the md-select-header's access to the
19646  // parent scope.
19647  SelectMenuController.$inject = ["$scope", "$attrs", "$element"];
19648  return {
19649    restrict: 'E',
19650    require: ['mdSelectMenu'],
19651    scope: false,
19652    controller: SelectMenuController,
19653    link: {pre: preLink}
19654  };
19655
19656  // We use preLink instead of postLink to ensure that the select is initialized before
19657  // its child options run postLink.
19658  function preLink(scope, element, attr, ctrls) {
19659    var selectCtrl = ctrls[0];
19660
19661    element.addClass('_md');     // private md component indicator for styling
19662
19663    $mdTheming(element);
19664    element.on('click', clickListener);
19665    element.on('keypress', keyListener);
19666
19667    function keyListener(e) {
19668      if (e.keyCode == 13 || e.keyCode == 32) {
19669        clickListener(e);
19670      }
19671    }
19672
19673    function clickListener(ev) {
19674      var option = $mdUtil.getClosest(ev.target, 'md-option');
19675      var optionCtrl = option && angular.element(option).data('$mdOptionController');
19676      if (!option || !optionCtrl) return;
19677      if (option.hasAttribute('disabled')) {
19678        ev.stopImmediatePropagation();
19679        return false;
19680      }
19681
19682      var optionHashKey = selectCtrl.hashGetter(optionCtrl.value);
19683      var isSelected = angular.isDefined(selectCtrl.selected[optionHashKey]);
19684
19685      scope.$apply(function() {
19686        if (selectCtrl.isMultiple) {
19687          if (isSelected) {
19688            selectCtrl.deselect(optionHashKey);
19689          } else {
19690            selectCtrl.select(optionHashKey, optionCtrl.value);
19691          }
19692        } else {
19693          if (!isSelected) {
19694            selectCtrl.deselect(Object.keys(selectCtrl.selected)[0]);
19695            selectCtrl.select(optionHashKey, optionCtrl.value);
19696          }
19697        }
19698        selectCtrl.refreshViewValue();
19699      });
19700    }
19701  }
19702
19703  function SelectMenuController($scope, $attrs, $element) {
19704    var self = this;
19705    self.isMultiple = angular.isDefined($attrs.multiple);
19706    // selected is an object with keys matching all of the selected options' hashed values
19707    self.selected = {};
19708    // options is an object with keys matching every option's hash value,
19709    // and values matching every option's controller.
19710    self.options = {};
19711
19712    $scope.$watchCollection(function() {
19713      return self.options;
19714    }, function() {
19715      self.ngModel.$render();
19716    });
19717
19718    var deregisterCollectionWatch;
19719    var defaultIsEmpty;
19720    self.setMultiple = function(isMultiple) {
19721      var ngModel = self.ngModel;
19722      defaultIsEmpty = defaultIsEmpty || ngModel.$isEmpty;
19723
19724      self.isMultiple = isMultiple;
19725      if (deregisterCollectionWatch) deregisterCollectionWatch();
19726
19727      if (self.isMultiple) {
19728        ngModel.$validators['md-multiple'] = validateArray;
19729        ngModel.$render = renderMultiple;
19730
19731        // watchCollection on the model because by default ngModel only watches the model's
19732        // reference. This allowed the developer to also push and pop from their array.
19733        $scope.$watchCollection(self.modelBinding, function(value) {
19734          if (validateArray(value)) renderMultiple(value);
19735          self.ngModel.$setPristine();
19736        });
19737
19738        ngModel.$isEmpty = function(value) {
19739          return !value || value.length === 0;
19740        };
19741      } else {
19742        delete ngModel.$validators['md-multiple'];
19743        ngModel.$render = renderSingular;
19744      }
19745
19746      function validateArray(modelValue, viewValue) {
19747        // If a value is truthy but not an array, reject it.
19748        // If value is undefined/falsy, accept that it's an empty array.
19749        return angular.isArray(modelValue || viewValue || []);
19750      }
19751    };
19752
19753    var searchStr = '';
19754    var clearSearchTimeout, optNodes, optText;
19755    var CLEAR_SEARCH_AFTER = 300;
19756
19757    self.optNodeForKeyboardSearch = function(e) {
19758      clearSearchTimeout && clearTimeout(clearSearchTimeout);
19759      clearSearchTimeout = setTimeout(function() {
19760        clearSearchTimeout = undefined;
19761        searchStr = '';
19762        optText = undefined;
19763        optNodes = undefined;
19764      }, CLEAR_SEARCH_AFTER);
19765
19766      // Support 1-9 on numpad
19767      var keyCode = e.keyCode - ($mdConstant.isNumPadKey(e) ? 48 : 0);
19768
19769      searchStr += String.fromCharCode(keyCode);
19770      var search = new RegExp('^' + searchStr, 'i');
19771      if (!optNodes) {
19772        optNodes = $element.find('md-option');
19773        optText = new Array(optNodes.length);
19774        angular.forEach(optNodes, function(el, i) {
19775          optText[i] = el.textContent.trim();
19776        });
19777      }
19778      for (var i = 0; i < optText.length; ++i) {
19779        if (search.test(optText[i])) {
19780          return optNodes[i];
19781        }
19782      }
19783    };
19784
19785    self.init = function(ngModel, binding) {
19786      self.ngModel = ngModel;
19787      self.modelBinding = binding;
19788
19789      // Setup a more robust version of isEmpty to ensure value is a valid option
19790      self.ngModel.$isEmpty = function($viewValue) {
19791        // We have to transform the viewValue into the hashKey, because otherwise the
19792        // OptionCtrl may not exist. Developers may have specified a trackBy function.
19793        return !self.options[self.hashGetter($viewValue)];
19794      };
19795
19796      // Allow users to provide `ng-model="foo" ng-model-options="{trackBy: 'foo.id'}"` so
19797      // that we can properly compare objects set on the model to the available options
19798      var trackByOption = $mdUtil.getModelOption(ngModel, 'trackBy');
19799
19800      if (trackByOption) {
19801        var trackByLocals = {};
19802        var trackByParsed = $parse(trackByOption);
19803        self.hashGetter = function(value, valueScope) {
19804          trackByLocals.$value = value;
19805          return trackByParsed(valueScope || $scope, trackByLocals);
19806        };
19807        // If the user doesn't provide a trackBy, we automatically generate an id for every
19808        // value passed in
19809      } else {
19810        self.hashGetter = function getHashValue(value) {
19811          if (angular.isObject(value)) {
19812            return 'object_' + (value.$$mdSelectId || (value.$$mdSelectId = ++selectNextId));
19813          }
19814          return value;
19815        };
19816      }
19817      self.setMultiple(self.isMultiple);
19818    };
19819
19820    self.selectedLabels = function(opts) {
19821      opts = opts || {};
19822      var mode = opts.mode || 'html';
19823      var selectedOptionEls = $mdUtil.nodesToArray($element[0].querySelectorAll('md-option[selected]'));
19824      if (selectedOptionEls.length) {
19825        var mapFn;
19826
19827        if (mode == 'html') {
19828          // Map the given element to its innerHTML string. If the element has a child ripple
19829          // container remove it from the HTML string, before returning the string.
19830          mapFn = function(el) {
19831            // If we do not have a `value` or `ng-value`, assume it is an empty option which clears the select
19832            if (el.hasAttribute('md-option-empty')) {
19833              return '';
19834            }
19835
19836            var html = el.innerHTML;
19837
19838            // Remove the ripple container from the selected option, copying it would cause a CSP violation.
19839            var rippleContainer = el.querySelector('.md-ripple-container');
19840            if (rippleContainer) {
19841              html = html.replace(rippleContainer.outerHTML, '');
19842            }
19843
19844            // Remove the checkbox container, because it will cause the label to wrap inside of the placeholder.
19845            // It should be not displayed inside of the label element.
19846            var checkboxContainer = el.querySelector('.md-container');
19847            if (checkboxContainer) {
19848              html = html.replace(checkboxContainer.outerHTML, '');
19849            }
19850
19851            return html;
19852          };
19853        } else if (mode == 'aria') {
19854          mapFn = function(el) { return el.hasAttribute('aria-label') ? el.getAttribute('aria-label') : el.textContent; };
19855        }
19856
19857        // Ensure there are no duplicates; see https://github.com/angular/material/issues/9442
19858        return $mdUtil.uniq(selectedOptionEls.map(mapFn)).join(', ');
19859      } else {
19860        return '';
19861      }
19862    };
19863
19864    self.select = function(hashKey, hashedValue) {
19865      var option = self.options[hashKey];
19866      option && option.setSelected(true);
19867      self.selected[hashKey] = hashedValue;
19868    };
19869    self.deselect = function(hashKey) {
19870      var option = self.options[hashKey];
19871      option && option.setSelected(false);
19872      delete self.selected[hashKey];
19873    };
19874
19875    self.addOption = function(hashKey, optionCtrl) {
19876      if (angular.isDefined(self.options[hashKey])) {
19877        throw new Error('Duplicate md-option values are not allowed in a select. ' +
19878          'Duplicate value "' + optionCtrl.value + '" found.');
19879      }
19880
19881      self.options[hashKey] = optionCtrl;
19882
19883      // If this option's value was already in our ngModel, go ahead and select it.
19884      if (angular.isDefined(self.selected[hashKey])) {
19885        self.select(hashKey, optionCtrl.value);
19886
19887        // When the current $modelValue of the ngModel Controller is using the same hash as
19888        // the current option, which will be added, then we can be sure, that the validation
19889        // of the option has occurred before the option was added properly.
19890        // This means, that we have to manually trigger a new validation of the current option.
19891        if (angular.isDefined(self.ngModel.$modelValue) && self.hashGetter(self.ngModel.$modelValue) === hashKey) {
19892          self.ngModel.$validate();
19893        }
19894
19895        self.refreshViewValue();
19896      }
19897    };
19898    self.removeOption = function(hashKey) {
19899      delete self.options[hashKey];
19900      // Don't deselect an option when it's removed - the user's ngModel should be allowed
19901      // to have values that do not match a currently available option.
19902    };
19903
19904    self.refreshViewValue = function() {
19905      var values = [];
19906      var option;
19907      for (var hashKey in self.selected) {
19908        // If this hashKey has an associated option, push that option's value to the model.
19909        if ((option = self.options[hashKey])) {
19910          values.push(option.value);
19911        } else {
19912          // Otherwise, the given hashKey has no associated option, and we got it
19913          // from an ngModel value at an earlier time. Push the unhashed value of
19914          // this hashKey to the model.
19915          // This allows the developer to put a value in the model that doesn't yet have
19916          // an associated option.
19917          values.push(self.selected[hashKey]);
19918        }
19919      }
19920      var usingTrackBy = $mdUtil.getModelOption(self.ngModel, 'trackBy');
19921
19922      var newVal = self.isMultiple ? values : values[0];
19923      var prevVal = self.ngModel.$modelValue;
19924
19925      if (usingTrackBy ? !angular.equals(prevVal, newVal) : (prevVal + '') !== newVal) {
19926        self.ngModel.$setViewValue(newVal);
19927        self.ngModel.$render();
19928      }
19929    };
19930
19931    function renderMultiple() {
19932      var newSelectedValues = self.ngModel.$modelValue || self.ngModel.$viewValue || [];
19933      if (!angular.isArray(newSelectedValues)) return;
19934
19935      var oldSelected = Object.keys(self.selected);
19936
19937      var newSelectedHashes = newSelectedValues.map(self.hashGetter);
19938      var deselected = oldSelected.filter(function(hash) {
19939        return newSelectedHashes.indexOf(hash) === -1;
19940      });
19941
19942      deselected.forEach(self.deselect);
vendor: 14,004 bytes, lines 19943-20420
19943      newSelectedHashes.forEach(function(hashKey, i) {
19944        self.select(hashKey, newSelectedValues[i]);
19945      });
19946    }
19947
19948    function renderSingular() {
19949      var value = self.ngModel.$viewValue || self.ngModel.$modelValue;
19950      Object.keys(self.selected).forEach(self.deselect);
19951      self.select(self.hashGetter(value), value);
19952    }
19953  }
19954
19955}
19956
19957function OptionDirective($mdButtonInkRipple, $mdUtil, $mdTheming) {
19958
19959  OptionController.$inject = ["$element"];
19960  return {
19961    restrict: 'E',
19962    require: ['mdOption', '^^mdSelectMenu'],
19963    controller: OptionController,
19964    compile: compile
19965  };
19966
19967  function compile(element, attr) {
19968    // Manual transclusion to avoid the extra inner <span> that ng-transclude generates
19969    element.append(angular.element('<div class="md-text">').append(element.contents()));
19970
19971    element.attr('tabindex', attr.tabindex || '0');
19972
19973    if (!hasDefinedValue(attr)) {
19974      element.attr('md-option-empty', '');
19975    }
19976
19977    return postLink;
19978  }
19979
19980  function hasDefinedValue(attr) {
19981    var value = attr.value;
19982    var ngValue = attr.ngValue;
19983
19984    return value || ngValue;
19985  }
19986
19987  function postLink(scope, element, attr, ctrls) {
19988    var optionCtrl = ctrls[0];
19989    var selectCtrl = ctrls[1];
19990
19991    $mdTheming(element);
19992
19993    if (selectCtrl.isMultiple) {
19994      element.addClass('md-checkbox-enabled');
19995      element.prepend(CHECKBOX_SELECTION_INDICATOR.clone());
19996    }
19997
19998    if (angular.isDefined(attr.ngValue)) {
19999      scope.$watch(attr.ngValue, setOptionValue);
20000    } else if (angular.isDefined(attr.value)) {
20001      setOptionValue(attr.value);
20002    } else {
20003      scope.$watch(function() {
20004        return element.text().trim();
20005      }, setOptionValue);
20006    }
20007
20008    attr.$observe('disabled', function(disabled) {
20009      if (disabled) {
20010        element.attr('tabindex', '-1');
20011      } else {
20012        element.attr('tabindex', '0');
20013      }
20014    });
20015
20016    scope.$$postDigest(function() {
20017      attr.$observe('selected', function(selected) {
20018        if (!angular.isDefined(selected)) return;
20019        if (typeof selected == 'string') selected = true;
20020        if (selected) {
20021          if (!selectCtrl.isMultiple) {
20022            selectCtrl.deselect(Object.keys(selectCtrl.selected)[0]);
20023          }
20024          selectCtrl.select(optionCtrl.hashKey, optionCtrl.value);
20025        } else {
20026          selectCtrl.deselect(optionCtrl.hashKey);
20027        }
20028        selectCtrl.refreshViewValue();
20029      });
20030    });
20031
20032    $mdButtonInkRipple.attach(scope, element);
20033    configureAria();
20034
20035    function setOptionValue(newValue, oldValue, prevAttempt) {
20036      if (!selectCtrl.hashGetter) {
20037        if (!prevAttempt) {
20038          scope.$$postDigest(function() {
20039            setOptionValue(newValue, oldValue, true);
20040          });
20041        }
20042        return;
20043      }
20044      var oldHashKey = selectCtrl.hashGetter(oldValue, scope);
20045      var newHashKey = selectCtrl.hashGetter(newValue, scope);
20046
20047      optionCtrl.hashKey = newHashKey;
20048      optionCtrl.value = newValue;
20049
20050      selectCtrl.removeOption(oldHashKey, optionCtrl);
20051      selectCtrl.addOption(newHashKey, optionCtrl);
20052    }
20053
20054    scope.$on('$destroy', function() {
20055      selectCtrl.removeOption(optionCtrl.hashKey, optionCtrl);
20056    });
20057
20058    function configureAria() {
20059      var ariaAttrs = {
20060        'role': 'option',
20061        'aria-selected': 'false'
20062      };
20063
20064      if (!element[0].hasAttribute('id')) {
20065        ariaAttrs.id = 'select_option_' + $mdUtil.nextUid();
20066      }
20067      element.attr(ariaAttrs);
20068    }
20069  }
20070
20071  function OptionController($element) {
20072    this.selected = false;
20073    this.setSelected = function(isSelected) {
20074      if (isSelected && !this.selected) {
20075        $element.attr({
20076          'selected': 'selected',
20077          'aria-selected': 'true'
20078        });
20079      } else if (!isSelected && this.selected) {
20080        $element.removeAttr('selected');
20081        $element.attr('aria-selected', 'false');
20082      }
20083      this.selected = isSelected;
20084    };
20085  }
20086
20087}
20088
20089function OptgroupDirective() {
20090  return {
20091    restrict: 'E',
20092    compile: compile
20093  };
20094  function compile(el, attrs) {
20095    // If we have a select header element, we don't want to add the normal label
20096    // header.
20097    if (!hasSelectHeader()) {
20098      setupLabelElement();
20099    }
20100
20101    function hasSelectHeader() {
20102      return el.parent().find('md-select-header').length;
20103    }
20104
20105    function setupLabelElement() {
20106      var labelElement = el.find('label');
20107      if (!labelElement.length) {
20108        labelElement = angular.element('<label>');
20109        el.prepend(labelElement);
20110      }
20111      labelElement.addClass('md-container-ignore');
20112      labelElement.attr('aria-hidden', 'true');
20113      if (attrs.label) labelElement.text(attrs.label);
20114    }
20115  }
20116}
20117
20118function SelectHeaderDirective() {
20119  return {
20120    restrict: 'E',
20121  };
20122}
20123
20124function SelectProvider($$interimElementProvider) {
20125  selectDefaultOptions.$inject = ["$mdSelect", "$mdConstant", "$mdUtil", "$window", "$q", "$$rAF", "$animateCss", "$animate", "$document"];
20126  return $$interimElementProvider('$mdSelect')
20127    .setDefaults({
20128      methods: ['target'],
20129      options: selectDefaultOptions
20130    });
20131
20132  /* @ngInject */
20133  function selectDefaultOptions($mdSelect, $mdConstant, $mdUtil, $window, $q, $$rAF, $animateCss, $animate, $document) {
20134    var ERROR_TARGET_EXPECTED = "$mdSelect.show() expected a target element in options.target but got '{0}'!";
20135    var animator = $mdUtil.dom.animator;
20136    var keyCodes = $mdConstant.KEY_CODE;
20137
20138    return {
20139      parent: 'body',
20140      themable: true,
20141      onShow: onShow,
20142      onRemove: onRemove,
20143      hasBackdrop: true,
20144      disableParentScroll: true
20145    };
20146
20147    /**
20148     * Interim-element onRemove logic....
20149     */
20150    function onRemove(scope, element, opts) {
20151      var animationRunner = null;
20152      var destroyListener = scope.$on('$destroy', function() {
20153        // Listen for the case where the element was destroyed while there was an
20154        // ongoing close animation. If this happens, we need to end the animation
20155        // manually.
20156        animationRunner.end();
20157      });
20158
20159      opts = opts || { };
20160      opts.cleanupInteraction();
20161      opts.cleanupResizing();
20162      opts.hideBackdrop();
20163
20164      // For navigation $destroy events, do a quick, non-animated removal,
20165      // but for normal closes (from clicks, etc) animate the removal
20166      return (opts.$destroy === true) ? cleanElement() : animateRemoval().then(cleanElement);
20167
20168      /**
20169       * For normal closes (eg clicks), animate the removal.
20170       * For forced closes (like $destroy events from navigation),
20171       * skip the animations
20172       */
20173      function animateRemoval() {
20174        animationRunner = $animateCss(element, {addClass: 'md-leave'});
20175        return animationRunner.start();
20176      }
20177
20178      /**
20179       * Restore the element to a closed state
20180       */
20181      function cleanElement() {
20182        destroyListener();
20183
20184        element
20185          .removeClass('md-active')
20186          .attr('aria-hidden', 'true')
20187          .css('display', 'none');
20188        element.parent().find('md-select-value').removeAttr('aria-hidden');
20189
20190        announceClosed(opts);
20191
20192        if (!opts.$destroy && opts.restoreFocus) {
20193          opts.target.focus();
20194        }
20195      }
20196
20197    }
20198
20199    /**
20200     * Interim-element onShow logic....
20201     */
20202    function onShow(scope, element, opts) {
20203
20204      watchAsyncLoad();
20205      sanitizeAndConfigure(scope, opts);
20206
20207      opts.hideBackdrop = showBackdrop(scope, element, opts);
20208
20209      return showDropDown(scope, element, opts)
20210        .then(function(response) {
20211          element.attr('aria-hidden', 'false');
20212          opts.alreadyOpen = true;
20213          opts.cleanupInteraction = activateInteraction();
20214          opts.cleanupResizing = activateResizing();
20215          autoFocus(opts.focusedNode);
20216
20217          return response;
20218        }, opts.hideBackdrop);
20219
20220      // ************************************
20221      // Closure Functions
20222      // ************************************
20223
20224      /**
20225       *  Attach the select DOM element(s) and animate to the correct positions
20226       *  and scalings...
20227       */
20228      function showDropDown(scope, element, opts) {
20229        if (opts.parent !== element.parent()) {
20230          element.parent().attr('aria-owns', element.attr('id'));
20231        }
20232        element.parent().find('md-select-value').attr('aria-hidden', 'true');
20233
20234        opts.parent.append(element);
20235
20236        return $q(function(resolve, reject) {
20237
20238          try {
20239
20240            $animateCss(element, {removeClass: 'md-leave', duration: 0})
20241              .start()
20242              .then(positionAndFocusMenu)
20243              .then(resolve);
20244
20245          } catch (e) {
20246            reject(e);
20247          }
20248
20249        });
20250      }
20251
20252      /**
20253       * Initialize container and dropDown menu positions/scale, then animate
20254       * to show.
20255       */
20256      function positionAndFocusMenu() {
20257        return $q(function(resolve) {
20258          if (opts.isRemoved) return $q.reject(false);
20259
20260          var info = calculateMenuPositions(scope, element, opts);
20261
20262          info.container.element.css(animator.toCss(info.container.styles));
20263          info.dropDown.element.css(animator.toCss(info.dropDown.styles));
20264
20265          $$rAF(function() {
20266            element.addClass('md-active');
20267            info.dropDown.element.css(animator.toCss({transform: ''}));
20268
20269            resolve();
20270          });
20271
20272        });
20273      }
20274
20275      /**
20276       * Show modal backdrop element...
20277       */
20278      function showBackdrop(scope, element, options) {
20279
20280        // If we are not within a dialog...
20281        if (options.disableParentScroll && !$mdUtil.getClosest(options.target, 'MD-DIALOG')) {
20282          // !! DO this before creating the backdrop; since disableScrollAround()
20283          //    configures the scroll offset; which is used by mdBackDrop postLink()
20284          options.restoreScroll = $mdUtil.disableScrollAround(options.element, options.parent);
20285        } else {
20286          options.disableParentScroll = false;
20287        }
20288
20289        if (options.hasBackdrop) {
20290          // Override duration to immediately show invisible backdrop
20291          options.backdrop = $mdUtil.createBackdrop(scope, "md-select-backdrop md-click-catcher");
20292          $animate.enter(options.backdrop, $document[0].body, null, {duration: 0});
20293        }
20294
20295        /**
20296         * Hide modal backdrop element...
20297         */
20298        return function hideBackdrop() {
20299          if (options.backdrop) options.backdrop.remove();
20300          if (options.disableParentScroll) options.restoreScroll();
20301
20302          delete options.restoreScroll;
20303        };
20304      }
20305
20306      /**
20307       *
20308       */
20309      function autoFocus(focusedNode) {
20310        if (focusedNode && !focusedNode.hasAttribute('disabled')) {
20311          focusedNode.focus();
20312        }
20313      }
20314
20315      /**
20316       * Check for valid opts and set some sane defaults
20317       */
20318      function sanitizeAndConfigure(scope, options) {
20319        var selectEl = element.find('md-select-menu');
20320
20321        if (!options.target) {
20322          throw new Error($mdUtil.supplant(ERROR_TARGET_EXPECTED, [options.target]));
20323        }
20324
20325        angular.extend(options, {
20326          isRemoved: false,
20327          target: angular.element(options.target), //make sure it's not a naked dom node
20328          parent: angular.element(options.parent),
20329          selectEl: selectEl,
20330          contentEl: element.find('md-content'),
20331          optionNodes: selectEl[0].getElementsByTagName('md-option')
20332        });
20333      }
20334
20335      /**
20336       * Configure various resize listeners for screen changes
20337       */
20338      function activateResizing() {
20339        var debouncedOnResize = (function(scope, target, options) {
20340
20341          return function() {
20342            if (options.isRemoved) return;
20343
20344            var updates = calculateMenuPositions(scope, target, options);
20345            var container = updates.container;
20346            var dropDown = updates.dropDown;
20347
20348            container.element.css(animator.toCss(container.styles));
20349            dropDown.element.css(animator.toCss(dropDown.styles));
20350          };
20351
20352        })(scope, element, opts);
20353
20354        var window = angular.element($window);
20355        window.on('resize', debouncedOnResize);
20356        window.on('orientationchange', debouncedOnResize);
20357
20358        // Publish deactivation closure...
20359        return function deactivateResizing() {
20360
20361          // Disable resizing handlers
20362          window.off('resize', debouncedOnResize);
20363          window.off('orientationchange', debouncedOnResize);
20364        };
20365      }
20366
20367      /**
20368       *  If asynchronously loading, watch and update internal
20369       *  '$$loadingAsyncDone' flag
20370       */
20371      function watchAsyncLoad() {
20372        if (opts.loadingAsync && !opts.isRemoved) {
20373          scope.$$loadingAsyncDone = false;
20374
20375          $q.when(opts.loadingAsync)
20376            .then(function() {
20377              scope.$$loadingAsyncDone = true;
20378              delete opts.loadingAsync;
20379            }).then(function() {
20380              $$rAF(positionAndFocusMenu);
20381            });
20382        }
20383      }
20384
20385      /**
20386       *
20387       */
20388      function activateInteraction() {
20389        if (opts.isRemoved) return;
20390
20391        var dropDown = opts.selectEl;
20392        var selectCtrl = dropDown.controller('mdSelectMenu') || {};
20393
20394        element.addClass('md-clickable');
20395
20396        // Close on backdrop click
20397        opts.backdrop && opts.backdrop.on('click', onBackdropClick);
20398
20399        // Escape to close
20400        // Cycling of options, and closing on enter
20401        dropDown.on('keydown', onMenuKeyDown);
20402        dropDown.on('click', checkCloseMenu);
20403
20404        return function cleanupInteraction() {
20405          opts.backdrop && opts.backdrop.off('click', onBackdropClick);
20406          dropDown.off('keydown', onMenuKeyDown);
20407          dropDown.off('click', checkCloseMenu);
20408
20409          element.removeClass('md-clickable');
20410          opts.isRemoved = true;
20411        };
20412
20413        // ************************************
20414        // Closure Functions
20415        // ************************************
20416
20417        function onBackdropClick(e) {
20418          e.preventDefault();
20419          e.stopPropagation();
20420          opts.restoreFocus = false;
20421          $mdUtil.nextTick($mdSelect.hide, true);
20422        }
20423
20424        function onMenuKeyDown(ev) {
20425          ev.preventDefault();
20426          ev.stopPropagation();
20427
20428          switch (ev.keyCode) {
20429            case keyCodes.UP_ARROW:
20430              return focusPrevOption();
20431            case keyCodes.DOWN_ARROW:
20432              return focusNextOption();
20433            case keyCodes.SPACE:
20434            case keyCodes.ENTER:
20435              var option = $mdUtil.getClosest(ev.target, 'md-option');
20436              if (option) {
20437                dropDown.triggerHandler({
20438                  type: 'click',
20439                  target: option
20440                });
20441                ev.preventDefault();
20442              }
20443              checkCloseMenu(ev);
20444              break;
20445            case keyCodes.TAB:
20446            case keyCodes.ESCAPE:
20447              ev.stopPropagation();
20448              ev.preventDefault();
20449              opts.restoreFocus = true;
20450              $mdUtil.nextTick($mdSelect.hide, true);
20451              break;
20452            default:
20453              if (shouldHandleKey(ev, $mdConstant)) {
20454                var optNode = dropDown.controller('mdSelectMenu').optNodeForKeyboardSearch(ev);
20455                opts.focusedNode = optNode || opts.focusedNode;
20456                optNode && optNode.focus();
20457              }
20458          }
20459        }
20460
20461        function focusOption(direction) {
20462          var optionsArray = $mdUtil.nodesToArray(opts.optionNodes);
20463          var index = optionsArray.indexOf(opts.focusedNode);
20464
20465          var newOption;
20466
20467          do {
20468            if (index === -1) {
20469              // We lost the previously focused element, reset to first option
20470              index = 0;
20471            } else if (direction === 'next' && index < optionsArray.length - 1) {
20472              index++;
20473            } else if (direction === 'prev' && index > 0) {
20474              index--;
20475            }
20476            newOption = optionsArray[index];
20477            if (newOption.hasAttribute('disabled')) newOption = undefined;
20478          } while (!newOption && index < optionsArray.length - 1 && index > 0);
20479
20480          newOption && newOption.focus();
20481          opts.focusedNode = newOption;
20482        }
20483
20484        function focusNextOption() {
20485          focusOption('next');
20486        }
20487
20488        function focusPrevOption() {
20489          focusOption('prev');
20490        }
20491
20492        function checkCloseMenu(ev) {
20493          if (ev && ( ev.type == 'click') && (ev.currentTarget != dropDown[0])) return;
20494          if ( mouseOnScrollbar() ) return;
20495
20496          var option = $mdUtil.getClosest(ev.target, 'md-option');
20497          if (option && option.hasAttribute && !option.hasAttribute('disabled')) {
20498            ev.preventDefault();
20499            ev.stopPropagation();
20500            if (!selectCtrl.isMultiple) {
20501              opts.restoreFocus = true;
20502
20503              $mdUtil.nextTick(function () {
20504                $mdSelect.hide(selectCtrl.ngModel.$viewValue);
20505              }, true);
20506            }
20507          }
20508          /**
20509           * check if the mouseup event was on a scrollbar
20510           */
20511          function mouseOnScrollbar() {
20512            var clickOnScrollbar = false;
20513            if (ev && (ev.currentTarget.children.length > 0)) {
20514              var child = ev.currentTarget.children[0];
20515              var hasScrollbar = child.scrollHeight > child.clientHeight;
20516              if (hasScrollbar && child.children.length > 0) {
20517                var relPosX = ev.pageX - ev.currentTarget.getBoundingClientRect().left;
20518                if (relPosX > child.querySelector('md-option').offsetWidth)
20519                  clickOnScrollbar = true;
20520              }
20521            }
20522            return clickOnScrollbar;
20523          }
20524        }
20525      }
20526
20527    }
20528
20529    /**
20530     * To notify listeners that the Select menu has closed,
20531     * trigger the [optional] user-defined expression
20532     */
20533    function announceClosed(opts) {
20534      var mdSelect = opts.selectCtrl;
20535      if (mdSelect) {
20536        var menuController = opts.selectEl.controller('mdSelectMenu');
20537        mdSelect.setLabelText(menuController ? menuController.selectedLabels() : '');
20538        mdSelect.triggerClose();
20539      }
20540    }
20541
20542
20543    /**
20544     * Calculate the
20545     */
20546    function calculateMenuPositions(scope, element, opts) {
20547      var
20548        containerNode = element[0],
20549        targetNode = opts.target[0].children[0], // target the label
20550        parentNode = $document[0].body,
20551        selectNode = opts.selectEl[0],
vendor: 9,582 bytes, lines 20552-20826
20552        contentNode = opts.contentEl[0],
20553        parentRect = parentNode.getBoundingClientRect(),
20554        targetRect = targetNode.getBoundingClientRect(),
20555        shouldOpenAroundTarget = false,
20556        bounds = {
20557          left: parentRect.left + SELECT_EDGE_MARGIN,
20558          top: SELECT_EDGE_MARGIN,
20559          bottom: parentRect.height - SELECT_EDGE_MARGIN,
20560          right: parentRect.width - SELECT_EDGE_MARGIN - ($mdUtil.floatingScrollbars() ? 16 : 0)
20561        },
20562        spaceAvailable = {
20563          top: targetRect.top - bounds.top,
20564          left: targetRect.left - bounds.left,
20565          right: bounds.right - (targetRect.left + targetRect.width),
20566          bottom: bounds.bottom - (targetRect.top + targetRect.height)
20567        },
20568        maxWidth = parentRect.width - SELECT_EDGE_MARGIN * 2,
20569        selectedNode = selectNode.querySelector('md-option[selected]'),
20570        optionNodes = selectNode.getElementsByTagName('md-option'),
20571        optgroupNodes = selectNode.getElementsByTagName('md-optgroup'),
20572        isScrollable = calculateScrollable(element, contentNode),
20573        centeredNode;
20574
20575      var loading = isPromiseLike(opts.loadingAsync);
20576      if (!loading) {
20577        // If a selected node, center around that
20578        if (selectedNode) {
20579          centeredNode = selectedNode;
20580          // If there are option groups, center around the first option group
20581        } else if (optgroupNodes.length) {
20582          centeredNode = optgroupNodes[0];
20583          // Otherwise - if we are not loading async - center around the first optionNode
20584        } else if (optionNodes.length) {
20585          centeredNode = optionNodes[0];
20586          // In case there are no options, center on whatever's in there... (eg progress indicator)
20587        } else {
20588          centeredNode = contentNode.firstElementChild || contentNode;
20589        }
20590      } else {
20591        // If loading, center on progress indicator
20592        centeredNode = contentNode.firstElementChild || contentNode;
20593      }
20594
20595      if (contentNode.offsetWidth > maxWidth) {
20596        contentNode.style['max-width'] = maxWidth + 'px';
20597      } else {
20598        contentNode.style.maxWidth = null;
20599      }
20600      if (shouldOpenAroundTarget) {
20601        contentNode.style['min-width'] = targetRect.width + 'px';
20602      }
20603
20604      // Remove padding before we compute the position of the menu
20605      if (isScrollable) {
20606        selectNode.classList.add('md-overflow');
20607      }
20608
20609      var focusedNode = centeredNode;
20610      if ((focusedNode.tagName || '').toUpperCase() === 'MD-OPTGROUP') {
20611        focusedNode = optionNodes[0] || contentNode.firstElementChild || contentNode;
20612        centeredNode = focusedNode;
20613      }
20614      // Cache for autoFocus()
20615      opts.focusedNode = focusedNode;
20616
20617      // Get the selectMenuRect *after* max-width is possibly set above
20618      containerNode.style.display = 'block';
20619      var selectMenuRect = selectNode.getBoundingClientRect();
20620      var centeredRect = getOffsetRect(centeredNode);
20621
20622      if (centeredNode) {
20623        var centeredStyle = $window.getComputedStyle(centeredNode);
20624        centeredRect.paddingLeft = parseInt(centeredStyle.paddingLeft, 10) || 0;
20625        centeredRect.paddingRight = parseInt(centeredStyle.paddingRight, 10) || 0;
20626      }
20627
20628      if (isScrollable) {
20629        var scrollBuffer = contentNode.offsetHeight / 2;
20630        contentNode.scrollTop = centeredRect.top + centeredRect.height / 2 - scrollBuffer;
20631
20632        if (spaceAvailable.top < scrollBuffer) {
20633          contentNode.scrollTop = Math.min(
20634            centeredRect.top,
20635            contentNode.scrollTop + scrollBuffer - spaceAvailable.top
20636          );
20637        } else if (spaceAvailable.bottom < scrollBuffer) {
20638          contentNode.scrollTop = Math.max(
20639            centeredRect.top + centeredRect.height - selectMenuRect.height,
20640            contentNode.scrollTop - scrollBuffer + spaceAvailable.bottom
20641          );
20642        }
20643      }
20644
20645      var left, top, transformOrigin, minWidth, fontSize;
20646      if (shouldOpenAroundTarget) {
20647        left = targetRect.left;
20648        top = targetRect.top + targetRect.height;
20649        transformOrigin = '50% 0';
20650        if (top + selectMenuRect.height > bounds.bottom) {
20651          top = targetRect.top - selectMenuRect.height;
20652          transformOrigin = '50% 100%';
20653        }
20654      } else {
20655        left = (targetRect.left + centeredRect.left - centeredRect.paddingLeft) + 2;
20656        top = Math.floor(targetRect.top + targetRect.height / 2 - centeredRect.height / 2 -
20657            centeredRect.top + contentNode.scrollTop) + 2;
20658
20659        transformOrigin = (centeredRect.left + targetRect.width / 2) + 'px ' +
20660          (centeredRect.top + centeredRect.height / 2 - contentNode.scrollTop) + 'px 0px';
20661
20662        minWidth = Math.min(targetRect.width + centeredRect.paddingLeft + centeredRect.paddingRight, maxWidth);
20663
20664        fontSize = window.getComputedStyle(targetNode)['font-size'];
20665      }
20666
20667      // Keep left and top within the window
20668      var containerRect = containerNode.getBoundingClientRect();
20669      var scaleX = Math.round(100 * Math.min(targetRect.width / selectMenuRect.width, 1.0)) / 100;
20670      var scaleY = Math.round(100 * Math.min(targetRect.height / selectMenuRect.height, 1.0)) / 100;
20671
20672      return {
20673        container: {
20674          element: angular.element(containerNode),
20675          styles: {
20676            left: Math.floor(clamp(bounds.left, left, bounds.right - containerRect.width)),
20677            top: Math.floor(clamp(bounds.top, top, bounds.bottom - containerRect.height)),
20678            'min-width': minWidth,
20679            'font-size': fontSize
20680          }
20681        },
20682        dropDown: {
20683          element: angular.element(selectNode),
20684          styles: {
20685            transformOrigin: transformOrigin,
20686            transform: !opts.alreadyOpen ? $mdUtil.supplant('scale({0},{1})', [scaleX, scaleY]) : ""
20687          }
20688        }
20689      };
20690
20691    }
20692
20693  }
20694
20695  function isPromiseLike(obj) {
20696    return obj && angular.isFunction(obj.then);
20697  }
20698
20699  function clamp(min, n, max) {
20700    return Math.max(min, Math.min(n, max));
20701  }
20702
20703  function getOffsetRect(node) {
20704    return node ? {
20705      left: node.offsetLeft,
20706      top: node.offsetTop,
20707      width: node.offsetWidth,
20708      height: node.offsetHeight
20709    } : {left: 0, top: 0, width: 0, height: 0};
20710  }
20711
20712  function calculateScrollable(element, contentNode) {
20713    var isScrollable = false;
20714
20715    try {
20716      var oldDisplay = element[0].style.display;
20717
20718      // Set the element's display to block so that this calculation is correct
20719      element[0].style.display = 'block';
20720
20721      isScrollable = contentNode.scrollHeight > contentNode.offsetHeight;
20722
20723      // Reset it back afterwards
20724      element[0].style.display = oldDisplay;
20725    } finally {
20726      // Nothing to do
20727    }
20728    return isScrollable;
20729  }
20730
20731}
20732
20733function shouldHandleKey(ev, $mdConstant) {
20734  var char = String.fromCharCode(ev.keyCode);
20735  var isNonUsefulKey = (ev.keyCode <= 31);
20736
20737  return (char && char.length && !isNonUsefulKey &&
20738    !$mdConstant.isMetaKey(ev) && !$mdConstant.isFnLockKey(ev) && !$mdConstant.hasModifierKey(ev));
20739}
20740
20741})();
20742(function(){
20743"use strict";
20744
20745/**
20746 * @ngdoc module
20747 * @name material.components.showHide
20748 */
20749
20750// Add additional handlers to ng-show and ng-hide that notify directives
20751// contained within that they should recompute their size.
20752// These run in addition to AngularJS's built-in ng-hide and ng-show directives.
20753angular.module('material.components.showHide', [
20754  'material.core'
20755])
20756  .directive('ngShow', createDirective('ngShow', true))
20757  .directive('ngHide', createDirective('ngHide', false));
20758
20759
20760function createDirective(name, targetValue) {
20761  return ['$mdUtil', '$window', function($mdUtil, $window) {
20762    return {
20763      restrict: 'A',
20764      multiElement: true,
20765      link: function($scope, $element, $attr) {
20766        var unregister = $scope.$on('$md-resize-enable', function() {
20767          unregister();
20768
20769          var node = $element[0];
20770          var cachedTransitionStyles = node.nodeType === $window.Node.ELEMENT_NODE ?
20771            $window.getComputedStyle(node) : {};
20772
20773          $scope.$watch($attr[name], function(value) {
20774            if (!!value === targetValue) {
20775              $mdUtil.nextTick(function() {
20776                $scope.$broadcast('$md-resize');
20777              });
20778
20779              var opts = {
20780                cachedTransitionStyles: cachedTransitionStyles
20781              };
20782
20783              $mdUtil.dom.animator.waitTransitionEnd($element, opts).then(function() {
20784                $scope.$broadcast('$md-resize');
20785              });
20786            }
20787          });
20788        });
20789      }
20790    };
20791  }];
20792}
20793
20794})();
20795(function(){
20796"use strict";
20797
20798/**
20799 * @ngdoc module
20800 * @name material.components.sidenav
20801 *
20802 * @description
20803 * A Sidenav QP component.
20804 */
20805SidenavService.$inject = ["$mdComponentRegistry", "$mdUtil", "$q", "$log"];
20806SidenavDirective.$inject = ["$mdMedia", "$mdUtil", "$mdConstant", "$mdTheming", "$mdInteraction", "$animate", "$compile", "$parse", "$log", "$q", "$document", "$window", "$$rAF"];
20807SidenavController.$inject = ["$scope", "$attrs", "$mdComponentRegistry", "$q", "$interpolate"];
20808angular
20809  .module('material.components.sidenav', [
20810    'material.core',
20811    'material.components.backdrop'
20812  ])
20813  .factory('$mdSidenav', SidenavService )
20814  .directive('mdSidenav', SidenavDirective)
20815  .directive('mdSidenavFocus', SidenavFocusDirective)
20816  .controller('$mdSidenavController', SidenavController);
20817
20818
20819/**
20820 * @ngdoc service
20821 * @name $mdSidenav
20822 * @module material.components.sidenav
20823 *
20824 * @description
20825 * `$mdSidenav` makes it easy to interact with multiple sidenavs
20826 * in an app. When looking up a sidenav instance, you can either look
20827 * it up synchronously or wait for it to be initializied asynchronously.
20828 * This is done by passing the second argument to `$mdSidenav`.
20829 *
20830 * @usage
20831 * <hljs lang="js">
20832 * // Async lookup for sidenav instance; will resolve when the instance is available
20833 * $mdSidenav(componentId, true).then(function(instance) {
20834 *   $log.debug( componentId + "is now ready" );
20835 * });
20836 * // Sync lookup for sidenav instance; this will resolve immediately.
20837 * $mdSidenav(componentId).then(function(instance) {
20838 *   $log.debug( componentId + "is now ready" );
20839 * });
20840 * // Async toggle the given sidenav;
20841 * // when instance is known ready and lazy lookup is not needed.
20842 * $mdSidenav(componentId)
20843 *    .toggle()
20844 *    .then(function(){
20845 *      $log.debug('toggled');
20846 *    });
20847 * // Async open the given sidenav
20848 * $mdSidenav(componentId)
20849 *    .open()
20850 *    .then(function(){
20851 *      $log.debug('opened');
20852 *    });
20853 * // Async close the given sidenav
20854 * $mdSidenav(componentId)
20855 *    .close()
20856 *    .then(function(){
20857 *      $log.debug('closed');
20858 *    });
20859 * // Sync check to see if the specified sidenav is set to be open
20860 * $mdSidenav(componentId).isOpen();
20861 * // Sync check to whether given sidenav is locked open
20862 * // If this is true, the sidenav will be open regardless of close()
20863 * $mdSidenav(componentId).isLockedOpen();
20864 * // On close callback to handle close, backdrop click or escape key pressed
20865 * // Callback happens BEFORE the close action occurs.
20866 * $mdSidenav(componentId).onClose(function () {
20867 *   $log.debug('closing');
20868 * });
20869 * </hljs>
20870 */
20871function SidenavService($mdComponentRegistry, $mdUtil, $q, $log) {
20872  var errorMsg = "SideNav '{0}' is not available! Did you use md-component-id='{0}'?";
20873  var service = {
20874        find    : findInstance,     //  sync  - returns proxy API
20875        waitFor : waitForInstance   //  async - returns promise
20876      };
20877
20878  /**
20879   * Service API that supports three (3) usages:
20880   *   $mdSidenav().find("left")                       // sync (must already exist) or returns undefined
20881   *   $mdSidenav("left").toggle();                    // sync (must already exist) or returns reject promise;
20882   *   $mdSidenav("left",true).then( function(left){   // async returns instance when available
vendor: 4,857 bytes, lines 20883-21035
20883   *    left.toggle();
20884   *   });
20885   */
20886  return function(handle, enableWait) {
20887    if ( angular.isUndefined(handle) ) return service;
20888
20889    var shouldWait = enableWait === true;
20890    var instance = service.find(handle, shouldWait);
20891    return  !instance && shouldWait ? service.waitFor(handle) :
20892            !instance && angular.isUndefined(enableWait) ? addLegacyAPI(service, handle) : instance;
20893  };
20894
20895  /**
20896   * For failed instance/handle lookups, older-clients expect an response object with noops
20897   * that include `rejected promise APIs`
20898   */
20899  function addLegacyAPI(service, handle) {
20900      var falseFn  = function() { return false; };
20901      var rejectFn = function() {
20902            return $q.when($mdUtil.supplant(errorMsg, [handle || ""]));
20903          };
20904
20905      return angular.extend({
20906        isLockedOpen : falseFn,
20907        isOpen       : falseFn,
20908        toggle       : rejectFn,
20909        open         : rejectFn,
20910        close        : rejectFn,
20911        onClose      : angular.noop,
20912        then : function(callback) {
20913          return waitForInstance(handle)
20914            .then(callback || angular.noop);
20915        }
20916       }, service);
20917    }
20918    /**
20919     * Synchronously lookup the controller instance for the specified sidNav instance which has been
20920     * registered with the markup `md-component-id`
20921     */
20922    function findInstance(handle, shouldWait) {
20923      var instance = $mdComponentRegistry.get(handle);
20924
20925      if (!instance && !shouldWait) {
20926
20927        // Report missing instance
20928        $log.error( $mdUtil.supplant(errorMsg, [handle || ""]) );
20929
20930        // The component has not registered itself... most like NOT yet created
20931        // return null to indicate that the Sidenav is not in the DOM
20932        return undefined;
20933      }
20934      return instance;
20935    }
20936
20937    /**
20938     * Asynchronously wait for the component instantiation,
20939     * Deferred lookup of component instance using $component registry
20940     */
20941    function waitForInstance(handle) {
20942      return $mdComponentRegistry.when(handle).catch($log.error);
20943    }
20944}
20945/**
20946 * @ngdoc directive
20947 * @name mdSidenavFocus
20948 * @module material.components.sidenav
20949 *
20950 * @restrict A
20951 *
20952 * @description
20953 * `mdSidenavFocus` provides a way to specify the focused element when a sidenav opens.
20954 * This is completely optional, as the sidenav itself is focused by default.
20955 *
20956 * @usage
20957 * <hljs lang="html">
20958 * <md-sidenav>
20959 *   <form>
20960 *     <md-input-container>
20961 *       <label for="testInput">Label</label>
20962 *       <input id="testInput" type="text" md-sidenav-focus>
20963 *     </md-input-container>
20964 *   </form>
20965 * </md-sidenav>
20966 * </hljs>
20967 **/
20968function SidenavFocusDirective() {
20969  return {
20970    restrict: 'A',
20971    require: '^mdSidenav',
20972    link: function(scope, element, attr, sidenavCtrl) {
20973      // @see $mdUtil.findFocusTarget(...)
20974    }
20975  };
20976}
20977/**
20978 * @ngdoc directive
20979 * @name mdSidenav
20980 * @module material.components.sidenav
20981 * @restrict E
20982 *
20983 * @description
20984 *
20985 * A Sidenav component that can be opened and closed programatically.
20986 *
20987 * By default, upon opening it will slide out on top of the main content area.
20988 *
20989 * For keyboard and screen reader accessibility, focus is sent to the sidenav wrapper by default.
20990 * It can be overridden with the `md-autofocus` directive on the child element you want focused.
20991 *
20992 * @usage
20993 * <hljs lang="html">
20994 * <div layout="row" ng-controller="MyController">
20995 *   <md-sidenav md-component-id="left" class="md-sidenav-left">
20996 *     Left Nav!
20997 *   </md-sidenav>
20998 *
20999 *   <md-content>
21000 *     Center Content
21001 *     <md-button ng-click="openLeftMenu()">
21002 *       Open Left Menu
21003 *     </md-button>
21004 *   </md-content>
21005 *
21006 *   <md-sidenav md-component-id="right"
21007 *     md-is-locked-open="$mdMedia('min-width: 333px')"
21008 *     class="md-sidenav-right">
21009 *     <form>
21010 *       <md-input-container>
21011 *         <label for="testInput">Test input</label>
21012 *         <input id="testInput" type="text"
21013 *                ng-model="data" md-autofocus>
21014 *       </md-input-container>
21015 *     </form>
21016 *   </md-sidenav>
21017 * </div>
21018 * </hljs>
21019 *
21020 * <hljs lang="js">
21021 * var app = angular.module('myApp', ['ngMaterial']);
21022 * app.controller('MyController', function($scope, $mdSidenav) {
21023 *   $scope.openLeftMenu = function() {
21024 *     $mdSidenav('left').toggle();
21025 *   };
21026 * });
21027 * </hljs>
21028 *
21029 * @param {expression=} md-is-open A model bound to whether the sidenav is opened.
21030 * @param {boolean=} md-disable-backdrop When present in the markup, the sidenav will not show a backdrop.
21031 * @param {string=} md-component-id componentId to use with $mdSidenav service.
21032 * @param {expression=} md-is-locked-open When this expression evaluates to true,
21033 * the sidenav 'locks open': it falls into the content's flow instead
21034 * of appearing over it. This overrides the `md-is-open` attribute.
21035 * @param {string=} md-disable-scroll-target Selector, pointing to an element, whose scrolling will
21036 * be disabled when the sidenav is opened. By default this is the sidenav's direct parent.
21037 *
21038* The $mdMedia() service is exposed to the is-locked-open attribute, which
21039 * can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
21040 * Examples:
21041 *
21042 *   - `<md-sidenav md-is-locked-open="shouldLockOpen"></md-sidenav>`
21043 *   - `<md-sidenav md-is-locked-open="$mdMedia('min-width: 1000px')"></md-sidenav>`
21044 *   - `<md-sidenav md-is-locked-open="$mdMedia('sm')"></md-sidenav>` (locks open on small screens)
21045 */
21046function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $mdInteraction, $animate,
21047                          $compile, $parse, $log, $q, $document, $window, $$rAF) {
21048  return {
21049    restrict: 'E',
21050    scope: {
21051      isOpen: '=?mdIsOpen'
21052    },
21053    controller: '$mdSidenavController',
21054    compile: function(element) {
21055      element.addClass('md-closed').attr('tabIndex', '-1');
21056      return postLink;
21057    }
21058  };
21059
21060  /**
21061   * Directive Post Link function...
21062   */
21063  function postLink(scope, element, attr, sidenavCtrl) {
21064    var lastParentOverFlow;
21065    var backdrop;
21066    var disableScrollTarget = null;
21067    var triggeringInteractionType;
21068    var triggeringElement = null;
21069    var previousContainerStyles;
21070    var promise = $q.when(true);
21071    var isLockedOpenParsed = $parse(attr.mdIsLockedOpen);
21072    var ngWindow = angular.element($window);
vendor: 46,763 bytes, lines 21073-22552
21073    var isLocked = function() {
21074      return isLockedOpenParsed(scope.$parent, {
21075        $media: function(arg) {
21076          $log.warn("$media is deprecated for is-locked-open. Use $mdMedia instead.");
21077          return $mdMedia(arg);
21078        },
21079        $mdMedia: $mdMedia
21080      });
21081    };
21082
21083    if (attr.mdDisableScrollTarget) {
21084      disableScrollTarget = $document[0].querySelector(attr.mdDisableScrollTarget);
21085
21086      if (disableScrollTarget) {
21087        disableScrollTarget = angular.element(disableScrollTarget);
21088      } else {
21089        $log.warn($mdUtil.supplant('mdSidenav: couldn\'t find element matching ' +
21090          'selector "{selector}". Falling back to parent.', { selector: attr.mdDisableScrollTarget }));
21091      }
21092    }
21093
21094    if (!disableScrollTarget) {
21095      disableScrollTarget = element.parent();
21096    }
21097
21098    // Only create the backdrop if the backdrop isn't disabled.
21099    if (!attr.hasOwnProperty('mdDisableBackdrop')) {
21100      backdrop = $mdUtil.createBackdrop(scope, "md-sidenav-backdrop md-opaque ng-enter");
21101    }
21102
21103    element.addClass('_md');     // private md component indicator for styling
21104    $mdTheming(element);
21105
21106    // The backdrop should inherit the sidenavs theme,
21107    // because the backdrop will take its parent theme by default.
21108    if ( backdrop ) $mdTheming.inherit(backdrop, element);
21109
21110    element.on('$destroy', function() {
21111      backdrop && backdrop.remove();
21112      sidenavCtrl.destroy();
21113    });
21114
21115    scope.$on('$destroy', function(){
21116      backdrop && backdrop.remove();
21117    });
21118
21119    scope.$watch(isLocked, updateIsLocked);
21120    scope.$watch('isOpen', updateIsOpen);
21121
21122
21123    // Publish special accessor for the Controller instance
21124    sidenavCtrl.$toggleOpen = toggleOpen;
21125
21126    /**
21127     * Toggle the DOM classes to indicate `locked`
21128     * @param isLocked
21129     */
21130    function updateIsLocked(isLocked, oldValue) {
21131      scope.isLockedOpen = isLocked;
21132      if (isLocked === oldValue) {
21133        element.toggleClass('md-locked-open', !!isLocked);
21134      } else {
21135        $animate[isLocked ? 'addClass' : 'removeClass'](element, 'md-locked-open');
21136      }
21137      if (backdrop) {
21138        backdrop.toggleClass('md-locked-open', !!isLocked);
21139      }
21140    }
21141
21142    /**
21143     * Toggle the SideNav view and attach/detach listeners
21144     * @param isOpen
21145     */
21146    function updateIsOpen(isOpen) {
21147      // Support deprecated md-sidenav-focus attribute as fallback
21148      var focusEl = $mdUtil.findFocusTarget(element) || $mdUtil.findFocusTarget(element,'[md-sidenav-focus]') || element;
21149      var parent = element.parent();
21150
21151      parent[isOpen ? 'on' : 'off']('keydown', onKeyDown);
21152      if (backdrop) backdrop[isOpen ? 'on' : 'off']('click', close);
21153
21154      var restorePositioning = updateContainerPositions(parent, isOpen);
21155
21156      if ( isOpen ) {
21157        // Capture upon opening..
21158        triggeringElement = $document[0].activeElement;
21159        triggeringInteractionType = $mdInteraction.getLastInteractionType();
21160      }
21161
21162      disableParentScroll(isOpen);
21163
21164      return promise = $q.all([
21165        isOpen && backdrop ? $animate.enter(backdrop, parent) : backdrop ?
21166                             $animate.leave(backdrop) : $q.when(true),
21167        $animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed')
21168      ]).then(function() {
21169        // Perform focus when animations are ALL done...
21170        if (scope.isOpen) {
21171          $$rAF(function() {
21172            // Notifies child components that the sidenav was opened. Should wait
21173            // a frame in order to allow for the element height to be computed.
21174            ngWindow.triggerHandler('resize');
21175          });
21176
21177          focusEl && focusEl.focus();
21178        }
21179
21180        // Restores the positioning on the sidenav and backdrop.
21181        restorePositioning && restorePositioning();
21182      });
21183    }
21184
21185    function updateContainerPositions(parent, willOpen) {
21186      var drawerEl = element[0];
21187      var scrollTop = parent[0].scrollTop;
21188
21189      if (willOpen && scrollTop) {
21190        previousContainerStyles = {
21191          top: drawerEl.style.top,
21192          bottom: drawerEl.style.bottom,
21193          height: drawerEl.style.height
21194        };
21195
21196        // When the parent is scrolled down, then we want to be able to show the sidenav at the current scroll
21197        // position. We're moving the sidenav down to the correct scroll position and apply the height of the
21198        // parent, to increase the performance. Using 100% as height, will impact the performance heavily.
21199        var positionStyle = {
21200          top: scrollTop + 'px',
21201          bottom: 'auto',
21202          height: parent[0].clientHeight + 'px'
21203        };
21204
21205        // Apply the new position styles to the sidenav and backdrop.
21206        element.css(positionStyle);
21207        backdrop.css(positionStyle);
21208      }
21209
21210      // When the sidenav is closing and we have previous defined container styles,
21211      // then we return a restore function, which resets the sidenav and backdrop.
21212      if (!willOpen && previousContainerStyles) {
21213        return function() {
21214          drawerEl.style.top = previousContainerStyles.top;
21215          drawerEl.style.bottom = previousContainerStyles.bottom;
21216          drawerEl.style.height = previousContainerStyles.height;
21217
21218          backdrop[0].style.top = null;
21219          backdrop[0].style.bottom = null;
21220          backdrop[0].style.height = null;
21221
21222          previousContainerStyles = null;
21223        };
21224      }
21225    }
21226
21227    /**
21228     * Prevent parent scrolling (when the SideNav is open)
21229     */
21230    function disableParentScroll(disabled) {
21231      if ( disabled && !lastParentOverFlow ) {
21232        lastParentOverFlow = disableScrollTarget.css('overflow');
21233        disableScrollTarget.css('overflow', 'hidden');
21234      } else if (angular.isDefined(lastParentOverFlow)) {
21235        disableScrollTarget.css('overflow', lastParentOverFlow);
21236        lastParentOverFlow = undefined;
21237      }
21238    }
21239
21240    /**
21241     * Toggle the sideNav view and publish a promise to be resolved when
21242     * the view animation finishes.
21243     *
21244     * @param isOpen
21245     * @returns {*}
21246     */
21247    function toggleOpen( isOpen ) {
21248      if (scope.isOpen == isOpen ) {
21249
21250        return $q.when(true);
21251
21252      } else {
21253        if (scope.isOpen && sidenavCtrl.onCloseCb) sidenavCtrl.onCloseCb();
21254
21255        return $q(function(resolve){
21256          // Toggle value to force an async `updateIsOpen()` to run
21257          scope.isOpen = isOpen;
21258
21259          $mdUtil.nextTick(function() {
21260            // When the current `updateIsOpen()` animation finishes
21261            promise.then(function(result) {
21262
21263              if ( !scope.isOpen && triggeringElement && triggeringInteractionType === 'keyboard') {
21264                // reset focus to originating element (if available) upon close
21265                triggeringElement.focus();
21266                triggeringElement = null;
21267              }
21268
21269              resolve(result);
21270            });
21271          });
21272
21273        });
21274
21275      }
21276    }
21277
21278    /**
21279     * Auto-close sideNav when the `escape` key is pressed.
21280     * @param evt
21281     */
21282    function onKeyDown(ev) {
21283      var isEscape = (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE);
21284      return isEscape ? close(ev) : $q.when(true);
21285    }
21286
21287    /**
21288     * With backdrop `clicks` or `escape` key-press, immediately
21289     * apply the CSS close transition... Then notify the controller
21290     * to close() and perform its own actions.
21291     */
21292    function close(ev) {
21293      ev.preventDefault();
21294
21295      return sidenavCtrl.close();
21296    }
21297
21298  }
21299}
21300
21301/*
21302 * @private
21303 * @ngdoc controller
21304 * @name SidenavController
21305 * @module material.components.sidenav
21306 */
21307function SidenavController($scope, $attrs, $mdComponentRegistry, $q, $interpolate) {
21308
21309  var self = this;
21310
21311  // Use Default internal method until overridden by directive postLink
21312
21313  // Synchronous getters
21314  self.isOpen = function() { return !!$scope.isOpen; };
21315  self.isLockedOpen = function() { return !!$scope.isLockedOpen; };
21316
21317  // Synchronous setters
21318  self.onClose = function (callback) {
21319    self.onCloseCb = callback;
21320    return self;
21321  };
21322
21323  // Async actions
21324  self.open   = function() { return self.$toggleOpen( true );  };
21325  self.close  = function() { return self.$toggleOpen( false ); };
21326  self.toggle = function() { return self.$toggleOpen( !$scope.isOpen );  };
21327  self.$toggleOpen = function(value) { return $q.when($scope.isOpen = value); };
21328
21329  // Evaluate the component id.
21330  var rawId = $attrs.mdComponentId;
21331  var hasDataBinding = rawId && rawId.indexOf($interpolate.startSymbol()) > -1;
21332  var componentId = hasDataBinding ? $interpolate(rawId)($scope.$parent) : rawId;
21333
21334  // Register the component.
21335  self.destroy = $mdComponentRegistry.register(self, componentId);
21336
21337  // Watch and update the component, if the id has changed.
21338  if (hasDataBinding) {
21339    $attrs.$observe('mdComponentId', function(id) {
21340      if (id && id !== self.$$mdHandle) {
21341        self.destroy(); // `destroy` only deregisters the old component id so we can add the new one.
21342        self.destroy = $mdComponentRegistry.register(self, id);
21343      }
21344    });
21345  }
21346}
21347
21348})();
21349(function(){
21350"use strict";
21351
21352  /**
21353   * @ngdoc module
21354   * @name material.components.slider
21355   */
21356SliderDirective.$inject = ["$$rAF", "$window", "$mdAria", "$mdUtil", "$mdConstant", "$mdTheming", "$mdGesture", "$parse", "$log", "$timeout"];
21357  angular.module('material.components.slider', [
21358    'material.core'
21359  ])
21360  .directive('mdSlider', SliderDirective)
21361  .directive('mdSliderContainer', SliderContainerDirective);
21362
21363/**
21364 * @ngdoc directive
21365 * @name mdSliderContainer
21366 * @module material.components.slider
21367 * @restrict E
21368 * @description
21369 * The `<md-slider-container>` contains slider with two other elements.
21370 *
21371 *
21372 * @usage
21373 * <h4>Normal Mode</h4>
21374 * <hljs lang="html">
21375 * </hljs>
21376 */
21377function SliderContainerDirective() {
21378  return {
21379    controller: function () {},
21380    compile: function (elem) {
21381      var slider = elem.find('md-slider');
21382
21383      if (!slider) {
21384        return;
21385      }
21386
21387      var vertical = slider.attr('md-vertical');
21388
21389      if (vertical !== undefined) {
21390        elem.attr('md-vertical', '');
21391      }
21392
21393      if(!slider.attr('flex')) {
21394        slider.attr('flex', '');
21395      }
21396
21397      return function postLink(scope, element, attr, ctrl) {
21398        element.addClass('_md');     // private md component indicator for styling
21399
21400        // We have to manually stop the $watch on ngDisabled because it exists
21401        // on the parent scope, and won't be automatically destroyed when
21402        // the component is destroyed.
21403        function setDisable(value) {
21404          element.children().attr('disabled', value);
21405          element.find('input').attr('disabled', value);
21406        }
21407
21408        var stopDisabledWatch = angular.noop;
21409
21410        if (attr.disabled) {
21411          setDisable(true);
21412        }
21413        else if (attr.ngDisabled) {
21414          stopDisabledWatch = scope.$watch(attr.ngDisabled, function (value) {
21415            setDisable(value);
21416          });
21417        }
21418
21419        scope.$on('$destroy', function () {
21420          stopDisabledWatch();
21421        });
21422
21423        var initialMaxWidth;
21424
21425        ctrl.fitInputWidthToTextLength = function (length) {
21426          var input = element[0].querySelector('md-input-container');
21427
21428          if (input) {
21429            var computedStyle = getComputedStyle(input);
21430            var minWidth = parseInt(computedStyle.minWidth);
21431            var padding = parseInt(computedStyle.padding) * 2;
21432
21433            initialMaxWidth = initialMaxWidth || parseInt(computedStyle.maxWidth);
21434            var newMaxWidth = Math.max(initialMaxWidth, minWidth + padding + (minWidth / 2 * length));
21435
21436            input.style.maxWidth = newMaxWidth + 'px';
21437          }
21438        };
21439      };
21440    }
21441  };
21442}
21443
21444/**
21445 * @ngdoc directive
21446 * @name mdSlider
21447 * @module material.components.slider
21448 * @restrict E
21449 * @description
21450 * The `<md-slider>` component allows the user to choose from a range of
21451 * values.
21452 *
21453 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
21454 * the slider is in the accent color by default. The primary color palette may be used with
21455 * the `md-primary` class.
21456 *
21457 * It has two modes: 'normal' mode, where the user slides between a wide range
21458 * of values, and 'discrete' mode, where the user slides between only a few
21459 * select values.
21460 *
21461 * To enable discrete mode, add the `md-discrete` attribute to a slider,
21462 * and use the `step` attribute to change the distance between
21463 * values the user is allowed to pick.
21464 *
21465 * @usage
21466 * <h4>Normal Mode</h4>
21467 * <hljs lang="html">
21468 * <md-slider ng-model="myValue" min="5" max="500">
21469 * </md-slider>
21470 * </hljs>
21471 * <h4>Discrete Mode</h4>
21472 * <hljs lang="html">
21473 * <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
21474 * </md-slider>
21475 * </hljs>
21476 * <h4>Invert Mode</h4>
21477 * <hljs lang="html">
21478 * <md-slider md-invert ng-model="myValue" step="10" min="10" max="130">
21479 * </md-slider>
21480 * </hljs>
21481 *
21482 * @param {boolean=} md-discrete Whether to enable discrete mode.
21483 * @param {boolean=} md-invert Whether to enable invert mode.
21484 * @param {number=} step The distance between values the user is allowed to pick. Default 1.
21485 * @param {number=} min The minimum value the user is allowed to pick. Default 0.
21486 * @param {number=} max The maximum value the user is allowed to pick. Default 100.
21487 * @param {number=} round The amount of numbers after the decimal point, maximum is 6 to prevent scientific notation. Default 3.
21488 */
21489function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdTheming, $mdGesture, $parse, $log, $timeout) {
21490  return {
21491    scope: {},
21492    require: ['?ngModel', '?^mdSliderContainer'],
21493    template:
21494      '<div class="md-slider-wrapper">' +
21495        '<div class="md-slider-content">' +
21496          '<div class="md-track-container">' +
21497            '<div class="md-track"></div>' +
21498            '<div class="md-track md-track-fill"></div>' +
21499            '<div class="md-track-ticks"></div>' +
21500          '</div>' +
21501          '<div class="md-thumb-container">' +
21502            '<div class="md-thumb"></div>' +
21503            '<div class="md-focus-thumb"></div>' +
21504            '<div class="md-focus-ring"></div>' +
21505            '<div class="md-sign">' +
21506              '<span class="md-thumb-text"></span>' +
21507            '</div>' +
21508            '<div class="md-disabled-thumb"></div>' +
21509          '</div>' +
21510        '</div>' +
21511      '</div>',
21512    compile: compile
21513  };
21514
21515  // **********************************************************
21516  // Private Methods
21517  // **********************************************************
21518
21519  function compile (tElement, tAttrs) {
21520    var wrapper = angular.element(tElement[0].getElementsByClassName('md-slider-wrapper'));
21521
21522    var tabIndex = tAttrs.tabindex || 0;
21523    wrapper.attr('tabindex', tabIndex);
21524
21525    if (tAttrs.disabled || tAttrs.ngDisabled) wrapper.attr('tabindex', -1);
21526
21527    wrapper.attr('role', 'slider');
21528
21529    $mdAria.expect(tElement, 'aria-label');
21530
21531    return postLink;
21532  }
21533
21534  function postLink(scope, element, attr, ctrls) {
21535    $mdTheming(element);
21536    var ngModelCtrl = ctrls[0] || {
21537      // Mock ngModelController if it doesn't exist to give us
21538      // the minimum functionality needed
21539      $setViewValue: function(val) {
21540        this.$viewValue = val;
21541        this.$viewChangeListeners.forEach(function(cb) { cb(); });
21542      },
21543      $parsers: [],
21544      $formatters: [],
21545      $viewChangeListeners: []
21546    };
21547
21548    var containerCtrl = ctrls[1];
21549    var container = angular.element($mdUtil.getClosest(element, '_md-slider-container', true));
21550    var isDisabled = attr.ngDisabled ? angular.bind(null, $parse(attr.ngDisabled), scope.$parent) : function () {
21551          return element[0].hasAttribute('disabled');
21552        };
21553
21554    var thumb = angular.element(element[0].querySelector('.md-thumb'));
21555    var thumbText = angular.element(element[0].querySelector('.md-thumb-text'));
21556    var thumbContainer = thumb.parent();
21557    var trackContainer = angular.element(element[0].querySelector('.md-track-container'));
21558    var activeTrack = angular.element(element[0].querySelector('.md-track-fill'));
21559    var tickContainer = angular.element(element[0].querySelector('.md-track-ticks'));
21560    var wrapper = angular.element(element[0].getElementsByClassName('md-slider-wrapper'));
21561    var content = angular.element(element[0].getElementsByClassName('md-slider-content'));
21562    var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
21563
21564    // Default values, overridable by attrs
21565    var DEFAULT_ROUND = 3;
21566    var vertical = angular.isDefined(attr.mdVertical);
21567    var discrete = angular.isDefined(attr.mdDiscrete);
21568    var invert = angular.isDefined(attr.mdInvert);
21569    angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(0);
21570    angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(100);
21571    angular.isDefined(attr.step)? attr.$observe('step', updateStep) : updateStep(1);
21572    angular.isDefined(attr.round)? attr.$observe('round', updateRound) : updateRound(DEFAULT_ROUND);
21573
21574    // We have to manually stop the $watch on ngDisabled because it exists
21575    // on the parent scope, and won't be automatically destroyed when
21576    // the component is destroyed.
21577    var stopDisabledWatch = angular.noop;
21578    if (attr.ngDisabled) {
21579      stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
21580    }
21581
21582    $mdGesture.register(wrapper, 'drag', { horizontal: !vertical });
21583
21584    scope.mouseActive = false;
21585
21586    wrapper
21587      .on('keydown', keydownListener)
21588      .on('mousedown', mouseDownListener)
21589      .on('focus', focusListener)
21590      .on('blur', blurListener)
21591      .on('$md.pressdown', onPressDown)
21592      .on('$md.pressup', onPressUp)
21593      .on('$md.dragstart', onDragStart)
21594      .on('$md.drag', onDrag)
21595      .on('$md.dragend', onDragEnd);
21596
21597    // On resize, recalculate the slider's dimensions and re-render
21598    function updateAll() {
21599      refreshSliderDimensions();
21600      ngModelRender();
21601    }
21602    setTimeout(updateAll, 0);
21603
21604    var debouncedUpdateAll = $$rAF.throttle(updateAll);
21605    angular.element($window).on('resize', debouncedUpdateAll);
21606
21607    scope.$on('$destroy', function() {
21608      angular.element($window).off('resize', debouncedUpdateAll);
21609    });
21610
21611    ngModelCtrl.$render = ngModelRender;
21612    ngModelCtrl.$viewChangeListeners.push(ngModelRender);
21613    ngModelCtrl.$formatters.push(minMaxValidator);
21614    ngModelCtrl.$formatters.push(stepValidator);
21615
21616    /**
21617     * Attributes
21618     */
21619    var min;
21620    var max;
21621    var step;
21622    var round;
21623    function updateMin(value) {
21624      min = parseFloat(value);
21625      element.attr('aria-valuemin', value);
21626      updateAll();
21627    }
21628    function updateMax(value) {
21629      max = parseFloat(value);
21630      element.attr('aria-valuemax', value);
21631      updateAll();
21632    }
21633    function updateStep(value) {
21634      step = parseFloat(value);
21635    }
21636    function updateRound(value) {
21637      // Set max round digits to 6, after 6 the input uses scientific notation
21638      round = minMaxValidator(parseInt(value), 0, 6);
21639    }
21640    function updateAriaDisabled() {
21641      element.attr('aria-disabled', !!isDisabled());
21642    }
21643
21644    // Draw the ticks with canvas.
21645    // The alternative to drawing ticks with canvas is to draw one element for each tick,
21646    // which could quickly become a performance bottleneck.
21647    var tickCanvas, tickCtx;
21648    function redrawTicks() {
21649      if (!discrete || isDisabled()) return;
21650      if ( angular.isUndefined(step) )         return;
21651
21652      if ( step <= 0 ) {
21653        var msg = 'Slider step value must be greater than zero when in discrete mode';
21654        $log.error(msg);
21655        throw new Error(msg);
21656      }
21657
21658      var numSteps = Math.floor( (max - min) / step );
21659      if (!tickCanvas) {
21660        tickCanvas = angular.element('<canvas>').css('position', 'absolute');
21661        tickContainer.append(tickCanvas);
21662
21663        tickCtx = tickCanvas[0].getContext('2d');
21664      }
21665
21666      var dimensions = getSliderDimensions();
21667
21668      // If `dimensions` doesn't have height and width it might be the first attempt so we will refresh dimensions
21669      if (dimensions && !dimensions.height && !dimensions.width) {
21670        refreshSliderDimensions();
21671        dimensions = sliderDimensions;
21672      }
21673
21674      tickCanvas[0].width = dimensions.width;
21675      tickCanvas[0].height = dimensions.height;
21676
21677      var distance;
21678      for (var i = 0; i <= numSteps; i++) {
21679        var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
21680        tickCtx.fillStyle = trackTicksStyle.color || 'black';
21681
21682        distance = Math.floor((vertical ? dimensions.height : dimensions.width) * (i / numSteps));
21683
21684        tickCtx.fillRect(vertical ? 0 : distance - 1,
21685          vertical ? distance - 1 : 0,
21686          vertical ? dimensions.width : 2,
21687          vertical ? 2 : dimensions.height);
21688      }
21689    }
21690
21691    function clearTicks() {
21692      if(tickCanvas && tickCtx) {
21693        var dimensions = getSliderDimensions();
21694        tickCtx.clearRect(0, 0, dimensions.width, dimensions.height);
21695      }
21696    }
21697
21698    /**
21699     * Refreshing Dimensions
21700     */
21701    var sliderDimensions = {};
21702    refreshSliderDimensions();
21703    function refreshSliderDimensions() {
21704      sliderDimensions = trackContainer[0].getBoundingClientRect();
21705    }
21706    function getSliderDimensions() {
21707      throttledRefreshDimensions();
21708      return sliderDimensions;
21709    }
21710
21711    /**
21712     * left/right/up/down arrow listener
21713     */
21714    function keydownListener(ev) {
21715      if (isDisabled()) return;
21716
21717      var changeAmount;
21718      if (vertical ? ev.keyCode === $mdConstant.KEY_CODE.DOWN_ARROW : ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
21719        changeAmount = -step;
21720      } else if (vertical ? ev.keyCode === $mdConstant.KEY_CODE.UP_ARROW : ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
21721        changeAmount = step;
21722      }
21723      changeAmount = invert ? -changeAmount : changeAmount;
21724      if (changeAmount) {
21725        if (ev.metaKey || ev.ctrlKey || ev.altKey) {
21726          changeAmount *= 4;
21727        }
21728        ev.preventDefault();
21729        ev.stopPropagation();
21730        scope.$evalAsync(function() {
21731          setModelValue(ngModelCtrl.$viewValue + changeAmount);
21732        });
21733      }
21734    }
21735
21736    function mouseDownListener() {
21737      redrawTicks();
21738
21739      scope.mouseActive = true;
21740      wrapper.removeClass('md-focused');
21741
21742      $timeout(function() {
21743        scope.mouseActive = false;
21744      }, 100);
21745    }
21746
21747    function focusListener() {
21748      if (scope.mouseActive === false) {
21749        wrapper.addClass('md-focused');
21750      }
21751    }
21752
21753    function blurListener() {
21754      wrapper.removeClass('md-focused');
21755      element.removeClass('md-active');
21756      clearTicks();
21757    }
21758
21759    /**
21760     * ngModel setters and validators
21761     */
21762    function setModelValue(value) {
21763      ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
21764    }
21765    function ngModelRender() {
21766      if (isNaN(ngModelCtrl.$viewValue)) {
21767        ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
21768      }
21769
21770      ngModelCtrl.$viewValue = minMaxValidator(ngModelCtrl.$viewValue);
21771
21772      var percent = valueToPercent(ngModelCtrl.$viewValue);
21773      scope.modelValue = ngModelCtrl.$viewValue;
21774      element.attr('aria-valuenow', ngModelCtrl.$viewValue);
21775      setSliderPercent(percent);
21776      thumbText.text( ngModelCtrl.$viewValue );
21777    }
21778
21779    function minMaxValidator(value, minValue, maxValue) {
21780      if (angular.isNumber(value)) {
21781        minValue = angular.isNumber(minValue) ? minValue : min;
21782        maxValue = angular.isNumber(maxValue) ? maxValue : max;
21783
21784        return Math.max(minValue, Math.min(maxValue, value));
21785      }
21786    }
21787
21788    function stepValidator(value) {
21789      if (angular.isNumber(value)) {
21790        var formattedValue = (Math.round((value - min) / step) * step + min);
21791        formattedValue = (Math.round(formattedValue * Math.pow(10, round)) / Math.pow(10, round));
21792
21793        if (containerCtrl && containerCtrl.fitInputWidthToTextLength){
21794          $mdUtil.debounce(function () {
21795            containerCtrl.fitInputWidthToTextLength(formattedValue.toString().length);
21796          }, 100)();
21797        }
21798
21799        return formattedValue;
21800      }
21801    }
21802
21803    /**
21804     * @param percent 0-1
21805     */
21806    function setSliderPercent(percent) {
21807
21808      percent = clamp(percent);
21809
21810      var thumbPosition = (percent * 100) + '%';
21811      var activeTrackPercent = invert ? (1 - percent) * 100 + '%' : thumbPosition;
21812
21813      if (vertical) {
21814        thumbContainer.css('bottom', thumbPosition);
21815      }
21816      else {
21817        $mdUtil.bidiProperty(thumbContainer, 'left', 'right', thumbPosition);
21818      }
21819
21820      
21821      activeTrack.css(vertical ? 'height' : 'width', activeTrackPercent);
21822
21823      element.toggleClass((invert ? 'md-max' : 'md-min'), percent === 0);
21824      element.toggleClass((invert ? 'md-min' : 'md-max'), percent === 1);
21825    }
21826
21827    /**
21828     * Slide listeners
21829     */
21830    var isDragging = false;
21831
21832    function onPressDown(ev) {
21833      if (isDisabled()) return;
21834
21835      element.addClass('md-active');
21836      element[0].focus();
21837      refreshSliderDimensions();
21838
21839      var exactVal = percentToValue( positionToPercent( vertical ? ev.pointer.y : ev.pointer.x ));
21840      var closestVal = minMaxValidator( stepValidator(exactVal) );
21841      scope.$apply(function() {
21842        setModelValue( closestVal );
21843        setSliderPercent( valueToPercent(closestVal));
21844      });
21845    }
21846    function onPressUp(ev) {
21847      if (isDisabled()) return;
21848
21849      element.removeClass('md-dragging');
21850
21851      var exactVal = percentToValue( positionToPercent( vertical ? ev.pointer.y : ev.pointer.x ));
21852      var closestVal = minMaxValidator( stepValidator(exactVal) );
21853      scope.$apply(function() {
21854        setModelValue(closestVal);
21855        ngModelRender();
21856      });
21857    }
21858    function onDragStart(ev) {
21859      if (isDisabled()) return;
21860      isDragging = true;
21861
21862      ev.stopPropagation();
21863
21864      element.addClass('md-dragging');
21865      setSliderFromEvent(ev);
21866    }
21867    function onDrag(ev) {
21868      if (!isDragging) return;
21869      ev.stopPropagation();
21870      setSliderFromEvent(ev);
21871    }
21872    function onDragEnd(ev) {
21873      if (!isDragging) return;
21874      ev.stopPropagation();
21875      isDragging = false;
21876    }
21877
21878    function setSliderFromEvent(ev) {
21879      // While panning discrete, update only the
21880      // visual positioning but not the model value.
21881      if ( discrete ) adjustThumbPosition( vertical ? ev.pointer.y : ev.pointer.x );
21882      else            doSlide( vertical ? ev.pointer.y : ev.pointer.x );
21883    }
21884
21885    /**
21886     * Slide the UI by changing the model value
21887     * @param x
21888     */
21889    function doSlide( x ) {
21890      scope.$evalAsync( function() {
21891        setModelValue( percentToValue( positionToPercent(x) ));
21892      });
21893    }
21894
21895    /**
21896     * Slide the UI without changing the model (while dragging/panning)
21897     * @param x
21898     */
21899    function adjustThumbPosition( x ) {
21900      var exactVal = percentToValue( positionToPercent( x ));
21901      var closestVal = minMaxValidator( stepValidator(exactVal) );
21902      setSliderPercent( positionToPercent(x) );
21903      thumbText.text( closestVal );
21904    }
21905
21906    /**
21907    * Clamps the value to be between 0 and 1.
21908    * @param {number} value The value to clamp.
21909    * @returns {number}
21910    */
21911    function clamp(value) {
21912      return Math.max(0, Math.min(value || 0, 1));
21913    }
21914
21915    /**
21916     * Convert position on slider to percentage value of offset from beginning...
21917     * @param position
21918     * @returns {number}
21919     */
21920    function positionToPercent( position ) {
21921      var offset = vertical ? sliderDimensions.top : sliderDimensions.left;
21922      var size = vertical ? sliderDimensions.height : sliderDimensions.width;
21923      var calc = (position - offset) / size;
21924
21925      if (!vertical && $mdUtil.bidi() === 'rtl') {
21926        calc = 1 - calc;
21927      }
21928
21929      return Math.max(0, Math.min(1, vertical ? 1 - calc : calc));
21930    }
21931
21932    /**
21933     * Convert percentage offset on slide to equivalent model value
21934     * @param percent
21935     * @returns {*}
21936     */
21937    function percentToValue( percent ) {
21938      var adjustedPercent = invert ? (1 - percent) : percent;
21939      return (min + adjustedPercent * (max - min));
21940    }
21941
21942    function valueToPercent( val ) {
21943      var percent = (val - min) / (max - min);
21944      return invert ? (1 - percent) : percent;
21945    }
21946  }
21947}
21948
21949})();
21950(function(){
21951"use strict";
21952
21953/**
21954 * @ngdoc module
21955 * @name material.components.sticky
21956 * @description
21957 * Sticky effects for md
21958 *
21959 */
21960MdSticky.$inject = ["$mdConstant", "$$rAF", "$mdUtil", "$compile"];
21961angular
21962  .module('material.components.sticky', [
21963    'material.core',
21964    'material.components.content'
21965  ])
21966  .factory('$mdSticky', MdSticky);
21967
21968/**
21969 * @ngdoc service
21970 * @name $mdSticky
21971 * @module material.components.sticky
21972 *
21973 * @description
21974 * The `$mdSticky`service provides a mixin to make elements sticky.
21975 *
21976 * Whenever the current browser supports stickiness natively, the `$mdSticky` service will just
21977 * use the native browser stickiness.
21978 *
21979 * By default the `$mdSticky` service compiles the cloned element, when not specified through the `elementClone`
21980 * parameter, in the same scope as the actual element lives.
21981 *
21982 *
21983 * <h3>Notes</h3>
21984 * When using an element which is containing a compiled directive, which changed its DOM structure during compilation,
21985 * you should compile the clone yourself using the plain template.<br/><br/>
21986 * See the right usage below:
21987 * <hljs lang="js">
21988 *   angular.module('myModule')
21989 *     .directive('stickySelect', function($mdSticky, $compile) {
21990 *       var SELECT_TEMPLATE =
21991 *         '<md-select ng-model="selected">' +
21992 *           '<md-option>Option 1</md-option>' +
21993 *         '</md-select>';
21994 *
21995 *       return {
21996 *         restrict: 'E',
21997 *         replace: true,
21998 *         template: SELECT_TEMPLATE,
21999 *         link: function(scope,element) {
22000 *           $mdSticky(scope, element, $compile(SELECT_TEMPLATE)(scope));
22001 *         }
22002 *       };
22003 *     });
22004 * </hljs>
22005 *
22006 * @usage
22007 * <hljs lang="js">
22008 *   angular.module('myModule')
22009 *     .directive('stickyText', function($mdSticky, $compile) {
22010 *       return {
22011 *         restrict: 'E',
22012 *         template: '<span>Sticky Text</span>',
22013 *         link: function(scope,element) {
22014 *           $mdSticky(scope, element);
22015 *         }
22016 *       };
22017 *     });
22018 * </hljs>
22019 *
22020 * @returns A `$mdSticky` function that takes three arguments:
22021 *   - `scope`
22022 *   - `element`: The element that will be 'sticky'
22023 *   - `elementClone`: A clone of the element, that will be shown
22024 *     when the user starts scrolling past the original element.
22025 *     If not provided, it will use the result of `element.clone()` and compiles it in the given scope.
22026 */
22027function MdSticky($mdConstant, $$rAF, $mdUtil, $compile) {
22028
22029  var browserStickySupport = $mdUtil.checkStickySupport();
22030
22031  /**
22032   * Registers an element as sticky, used internally by directives to register themselves
22033   */
22034  return function registerStickyElement(scope, element, stickyClone) {
22035    var contentCtrl = element.controller('mdContent');
22036    if (!contentCtrl) return;
22037
22038    if (browserStickySupport) {
22039      element.css({
22040        position: browserStickySupport,
22041        top: 0,
22042        'z-index': 2
22043      });
22044    } else {
22045      var $$sticky = contentCtrl.$element.data('$$sticky');
22046      if (!$$sticky) {
22047        $$sticky = setupSticky(contentCtrl);
22048        contentCtrl.$element.data('$$sticky', $$sticky);
22049      }
22050
22051      // Compile our cloned element, when cloned in this service, into the given scope.
22052      var cloneElement = stickyClone || $compile(element.clone())(scope);
22053
22054      var deregister = $$sticky.add(element, cloneElement);
22055      scope.$on('$destroy', deregister);
22056    }
22057  };
22058
22059  function setupSticky(contentCtrl) {
22060    var contentEl = contentCtrl.$element;
22061
22062    // Refresh elements is very expensive, so we use the debounced
22063    // version when possible.
22064    var debouncedRefreshElements = $$rAF.throttle(refreshElements);
22065
22066    // setupAugmentedScrollEvents gives us `$scrollstart` and `$scroll`,
22067    // more reliable than `scroll` on android.
22068    setupAugmentedScrollEvents(contentEl);
22069    contentEl.on('$scrollstart', debouncedRefreshElements);
22070    contentEl.on('$scroll', onScroll);
22071
22072    var self;
22073    return self = {
22074      prev: null,
22075      current: null, //the currently stickied item
22076      next: null,
22077      items: [],
22078      add: add,
22079      refreshElements: refreshElements
22080    };
22081
22082    /***************
22083     * Public
22084     ***************/
22085    // Add an element and its sticky clone to this content's sticky collection
22086    function add(element, stickyClone) {
22087      stickyClone.addClass('md-sticky-clone');
22088
22089      var item = {
22090        element: element,
22091        clone: stickyClone
22092      };
22093      self.items.push(item);
22094
22095      $mdUtil.nextTick(function() {
22096        contentEl.prepend(item.clone);
22097      });
22098
22099      debouncedRefreshElements();
22100
22101      return function remove() {
22102        self.items.forEach(function(item, index) {
22103          if (item.element[0] === element[0]) {
22104            self.items.splice(index, 1);
22105            item.clone.remove();
22106          }
22107        });
22108        debouncedRefreshElements();
22109      };
22110    }
22111
22112    function refreshElements() {
22113      // Sort our collection of elements by their current position in the DOM.
22114      // We need to do this because our elements' order of being added may not
22115      // be the same as their order of display.
22116      self.items.forEach(refreshPosition);
22117      self.items = self.items.sort(function(a, b) {
22118        return a.top < b.top ? -1 : 1;
22119      });
22120
22121      // Find which item in the list should be active, 
22122      // based upon the content's current scroll position
22123      var item;
22124      var currentScrollTop = contentEl.prop('scrollTop');
22125      for (var i = self.items.length - 1; i >= 0; i--) {
22126        if (currentScrollTop > self.items[i].top) {
22127          item = self.items[i];
22128          break;
22129        }
22130      }
22131      setCurrentItem(item);
22132    }
22133
22134    /***************
22135     * Private
22136     ***************/
22137
22138    // Find the `top` of an item relative to the content element,
22139    // and also the height.
22140    function refreshPosition(item) {
22141      // Find the top of an item by adding to the offsetHeight until we reach the 
22142      // content element.
22143      var current = item.element[0];
22144      item.top = 0;
22145      item.left = 0;
22146      item.right = 0;
22147      while (current && current !== contentEl[0]) {
22148        item.top += current.offsetTop;
22149        item.left += current.offsetLeft;
22150        if ( current.offsetParent ){
22151          item.right += current.offsetParent.offsetWidth - current.offsetWidth - current.offsetLeft; //Compute offsetRight
22152        }
22153        current = current.offsetParent;
22154      }
22155      item.height = item.element.prop('offsetHeight');
22156
22157      var defaultVal = $mdUtil.floatingScrollbars() ? '0' : undefined;
22158      $mdUtil.bidi(item.clone, 'margin-left', item.left, defaultVal);
22159      $mdUtil.bidi(item.clone, 'margin-right', defaultVal, item.right);
22160    }
22161
22162    // As we scroll, push in and select the correct sticky element.
22163    function onScroll() {
22164      var scrollTop = contentEl.prop('scrollTop');
22165      var isScrollingDown = scrollTop > (onScroll.prevScrollTop || 0);
22166
22167      // Store the previous scroll so we know which direction we are scrolling
22168      onScroll.prevScrollTop = scrollTop;
22169
22170      //
22171      // AT TOP (not scrolling)
22172      //
22173      if (scrollTop === 0) {
22174        // If we're at the top, just clear the current item and return
22175        setCurrentItem(null);
22176        return;
22177      }
22178
22179      //
22180      // SCROLLING DOWN (going towards the next item)
22181      //
22182      if (isScrollingDown) {
22183
22184        // If we've scrolled down past the next item's position, sticky it and return
22185        if (self.next && self.next.top <= scrollTop) {
22186          setCurrentItem(self.next);
22187          return;
22188        }
22189
22190        // If the next item is close to the current one, push the current one up out of the way
22191        if (self.current && self.next && self.next.top - scrollTop <= self.next.height) {
22192          translate(self.current, scrollTop + (self.next.top - self.next.height - scrollTop));
22193          return;
22194        }
22195      }
22196
22197      //
22198      // SCROLLING UP (not at the top & not scrolling down; must be scrolling up)
22199      //
22200      if (!isScrollingDown) {
22201
22202        // If we've scrolled up past the previous item's position, sticky it and return
22203        if (self.current && self.prev && scrollTop < self.current.top) {
22204          setCurrentItem(self.prev);
22205          return;
22206        }
22207
22208        // If the next item is close to the current one, pull the current one down into view
22209        if (self.next && self.current && (scrollTop >= (self.next.top - self.current.height))) {
22210          translate(self.current, scrollTop + (self.next.top - scrollTop - self.current.height));
22211          return;
22212        }
22213      }
22214
22215      //
22216      // Otherwise, just move the current item to the proper place (scrolling up or down)
22217      //
22218      if (self.current) {
22219        translate(self.current, scrollTop);
22220      }
22221    }
22222
22223    function setCurrentItem(item) {
22224      if (self.current === item) return;
22225      // Deactivate currently active item
22226      if (self.current) {
22227        translate(self.current, null);
22228        setStickyState(self.current, null);
22229      }
22230
22231      // Activate new item if given
22232      if (item) {
22233        setStickyState(item, 'active');
22234      }
22235
22236      self.current = item;
22237      var index = self.items.indexOf(item);
22238      // If index === -1, index + 1 = 0. It works out.
22239      self.next = self.items[index + 1];
22240      self.prev = self.items[index - 1];
22241      setStickyState(self.next, 'next');
22242      setStickyState(self.prev, 'prev');
22243    }
22244
22245    function setStickyState(item, state) {
22246      if (!item || item.state === state) return;
22247      if (item.state) {
22248        item.clone.attr('sticky-prev-state', item.state);
22249        item.element.attr('sticky-prev-state', item.state);
22250      }
22251      item.clone.attr('sticky-state', state);
22252      item.element.attr('sticky-state', state);
22253      item.state = state;
22254    }
22255
22256    function translate(item, amount) {
22257      if (!item) return;
22258      if (amount === null || amount === undefined) {
22259        if (item.translateY) {
22260          item.translateY = null;
22261          item.clone.css($mdConstant.CSS.TRANSFORM, '');
22262        }
22263      } else {
22264        item.translateY = amount;
22265
22266        $mdUtil.bidi( item.clone, $mdConstant.CSS.TRANSFORM,
22267          'translate3d(' + item.left + 'px,' + amount + 'px,0)',
22268          'translateY(' + amount + 'px)'
22269        );
22270      }
22271    }
22272  }
22273
22274
22275  // Android 4.4 don't accurately give scroll events.
22276  // To fix this problem, we setup a fake scroll event. We say:
22277  // > If a scroll or touchmove event has happened in the last DELAY milliseconds, 
22278  //   then send a `$scroll` event every animationFrame.
22279  // Additionally, we add $scrollstart and $scrollend events.
22280  function setupAugmentedScrollEvents(element) {
22281    var SCROLL_END_DELAY = 200;
22282    var isScrolling;
22283    var lastScrollTime;
22284    element.on('scroll touchmove', function() {
22285      if (!isScrolling) {
22286        isScrolling = true;
22287        $$rAF.throttle(loopScrollEvent);
22288        element.triggerHandler('$scrollstart');
22289      }
22290      element.triggerHandler('$scroll');
22291      lastScrollTime = +$mdUtil.now();
22292    });
22293
22294    function loopScrollEvent() {
22295      if (+$mdUtil.now() - lastScrollTime > SCROLL_END_DELAY) {
22296        isScrolling = false;
22297        element.triggerHandler('$scrollend');
22298      } else {
22299        element.triggerHandler('$scroll');
22300        $$rAF.throttle(loopScrollEvent);
22301      }
22302    }
22303  }
22304
22305}
22306
22307})();
22308(function(){
22309"use strict";
22310
22311/**
22312 * @ngdoc module
22313 * @name material.components.subheader
22314 * @description
22315 * SubHeader module
22316 *
22317 *  Subheaders are special list tiles that delineate distinct sections of a
22318 *  list or grid list and are typically related to the current filtering or
22319 *  sorting criteria. Subheader tiles are either displayed inline with tiles or
22320 *  can be associated with content, for example, in an adjacent column.
22321 *
22322 *  Upon scrolling, subheaders remain pinned to the top of the screen and remain
22323 *  pinned until pushed on or off screen by the next subheader. @see [Material
22324 *  Design Specifications](https://www.google.com/design/spec/components/subheaders.html)
22325 *
22326 *  > To improve the visual grouping of content, use the system color for your subheaders.
22327 *
22328 */
22329MdSubheaderDirective.$inject = ["$mdSticky", "$compile", "$mdTheming", "$mdUtil", "$mdAria"];
22330angular
22331  .module('material.components.subheader', [
22332    'material.core',
22333    'material.components.sticky'
22334  ])
22335  .directive('mdSubheader', MdSubheaderDirective);
22336
22337/**
22338 * @ngdoc directive
22339 * @name mdSubheader
22340 * @module material.components.subheader
22341 *
22342 * @restrict E
22343 *
22344 * @description
22345 * The `md-subheader` directive creates a sticky subheader for a section.
22346 *
22347 * Developers are able to disable the stickiness of the subheader by using the following markup
22348 *
22349 * <hljs lang="html">
22350 *   <md-subheader class="md-no-sticky">Not Sticky</md-subheader>
22351 * </hljs>
22352 *
22353 * ### Notes
22354 * - The `md-subheader` directive uses the <a ng-href="api/service/$mdSticky">$mdSticky</a> service
22355 * to make the subheader sticky.
22356 *
22357 * > Whenever the current browser doesn't support stickiness natively, the subheader
22358 * will be compiled twice to create a sticky clone of the subheader.
22359 *
22360 * @usage
22361 * <hljs lang="html">
22362 * <md-subheader>Online Friends</md-subheader>
22363 * </hljs>
22364 */
22365
22366function MdSubheaderDirective($mdSticky, $compile, $mdTheming, $mdUtil, $mdAria) {
22367  return {
22368    restrict: 'E',
22369    replace: true,
22370    transclude: true,
22371    template: (
22372    '<div class="md-subheader _md">' +
22373    '  <div class="md-subheader-inner">' +
22374    '    <div class="md-subheader-content"></div>' +
22375    '  </div>' +
22376    '</div>'
22377    ),
22378    link: function postLink(scope, element, attr, controllers, transclude) {
22379      $mdTheming(element);
22380      element.addClass('_md');
22381
22382      // Remove the ngRepeat attribute from the root element, because we don't want to compile
22383      // the ngRepeat for the sticky clone again.
22384      $mdUtil.prefixer().removeAttribute(element, 'ng-repeat');
22385
22386      var outerHTML = element[0].outerHTML;
22387
22388      function getContent(el) {
22389        return angular.element(el[0].querySelector('.md-subheader-content'));
22390      }
22391
22392      // Set the ARIA attributes on the original element since it keeps it's original place in
22393      // the DOM, whereas the clones are in reverse order. Should be done after the outerHTML,
22394      // in order to avoid having multiple element be marked as headers.
22395      attr.$set('role', 'heading');
22396      $mdAria.expect(element, 'aria-level', '2');
22397
22398      // Transclude the user-given contents of the subheader
22399      // the conventional way.
22400      transclude(scope, function(clone) {
22401        getContent(element).append(clone);
22402      });
22403
22404      // Create another clone, that uses the outer and inner contents
22405      // of the element, that will be 'stickied' as the user scrolls.
22406      if (!element.hasClass('md-no-sticky')) {
22407        transclude(scope, function(clone) {
22408          // If the user adds an ng-if or ng-repeat directly to the md-subheader element, the
22409          // compiled clone below will only be a comment tag (since they replace their elements with
22410          // a comment) which cannot be properly passed to the $mdSticky; so we wrap it in our own
22411          // DIV to ensure we have something $mdSticky can use
22412          var wrapper = $compile('<div class="md-subheader-wrapper" aria-hidden="true">' + outerHTML + '</div>')(scope);
22413
22414          // Delay initialization until after any `ng-if`/`ng-repeat`/etc has finished before
22415          // attempting to create the clone
22416          $mdUtil.nextTick(function() {
22417            // Append our transcluded clone into the wrapper.
22418            // We don't have to recompile the element again, because the clone is already
22419            // compiled in it's transclusion scope. If we recompile the outerHTML of the new clone, we would lose
22420            // our ngIf's and other previous registered bindings / properties.
22421            getContent(wrapper).append(clone);
22422          });
22423
22424          // Make the element sticky and provide the stickyClone our self, to avoid recompilation of the subheader
22425          // element.
22426          $mdSticky(scope, element, wrapper);
22427        });
22428      }
22429    }
22430  };
22431}
22432
22433})();
22434(function(){
22435"use strict";
22436
22437/**
22438 * @ngdoc module
22439 * @name material.components.swipe
22440 * @description Swipe module!
22441 */
22442/**
22443 * @ngdoc directive
22444 * @module material.components.swipe
22445 * @name mdSwipeLeft
22446 *
22447 * @restrict A
22448 *
22449 * @description
22450 * The md-swipe-left directive allows you to specify custom behavior when an element is swiped
22451 * left.
22452 *
22453 * @usage
22454 * <hljs lang="html">
22455 * <div md-swipe-left="onSwipeLeft()">Swipe me left!</div>
22456 * </hljs>
22457 */
22458/**
22459 * @ngdoc directive
22460 * @module material.components.swipe
22461 * @name mdSwipeRight
22462 *
22463 * @restrict A
22464 *
22465 * @description
22466 * The md-swipe-right directive allows you to specify custom behavior when an element is swiped
22467 * right.
22468 *
22469 * @usage
22470 * <hljs lang="html">
22471 * <div md-swipe-right="onSwipeRight()">Swipe me right!</div>
22472 * </hljs>
22473 */
22474/**
22475 * @ngdoc directive
22476 * @module material.components.swipe
22477 * @name mdSwipeUp
22478 *
22479 * @restrict A
22480 *
22481 * @description
22482 * The md-swipe-up directive allows you to specify custom behavior when an element is swiped
22483 * up.
22484 *
22485 * @usage
22486 * <hljs lang="html">
22487 * <div md-swipe-up="onSwipeUp()">Swipe me up!</div>
22488 * </hljs>
22489 */
22490/**
22491 * @ngdoc directive
22492 * @module material.components.swipe
22493 * @name mdSwipeDown
22494 *
22495 * @restrict A
22496 *
22497 * @description
22498 * The md-swipe-down directive allows you to specify custom behavior when an element is swiped
22499 * down.
22500 *
22501 * @usage
22502 * <hljs lang="html">
22503 * <div md-swipe-down="onSwipDown()">Swipe me down!</div>
22504 * </hljs>
22505 */
22506
22507angular.module('material.components.swipe', ['material.core'])
22508    .directive('mdSwipeLeft', getDirective('SwipeLeft'))
22509    .directive('mdSwipeRight', getDirective('SwipeRight'))
22510    .directive('mdSwipeUp', getDirective('SwipeUp'))
22511    .directive('mdSwipeDown', getDirective('SwipeDown'));
22512
22513function getDirective(name) {
22514    DirectiveFactory.$inject = ["$parse"];
22515  var directiveName = 'md' + name;
22516  var eventName = '$md.' + name.toLowerCase();
22517
22518  return DirectiveFactory;
22519
22520  /* @ngInject */
22521  function DirectiveFactory($parse) {
22522      return { restrict: 'A', link: postLink };
22523      function postLink(scope, element, attr) {
22524        var fn = $parse(attr[directiveName]);
22525        element.on(eventName, function(ev) {
22526          scope.$applyAsync(function() { fn(scope, { $event: ev }); });
22527        });
22528      }
22529    }
22530}
22531
22532
22533
22534})();
22535(function(){
22536"use strict";
22537
22538/**
22539 * @ngdoc module
22540 * @name material.components.switch
22541 */
22542
22543MdSwitch.$inject = ["mdCheckboxDirective", "$mdUtil", "$mdConstant", "$parse", "$$rAF", "$mdGesture", "$timeout"];
22544angular.module('material.components.switch', [
22545  'material.core',
22546  'material.components.checkbox'
22547])
22548  .directive('mdSwitch', MdSwitch);
22549
22550/**
22551 * @ngdoc directive
22552 * @module material.components.switch
22553 * @name mdSwitch
22554 * @restrict E
22555 *
22556 * The switch directive is used very much like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
22557 *
22558 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
22559 * the switch is in the accent color by default. The primary color palette may be used with
22560 * the `md-primary` class.
22561 *
22562 * @param {string} ng-model Assignable angular expression to data-bind to.
22563 * @param {string=} name Property name of the form under which the control is published.
22564 * @param {expression=} ng-true-value The value to which the expression should be set when selected.
22565 * @param {expression=} ng-false-value The value to which the expression should be set when not selected.
22566 * @param {string=} ng-change AngularJS expression to be executed when input changes due to user interaction with the input element.
22567 * @param {expression=} ng-disabled En/Disable based on the expression.
22568 * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects.
22569 * @param {string=} aria-label Publish the button label used by screen-readers for accessibility. Defaults to the switch's text.
22570 * @param {boolean=} md-invert When set to true, the switch will be inverted.
22571 *
22572 * @usage
22573 * <hljs lang="html">
22574 * <md-switch ng-model="isActive" aria-label="Finished?">
22575 *   Finished ?
22576 * </md-switch>
22577 *
22578 * <md-switch md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
22579 *   No Ink Effects
22580 * </md-switch>
22581 *
22582 * <md-switch ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
22583 *   Disabled
22584 * </md-switch>
22585 *
22586 * </hljs>
22587 */
22588function MdSwitch(mdCheckboxDirective, $mdUtil, $mdConstant, $parse, $$rAF, $mdGesture, $timeout) {
22589  var checkboxDirective = mdCheckboxDirective[0];
22590
22591  return {
22592    restrict: 'E',
22593    priority: $mdConstant.BEFORE_NG_ARIA,
22594    transclude: true,
22595    template:
22596      '<div class="md-container">' +
22597        '<div class="md-bar"></div>' +
22598        '<div class="md-thumb-container">' +
22599          '<div class="md-thumb" md-ink-ripple md-ink-ripple-checkbox></div>' +
22600        '</div>'+
22601      '</div>' +
22602      '<div ng-transclude class="md-label"></div>',
22603    require: ['^?mdInputContainer', '?ngModel', '?^form'],
22604    compile: mdSwitchCompile
22605  };
22606
22607  function mdSwitchCompile(element, attr) {
22608    var checkboxLink = checkboxDirective.compile(element, attr).post;
22609    // No transition on initial load.
22610    element.addClass('md-dragging');
22611
22612    return function (scope, element, attr, ctrls) {
22613      var containerCtrl = ctrls[0];
22614      var ngModel = ctrls[1] || $mdUtil.fakeNgModel();
22615      var formCtrl = ctrls[2];
22616
22617      var disabledGetter = null;
22618      if (attr.disabled != null) {
22619        disabledGetter = function() { return true; };
22620      } else if (attr.ngDisabled) {
22621        disabledGetter = $parse(attr.ngDisabled);
22622      }
22623
22624      var thumbContainer = angular.element(element[0].querySelector('.md-thumb-container'));
22625      var switchContainer = angular.element(element[0].querySelector('.md-container'));
22626      var labelContainer = angular.element(element[0].querySelector('.md-label'));
22627
22628      // no transition on initial load
22629      $$rAF(function() {
22630        element.removeClass('md-dragging');
22631      });
22632
22633      checkboxLink(scope, element, attr, ctrls);
22634
22635      if (disabledGetter) {
22636        scope.$watch(disabledGetter, function(isDisabled) {
22637          element.attr('tabindex', isDisabled ? -1 : 0);
22638        });
22639      }
22640
22641      attr.$observe('mdInvert', function(newValue) {
22642        var isInverted = $mdUtil.parseAttributeBoolean(newValue);
22643
22644        isInverted ? element.prepend(labelContainer) : element.prepend(switchContainer);
22645
22646        // Toggle a CSS class to update the margin.
22647        element.toggleClass('md-inverted', isInverted);
22648      });
22649
22650      // These events are triggered by setup drag
22651      $mdGesture.register(switchContainer, 'drag');
22652      switchContainer
22653        .on('$md.dragstart', onDragStart)
22654        .on('$md.drag', onDrag)
22655        .on('$md.dragend', onDragEnd);
22656
22657      var drag;
22658      function onDragStart(ev) {
22659        // Don't go if the switch is disabled.
22660        if (disabledGetter && disabledGetter(scope)) return;
22661        ev.stopPropagation();
22662
22663        element.addClass('md-dragging');
22664        drag = {width: thumbContainer.prop('offsetWidth')};
22665      }
22666
22667      function onDrag(ev) {
22668        if (!drag) return;
22669        ev.stopPropagation();
22670        ev.srcEvent && ev.srcEvent.preventDefault();
22671
22672        var percent = ev.pointer.distanceX / drag.width;
22673
22674        //if checked, start from right. else, start from left
22675        var translate = ngModel.$viewValue ?  1 + percent : percent;
22676        // Make sure the switch stays inside its bounds, 0-1%
22677        translate = Math.max(0, Math.min(1, translate));
22678
22679        thumbContainer.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + (100*translate) + '%,0,0)');
22680        drag.translate = translate;
22681      }
22682
22683      function onDragEnd(ev) {
22684        if (!drag) return;
22685        ev.stopPropagation();
22686
22687        element.removeClass('md-dragging');
22688        thumbContainer.css($mdConstant.CSS.TRANSFORM, '');
22689
22690        // We changed if there is no distance (this is a click a click),
22691        // or if the drag distance is >50% of the total.
22692        var isChanged = ngModel.$viewValue ? drag.translate < 0.5 : drag.translate > 0.5;
22693        if (isChanged) {
22694          applyModelValue(!ngModel.$viewValue);
22695        }
22696        drag = null;
22697
22698        // Wait for incoming mouse click
22699        scope.skipToggle = true;
22700        $timeout(function() {
22701          scope.skipToggle = false;
vendor: 7,444 bytes, lines 22702-22972
22702        }, 1);
22703      }
22704
22705      function applyModelValue(newValue) {
22706        scope.$apply(function() {
22707          ngModel.$setViewValue(newValue);
22708          ngModel.$render();
22709        });
22710      }
22711
22712    };
22713  }
22714
22715
22716}
22717
22718})();
22719(function(){
22720"use strict";
22721
22722/**
22723 * @ngdoc module
22724 * @name material.components.tabs
22725 * @description
22726 *
22727 *  Tabs, created with the `<md-tabs>` directive provide *tabbed* navigation with different styles.
22728 *  The Tabs component consists of clickable tabs that are aligned horizontally side-by-side.
22729 *
22730 *  Features include support for:
22731 *
22732 *  - static or dynamic tabs,
22733 *  - responsive designs,
22734 *  - accessibility support (ARIA),
22735 *  - tab pagination,
22736 *  - external or internal tab content,
22737 *  - focus indicators and arrow-key navigations,
22738 *  - programmatic lookup and access to tab controllers, and
22739 *  - dynamic transitions through different tab contents.
22740 *
22741 */
22742/*
22743 * @see js folder for tabs implementation
22744 */
22745angular.module('material.components.tabs', [
22746  'material.core',
22747  'material.components.icon'
22748]);
22749
22750})();
22751(function(){
22752"use strict";
22753
22754angular
22755.module('material.components.tabs')
22756.service('MdTabsPaginationService', MdTabsPaginationService);
22757
22758/**
22759 * @private
22760 * @module material.components.tabs
22761 * @name MdTabsPaginationService
22762 * @description Provides many standalone functions to ease in pagination calculations.
22763 *
22764 * Most functions accept the elements and the current offset.
22765 *
22766 * The `elements` parameter is typically the value returned from the `getElements()` function of the
22767 * tabsController.
22768 *
22769 * The `offset` parameter is always positive regardless of LTR or RTL (we simply make the LTR one
22770 * negative when we apply our transform). This is typically the `ctrl.leftOffset` variable in the
22771 * tabsController.
22772 *
22773 * @returns MdTabsPaginationService
22774 * @constructor
22775 */
22776function MdTabsPaginationService() {
22777  return {
22778    decreasePageOffset: decreasePageOffset,
22779    increasePageOffset: increasePageOffset,
22780    getTabOffsets: getTabOffsets,
22781    getTotalTabsWidth: getTotalTabsWidth
22782  };
22783
22784  /**
22785   * Returns the offset for the next decreasing page.
22786   *
22787   * @param elements
22788   * @param currentOffset
22789   * @returns {number}
22790   */
22791  function decreasePageOffset(elements, currentOffset) {
22792    var canvas       = elements.canvas,
22793        tabOffsets   = getTabOffsets(elements),
22794        i, firstVisibleTabOffset;
22795
22796    // Find the first fully visible tab in offset range
22797    for (i = 0; i < tabOffsets.length; i++) {
22798      if (tabOffsets[i] >= currentOffset) {
22799        firstVisibleTabOffset = tabOffsets[i];
22800        break;
22801      }
22802    }
22803
22804    // Return (the first visible tab offset - the tabs container width) without going negative
22805    return Math.max(0, firstVisibleTabOffset - canvas.clientWidth);
22806  }
22807
22808  /**
22809   * Returns the offset for the next increasing page.
22810   *
22811   * @param elements
22812   * @param currentOffset
22813   * @returns {number}
22814   */
22815  function increasePageOffset(elements, currentOffset) {
22816    var canvas       = elements.canvas,
22817        maxOffset    = getTotalTabsWidth(elements) - canvas.clientWidth,
22818        tabOffsets   = getTabOffsets(elements),
22819        i, firstHiddenTabOffset;
22820
22821    // Find the first partially (or fully) invisible tab
22822    for (i = 0; i < tabOffsets.length, tabOffsets[i] <= currentOffset + canvas.clientWidth; i++) {
22823      firstHiddenTabOffset = tabOffsets[i];
22824    }
22825
22826    // Return the offset of the first hidden tab, or the maximum offset (whichever is smaller)
22827    return Math.min(maxOffset, firstHiddenTabOffset);
22828  }
22829
22830  /**
22831   * Returns the offsets of all of the tabs based on their widths.
22832   *
22833   * @param elements
22834   * @returns {number[]}
22835   */
22836  function getTabOffsets(elements) {
22837    var i, tab, currentOffset = 0, offsets = [];
22838
22839    for (i = 0; i < elements.tabs.length; i++) {
22840      tab = elements.tabs[i];
22841      offsets.push(currentOffset);
22842      currentOffset += tab.offsetWidth;
22843    }
22844
22845    return offsets;
22846  }
22847
22848  /**
22849   * Sum the width of all tabs.
22850   *
22851   * @param elements
22852   * @returns {number}
22853   */
22854  function getTotalTabsWidth(elements) {
22855    var sum = 0, i, tab;
22856
22857    for (i = 0; i < elements.tabs.length; i++) {
22858      tab = elements.tabs[i];
22859      sum += tab.offsetWidth;
22860    }
22861
22862    return sum;
22863  }
22864
22865}
22866
22867})();
22868(function(){
22869"use strict";
22870
22871/**
22872  * @ngdoc module
22873  * @name material.components.toast
22874  * @description
22875  * Toast
22876  */
22877MdToastDirective.$inject = ["$mdToast"];
22878MdToastProvider.$inject = ["$$interimElementProvider"];
22879angular.module('material.components.toast', [
22880  'material.core',
22881  'material.components.button'
22882])
22883  .directive('mdToast', MdToastDirective)
22884  .provider('$mdToast', MdToastProvider);
22885
22886/* @ngInject */
22887function MdToastDirective($mdToast) {
22888  return {
22889    restrict: 'E',
22890    link: function postLink(scope, element) {
22891      element.addClass('_md');     // private md component indicator for styling
22892      
22893      // When navigation force destroys an interimElement, then
22894      // listen and $destroy() that interim instance...
22895      scope.$on('$destroy', function() {
22896        $mdToast.destroy();
22897      });
22898    }
22899  };
22900}
22901
22902/**
22903  * @ngdoc service
22904  * @name $mdToast
22905  * @module material.components.toast
22906  *
22907  * @description
22908  * `$mdToast` is a service to build a toast notification on any position
22909  * on the screen with an optional duration, and provides a simple promise API.
22910  *
22911  * The toast will be always positioned at the `bottom`, when the screen size is
22912  * between `600px` and `959px` (`sm` breakpoint)
22913  *
22914  * ## Restrictions on custom toasts
22915  * - The toast's template must have an outer `<md-toast>` element.
22916  * - For a toast action, use element with class `md-action`.
22917  * - Add the class `md-capsule` for curved corners.
22918  *
22919  * ### Custom Presets
22920  * Developers are also able to create their own preset, which can be easily used without repeating
22921  * their options each time.
22922  *
22923  * <hljs lang="js">
22924  *   $mdToastProvider.addPreset('testPreset', {
22925  *     options: function() {
22926  *       return {
22927  *         template:
22928  *           '<md-toast>' +
22929  *             '<div class="md-toast-content">' +
22930  *               'This is a custom preset' +
22931  *             '</div>' +
22932  *           '</md-toast>',
22933  *         controllerAs: 'toast',
22934  *         bindToController: true
22935  *       };
22936  *     }
22937  *   });
22938  * </hljs>
22939  *
22940  * After you created your preset at config phase, you can easily access it.
22941  *
22942  * <hljs lang="js">
22943  *   $mdToast.show(
22944  *     $mdToast.testPreset()
22945  *   );
22946  * </hljs>
22947  *
22948  * ## Parent container notes
22949  *
22950  * The toast is positioned using absolute positioning relative to its first non-static parent
22951  * container. Thus, if the requested parent container uses static positioning, we will temporarily
22952  * set its positioning to `relative` while the toast is visible and reset it when the toast is
22953  * hidden.
22954  *
22955  * Because of this, it is usually best to ensure that the parent container has a fixed height and
22956  * prevents scrolling by setting the `overflow: hidden;` style. Since the position is based off of
22957  * the parent's height, the toast may be mispositioned if you allow the parent to scroll.
22958  *
22959  * You can, however, have a scrollable element inside of the container; just make sure the
22960  * container itself does not scroll.
22961  *
22962  * <hljs lang="html">
22963  * <div layout-fill id="toast-container">
22964  *   <md-content>
22965  *     I can have lots of content and scroll!
22966  *   </md-content>
22967  * </div>
22968  * </hljs>
22969  *
22970  * @usage
22971  * <hljs lang="html">
22972  * <div ng-controller="MyController">
22973  *   <md-button ng-click="openToast()">
22974  *     Open a Toast!
22975  *   </md-button>
22976  * </div>
22977  * </hljs>
22978  *
22979  * <hljs lang="js">
22980  * var app = angular.module('app', ['ngMaterial']);
22981  * app.controller('MyController', function($scope, $mdToast) {
22982  *   $scope.openToast = function($event) {
22983  *     $mdToast.show($mdToast.simple().textContent('Hello!'));
22984  *     // Could also do $mdToast.showSimple('Hello');
22985  *   };
22986  * });
22987  * </hljs>
22988  */
22989
22990/**
22991 * @ngdoc method
22992 * @name $mdToast#showSimple
22993 * 
22994 * @param {string} message The message to display inside the toast
22995 * @description
22996 * Convenience method which builds and shows a simple toast.
22997 *
22998 * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
22999 * rejected with `$mdToast.cancel()`.
23000 *
23001 */
23002
23003 /**
23004  * @ngdoc method
23005  * @name $mdToast#simple
23006  *
23007  * @description
23008  * Builds a preconfigured toast.
23009  *
23010  * @returns {obj} a `$mdToastPreset` with the following chainable configuration methods.
23011  *
23012  * _**Note:** These configuration methods are provided in addition to the methods provided by
23013  * the `build()` and `show()` methods below._
23014  *
23015  * <table class="md-api-table methods">
23016  *    <thead>
23017  *      <tr>
23018  *        <th>Method</th>
23019  *        <th>Description</th>
23020  *      </tr>
23021  *    </thead>
23022  *    <tbody>
23023  *      <tr>
23024  *        <td>`.textContent(string)`</td>
23025  *        <td>Sets the toast content to the specified string</td>
23026  *      </tr>
23027  *      <tr>
23028  *        <td>`.action(string)`</td>
23029  *        <td>
23030  *          Adds an action button. <br/>
23031  *          If clicked, the promise (returned from `show()`)
23032  *          will resolve with the value `'ok'`; otherwise, it is resolved with `true` after a `hideDelay`
23033  *          timeout
23034  *        </td>
23035  *      </tr>
23036  *      <tr>
23037  *        <td>`.highlightAction(boolean)`</td>
23038  *        <td>
23039  *          Whether or not the action button will have an additional highlight class.<br/>
23040  *          By default the `accent` color will be applied to the action button.
23041  *        </td>
23042  *      </tr>
23043  *      <tr>
23044  *        <td>`.highlightClass(string)`</td>
23045  *        <td>
23046  *          If set, the given class will be applied to the highlighted action button.<br/>
23047  *          This allows you to specify the highlight color easily. Highlight classes are `md-primary`, `md-warn`
23048  *          and `md-accent`
23049  *        </td>
23050  *      </tr>
23051  *      <tr>
23052  *        <td>`.capsule(boolean)`</td>
23053  *        <td>Whether or not to add the `md-capsule` class to the toast to provide rounded corners</td>
23054  *      </tr>
23055  *      <tr>
23056  *        <td>`.theme(string)`</td>
23057  *        <td>Sets the theme on the toast to the requested theme. Default is `$mdThemingProvider`'s default.</td>
23058  *      </tr>
23059  *      <tr>
23060  *        <td>`.toastClass(string)`</td>
23061  *        <td>Sets a class on the toast element</td>
23062  *      </tr>
23063  *    </tbody>
23064  * </table>
23065  *
23066  */
23067
23068/**
23069  * @ngdoc method
23070  * @name $mdToast#updateTextContent
23071  *
23072  * @description
23073  * Updates the content of an existing toast. Useful for updating things like counts, etc.
23074  *
23075  */
23076
23077 /**
23078  * @ngdoc method
23079  * @name $mdToast#build
23080  *
23081  * @description
23082  * Creates a custom `$mdToastPreset` that you can configure.
23083  *
23084  * @returns {obj} a `$mdToastPreset` with the chainable configuration methods for shows' options (see below).
23085  */
23086
23087 /**
23088  * @ngdoc method
23089  * @name $mdToast#show
23090  *
23091  * @description Shows the toast.
23092  *
23093  * @param {object} optionsOrPreset Either provide an `$mdToastPreset` returned from `simple()`
23094  * and `build()`, or an options object with the following properties:
23095  *
23096  *   - `templateUrl` - `{string=}`: The url of an html template file that will
23097  *     be used as the content of the toast. Restrictions: the template must
23098  *     have an outer `md-toast` element.
23099  *   - `template` - `{string=}`: Same as templateUrl, except this is an actual
23100  *     template string.
23101  *   - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template content with a
23102  *     `<div class="md-toast-content">` if one is not provided. Defaults to true. Can be disabled if you provide a
23103  *     custom toast directive.
23104  *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
23105  *     This scope will be destroyed when the toast is removed unless `preserveScope` is set to true.
23106  *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
23107  *   - `hideDelay` - `{number=}`: How many milliseconds the toast should stay
23108  *     active before automatically closing.  Set to 0 or false to have the toast stay open until
23109  *     closed manually. Default: 3000.
23110  *   - `position` - `{string=}`: Sets the position of the toast. <br/>
23111  *     Available: any combination of `'bottom'`, `'left'`, `'top'`, `'right'`, `'end'` and `'start'`.
23112  *     The properties `'end'` and `'start'` are dynamic and can be used for RT
23112L support.<br/>
23113  *     Default combination: `'bottom left'`.
23114  *   - `toastClass` - `{string=}`: A class to set on the toast element.
23115  *   - `controller` - `{string=}`: The controller to associate with this toast.
23116  *     The controller will be injected the local `$mdToast.hide( )`, which is a function
23117  *     used to hide the toast.
23118  *   - `locals` - `{string=}`: An object containing key/value pairs. The keys will
23119  *     be used as names of values to inject into the controller. For example,
23120  *     `locals: {three: 3}` would inject `three` into the controller with the value
23121  *     of 3.
23122  *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
23123  *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
23124  *     and the toast will not open until the promises resolve.
23125  *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
23126  *   - `parent` - `{element=}`: The element to append the toast to. Defaults to appending
23127  *     to the root element of the application.
23128  *
23129  * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
23130  * rejected with `$mdToast.cancel()`. `$mdToast.hide()` will resolve either with a Boolean
23131  * value == 'true' or the value passed as an argument to `$mdToast.hide()`.
23132  * And `$mdToast.cancel()` will resolve the promise with a Boolean value == 'false'
23133  */
23134
23135/**
23136  * @ngdoc method
23137  * @name $mdToast#hide
23138  *
23139  * @description
23140  * Hide an existing toast and resolve the promise returned from `$mdToast.show()`.
23141  *
23142  * @param {*=} response An argument for the resolved promise.
23143  *
23144  * @returns {promise} a promise that is called when the existing element is removed from the DOM.
23145  * The promise is resolved with either a Boolean value == 'true' or the value passed as the
23146  * argument to `.hide()`.
23147  *
23148  */
23149
23150/**
23151  * @ngdoc method
23152  * @name $mdToast#cancel
23153  *
23154  * @description
23155  * `DEPRECATED` - The promise returned from opening a toast is used only to notify about the closing of the toast.
23156  * As such, there isn't any reason to also allow that promise to be rejected,
23157  * since it's not clear what the difference between resolve and reject would be.
23158  *
23159  * Hide the existing toast and reject the promise returned from
23160  * `$mdToast.show()`.
23161  *
23162  * @param {*=} response An argument for the rejected promise.
23163  *
23164  * @returns {promise} a promise that is called when the existing element is removed from the DOM
23165  * The promise is resolved with a Boolean value == 'false'.
23166  *
23167  */
23168
23169function MdToastProvider($$interimElementProvider) {
23170  // Differentiate promise resolves: hide timeout (value == true) and hide action clicks (value == ok).
23171  MdToastController.$inject = ["$mdToast", "$scope"];
23172  toastDefaultOptions.$inject = ["$animate", "$mdToast", "$mdUtil", "$mdMedia"];
23173  var ACTION_RESOLVE = 'ok';
23174
23175  var activeToastContent;
23176  var $mdToast = $$interimElementProvider('$mdToast')
23177    .setDefaults({
23178      methods: ['position', 'hideDelay', 'capsule', 'parent', 'position', 'toastClass'],
23179      options: toastDefaultOptions
23180    })
23181    .addPreset('simple', {
23182      argOption: 'textContent',
23183      methods: ['textContent', 'content', 'action', 'highlightAction', 'highlightClass', 'theme', 'parent' ],
23184      options: /* @ngInject */ ["$mdToast", "$mdTheming", function($mdToast, $mdTheming) {
23185        return {
23186          template:
23187            '<md-toast md-theme="{{ toast.theme }}" ng-class="{\'md-capsule\': toast.capsule}">' +
23188            '  <div class="md-toast-content">' +
23189            '    <span class="md-toast-text" role="alert" aria-relevant="all" aria-atomic="true">' +
23190            '      {{ toast.content }}' +
23191            '    </span>' +
23192            '    <md-button class="md-action" ng-if="toast.action" ng-click="toast.resolve()" ' +
23193            '        ng-class="highlightClasses">' +
23194            '      {{ toast.action }}' +
23195            '    </md-button>' +
23196            '  </div>' +
23197            '</md-toast>',
23198          controller: MdToastController,
23199          theme: $mdTheming.defaultTheme(),
23200          controllerAs: 'toast',
23201          bindToController: true
23202        };
23203      }]
23204    })
23205    .addMethod('updateTextContent', updateTextContent)
23206    .addMethod('updateContent', updateTextContent);
23207
23208    function updateTextContent(newContent) {
23209      activeToastContent = newContent;
23210    }
23211
23212    return $mdToast;
23213
23214  /**
23215   * Controller for the Toast interim elements.
23216   * @ngInject
23217   */
23218  function MdToastController($mdToast, $scope) {
23219    // For compatibility with AngularJS 1.6+, we should always use the $onInit hook in
23220    // interimElements. The $mdCompiler simulates the $onInit hook for all versions.
23221    this.$onInit = function() {
23222      var self = this;
23223
23224      if (self.highlightAction) {
23225        $scope.highlightClasses = [
23226          'md-highlight',
23227          self.highlightClass
23228        ]
23229      }
23230
23231      $scope.$watch(function() { return activeToastContent; }, function() {
23232        self.content = activeToastContent;
23233      });
23234
23235      this.resolve = function() {
23236        $mdToast.hide( ACTION_RESOLVE );
23237      };
23238    }
23239  }
23240
23241  /* @ngInject */
23242  function toastDefaultOptions($animate, $mdToast, $mdUtil, $mdMedia) {
23243    var SWIPE_EVENTS = '$md.swipeleft $md.swiperight $md.swipeup $md.swipedown';
23244    return {
23245      onShow: onShow,
23246      onRemove: onRemove,
23247      toastClass: '',
23248      position: 'bottom left',
23249      themable: true,
23250      hideDelay: 3000,
23251      autoWrap: true,
23252      transformTemplate: function(template, options) {
23253        var shouldAddWrapper = options.autoWrap && template && !/md-toast-content/g.test(template);
23254
23255        if (shouldAddWrapper) {
23256          // Root element of template will be <md-toast>. We need to wrap all of its content inside of
23257          // of <div class="md-toast-content">. All templates provided here should be static, developer-controlled
23258          // content (meaning we're not attempting to guard against XSS).
23259          var templateRoot = document.createElement('md-template');
23260          templateRoot.innerHTML = template;
23261
23262          // Iterate through all root children, to detect possible md-toast directives.
23263          for (var i = 0; i < templateRoot.children.length; i++) {
23264            if (templateRoot.children[i].nodeName === 'MD-TOAST') {
23265              var wrapper = angular.element('<div class="md-toast-content">');
23266
23267              // Wrap the children of the `md-toast` directive in jqLite, to be able to append multiple
23268              // nodes with the same execution.
23269              wrapper.append(angular.element(templateRoot.children[i].childNodes));
23270
23271              // Append the new wrapped element to the `md-toast` directive.
23272              templateRoot.children[i].appendChild(wrapper[0]);
23273            }
23274          }
23275
23276          // We have to return the innerHTMl, because we do not want to have the `md-template` element to be
23277          // the root element of our interimElement.
23278          return templateRoot.innerHTML;
23279        }
23280
23281        return template || '';
23282      }
23283    };
23284
23285    function onShow(scope, element, options) {
23286      activeToastContent = options.textContent || options.content; // support deprecated #content method
23287
23288      var isSmScreen = !$mdMedia('gt-sm');
23289
23290      element = $mdUtil.extractElementByName(element, 'md-toast', true);
23291      options.element = element;
23292
23293      options.onSwipe = function(ev, gesture) {
23294        //Add the relevant swipe class to the element so it can animate correctly
23295        var swipe = ev.type.replace('$md.','');
23296        var direction = swipe.replace('swipe', '');
23297
23298        // If the swipe direction is down/up but the toast came from top/bottom don't fade away
23299        // Unless the screen is small, then the toast always on bottom
23300        if ((direction === 'down' && options.position.indexOf('top') != -1 && !isSmScreen) ||
23301            (direction === 'up' && (options.position.indexOf('bottom') != -1 || isSmScreen)
23301)) {
23302          return;
23303        }
23304
23305        if ((direction === 'left' || direction === 'right') && isSmScreen) {
23306          return;
23307        }
23308
23309        element.addClass('md-' + swipe);
23310        $mdUtil.nextTick($mdToast.cancel);
23311      };
23312      options.openClass = toastOpenClass(options.position);
23313
23314      element.addClass(options.toastClass);
23315
23316      // 'top left' -> 'md-top md-left'
23317      options.parent.addClass(options.openClass);
23318
23319      // static is the default position
23320      if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {
23321        options.parent.css('position', 'relative');
23322      }
23323
23324      element.on(SWIPE_EVENTS, options.onSwipe);
23325      element.addClass(isSmScreen ? 'md-bottom' : options.position.split(' ').map(function(pos) {
23326        return 'md-' + pos;
23327      }).join(' '));
23328
23329      if (options.parent) options.parent.addClass('md-toast-animating');
23330      return $animate.enter(element, options.parent).then(function() {
23331        if (options.parent) options.parent.removeClass('md-toast-animating');
23332      });
23333    }
23334
23335    function onRemove(scope, element, options) {
23336      element.off(SWIPE_EVENTS, options.onSwipe);
23337      if (options.parent) options.parent.addClass('md-toast-animating');
23338      if (options.openClass) options.parent.removeClass(options.openClass);
23339
23340      return ((options.$destroy == true) ? element.remove() : $animate.leave(element))
23341        .then(function () {
23342          if (options.parent) options.parent.removeClass('md-toast-animating');
23343          if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {
23344            options.parent.css('position', '');
23345          }
23346        });
23347    }
23348
23349    function toastOpenClass(position) {
23350      // For mobile, always open full-width on bottom
23351      if (!$mdMedia('gt-xs')) {
23352        return 'md-toast-open-bottom';
23353      }
23354
23355      return 'md-toast-open-' +
23356        (position.indexOf('top') > -1 ? 'top' : 'bottom');
23357    }
23358  }
23359
23360}
23361
23362})();
23363(function(){
23364"use strict";
23365
23366/**
23367 * @ngdoc module
23368 * @name material.components.toolbar
23369 */
23370mdToolbarDirective.$inject = ["$$rAF", "$mdConstant", "$mdUtil", "$mdTheming", "$animate"];
23371angular.module('material.components.toolbar', [
23372  'material.core',
23373  'material.components.content'
23374])
23375  .directive('mdToolbar', mdToolbarDirective);
23376
23377/**
23378 * @ngdoc directive
23379 * @name mdToolbar
23380 * @module material.components.toolbar
23381 * @restrict E
23382 * @description
23383 * `md-toolbar` is used to place a toolbar in your app.
23384 *
23385 * Toolbars are usually used above a content area to display the title of the
23386 * current page, and show relevant action buttons for that page.
23387 *
23388 * You can change the height of the toolbar by adding either the
23389 * `md-medium-tall` or `md-tall` class to the toolbar.
23390 *
23391 * @usage
23392 * <hljs lang="html">
23393 * <div layout="column" layout-fill>
23394 *   <md-toolbar>
23395 *
23396 *     <div class="md-toolbar-tools">
23397 *       <h2 md-truncate flex>My App's Title</h2>
23398 *
23399 *       <md-button>
23400 *         Right Bar Button
23401 *       </md-button>
23402 *     </div>
23403 *
23404 *   </md-toolbar>
23405 *   <md-content>
23406 *     Hello!
23407 *   </md-content>
23408 * </div>
23409 * </hljs>
23410 *
23411 * <i><b>Note:</b> The code above shows usage with the `md-truncate` component which provides an
23412 * ellipsis if the title is longer than the width of the Toolbar.</i>
23413 *
23414 * ## CSS & Styles
23415 *
23416 * The `<md-toolbar>` provides a few custom CSS classes that you may use to enhance the
23417 * functionality of your toolbar.
23418 *
23419 * <div>
23420 * <docs-css-api-table>
23421 *
23422 *   <docs-css-selector code="md-toolbar .md-toolbar-tools">
23423 *     The `md-toolbar-tools` class provides quite a bit of automatic styling for your toolbar
23424 *     buttons and text. When applied, it will center the buttons and text vertically for you.
23425 *   </docs-css-selector>
23426 *
23427 * </docs-css-api-table>
23428 * </div>
23429 *
23430 * ### Private Classes
23431 *
23432 * Currently, the only private class is the `md-toolbar-transitions` class. All other classes are
23433 * considered public.
23434 *
23435 * @param {boolean=} md-scroll-shrink Whether the header should shrink away as
23436 * the user scrolls down, and reveal itself as the user scrolls up.
23437 *
23438 * _**Note (1):** for scrollShrink to work, the toolbar must be a sibling of a
23439 * `md-content` element, placed before it. See the scroll shrink demo._
23440 *
23441 * _**Note (2):** The `md-scroll-shrink` attribute is only parsed on component
23442 * initialization, it does not watch for scope changes._
23443 *
23444 *
23445 * @param {number=} md-shrink-speed-factor How much to change the speed of the toolbar's
23446 * shrinking by. For example, if 0.25 is given then the toolbar will shrink
23447 * at one fourth the rate at which the user scrolls down. Default 0.5.
vendor: 6,872 bytes, lines 23448-23667
23448 *
23449 */
23450
23451function mdToolbarDirective($$rAF, $mdConstant, $mdUtil, $mdTheming, $animate) {
23452  var translateY = angular.bind(null, $mdUtil.supplant, 'translate3d(0,{0}px,0)');
23453
23454  return {
23455    template: '',
23456    restrict: 'E',
23457
23458    link: function(scope, element, attr) {
23459
23460      element.addClass('_md');     // private md component indicator for styling
23461      $mdTheming(element);
23462
23463      $mdUtil.nextTick(function () {
23464        element.addClass('_md-toolbar-transitions');     // adding toolbar transitions after digest
23465      }, false);
23466
23467      if (angular.isDefined(attr.mdScrollShrink)) {
23468        setupScrollShrink();
23469      }
23470
23471      function setupScrollShrink() {
23472
23473        var toolbarHeight;
23474        var contentElement;
23475        var disableScrollShrink = angular.noop;
23476
23477        // Current "y" position of scroll
23478        // Store the last scroll top position
23479        var y = 0;
23480        var prevScrollTop = 0;
23481        var shrinkSpeedFactor = attr.mdShrinkSpeedFactor || 0.5;
23482
23483        var debouncedContentScroll = $$rAF.throttle(onContentScroll);
23484        var debouncedUpdateHeight = $mdUtil.debounce(updateToolbarHeight, 5 * 1000);
23485
23486        // Wait for $mdContentLoaded event from mdContent directive.
23487        // If the mdContent element is a sibling of our toolbar, hook it up
23488        // to scroll events.
23489
23490        scope.$on('$mdContentLoaded', onMdContentLoad);
23491
23492        // If the toolbar is used inside an ng-if statement, we may miss the
23493        // $mdContentLoaded event, so we attempt to fake it if we have a
23494        // md-content close enough.
23495
23496        attr.$observe('mdScrollShrink', onChangeScrollShrink);
23497
23498        // If the toolbar has ngShow or ngHide we need to update height immediately as it changed
23499        // and not wait for $mdUtil.debounce to happen
23500
23501        if (attr.ngShow) { scope.$watch(attr.ngShow, updateToolbarHeight); }
23502        if (attr.ngHide) { scope.$watch(attr.ngHide, updateToolbarHeight); }
23503
23504        // If the scope is destroyed (which could happen with ng-if), make sure
23505        // to disable scroll shrinking again
23506
23507        scope.$on('$destroy', disableScrollShrink);
23508
23509        /**
23510         *
23511         */
23512        function onChangeScrollShrink(shrinkWithScroll) {
23513          var closestContent = element.parent().find('md-content');
23514
23515          // If we have a content element, fake the call; this might still fail
23516          // if the content element isn't a sibling of the toolbar
23517
23518          if (!contentElement && closestContent.length) {
23519            onMdContentLoad(null, closestContent);
23520          }
23521
23522          // Evaluate the expression
23523          shrinkWithScroll = scope.$eval(shrinkWithScroll);
23524
23525          // Disable only if the attribute's expression evaluates to false
23526          if (shrinkWithScroll === false) {
23527            disableScrollShrink();
23528          } else {
23529            disableScrollShrink = enableScrollShrink();
23530          }
23531        }
23532
23533        /**
23534         *
23535         */
23536        function onMdContentLoad($event, newContentEl) {
23537          // Toolbar and content must be siblings
23538          if (newContentEl && element.parent()[0] === newContentEl.parent()[0]) {
23539            // unhook old content event listener if exists
23540            if (contentElement) {
23541              contentElement.off('scroll', debouncedContentScroll);
23542            }
23543
23544            contentElement = newContentEl;
23545            disableScrollShrink = enableScrollShrink();
23546          }
23547        }
23548
23549        /**
23550         *
23551         */
23552        function onContentScroll(e) {
23553          var scrollTop = e ? e.target.scrollTop : prevScrollTop;
23554
23555          debouncedUpdateHeight();
23556
23557          y = Math.min(
23558            toolbarHeight / shrinkSpeedFactor,
23559            Math.max(0, y + scrollTop - prevScrollTop)
23560          );
23561
23562          element.css($mdConstant.CSS.TRANSFORM, translateY([-y * shrinkSpeedFactor]));
23563          contentElement.css($mdConstant.CSS.TRANSFORM, translateY([(toolbarHeight - y) * shrinkSpeedFactor]));
23564
23565          prevScrollTop = scrollTop;
23566
23567          $mdUtil.nextTick(function() {
23568            var hasWhiteFrame = element.hasClass('md-whiteframe-z1');
23569
23570            if (hasWhiteFrame && !y) {
23571              $animate.removeClass(element, 'md-whiteframe-z1');
23572            } else if (!hasWhiteFrame && y) {
23573              $animate.addClass(element, 'md-whiteframe-z1');
23574            }
23575          });
23576
23577        }
23578
23579        /**
23580         *
23581         */
23582        function enableScrollShrink() {
23583          if (!contentElement)     return angular.noop;           // no md-content
23584
23585          contentElement.on('scroll', debouncedContentScroll);
23586          contentElement.attr('scroll-shrink', 'true');
23587
23588          $mdUtil.nextTick(updateToolbarHeight, false);
23589
23590          return function disableScrollShrink() {
23591            contentElement.off('scroll', debouncedContentScroll);
23592            contentElement.attr('scroll-shrink', 'false');
23593
23594            updateToolbarHeight();
23595          };
23596        }
23597
23598        /**
23599         *
23600         */
23601        function updateToolbarHeight() {
23602          toolbarHeight = element.prop('offsetHeight');
23603          // Add a negative margin-top the size of the toolbar to the content el.
23604          // The content will start transformed down the toolbarHeight amount,
23605          // so everything looks normal.
23606          //
23607          // As the user scrolls down, the content will be transformed up slowly
23608          // to put the content underneath where the toolbar was.
23609          var margin = (-toolbarHeight * shrinkSpeedFactor) + 'px';
23610
23611          contentElement.css({
23612            "margin-top": margin,
23613            "margin-bottom": margin
23614          });
23615
23616          onContentScroll();
23617        }
23618
23619      }
23620
23621    }
23622  };
23623
23624}
23625
23626})();
23627(function(){
23628"use strict";
23629
23630/**
23631 * @ngdoc module
23632 * @name material.components.tooltip
23633 */
23634MdTooltipDirective.$inject = ["$timeout", "$window", "$$rAF", "$document", "$interpolate", "$mdUtil", "$mdPanel", "$$mdTooltipRegistry"];
23635angular
23636    .module('material.components.tooltip', [
23637      'material.core',
23638      'material.components.panel'
23639    ])
23640    .directive('mdTooltip', MdTooltipDirective)
23641    .service('$$mdTooltipRegistry', MdTooltipRegistry);
23642
23643
23644/**
23645 * @ngdoc directive
23646 * @name mdTooltip
23647 * @module material.components.tooltip
23648 * @description
23649 * Tooltips are used to describe elements that are interactive and primarily
23650 * graphical (not textual).
23651 *
23652 * Place a `<md-tooltip>` as a child of the element it describes.
23653 *
23654 * A tooltip will activate when the user hovers over, focuses, or touches the
23655 * parent element.
23656 *
23657 * @usage
23658 * <hljs lang="html">
23659 *   <md-button class="md-fab md-accent" aria-label="Play">
23660 *     <md-tooltip>Play Music</md-tooltip>
23661 *     <md-icon md-svg-src="img/icons/ic_play_arrow_24px.svg"></md-icon>
23662 *   </md-button>
23663 * </hljs>
23664 *
23665 * @param {number=} md-z-index The visual level that the tooltip will appear
23666 *     in comparison with the rest of the elements of the application.
23667 * @param {expression=}
23667 md-visible Boolean bound to whether the tooltip is
23668 *     currently visible.
23669 * @param {number=} md-delay How many milliseconds to wait to show the tooltip
23670 *     after the user hovers over, focuses, or touches the parent element.
23671 *     Defaults to 0ms on non-touch devices and 75ms on touch.
23672 * @param {boolean=} md-autohide If present or provided with a boolean value,
23673 *     the tooltip will hide on mouse leave, regardless of focus.
23674 * @param {string=} md-direction The direction that the tooltip is shown,
23675 *     relative to the parent element. Supports top, right, bottom, and left.
23676 *     Defaults to bottom.
23677 */
23678function MdTooltipDirective($timeout, $window, $$rAF, $document, $interpolate,
23679    $mdUtil, $mdPanel, $$mdTooltipRegistry) {
23680
23681  var ENTER_EVENTS = 'focus touchstart mouseenter';
23682  var LEAVE_EVENTS = 'blur touchcancel mouseleave';
23683  var TOOLTIP_DEFAULT_Z_INDEX = 100;
23684  var TOOLTIP_DEFAULT_SHOW_DELAY = 0;
23685  var TOOLTIP_DEFAULT_DIRECTION = 'bottom';
23686  var TOOLTIP_DIRECTIONS = {
23687    top: { x: $mdPanel.xPosition.CENTER, y: $mdPanel.yPosition.ABOVE },
23688    right: { x: $mdPanel.xPosition.OFFSET_END, y: $mdPanel.yPosition.CENTER },
23689    bottom: { x: $mdPanel.xPosition.CENTER, y: $mdPanel.yPosition.BELOW },
23690    left: { x: $mdPanel.xPosition.OFFSET_START, y: $mdPanel.yPosition.CENTER }
23691  };
23692
23693  return {
23694    restrict: 'E',
23695    priority: 210, // Before ngAria
23696    scope: {
23697      mdZIndex: '=?mdZIndex',
23698      mdDelay: '=?mdDelay',
23699      mdVisible: '=?mdVisible',
23700      mdAutohide: '=?mdAutohide',
23701      mdDirection: '@?mdDirection' // Do not expect expressions.
23702    },
23703    link: linkFunc
23704  };
23705
23706  function linkFunc(scope, element, attr) {
23707    // Set constants.
23708    var tooltipId = 'md-tooltip-' + $mdUtil.nextUid();
23709    var parent = $mdUtil.getParentWithPointerEvents(element);
23710    var debouncedOnResize = $$rAF.throttle(updatePosition);
23711    var mouseActive = false;
23712    var origin, position, panelPosition, panelRef, autohide, showTimeout,
23713        elementFocusedOnWindowBlur = null;
23714
23715    // Set defaults
23716    setDefaults();
23717
23718    // Set parent aria-label.
23719    addAriaLabel();
23720
23721    // Remove the element from its current DOM position.
23722    element.detach();
23723
23724    updatePosition();
23725    bindEvents();
23726    configureWatchers();
23727
23728    function setDefaults() {
23729      scope.mdZIndex = scope.mdZIndex || TOOLTIP_DEFAULT_Z_INDEX;
23730      scope.mdDelay = scope.mdDelay || TOOLTIP_DEFAULT_SHOW_DELAY;
23731      if (!TOOLTIP_DIRECTIONS[scope.mdDirection]) {
23732        scope.mdDirection = TOOLTIP_DEFAULT_DIRECTION;
23733      }
23734    }
23735
23736    function addAriaLabel(labelText) {
23737      // Only interpolate the text from the HTML element because otherwise the custom text could
23738      // be interpolated twice and cause XSS violations.
23739      var interpolatedText = labelText || $interpolate(element.text().trim())(scope.$parent);
23740
23741      // Only add the `aria-label` to the parent if there isn't already one, if there isn't an
23742      // already present `aria-labelledby`, or if the previous `aria-label` was added by the
23743      // tooltip directive.
23744      if (
23745        (!parent.attr('aria-label') && !parent.attr('aria-labelledby')) ||
23746        parent.attr('md-labeled-by-tooltip')
23747      ) {
23748        parent.attr('aria-label', interpolatedText);
23749
23750        // Set the `md-labeled-by-tooltip` attribute if it has not already been set.
23751        if (!parent.attr('md-labeled-by-tooltip')) {
23752          parent.attr('md-labeled-by-tooltip', tooltipId);
23753        }
23754      }
23755    }
23756
23757    function updatePosition() {
23758      setDefaults();
23759
23760      // If the panel has already been created, remove the current origin
23761      // class from the panel element.
23762      if (panelRef && panelRef.panelEl) {
23763        panelRef.panelEl.removeClass(origin);
23764      }
23765
23766      // Set the panel element origin class based off of the current
23767      // mdDirection.
23768      origin = 'md-origin-' + scope.mdDirection;
23769
23770      // Create the position of the panel based off of the mdDirection.
23771      position = TOOLTIP_DIRECTIONS[scope.mdDirection];
23772
23773      // Using the newly created position object, use the MdPanel
23774      // panelPosition API to build the panel's position.
23775      panelPosition = $mdPanel.newPanelPosition()
23776          .relativeTo(parent)
23777          .addPanelPosition(position.x, position.y);
23778
23779      // If the panel has already been created, add the new origin class to
23780      // the panel element and update it's position with the panelPosition.
23781      if (panelRef && panelRef.panelEl) {
23782        panelRef.panelEl.addClass(origin);
23783        panelRef.updatePosition(panelPosition);
23784      }
23785    }
23786
23787    function bindEvents() {
23788      // Add a mutationObserver where there is support for it and the need
23789      // for it in the form of viable host(parent[0]).
23790      if (parent[0] && 'MutationObserver' in $window) {
23791        // Use a mutationObserver to tackle #2602.
23792        var attributeObserver = new MutationObserver(function(mutations) {
23793          if (isDisabledMutation(mutations)) {
23794            $mdUtil.nextTick(function() {
23795              setVisible(false);
23796            });
23797          }
23798        });
23799
23800        attributeObserver.observe(parent[0], {
23801          attributes: true
23802        });
23803      }
23804
23805      elementFocusedOnWindowBlur = false;
vendor: 28,185 bytes, lines 23806-24657
23806
23807      $$mdTooltipRegistry.register('scroll', windowScrollEventHandler, true);
23808      $$mdTooltipRegistry.register('blur', windowBlurEventHandler);
23809      $$mdTooltipRegistry.register('resize', debouncedOnResize);
23810
23811      scope.$on('$destroy', onDestroy);
23812
23813      // To avoid 'synthetic clicks', we listen to mousedown instead of
23814      // 'click'.
23815      parent.on('mousedown', mousedownEventHandler);
23816      parent.on(ENTER_EVENTS, enterEventHandler);
23817
23818      function isDisabledMutation(mutations) {
23819        mutations.some(function(mutation) {
23820          return mutation.attributeName === 'disabled' && parent[0].disabled;
23821        });
23822        return false;
23823      }
23824
23825      function windowScrollEventHandler() {
23826        setVisible(false);
23827      }
23828
23829      function windowBlurEventHandler() {
23830        elementFocusedOnWindowBlur = document.activeElement === parent[0];
23831      }
23832
23833      function enterEventHandler($event) {
23834        // Prevent the tooltip from showing when the window is receiving
23835        // focus.
23836        if ($event.type === 'focus' && elementFocusedOnWindowBlur) {
23837          elementFocusedOnWindowBlur = false;
23838        } else if (!scope.mdVisible) {
23839          parent.on(LEAVE_EVENTS, leaveEventHandler);
23840          setVisible(true);
23841
23842          // If the user is on a touch device, we should bind the tap away
23843          // after the 'touched' in order to prevent the tooltip being
23844          // removed immediately.
23845          if ($event.type === 'touchstart') {
23846            parent.one('touchend', function() {
23847              $mdUtil.nextTick(function() {
23848                $document.one('touchend', leaveEventHandler);
23849              }, false);
23850            });
23851          }
23852        }
23853      }
23854
23855      function leaveEventHandler() {
23856        autohide = scope.hasOwnProperty('mdAutohide') ?
23857            scope.mdAutohide :
23858            attr.hasOwnProperty('mdAutohide');
23859
23860        if (autohide || mouseActive ||
23861            $document[0].activeElement !== parent[0]) {
23862          // When a show timeout is currently in progress, then we have
23863          // to cancel it, otherwise the tooltip will remain showing
23864          // without focus or hover.
23865          if (showTimeout) {
23866            $timeout.cancel(showTimeout);
23867            setVisible.queued = false;
23868            showTimeout = null;
23869          }
23870
23871          parent.off(LEAVE_EVENTS, leaveEventHandler);
23872          parent.triggerHandler('blur');
23873          setVisible(false);
23874        }
23875        mouseActive = false;
23876      }
23877
23878      function mousedownEventHandler() {
23879        mouseActive = true;
23880      }
23881
23882      function onDestroy() {
23883        $$mdTooltipRegistry.deregister('scroll', windowScrollEventHandler, true);
23884        $$mdTooltipRegistry.deregister('blur', windowBlurEventHandler);
23885        $$mdTooltipRegistry.deregister('resize', debouncedOnResize);
23886
23887        parent
23888            .off(ENTER_EVENTS, enterEventHandler)
23889            .off(LEAVE_EVENTS, leaveEventHandler)
23890            .off('mousedown', mousedownEventHandler);
23891
23892        // Trigger the handler in case any of the tooltips are
23893        // still visible.
23894        leaveEventHandler();
23895        attributeObserver && attributeObserver.disconnect();
23896      }
23897    }
23898
23899    function configureWatchers() {
23900      if (element[0] && 'MutationObserver' in $window) {
23901        var attributeObserver = new MutationObserver(function(mutations) {
23902          mutations.forEach(function(mutation) {
23903            if (mutation.attributeName === 'md-visible' &&
23904                !scope.visibleWatcher ) {
23905              scope.visibleWatcher = scope.$watch('mdVisible',
23906                  onVisibleChanged);
23907            }
23908          });
23909        });
23910
23911        attributeObserver.observe(element[0], {
23912          attributes: true
23913        });
23914
23915        // Build watcher only if mdVisible is being used.
23916        if (attr.hasOwnProperty('mdVisible')) {
23917          scope.visibleWatcher = scope.$watch('mdVisible',
23918              onVisibleChanged);
23919        }
23920      } else {
23921        // MutationObserver not supported
23922        scope.visibleWatcher = scope.$watch('mdVisible', onVisibleChanged);
23923      }
23924
23925      // Direction watcher
23926      scope.$watch('mdDirection', updatePosition);
23927
23928      // Clean up if the element or parent was removed via jqLite's .remove.
23929      // A couple of notes:
23930      //   - In these cases the scope might not have been destroyed, which
23931      //     is why we destroy it manually. An example of this can be having
23932      //     `md-visible="false"` and adding tooltips while they're
23933      //     invisible. If `md-visible` becomes true, at some point, you'd
23934      //     usually get a lot of tooltips.
23935      //   - We use `.one`, not `.on`, because this only needs to fire once.
23936      //     If we were using `.on`, it would get thrown into an infinite
23937      //     loop.
23938      //   - This kicks off the scope's `$destroy` event which finishes the
23939      //     cleanup.
23940      element.one('$destroy', onElementDestroy);
23941      parent.one('$destroy', onElementDestroy);
23942      scope.$on('$destroy', function() {
23943        setVisible(false);
23944        panelRef && panelRef.destroy();
23945        attributeObserver && attributeObserver.disconnect();
23946        element.remove();
23947      });
23948
23949      // Updates the aria-label when the element text changes. This watch
23950      // doesn't need to be set up if the element doesn't have any data
23951      // bindings.
23952      if (element.text().indexOf($interpolate.startSymbol()) > -1) {
23953        scope.$watch(function() {
23954          return element.text().trim();
23955        }, addAriaLabel);
23956      }
23957
23958      function onElementDestroy() {
23959        scope.$destroy();
23960      }
23961    }
23962
23963    function setVisible(value) {
23964      // Break if passed value is already in queue or there is no queue and
23965      // passed value is current in the controller.
23966      if (setVisible.queued && setVisible.value === !!value ||
23967          !setVisible.queued && scope.mdVisible === !!value) {
23968        return;
23969      }
23970      setVisible.value = !!value;
23971
23972      if (!setVisible.queued) {
23973        if (value) {
23974          setVisible.queued = true;
23975          showTimeout = $timeout(function() {
23976            scope.mdVisible = setVisible.value;
23977            setVisible.queued = false;
23978            showTimeout = null;
23979            if (!scope.visibleWatcher) {
23980              onVisibleChanged(scope.mdVisible);
23981            }
23982          }, scope.mdDelay);
23983        } else {
23984          $mdUtil.nextTick(function() {
23985            scope.mdVisible = false;
23986            if (!scope.visibleWatcher) {
23987              onVisibleChanged(false);
23988            }
23989          });
23990        }
23991      }
23992    }
23993
23994    function onVisibleChanged(isVisible) {
23995      isVisible ? showTooltip() : hideTooltip();
23996    }
23997
23998    function showTooltip() {
23999      // Do not show the tooltip if the text is empty.
24000      if (!element[0].textContent.trim()) {
24001        throw new Error('Text for the tooltip has not been provided. ' +
24002            'Please include text within the mdTooltip element.');
24003      }
24004
24005      if (!panelRef) {
24006        var attachTo = angular.element(document.body);
24007        var panelAnimation = $mdPanel.newPanelAnimation()
24008            .openFrom(parent)
24009            .closeTo(parent)
24010            .withAnimation({
24011              open: 'md-show',
24012              close: 'md-hide'
24013            });
24014
24015        var panelConfig = {
24016          id: tooltipId,
24017          attachTo: attachTo,
24018          contentElement: element,
24019          propagateContainerEvents: true,
24020          panelClass: 'md-tooltip ' + origin,
24021          animation: panelAnimation,
24022          position: panelPosition,
24023          zIndex: scope.mdZIndex,
24024          focusOnOpen: false
24025        };
24026
24027        panelRef = $mdPanel.create(panelConfig);
24028      }
24029
24030      panelRef.open().then(function() {
24031        panelRef.panelEl.attr('role', 'tooltip');
24032      });
24033    }
24034
24035    function hideTooltip() {
24036      panelRef && panelRef.close();
24037    }
24038  }
24039
24040}
24041
24042
24043/**
24044 * Service that is used to reduce the amount of listeners that are being
24045 * registered on the `window` by the tooltip component. Works by collecting
24046 * the individual event handlers and dispatching them from a global handler.
24047 *
24048 * @ngInject
24049 */
24050function MdTooltipRegistry() {
24051  var listeners = {};
24052  var ngWindow = angular.element(window);
24053
24054  return {
24055    register: register,
24056    deregister: deregister
24057  };
24058
24059  /**
24060   * Global event handler that dispatches the registered handlers in the
24061   * service.
24062   * @param {!Event} event Event object passed in by the browser
24063   */
24064  function globalEventHandler(event) {
24065    if (listeners[event.type]) {
24066      listeners[event.type].forEach(function(currentHandler) {
24067        currentHandler.call(this, event);
24068      }, this);
24069    }
24070  }
24071
24072  /**
24073   * Registers a new handler with the service.
24074   * @param {string} type Type of event to be registered.
24075   * @param {!Function} handler Event handler.
24076   * @param {boolean} useCapture Whether to use event capturing.
24077   */
24078  function register(type, handler, useCapture) {
24079    var handlers = listeners[type] = listeners[type] || [];
24080
24081    if (!handlers.length) {
24082      useCapture ? window.addEventListener(type, globalEventHandler, true) :
24083          ngWindow.on(type, globalEventHandler);
24084    }
24085
24086    if (handlers.indexOf(handler) === -1) {
24087      handlers.push(handler);
24088    }
24089  }
24090
24091  /**
24092   * Removes an event handler from the service.
24093   * @param {string} type Type of event handler.
24094   * @param {!Function} handler The event handler itself.
24095   * @param {boolean} useCapture Whether the event handler used event capturing.
24096   */
24097  function deregister(type, handler, useCapture) {
24098    var handlers = listeners[type];
24099    var index = handlers ? handlers.indexOf(handler) : -1;
24100
24101    if (index > -1) {
24102      handlers.splice(index, 1);
24103
24104      if (handlers.length === 0) {
24105        useCapture ? window.removeEventListener(type, globalEventHandler, true) :
24106            ngWindow.off(type, globalEventHandler);
24107      }
24108    }
24109  }
24110}
24111
24112})();
24113(function(){
24114"use strict";
24115
24116/**
24117 * @ngdoc module
24118 * @name material.components.truncate
24119 */
24120MdTruncateController.$inject = ["$element"];
24121angular.module('material.components.truncate', ['material.core'])
24122  .directive('mdTruncate', MdTruncateDirective);
24123
24124/**
24125 * @ngdoc directive
24126 * @name mdTruncate
24127 * @module material.components.truncate
24128 * @restrict AE
24129 * @description
24130 *
24131 * The `md-truncate` component displays a label that will automatically clip text which is wider
24132 * than the component. By default, it displays an ellipsis, but you may apply the `md-clip` CSS
24133 * class to override this default and use a standard "clipping" approach.
24134 *
24135 * <i><b>Note:</b> The `md-truncate` component does not automatically adjust it's width. You must
24136 * provide the `flex` attribute, or some other CSS-based width management. See the
24137 * <a ng-href="./demo/truncate">demos</a> for examples.</i>
24138 *
24139 * @usage
24140 *
24141 * ### As an Element
24142 *
24143 * <hljs lang="html">
24144 *   <div layout="row">
24145 *     <md-button>Back</md-button>
24146 *
24147 *     <md-truncate flex>Chapter 1 - The Way of the Old West</md-truncate>
24148 *
24149 *     <md-button>Forward</md-button>
24150 *   </div>
24151 * </hljs>
24152 *
24153 * ### As an Attribute
24154 *
24155 * <hljs lang="html">
24156 *   <h2 md-truncate style="max-width: 100px;">Some Title With a Lot of Text</h2>
24157 * </hljs>
24158 *
24159 * ## CSS & Styles
24160 *
24161 * `<md-truncate>` provides two CSS classes that you may use to control the type of clipping.
24162 *
24163 * <i><b>Note:</b> The `md-truncate` also applies a setting of `width: 0;` when used with the `flex`
24164 * attribute to fix an issue with the flex element not shrinking properly.</i>
24165 *
24166 * <div>
24167 * <docs-css-api-table>
24168 *
24169 *   <docs-css-selector code=".md-ellipsis">
24170 *     Assigns the "ellipsis" behavior (default) which will cut off mid-word and append an ellipsis
24171 *     (&hellip;) to the end of the text.
24172 *   </docs-css-selector>
24173 *
24174 *   <docs-css-selector code=".md-clip">
24175 *     Assigns the "clipping" behavior which will simply chop off the text. This may happen
24176 *     mid-word or even mid-character.
24177 *   </docs-css-selector>
24178 *
24179 * </docs-css-api-table>
24180 * </div>
24181 */
24182function MdTruncateDirective() {
24183  return {
24184    restrict: 'AE',
24185
24186    controller: MdTruncateController,
24187    controllerAs: '$ctrl',
24188    bindToController: true
24189  }
24190}
24191
24192/**
24193 * Controller for the <md-truncate> component.
24194 *
24195 * @param $element The md-truncate element.
24196 *
24197 * @constructor
24198 * @ngInject
24199 */
24200function MdTruncateController($element) {
24201  $element.addClass('md-truncate');
24202}
24203
24204})();
24205(function(){
24206"use strict";
24207
24208/**
24209 * @ngdoc module
24210 * @name material.components.virtualRepeat
24211 */
24212VirtualRepeatContainerController.$inject = ["$$rAF", "$mdUtil", "$mdConstant", "$parse", "$rootScope", "$window", "$scope", "$element", "$attrs"];
24213VirtualRepeatController.$inject = ["$scope", "$element", "$attrs", "$browser", "$document", "$rootScope", "$$rAF", "$mdUtil"];
24214VirtualRepeatDirective.$inject = ["$parse"];
24215angular.module('material.components.virtualRepeat', [
24216  'material.core',
24217  'material.components.showHide'
24218])
24219.directive('mdVirtualRepeatContainer', VirtualRepeatContainerDirective)
24220.directive('mdVirtualRepeat', VirtualRepeatDirective)
24221.directive('mdForceHeight', ForceHeightDirective);
24222
24223
24224/**
24225 * @ngdoc directive
24226 * @name mdVirtualRepeatContainer
24227 * @module material.components.virtualRepeat
24228 * @restrict E
24229 * @description
24230 * `md-virtual-repeat-container` provides the scroll container for md-virtual-repeat.
24231 *
24232 * VirtualRepeat is a limited substitute for ng-repeat that renders only
24233 * enough DOM nodes to fill the container and recycling them as the user scrolls.
24234 *
24235 * Once an element is not visible anymore, the VirtualRepeat recycles it and will reuse it for
24236 * another visible item by replacing the previous dataset with the new one.
24237 *
24238 * ### Common Issues
24239 *
24240 * - When having one-time bindings inside of the view template, the VirtualRepeat will not properly
24241 *   update the bindings for new items, since the view will be recycled.
24242 *
24243 * - Directives inside of a VirtualRepeat will be only compiled (linked) once, because those
24244 *   are will be recycled items and used for other items.
24245 *   The VirtualRepeat just updates the scope bindings.
24246 *
24247 *
24248 * ### Notes
24249 *
24250 * > The VirtualRepeat is a similar implementation to the Android
24251 * [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)
24252 *
24253 * <!-- This comment forces a break between blockquotes //-->
24254 *
24255 * > Please also review the <a ng-href="api/directive/mdVirtualRepeat">VirtualRepeat</a>
24256 * documentation for more information.
24257 *
24258 *
24259 * @usage
24260 * <hljs lang="html">
24261 *
24262 * <md-virtual-repeat-container md-top-index="topIndex">
24263 *   <div md-virtual-repeat="i in items" md-item-size="20">Hello {{i}}!</div>
24264 * </md-virtual-repeat-container>
24265 * </hljs>
24266 *
24267 * @param {number=} md-top-index Binds the index of the item that is at the top of the scroll
24268 *     container to $scope. It can both read and set the scroll position.
24269 * @param {boolean=} md-orient-horizontal Whether the container should scroll horizontally
24270 *     (defaults to orientation and scrolling vertically).
24271 * @param {boolean=} md-auto-shrink When present, the container will shrink to fit
24272 *     the number of items when that number is less than its original size.
24273 * @param {number=} md-auto-shrink-min Minimum number of items that md-auto-shrink
24274 *     will shrink to (default: 0).
24275 */
24276function VirtualRepeatContainerDirective() {
24277  return {
24278    controller: VirtualRepeatContainerController,
24279    template: virtualRepeatContainerTemplate,
24280    compile: function virtualRepeatContainerCompile($element, $attrs) {
24281      $element
24282          .addClass('md-virtual-repeat-container')
24283          .addClass($attrs.hasOwnProperty('mdOrientHorizontal')
24284              ? 'md-orient-horizontal'
24285              : 'md-orient-vertical');
24286    }
24287  };
24288}
24289
24290
24291function virtualRepeatContainerTemplate($element) {
24292  return '<div class="md-virtual-repeat-scroller" role="presentation">' +
24293    '<div class="md-virtual-repeat-sizer" role="presentation"></div>' +
24294    '<div class="md-virtual-repeat-offsetter" role="presentation">' +
24295      $element[0].innerHTML +
24296    '</div></div>';
24297}
24298
24299/**
24300 * Number of additional elements to render above and below the visible area inside
24301 * of the virtual repeat container. A higher number results in less flicker when scrolling
24302 * very quickly in Safari, but comes with a higher rendering and dirty-checking cost.
24303 * @const {number}
24304 */
24305var NUM_EXTRA = 3;
24306
24307/** @ngInject */
24308function VirtualRepeatContainerController($$rAF, $mdUtil, $mdConstant, $parse, $rootScope, $window, $scope,
24309                                          $element, $attrs) {
24310  this.$rootScope = $rootScope;
24311  this.$scope = $scope;
24312  this.$element = $element;
24313  this.$attrs = $attrs;
24314
24315  /** @type {number} The width or height of the container */
24316  this.size = 0;
24317  /** @type {number} The scroll width or height of the scroller */
24318  this.scrollSize = 0;
24319  /** @type {number} The scrollLeft or scrollTop of the scroller */
24320  this.scrollOffset = 0;
24321  /** @type {boolean} Whether the scroller is oriented horizontally */
24322  this.horizontal = this.$attrs.hasOwnProperty('mdOrientHorizontal');
24323  /** @type {!VirtualRepeatController} The repeater inside of this container */
24324  this.repeater = null;
24325  /** @type {boolean} Whether auto-shrink is enabled */
24326  this.autoShrink = this.$attrs.hasOwnProperty('mdAutoShrink');
24327  /** @type {number} Minimum number of items to auto-shrink to */
24328  this.autoShrinkMin = parseInt(this.$attrs.mdAutoShrinkMin, 10) || 0;
24329  /** @type {?number} Original container size when shrank */
24330  this.originalSize = null;
24331  /** @type {number} Amount to offset the total scroll size by. */
24332  this.offsetSize = parseInt(this.$attrs.mdOffsetSize, 10) || 0;
24333  /** @type {?string} height or width element style on the container prior to auto-shrinking. */
24334  this.oldElementSize = null;
24335  /** @type {!number} Maximum amount of pixels allowed for a single DOM element */
24336  this.maxElementPixels = $mdConstant.ELEMENT_MAX_PIXELS;
24337
24338  if (this.$attrs.mdTopIndex) {
24339    /** @type {function(angular.Scope): number} Binds to topIndex on AngularJS scope */
24340    this.bindTopIndex = $parse(this.$attrs.mdTopIndex);
24341    /** @type {number} The index of the item that is at the top of the scroll container */
24342    this.topIndex = this.bindTopIndex(this.$scope);
24343
24344    if (!angular.isDefined(this.topIndex)) {
24345      this.topIndex = 0;
24346      this.bindTopIndex.assign(this.$scope, 0);
24347    }
24348
24349    this.$scope.$watch(this.bindTopIndex, angular.bind(this, function(newIndex) {
24350      if (newIndex !== this.topIndex) {
24351        this.scrollToIndex(newIndex);
24352      }
24353    }));
24354  } else {
24355    this.topIndex = 0;
24356  }
24357
24358  this.scroller = $element[0].querySelector('.md-virtual-repeat-scroller');
24359  this.sizer = this.scroller.querySelector('.md-virtual-repeat-sizer');
24360  this.offsetter = this.scroller.querySelector('.md-virtual-repeat-offsetter');
24361
24362  // After the dom stablizes, measure the initial size of the container and
24363  // make a best effort at re-measuring as it changes.
24364  var boundUpdateSize = angular.bind(this, this.updateSize);
24365
24366  $$rAF(angular.bind(this, function() {
24367    boundUpdateSize();
24368
24369    var debouncedUpdateSize = $mdUtil.debounce(boundUpdateSize, 10, null, false);
24370    var jWindow = angular.element($window);
24371
24372    // Make one more attempt to get the size if it is 0.
24373    // This is not by any means a perfect approach, but there's really no
24374    // silver bullet here.
24375    if (!this.size) {
24376      debouncedUpdateSize();
24377    }
24378
24379    jWindow.on('resize', debouncedUpdateSize);
24380    $scope.$on('$destroy', function() {
24381      jWindow.off('resize', debouncedUpdateSize);
24382    });
24383
24384    $scope.$emit('$md-resize-enable');
24385    $scope.$on('$md-resize', boundUpdateSize);
24386  }));
24387}
24388
24389
24390/** Called by the md-virtual-repeat inside of the container at startup. */
24391VirtualRepeatContainerController.prototype.register = function(repeaterCtrl) {
24392  this.repeater = repeaterCtrl;
24393
24394  angular.element(this.scroller)
24395      .on('scroll wheel touchmove touchend', angular.bind(this, this.handleScroll_));
24396};
24397
24398
24399/** @return {boolean} Whether the container is configured for horizontal scrolling. */
24400VirtualRepeatContainerController.prototype.isHorizontal = function() {
24401  return this.horizontal;
24402};
24403
24404
24405/** @return {number} The size (width or height) of the container. */
24406VirtualRepeatContainerController.prototype.getSize = function() {
24407  return this.size;
24408};
24409
24410
24411/**
24412 * Resizes the container.
24413 * @private
24414 * @param {number} size The new size to set.
24415 */
24416VirtualRepeatContainerController.prototype.setSize_ = function(size) {
24417  var dimension = this.getDimensionName_();
24418
24419  this.size = size;
24420  this.$element[0].style[dimension] = size + 'px';
24421};
24422
24423
24424VirtualRepeatContainerController.prototype.unsetSize_ = function() {
24425  this.$element[0].style[this.getDimensionName_()] = this.oldElementSize;
24426  this.oldElementSize = null;
24427};
24428
24429
24430/** Instructs the container to re-measure its size. */
24431VirtualRepeatContainerController.prototype.updateSize = function() {
24432  // If the original size is already determined, we can skip the update.
24433  if (this.originalSize) return;
24434
24435  this.size = this.isHorizontal()
24436      ? this.$element[0].clientWidth
24437      : this.$element[0].clientHeight;
24438
24439  // Recheck the scroll position after updating the size. This resolves
24440  // problems that can result if the scroll position was measured while the
24441  // element was display: none or detached from the document.
24442  this.handleScroll_();
24443
24444  this.repeater && this.repeater.containerUpdated();
24445};
24446
24447
24448/** @return {number} The container's scrollHeight or scrollWidth. */
24449VirtualRepeatContainerController.prototype.getScrollSize = function() {
24450  return this.scrollSize;
24451};
24452
24453
24454VirtualRepeatContainerController.prototype.getDimensionName_ = function() {
24455  return this.isHorizontal() ? 'width' : 'height';
24456};
24457
24458
24459/**
24460 * Sets the scroller element to the specified size.
24461 * @private
24462 * @param {number} size The new size.
24463 */
24464VirtualRepeatContainerController.prototype.sizeScroller_ = function(size) {
24465  var dimension =  this.getDimensionName_();
24466  var crossDimension = this.isHorizontal() ? 'height' : 'width';
24467
24468  // Clear any existing dimensions.
24469  this.sizer.innerHTML = '';
24470
24471  // If the size falls within the browser's maximum explicit size for a single element, we can
24472  // set the size and be done. Otherwise, we have to create children that add up the the desired
24473  // size.
24474  if (size < this.maxElementPixels) {
24475    this.sizer.style[dimension] = size + 'px';
24476  } else {
24477    this.sizer.style[dimension] = 'auto';
24478    this.sizer.style[crossDimension] = 'auto';
24479
24480    // Divide the total size we have to render into N max-size pieces.
24481    var numChildren = Math.floor(size / this.maxElementPixels);
24482
24483    // Element template to clone for each max-size piece.
24484    var sizerChild = document.createElement('div');
24485    sizerChild.style[dimension] = this.maxElementPixels + 'px';
24486    sizerChild.style[crossDimension] = '1px';
24487
24488    for (var i = 0; i < numChildren; i++) {
24489      this.sizer.appendChild(sizerChild.cloneNode(false));
24490    }
24491
24492    // Re-use the element template for the remainder.
24493    sizerChild.style[dimension] = (size - (numChildren * this.maxElementPixels)) + 'px';
24494    this.sizer.appendChild(sizerChild);
24495  }
24496};
24497
24498
24499/**
24500 * If auto-shrinking is enabled, shrinks or unshrinks as appropriate.
24501 * @private
24502 * @param {number} size The new size.
24503 */
24504VirtualRepeatContainerController.prototype.autoShrink_ = function(size) {
24505  var shrinkSize = Math.max(size, this.autoShrinkMin * this.repeater.getItemSize());
24506
24507  if (this.autoShrink && shrinkSize !== this.size) {
24508    if (this.oldElementSize === null) {
24509      this.oldElementSize = this.$element[0].style[this.getDimensionName_()];
24510    }
24511
24512    var currentSize = this.originalSize || this.size;
24513
24514    if (!currentSize || shrinkSize < currentSize) {
24515      if (!this.originalSize) {
24516        this.originalSize = this.size;
24517      }
24518
24519      // Now we update the containers size, because shrinking is enabled.
24520      this.setSize_(shrinkSize);
24521    } else if (this.originalSize !== null) {
24522      // Set the size back to our initial size.
24523      this.unsetSize_();
24524
24525      var _originalSize = this.originalSize;
24526      this.originalSize = null;
24527
24528      // We determine the repeaters size again, if the original size was zero.
24529      // The originalSize needs to be null, to be able to determine the size.
24530      if (!_originalSize) this.updateSize();
24531
24532      // Apply the original size or the determined size back to the container, because
24533      // it has been overwritten before, in the shrink block.
24534      this.setSize_(_originalSize || this.size);
24535    }
24536
24537    this.repeater.containerUpdated();
24538  }
24539};
24540
24541
24542/**
24543 * Sets the scrollHeight or scrollWidth. Called by the repeater based on
24544 * its item count and item size.
24545 * @param {number} itemsSize The total size of the items.
24546 */
24547VirtualRepeatContainerController.prototype.setScrollSize = function(itemsSize) {
24548  var size = itemsSize + this.offsetSize;
24549  if (this.scrollSize === size) return;
24550
24551  this.sizeScroller_(size);
24552  this.autoShrink_(size);
24553  this.scrollSize = size;
24554};
24555
24556
24557/** @return {number} The container's current scroll offset. */
24558VirtualRepeatContainerController.prototype.getScrollOffset = function() {
24559  return this.scrollOffset;
24560};
24561
24562/**
24563 * Scrolls to a given scrollTop position.
24564 * @param {number} position
24565 */
24566VirtualRepeatContainerController.prototype.scrollTo = function(position) {
24567  this.scroller[this.isHorizontal() ? 'scrollLeft' : 'scrollTop'] = position;
24568  this.handleScroll_();
24569};
24570
24571/**
24572 * Scrolls the item with the given index to the top of the scroll container.
24573 * @param {number} index
24574 */
24575VirtualRepeatContainerController.prototype.scrollToIndex = function(index) {
24576  var itemSize = this.repeater.getItemSize();
24577  var itemsLength = this.repeater.itemsLength;
24578  if(index > itemsLength) {
24579    index = itemsLength - 1;
24580  }
24581  this.scrollTo(itemSize * index);
24582};
24583
24584VirtualRepeatContainerController.prototype.resetScroll = function() {
24585  this.scrollTo(0);
24586};
24587
24588
24589VirtualRepeatContainerController.prototype.handleScroll_ = function() {
24590  var ltr = document.dir != 'rtl' && document.body.dir != 'rtl';
24591  if(!ltr && !this.maxSize) {
24592    this.scroller.scrollLeft = this.scrollSize;
24593    this.maxSize = this.scroller.scrollLeft;
24594  }
24595  var offset = this.isHorizontal() ?
24596      (ltr?this.scroller.scrollLeft : this.maxSize - this.scroller.scrollLeft)
24597      : this.scroller.scrollTop;
24598  if (offset === this.scrollOffset || offset > this.scrollSize - this.size) return;
24599
24600  var itemSize = this.repeater.getItemSize();
24601  if (!itemSize) return;
24602
24603  var numItems = Math.max(0, Math.floor(offset / itemSize) - NUM_EXTRA);
24604
24605  var transform = (this.isHorizontal() ? 'translateX(' : 'translateY(') +
24606      (!this.isHorizontal() || ltr ? (numItems * itemSize) : - (numItems * itemSize))  + 'px)';
24607
24608  this.scrollOffset = offset;
24609  this.offsetter.style.webkitTransform = transform;
24610  this.offsetter.style.transform = transform;
24611
24612  if (this.bindTopIndex) {
24613    var topIndex = Math.floor(offset / itemSize);
24614    if (topIndex !== this.topIndex && topIndex < this.repeater.getItemCount()) {
24615      this.topIndex = topIndex;
24616      this.bindTopIndex.assign(this.$scope, topIndex);
24617      if (!this.$rootScope.$$phase) this.$scope.$digest();
24618    }
24619  }
24620
24621  this.repeater.containerUpdated();
24622};
24623
24624
24625/**
24626 * @ngdoc directive
24627 * @name mdVirtualRepeat
24628 * @module material.components.virtualRepeat
24629 * @restrict A
24630 * @priority 1000
24631 * @description
24632 * `md-virtual-repeat` specifies an element to repeat using virtual scrolling.
24633 *
24634 * Virtual repeat is a limited substitute for ng-repeat that renders only
24635 * enough DOM nodes to fill the container and recycling them as the user scrolls.
24636 *
24637 * Arrays, but not objects are supported for iteration.
24638 * Track by, as alias, and (key, value) syntax are not supported.
24639 *
24640 * ### On-Demand Async Item Loading
24641 *
24642 * When using the `md-on-demand` attribute and loading some asynchronous data, the `getItemAtIndex` function will
24643 * mostly return nothing.
24644 *
24645 * <hljs lang="js">
24646 *   DynamicItems.prototype.getItemAtIndex = function(index) {
24647 *     if (this.pages[index]) {
24648 *       return this.pages[index];
24649 *     } else {
24650 *       // This is an asynchronous action and does not return any value.
24651 *       this.loadPage(index);
24652 *     }
24653 *   };
24654 * </hljs>
24655 *
24656 * This means that the VirtualRepeat will not have any value for the given index.<br/>
24657 * After the data loading completed, the user expects the VirtualRepeat to re
24657cognize the change.
24658 *
24659 * To make sure that the VirtualRepeat properly detects any change, you need to run the operation
24660 * in another digest.
24661 *
24662 * <hljs lang="js">
24663 *   DynamicItems.prototype.loadPage = function(index) {
24664 *     var self = this;
24665 *
24666 *     // Trigger a new digest by using $timeout
24667 *     $timeout(function() {
24668 *       self.pages[index] = Data;
24669 *     });
24670 *   };
24671 * </hljs>
24672 *
24673 * > <b>Note:</b> Please also review the
24674 *   <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeatContainer</a> documentation
24675 *   for more information.
24676 *
24677 * @usage
24678 * <hljs lang="html">
24679 * <md-virtual-repeat-container>
24680 *   <div md-virtual-repeat="i in items">Hello {{i}}!</div>
24681 * </md-virtual-repeat-container>
24682 *
24683 * <md-virtual-repeat-container md-orient-horizontal>
24684 *   <div md-virtual-repeat="i in items" md-item-size="20">Hello {{i}}!</div>
24685 * </md-virtual-repeat-container>
24686 * </hljs>
24687 *
24688 * @param {number=} md-item-size The height or width of the repeated elements (which must be
24689 *   identical for each element). Optional. Will attempt to read the size from the dom if missing,
24690 *   but still assumes that all repeated nodes have same height or width.
24691 * @param {string=} md-extra-name Evaluates to an additional name to which the current iterated item
24692 *   can be assigned on the repeated scope (needed for use in `md-autocomplete`).
24693 * @param {boolean=} md-on-demand When present, treats the md-virtual-repeat argument as an object
24694 *   that can fetch rows rather than an array.
24695 *
24696 *   **NOTE:** This object must implement the following interface with two (2) methods:
24697 *
24698 *   - `getItemAtIndex: function(index) [object]` The item at that index or null if it is not yet
24699 *     loaded (it should start downloading the item in that case).
24700 *   - `getLength: function() [number]` The data length to which the repeater container
24701 *     should be sized. Ideally, when the count is known, this method should return it.
24702 *     Otherwise, return a higher number than the currently loaded items to produce an
24703 *     infinite-scroll behavior.
24704 */
24705function VirtualRepeatDirective($parse) {
24706  return {
24707    controller: VirtualRepeatController,
24708    priority: 1000,
24709    require: ['mdVirtualRepeat', '^^mdVirtualRepeatContainer'],
24710    restrict: 'A',
24711    terminal: true,
24712    transclude: 'element',
24713    compile: function VirtualRepeatCompile($element, $attrs) {
24714      var expression = $attrs.mdVirtualRepeat;
24715      var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/);
24716      var repeatName = match[1];
24717      var repeatListExpression = $parse(match[2]);
24718      var extraName = $attrs.mdExtraName && $parse($attrs.mdExtraName);
24719
24720      return function VirtualRepeatLink($scope, $element, $attrs, ctrl, $transclude) {
24721        ctrl[0].link_(ctrl[1], $transclude, repeatName, repeatListExpression, extraName);
24722      };
24723    }
24724  };
24725}
24726
24727
24728/** @ngInject */
24729function VirtualRepeatController($scope, $element, $attrs, $browser, $document, $rootScope,
24730    $$rAF, $mdUtil) {
24731  this.$scope = $scope;
24732  this.$element = $element;
24733  this.$attrs = $attrs;
24734  this.$browser = $browser;
24735  this.$document = $document;
24736  this.$mdUtil = $mdUtil;
24737  this.$rootScope = $rootScope;
24738  this.$$rAF = $$rAF;
24739
24740  /** @type {boolean} Whether we are in on-demand mode. */
24741  this.onDemand = $mdUtil.parseAttributeBoolean($attrs.mdOnDemand);
24742  /** @type {!Function} Backup reference to $browser.$$checkUrlChange */
24743  this.browserCheckUrlChange = $browser.$$checkUrlChange;
24744  /** @type {number} Most recent starting repeat index (based on scroll offset) */
24745  this.newStartIndex = 0;
24746  /** @type {number} Most recent ending repeat index (based on scroll offset) */
24747  this.newEndIndex = 0;
24748  /** @type {number} Most recent end visible index (based on scroll offset) */
24749  this.newVisibleEnd = 0;
24750  /** @type {number} Previous starting repeat index (based on scroll offset) */
24751  this.startIndex = 0;
24752  /** @type {number} Previous ending repeat index (based on scroll offset) */
24753  this.endIndex = 0;
24754  // TODO: measure width/height of first element from dom if not provided.
24755  // getComputedStyle?
24756  /** @type {?number} Height/width of repeated elements. */
24757  this.itemSize = $scope.$eval($attrs.mdItemSize) || null;
24758
24759  /** @type {boolean} Whether this is the first time that items are rendered. */
24760  this.isFirstRender = true;
24761
24762  /**
24763   * @private {boolean} Whether the items in the list are already being updated. Used to prevent
24764   *     nested calls to virtualRepeatUpdate_.
24765   */
24766  this.isVirtualRepeatUpdating_ = false;
vendor: 4,741 bytes, lines 24767-24915
24767
24768  /** @type {number} Most recently seen length of items. */
24769  this.itemsLength = 0;
24770
24771  /**
24772   * @type {!Function} Unwatch callback for item size (when md-items-size is
24773   *     not specified), or angular.noop otherwise.
24774   */
24775  this.unwatchItemSize_ = angular.noop;
24776
24777  /**
24778   * Presently rendered blocks by repeat index.
24779   * @type {Object<number, !VirtualRepeatController.Block}
24780   */
24781  this.blocks = {};
24782  /** @type {Array<!VirtualRepeatController.Block>} A pool of presently unused blocks. */
24783  this.pooledBlocks = [];
24784
24785  $scope.$on('$destroy', angular.bind(this, this.cleanupBlocks_));
24786}
24787
24788
24789/**
24790 * An object representing a repeated item.
24791 * @typedef {{element: !jqLite, new: boolean, scope: !angular.Scope}}
24792 */
24793VirtualRepeatController.Block;
24794
24795
24796/**
24797 * Called at startup by the md-virtual-repeat postLink function.
24798 * @param {!VirtualRepeatContainerController} container The container's controller.
24799 * @param {!Function} transclude The repeated element's bound transclude function.
24800 * @param {string} repeatName The left hand side of the repeat expression, indicating
24801 *     the name for each item in the array.
24802 * @param {!Function} repeatListExpression A compiled expression based on the right hand side
24803 *     of the repeat expression. Points to the array to repeat over.
24804 * @param {string|undefined} extraName The optional extra repeatName.
24805 */
24806VirtualRepeatController.prototype.link_ =
24807    function(container, transclude, repeatName, repeatListExpression, extraName) {
24808  this.container = container;
24809  this.transclude = transclude;
24810  this.repeatName = repeatName;
24811  this.rawRepeatListExpression = repeatListExpression;
24812  this.extraName = extraName;
24813  this.sized = false;
24814
24815  this.repeatListExpression = angular.bind(this, this.repeatListExpression_);
24816
24817  this.container.register(this);
24818};
24819
24820
24821/** @private Cleans up unused blocks. */
24822VirtualRepeatController.prototype.cleanupBlocks_ = function() {
24823  angular.forEach(this.pooledBlocks, function cleanupBlock(block) {
24824    block.element.remove();
24825  });
24826};
24827
24828
24829/** @private Attempts to set itemSize by measuring a repeated element in the dom */
24830VirtualRepeatController.prototype.readItemSize_ = function() {
24831  if (this.itemSize) {
24832    // itemSize was successfully read in a different asynchronous call.
24833    return;
24834  }
24835
24836  this.items = this.repeatListExpression(this.$scope);
24837  this.parentNode = this.$element[0].parentNode;
24838  var block = this.getBlock_(0);
24839  if (!block.element[0].parentNode) {
24840    this.parentNode.appendChild(block.element[0]);
24841  }
24842
24843  this.itemSize = block.element[0][
24844      this.container.isHorizontal() ? 'offsetWidth' : 'offsetHeight'] || null;
24845
24846  this.blocks[0] = block;
24847  this.poolBlock_(0);
24848
24849  if (this.itemSize) {
24850    this.containerUpdated();
24851  }
24852};
24853
24854
24855/**
24856 * Returns the user-specified repeat list, transforming it into an array-like
24857 * object in the case of infinite scroll/dynamic load mode.
24858 * @param {!angular.Scope} The scope.
24859 * @return {!Array|!Object} An array or array-like object for iteration.
24860 */
24861VirtualRepeatController.prototype.repeatListExpression_ = function(scope) {
24862  var repeatList = this.rawRepeatListExpression(scope);
24863
24864  if (this.onDemand && repeatList) {
24865    var virtualList = new VirtualRepeatModelArrayLike(repeatList);
24866    virtualList.$$includeIndexes(this.newStartIndex, this.newVisibleEnd);
24867    return virtualList;
24868  } else {
24869    return repeatList;
24870  }
24871};
24872
24873
24874/**
24875 * Called by the container. Informs us that the containers scroll or size has
24876 * changed.
24877 */
24878VirtualRepeatController.prototype.containerUpdated = function() {
24879  // If itemSize is unknown, attempt to measure it.
24880  if (!this.itemSize) {
24881    // Make sure to clean up watchers if we can (see #8178)
24882    if(this.unwatchItemSize_ && this.unwatchItemSize_ !== angular.noop){
24883      this.unwatchItemSize_();
24884    }
24885    this.unwatchItemSize_ = this.$scope.$watchCollection(
24886        this.repeatListExpression,
24887        angular.bind(this, function(items) {
24888          if (items && items.length) {
24889            this.readItemSize_();
24890          }
24891        }));
24892    if (!this.$rootScope.$$phase) this.$scope.$digest();
24893
24894    return;
24895  } else if (!this.sized) {
24896    this.items = this.repeatListExpression(this.$scope);
24897  }
24898
24899  if (!this.sized) {
24900    this.unwatchItemSize_();
24901    this.sized = true;
24902    this.$scope.$watchCollection(this.repeatListExpression,
24903        angular.bind(this, function(items, oldItems) {
24904          if (!this.isVirtualRepeatUpdating_) {
24905            this.virtualRepeatUpdate_(items, oldItems);
24906          }
24907        }));
24908  }
24909
24910  this.updateIndexes_();
24911
24912  if (this.newStartIndex !== this.startIndex ||
24913      this.newEndIndex !== this.endIndex ||
24914      this.container.getScrollOffset() > this.container.getScrollSize()) {
24915    if (this.items instanceof VirtualRepeatModelArrayLike) {
24916      this.items.$$includeIndexes(this.newStartIndex, this.newEndIndex);
24917    }
24918    this.virtualRepeatUpdate_(this.items, this.items);
24919  }
24920};
24921
24922
24923/**
24924 * Called by the container. Returns the size of a single repeated item.
24925 * @return {?number} Size of a repeated item.
24926 */
24927VirtualRepeatController.prototype.getItemSize = function() {
24928  return this.itemSize;
24929};
24930
24931
24932/**
24933 * Called by the container. Returns the size of a single repeated item.
24934 * @return {?number} Size of a repeated item.
24935 */
24936VirtualRepeatController.prototype.getItemCount = function() {
24937  return this.itemsLength;
24938};
24939
24940
24941/**
24942 * Updates the order and visible offset of repeated blocks in response to scrolling
24943 * or items updates.
vendor: 4,559 bytes, lines 24943-25096
24943
24944 * @private
24945 */
24946VirtualRepeatController.prototype.virtualRepeatUpdate_ = function(items, oldItems) {
24947  this.isVirtualRepeatUpdating_ = true;
24948
24949  var itemsLength = items && items.length || 0;
24950  var lengthChanged = false;
24951
24952  // If the number of items shrank, keep the scroll position.
24953  if (this.items && itemsLength < this.items.length && this.container.getScrollOffset() !== 0) {
24954    this.items = items;
24955    var previousScrollOffset = this.container.getScrollOffset();
24956    this.container.resetScroll();
24957    this.container.scrollTo(previousScrollOffset);
24958  }
24959
24960  if (itemsLength !== this.itemsLength) {
24961    lengthChanged = true;
24962    this.itemsLength = itemsLength;
24963  }
24964
24965  this.items = items;
24966  if (items !== oldItems || lengthChanged) {
24967    this.updateIndexes_();
24968  }
24969
24970  this.parentNode = this.$element[0].parentNode;
24971
24972  if (lengthChanged) {
24973    this.container.setScrollSize(itemsLength * this.itemSize);
24974  }
24975
24976  // Detach and pool any blocks that are no longer in the viewport.
24977  Object.keys(this.blocks).forEach(function(blockIndex) {
24978    var index = parseInt(blockIndex, 10);
24979    if (index < this.newStartIndex || index >= this.newEndIndex) {
24980      this.poolBlock_(index);
24981    }
24982  }, this);
24983
24984  // Add needed blocks.
24985  // For performance reasons, temporarily block browser url checks as we digest
24986  // the restored block scopes ($$checkUrlChange reads window.location to
24987  // check for changes and trigger route change, etc, which we don't need when
24988  // trying to scroll at 60fps).
24989  this.$browser.$$checkUrlChange = angular.noop;
24990
24991  var i, block,
24992      newStartBlocks = [],
24993      newEndBlocks = [];
24994
24995  // Collect blocks at the top.
24996  for (i = this.newStartIndex; i < this.newEndIndex && this.blocks[i] == null; i++) {
24997    block = this.getBlock_(i);
24998    this.updateBlock_(block, i);
24999    newStartBlocks.push(block);
25000  }
25001
25002  // Update blocks that are already rendered.
25003  for (; this.blocks[i] != null; i++) {
25004    this.updateBlock_(this.blocks[i], i);
25005  }
25006  var maxIndex = i - 1;
25007
25008  // Collect blocks at the end.
25009  for (; i < this.newEndIndex; i++) {
25010    block = this.getBlock_(i);
25011    this.updateBlock_(block, i);
25012    newEndBlocks.push(block);
25013  }
25014
25015  // Attach collected blocks to the document.
25016  if (newStartBlocks.length) {
25017    this.parentNode.insertBefore(
25018        this.domFragmentFromBlocks_(newStartBlocks),
25019        this.$element[0].nextSibling);
25020  }
25021  if (newEndBlocks.length) {
25022    this.parentNode.insertBefore(
25023        this.domFragmentFromBlocks_(newEndBlocks),
25024        this.blocks[maxIndex] && this.blocks[maxIndex].element[0].nextSibling);
25025  }
25026
25027  // Restore $$checkUrlChange.
25028  this.$browser.$$checkUrlChange = this.browserCheckUrlChange;
25029
25030  this.startIndex = this.newStartIndex;
25031  this.endIndex = this.newEndIndex;
25032
25033  if (this.isFirstRender) {
25034    this.isFirstRender = false;
25035    var firstRenderStartIndex = this.$attrs.mdStartIndex ?
25036      this.$scope.$eval(this.$attrs.mdStartIndex) :
25037      this.container.topIndex;
25038
25039    // The first call to virtualRepeatUpdate_ may not be when the virtual repeater is ready.
25040    // Introduce a slight delay so that the update happens when it is actually ready.
25041    this.$mdUtil.nextTick(function() {
25042      this.container.scrollToIndex(firstRenderStartIndex);
25043    }.bind(this));
25044  }
25045
25046  this.isVirtualRepeatUpdating_ = false;
25047};
25048
25049
25050/**
25051 * @param {number} index Where the block is to be in the repeated list.
25052 * @return {!VirtualRepeatController.Block} A new or pooled block to place at the specified index.
25053 * @private
25054 */
25055VirtualRepeatController.prototype.getBlock_ = function(index) {
25056  if (this.pooledBlocks.length) {
25057    return this.pooledBlocks.pop();
25058  }
25059
25060  var block;
25061  this.transclude(angular.bind(this, function(clone, scope) {
25062    block = {
25063      element: clone,
25064      new: true,
25065      scope: scope
25066    };
25067
25068    this.updateScope_(scope, index);
25069    this.parentNode.appendChild(clone[0]);
25070  }));
25071
25072  return block;
25073};
25074
25075
25076/**
25077 * Updates and if not in a digest cycle, digests the specified block's scope to the data
25078 * at the specified index.
25079 * @param {!VirtualRepeatController.Block} block The block whose scope should be updated.
25080 * @param {number} index The index to set.
25081 * @private
25082 */
25083VirtualRepeatController.prototype.updateBlock_ = function(block, index) {
25084  this.blocks[index] = block;
25085
25086  if (!block.new &&
25087      (block.scope.$index === index && block.scope[this.repeatName] === this.items[index])) {
25088    return;
25089  }
25090  block.new = false;
25091
25092  // Update and digest the block's scope.
25093  this.updateScope_(block.scope, index);
25094
25095  // Perform digest before reattaching the block.
25096  // Any resulting synchronous 
vendor: 7,709 bytes, lines 25096-25348
25096dom mutations should be much faster as a result.
25097  // This might break some directives, but I'm going to try it for now.
25098  if (!this.$rootScope.$$phase) {
25099    block.scope.$digest();
25100  }
25101};
25102
25103
25104/**
25105 * Updates scope to the data at the specified index.
25106 * @param {!angular.Scope} scope The scope which should be updated.
25107 * @param {number} index The index to set.
25108 * @private
25109 */
25110VirtualRepeatController.prototype.updateScope_ = function(scope, index) {
25111  scope.$index = index;
25112  scope[this.repeatName] = this.items && this.items[index];
25113  if (this.extraName) scope[this.extraName(this.$scope)] = this.items[index];
25114};
25115
25116
25117/**
25118 * Pools the block at the specified index (Pulls its element out of the dom and stores it).
25119 * @param {number} index The index at which the block to pool is stored.
25120 * @private
25121 */
25122VirtualRepeatController.prototype.poolBlock_ = function(index) {
25123  this.pooledBlocks.push(this.blocks[index]);
25124  this.parentNode.removeChild(this.blocks[index].element[0]);
25125  delete this.blocks[index];
25126};
25127
25128
25129/**
25130 * Produces a dom fragment containing the elements from the list of blocks.
25131 * @param {!Array<!VirtualRepeatController.Block>} blocks The blocks whose elements
25132 *     should be added to the document fragment.
25133 * @return {DocumentFragment}
25134 * @private
25135 */
25136VirtualRepeatController.prototype.domFragmentFromBlocks_ = function(blocks) {
25137  var fragment = this.$document[0].createDocumentFragment();
25138  blocks.forEach(function(block) {
25139    fragment.appendChild(block.element[0]);
25140  });
25141  return fragment;
25142};
25143
25144
25145/**
25146 * Updates start and end indexes based on length of repeated items and container size.
25147 * @private
25148 */
25149VirtualRepeatController.prototype.updateIndexes_ = function() {
25150  var itemsLength = this.items ? this.items.length : 0;
25151  var containerLength = Math.ceil(this.container.getSize() / this.itemSize);
25152
25153  this.newStartIndex = Math.max(0, Math.min(
25154      itemsLength - containerLength,
25155      Math.floor(this.container.getScrollOffset() / this.itemSize)));
25156  this.newVisibleEnd = this.newStartIndex + containerLength + NUM_EXTRA;
25157  this.newEndIndex = Math.min(itemsLength, this.newVisibleEnd);
25158  this.newStartIndex = Math.max(0, this.newStartIndex - NUM_EXTRA);
25159};
25160
25161/**
25162 * This VirtualRepeatModelArrayLike class enforces the interface requirements
25163 * for infinite scrolling within a mdVirtualRepeatContainer. An object with this
25164 * interface must implement the following interface with two (2) methods:
25165 *
25166 * getItemAtIndex: function(index) -> item at that index or null if it is not yet
25167 *     loaded (It should start downloading the item in that case).
25168 *
25169 * getLength: function() -> number The data legnth to which the repeater container
25170 *     should be sized. Ideally, when the count is known, this method should return it.
25171 *     Otherwise, return a higher number than the currently loaded items to produce an
25172 *     infinite-scroll behavior.
25173 *
25174 * @usage
25175 * <hljs lang="html">
25176 *  <md-virtual-repeat-container md-orient-horizontal>
25177 *    <div md-virtual-repeat="i in items" md-on-demand>
25178 *      Hello {{i}}!
25179 *    </div>
25180 *  </md-virtual-repeat-container>
25181 * </hljs>
25182 *
25183 */
25184function VirtualRepeatModelArrayLike(model) {
25185  if (!angular.isFunction(model.getItemAtIndex) ||
25186      !angular.isFunction(model.getLength)) {
25187    throw Error('When md-on-demand is enabled, the Object passed to md-virtual-repeat must implement ' +
25188        'functions getItemAtIndex() and getLength() ');
25189  }
25190
25191  this.model = model;
25192}
25193
25194
25195VirtualRepeatModelArrayLike.prototype.$$includeIndexes = function(start, end) {
25196  for (var i = start; i < end; i++) {
25197    if (!this.hasOwnProperty(i)) {
25198      this[i] = this.model.getItemAtIndex(i);
25199    }
25200  }
25201  this.length = this.model.getLength();
25202};
25203
25204/**
25205 * @ngdoc directive
25206 * @name mdForceHeight
25207 * @module material.components.virtualRepeat
25208 * @restrict A
25209 * @description
25210 *
25211 * Force an element to have a certain px height. This is used in place of a style tag in order to
25212 * conform to the Content Security Policy regarding unsafe-inline style tags.
25213 *
25214 * @usage
25215 * <hljs lang="html">
25216 *   <div md-force-height="'100px'"></div>
25217 * </hljs>
25218 */
25219function ForceHeightDirective($mdUtil) {
25220  return {
25221    restrict: 'A',
25222    link: function(scope, element, attrs) {
25223      var height = scope.$eval(attrs.mdForceHeight) || null;
25224
25225      if (height && element) {
25226        element[0].style.height = height;
25227      }
25228    }
25229  }
25230}
25231ForceHeightDirective.$inject = ['$mdUtil'];
25232
25233})();
25234(function(){
25235"use strict";
25236
25237/**
25238 * @ngdoc module
25239 * @name material.components.whiteframe
25240 */
25241MdWhiteframeDirective.$inject = ["$log"];
25242angular
25243  .module('material.components.whiteframe', ['material.core'])
25244  .directive('mdWhiteframe', MdWhiteframeDirective);
25245
25246/**
25247 * @ngdoc directive
25248 * @module material.components.whiteframe
25249 * @name mdWhiteframe
25250 *
25251 * @description
25252 * The md-whiteframe directive allows you to apply an elevation shadow to an element.
25253 *
25254 * The attribute values needs to be a number between 1 and 24 or -1.
25255 * When set to -1 no style is applied.
25256 *
25257 * ### Notes
25258 * - If there is no value specified it defaults to 4dp.
25259 * - If the value is not valid it defaults to 4dp.
25260
25261 * @usage
25262 * <hljs lang="html">
25263 * <div md-whiteframe="3">
25264 *   <span>Elevation of 3dp</span>
25265 * </div>
25266 * </hljs>
25267 *
25268 * <hljs lang="html">
25269 * <div md-whiteframe="-1">
25270 *   <span>No elevation shadow applied</span>
25271 * </div>
25272 * </hljs>
25273 *
25274 * <hljs lang="html">
25275 * <div ng-init="elevation = 5" md-whiteframe="{{elevation}}">
25276 *   <span>Elevation of 5dp with an interpolated value</span>
25277 * </div>
25278 * </hljs>
25279 */
25280function MdWhiteframeDirective($log) {
25281  var DISABLE_DP = -1;
25282  var MIN_DP = 1;
25283  var MAX_DP = 24;
25284  var DEFAULT_DP = 4;
25285
25286  return {
25287    link: postLink
25288  };
25289
25290  function postLink(scope, element, attr) {
25291    var oldClass = '';
25292
25293    attr.$observe('mdWhiteframe', function(elevation) {
25294      elevation = parseInt(elevation, 10) || DEFAULT_DP;
25295
25296      if (elevation != DISABLE_DP && (elevation > MAX_DP || elevation < MIN_DP)) {
25297        $log.warn('md-whiteframe attribute value is invalid. It should be a number between ' + MIN_DP + ' and ' + MAX_DP, element[0]);
25298        elevation = DEFAULT_DP;
25299      }
25300
25301      var newClass = elevation == DISABLE_DP ? '' : 'md-whiteframe-' + elevation + 'dp';
25302      attr.$updateClass(newClass, oldClass);
25303      oldClass = newClass;
25304    });
25305  }
25306}
25307
25308
25309})();
25310(function(){
25311"use strict";
25312
25313
25314MdAutocompleteCtrl.$inject = ["$scope", "$element", "$mdUtil", "$mdConstant", "$mdTheming", "$window", "$animate", "$rootElement", "$attrs", "$q", "$log", "$mdLiveAnnouncer"];angular
25315    .module('material.components.autocomplete')
25316    .controller('MdAutocompleteCtrl', MdAutocompleteCtrl);
25317
25318var ITEM_HEIGHT   = 48,
25319    MAX_ITEMS     = 5,
25320    MENU_PADDING  = 8,
25321    INPUT_PADDING = 2; // Padding provided by `md-input-container`
25322
25323function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, $window,
25324                             $animate, $rootElement, $attrs, $q, $log, $mdLiveAnnouncer) {
25325
25326  // Internal Variables.
25327  var ctrl                 = this,
25328      itemParts            = $scope.itemsExpr.split(/ in /i),
25329      itemExpr             = itemParts[ 1 ],
25330      elements             = null,
25331      cache                = {},
25332      noBlur               = false,
25333      selectedItemWatchers = [],
25334      hasFocus             = false,
25335      fetchesInProgress    = 0,
25336      enableWrapScroll     = null,
25337      inputModelCtrl       = null,
25338      debouncedOnResize    = $mdUtil.debounce(onWindowResize);
25339
25340  // Public Exported Variables with handlers
25341  defineProperty('hidden', handleHiddenChange, true);
25342
25343  // Public Exported Variables
25344  ctrl.scope      = $scope;
25345  ctrl.parent     = $scope.$parent;
25346  ctrl.itemName   = itemParts[ 0 ];
25347  ctrl.matches    = [];
25348  ctrl.loading    = false;
25349  ctrl.hidden     = true;
25350  ctrl.index      = null;
25351  ctrl.id         = $mdUtil.nextUid();
25352  ctrl.isDisabled = null;
25353  ctrl.isRequired = null;
25354  ctrl.isReadonly = null;
25355  ctrl.hasNotFound = false;
25356
25357  // Public Exported Methods
25358  ctrl.keydown                       = keydown;
25359  ctrl.blur                          = blur;
25360  ctrl.focus                         = focus;
25361  ctrl.clear                         = clearValue;
25362  ctrl.select                        = select;
25363  ctrl.listEnter                     = onListEnter;
25364  ctrl.listLeave                     = onListLeave;
25365  ctrl.mouseUp                       = onMouseup;
25366  ctrl.getCurrentDisplayValue        = getCurrentDisplayValue;
25367  ctrl.registerSelectedItemWatcher   = registerSelectedItemWatcher;
25368  ctrl.unregisterSelectedItemWatcher = unregisterSelectedItemWatcher;
25369  ctrl.notFoundVisible               = notFoundVisible;
25370  ctrl.loadingIsVisible              = loadingIsVisible;
25371  ctrl.positionDropdown              = positionDropdown;
25372
25373  /**
25374   * Report types to be used for the $mdLiveAnnouncer
25375   * @enum {number} Unique flag id.
25376   */
25377  var ReportType = {
25378    Count: 1,
25379    Selected: 2
25380  };
25381
25382  return init();
25383
25384  //-- initialization methods
25385
25386  /**
25387   * Initialize the controller, setup watchers, gather elements
25388   */
25389  function init () {
25390
25391    $mdUtil.initOptionalProperties($scope, $attrs, {
25392      searchText: '',
25393      selectedItem: null,
vendor: 15,269 bytes, lines 25394-25931
25394      clearButton: false
25395    });
25396
25397    $mdTheming($element);
25398    configureWatchers();
25399    $mdUtil.nextTick(function () {
25400
25401      gatherElements();
25402      moveDropdown();
25403
25404      // Forward all focus events to the input element when autofocus is enabled
25405      if ($scope.autofocus) {
25406        $element.on('focus', focusInputElement);
25407      }
25408    });
25409  }
25410
25411  function updateModelValidators() {
25412    if (!$scope.requireMatch || !inputModelCtrl) return;
25413
25414    inputModelCtrl.$setValidity('md-require-match', !!$scope.selectedItem || !$scope.searchText);
25415  }
25416
25417  /**
25418   * Calculates the dropdown's position and applies the new styles to the menu element
25419   * @returns {*}
25420   */
25421  function positionDropdown () {
25422    if (!elements) {
25423      return $mdUtil.nextTick(positionDropdown, false, $scope);
25424    }
25425
25426    var dropdownHeight = ($scope.dropdownItems || MAX_ITEMS) * ITEM_HEIGHT;
25427
25428    var hrect  = elements.wrap.getBoundingClientRect(),
25429        vrect  = elements.snap.getBoundingClientRect(),
25430        root   = elements.root.getBoundingClientRect(),
25431        top    = vrect.bottom - root.top,
25432        bot    = root.bottom - vrect.top,
25433        left   = hrect.left - root.left,
25434        width  = hrect.width,
25435        offset = getVerticalOffset(),
25436        position = $scope.dropdownPosition,
25437        styles;
25438
25439    // Automatically determine dropdown placement based on available space in viewport.
25440    if (!position) {
25441      position = (top > bot && root.height - top - MENU_PADDING < dropdownHeight) ? 'top' : 'bottom';
25442    }
25443    // Adjust the width to account for the padding provided by `md-input-container`
25444    if ($attrs.mdFloatingLabel) {
25445      left += INPUT_PADDING;
25446      width -= INPUT_PADDING * 2;
25447    }
25448    styles = {
25449      left:     left + 'px',
25450      minWidth: width + 'px',
25451      maxWidth: Math.max(hrect.right - root.left, root.right - hrect.left) - MENU_PADDING + 'px'
25452    };
25453
25454    if (position === 'top') {
25455      styles.top       = 'auto';
25456      styles.bottom    = bot + 'px';
25457      styles.maxHeight = Math.min(dropdownHeight, hrect.top - root.top - MENU_PADDING) + 'px';
25458    } else {
25459      var bottomSpace = root.bottom - hrect.bottom - MENU_PADDING + $mdUtil.getViewportTop();
25460
25461      styles.top       = (top - offset) + 'px';
25462      styles.bottom    = 'auto';
25463      styles.maxHeight = Math.min(dropdownHeight, bottomSpace) + 'px';
25464    }
25465
25466    elements.$.scrollContainer.css(styles);
25467    $mdUtil.nextTick(correctHorizontalAlignment, false);
25468
25469    /**
25470     * Calculates the vertical offset for floating label examples to account for ngMessages
25471     * @returns {number}
25472     */
25473    function getVerticalOffset () {
25474      var offset = 0;
25475      var inputContainer = $element.find('md-input-container');
25476      if (inputContainer.length) {
25477        var input = inputContainer.find('input');
25478        offset = inputContainer.prop('offsetHeight');
25479        offset -= input.prop('offsetTop');
25480        offset -= input.prop('offsetHeight');
25481        // add in the height left up top for the floating label text
25482        offset += inputContainer.prop('offsetTop');
25483      }
25484      return offset;
25485    }
25486
25487    /**
25488     * Makes sure that the menu doesn't go off of the screen on either side.
25489     */
25490    function correctHorizontalAlignment () {
25491      var dropdown = elements.scrollContainer.getBoundingClientRect(),
25492          styles   = {};
25493      if (dropdown.right > root.right - MENU_PADDING) {
25494        styles.left = (hrect.right - dropdown.width) + 'px';
25495      }
25496      elements.$.scrollContainer.css(styles);
25497    }
25498  }
25499
25500  /**
25501   * Moves the dropdown menu to the body tag in order to avoid z-index and overflow issues.
25502   */
25503  function moveDropdown () {
25504    if (!elements.$.root.length) return;
25505    $mdTheming(elements.$.scrollContainer);
25506    elements.$.scrollContainer.detach();
25507    elements.$.root.append(elements.$.scrollContainer);
25508    if ($animate.pin) $animate.pin(elements.$.scrollContainer, $rootElement);
25509  }
25510
25511  /**
25512   * Sends focus to the input element.
25513   */
25514  function focusInputElement () {
25515    elements.input.focus();
25516  }
25517
25518  /**
25519   * Sets up any watchers used by autocomplete
25520   */
25521  function configureWatchers () {
25522    var wait = parseInt($scope.delay, 10) || 0;
25523
25524    $attrs.$observe('disabled', function (value) { ctrl.isDisabled = $mdUtil.parseAttributeBoolean(value, false); });
25525    $attrs.$observe('required', function (value) { ctrl.isRequired = $mdUtil.parseAttributeBoolean(value, false); });
25526    $attrs.$observe('readonly', function (value) { ctrl.isReadonly = $mdUtil.parseAttributeBoolean(value, false); });
25527
25528    $scope.$watch('searchText', wait ? $mdUtil.debounce(handleSearchText, wait) : handleSearchText);
25529    $scope.$watch('selectedItem', selectedItemChange);
25530
25531    angular.element($window).on('resize', debouncedOnResize);
25532
25533    $scope.$on('$destroy', cleanup);
25534  }
25535
25536  /**
25537   * Removes any events or leftover elements created by this controller
25538   */
25539  function cleanup () {
25540    if (!ctrl.hidden) {
25541      $mdUtil.enableScrolling();
25542    }
25543
25544    angular.element($window).off('resize', debouncedOnResize);
25545
25546    if ( elements ){
25547      var items = ['ul', 'scroller', 'scrollContainer', 'input'];
25548      angular.forEach(items, function(key){
25549        elements.$[key].remove();
25550      });
25551    }
25552  }
25553
25554  /**
25555   * Event handler to be called whenever the window resizes.
25556   */
25557  function onWindowResize() {
25558    if (!ctrl.hidden) {
25559      positionDropdown();
25560    }
25561  }
25562
25563  /**
25564   * Gathers all of the elements needed for this controller
25565   */
25566  function gatherElements () {
25567
25568    var snapWrap = gatherSnapWrap();
25569
25570    elements = {
25571      main:  $element[0],
25572      scrollContainer: $element[0].querySelector('.md-virtual-repeat-container'),
25573      scroller: $element[0].querySelector('.md-virtual-repeat-scroller'),
25574      ul:    $element.find('ul')[0],
25575      input: $element.find('input')[0],
25576      wrap:  snapWrap.wrap,
25577      snap:  snapWrap.snap,
25578      root:  document.body
25579    };
25580
25581    elements.li   = elements.ul.getElementsByTagName('li');
25582    elements.$    = getAngularElements(elements);
25583
25584    inputModelCtrl = elements.$.input.controller('ngModel');
25585  }
25586
25587  /**
25588   * Gathers the snap and wrap elements
25589   *
25590   */
25591  function gatherSnapWrap() {
25592    var element;
25593    var value;
25594    for (element = $element; element.length; element = element.parent()) {
25595      value = element.attr('md-autocomplete-snap');
25596      if (angular.isDefined(value)) break;
25597    }
25598
25599    if (element.length) {
25600      return {
25601        snap: element[0],
25602        wrap: (value.toLowerCase() === 'width') ? element[0] : $element.find('md-autocomplete-wrap')[0]
25603      };
25604    }
25605
25606    var wrap = $element.find('md-autocomplete-wrap')[0];
25607    return {
25608      snap: wrap,
25609      wrap: wrap
25610    };
25611  }
25612
25613  /**
25614   * Gathers angular-wrapped versions of each element
25615   * @param elements
25616   * @returns {{}}
25617   */
25618  function getAngularElements (elements) {
25619    var obj = {};
25620    for (var key in elements) {
25621      if (elements.hasOwnProperty(key)) obj[ key ] = angular.element(elements[ key ]);
25622    }
25623    return obj;
25624  }
25625
25626  //-- event/change handlers
25627
25628  /**
25629   * Handles changes to the `hidden` property.
25630   * @param hidden
25631   * @param oldHidden
25632   */
25633  function handleHiddenChange (hidden, oldHidden) {
25634    if (!hidden && oldHidden) {
25635      positionDropdown();
25636
25637      // Report in polite mode, because the screenreader should finish the default description of
25638      // the input. element.
25639      reportMessages(true, ReportType.Count | ReportType.Selected);
25640
25641      if (elements) {
25642        $mdUtil.disableScrollAround(elements.ul);
25643        enableWrapScroll = disableElementScrollEvents(angular.element(elements.wrap));
25644      }
25645    } else if (hidden && !oldHidden) {
25646      $mdUtil.enableScrolling();
25647
25648      if (enableWrapScroll) {
25649        enableWrapScroll();
25650        enableWrapScroll = null;
25651      }
25652    }
25653  }
25654
25655  /**
25656   * Disables scrolling for a specific element
25657   */
25658  function disableElementScrollEvents(element) {
25659
25660    function preventDefault(e) {
25661      e.preventDefault();
25662    }
25663
25664    element.on('wheel', preventDefault);
25665    element.on('touchmove', preventDefault);
25666
25667    return function() {
25668      element.off('wheel', preventDefault);
25669      element.off('touchmove', preventDefault);
25670    };
25671  }
25672
25673  /**
25674   * When the user mouses over the dropdown menu, ignore blur events.
25675   */
25676  function onListEnter () {
25677    noBlur = true;
25678  }
25679
25680  /**
25681   * When the user's mouse leaves the menu, blur events may hide the menu again.
25682   */
25683  function onListLeave () {
25684    if (!hasFocus && !ctrl.hidden) elements.input.focus();
25685    noBlur = false;
25686    ctrl.hidden = shouldHide();
25687  }
25688
25689  /**
25690   * When the mouse button is released, send focus back to the input field.
25691   */
25692  function onMouseup () {
25693    elements.input.focus();
25694  }
25695
25696  /**
25697   * Handles changes to the selected item.
25698   * @param selectedItem
25699   * @param previousSelectedItem
25700   */
25701  function selectedItemChange (selectedItem, previousSelectedItem) {
25702
25703    updateModelValidators();
25704
25705    if (selectedItem) {
25706      getDisplayValue(selectedItem).then(function (val) {
25707        $scope.searchText = val;
25708        handleSelectedItemChange(selectedItem, previousSelectedItem);
25709      });
25710    } else if (previousSelectedItem && $scope.searchText) {
25711      getDisplayValue(previousSelectedItem).then(function(displayValue) {
25712        // Clear the searchText, when the selectedItem is set to null.
25713        // Do not clear the searchText, when the searchText isn't matching with the previous
25714        // selected item.
25715        if (angular.isString($scope.searchText)
25716          && displayValue.toString().toLowerCase() === $scope.searchText.toLowerCase()) {
25717          $scope.searchText = '';
25718        }
25719      });
25720    }
25721
25722    if (selectedItem !== previousSelectedItem) announceItemChange();
25723  }
25724
25725  /**
25726   * Use the user-defined expression to announce changes each time a new item is selected
25727   */
25728  function announceItemChange () {
25729    angular.isFunction($scope.itemChange) && $scope.itemChange(getItemAsNameVal($scope.selectedItem));
25730  }
25731
25732  /**
25733   * Use the user-defined expression to announce changes each time the search text is changed
25734   */
25735  function announceTextChange () {
25736    angular.isFunction($scope.textChange) && $scope.textChange();
25737  }
25738
25739  /**
25740   * Calls any external watchers listening for the selected item.  Used in conjunction with
25741   * `registerSelectedItemWatcher`.
25742   * @param selectedItem
25743   * @param previousSelectedItem
25744   */
25745  function handleSelectedItemChange (selectedItem, previousSelectedItem) {
25746    selectedItemWatchers.forEach(function (watcher) { watcher(selectedItem, previousSelectedItem); });
25747  }
25748
25749  /**
25750   * Register a function to be called when the selected item changes.
25751   * @param cb
25752   */
25753  function registerSelectedItemWatcher (cb) {
25754    if (selectedItemWatchers.indexOf(cb) == -1) {
25755      selectedItemWatchers.push(cb);
25756    }
25757  }
25758
25759  /**
25760   * Unregister a function previously registered for selected item changes.
25761   * @param cb
25762   */
25763  function unregisterSelectedItemWatcher (cb) {
25764    var i = selectedItemWatchers.indexOf(cb);
25765    if (i != -1) {
25766      selectedItemWatchers.splice(i, 1);
25767    }
25768  }
25769
25770  /**
25771   * Handles changes to the searchText property.
25772   * @param searchText
25773   * @param previousSearchText
25774   */
25775  function handleSearchText (searchText, previousSearchText) {
25776    ctrl.index = getDefaultIndex();
25777
25778    // do nothing on init
25779    if (searchText === previousSearchText) return;
25780
25781    updateModelValidators();
25782
25783    getDisplayValue($scope.selectedItem).then(function (val) {
25784      // clear selected item if search text no longer matches it
25785      if (searchText !== val) {
25786        $scope.selectedItem = null;
25787
25788
25789        // trigger change event if available
25790        if (searchText !== previousSearchText) announceTextChange();
25791
25792        // cancel results if search text is not long enough
25793        if (!isMinLengthMet()) {
25794          ctrl.matches = [];
25795
25796          setLoading(false);
25797          reportMessages(false, ReportType.Count);
25798
25799        } else {
25800          handleQuery();
25801        }
25802      }
25803    });
25804
25805  }
25806
25807  /**
25808   * Handles input blur event, determines if the dropdown should hide.
25809   */
25810  function blur($event) {
25811    hasFocus = false;
25812
25813    if (!noBlur) {
25814      ctrl.hidden = shouldHide();
25815      evalAttr('ngBlur', { $event: $event });
25816    }
25817  }
25818
25819  /**
25820   * Force blur on input element
25821   * @param forceBlur
25822   */
25823  function doBlur(forceBlur) {
25824    if (forceBlur) {
25825      noBlur = false;
25826      hasFocus = false;
25827    }
25828    elements.input.blur();
25829  }
25830
25831  /**
25832   * Handles input focus event, determines if the dropdown should show.
25833   */
25834  function focus($event) {
25835    hasFocus = true;
25836
25837    if (isSearchable() && isMinLengthMet()) {
25838      handleQuery();
25839    }
25840
25841    ctrl.hidden = shouldHide();
25842
25843    evalAttr('ngFocus', { $event: $event });
25844  }
25845
25846  /**
25847   * Handles keyboard input.
25848   * @param event
25849   */
25850  function keydown (event) {
25851    switch (event.keyCode) {
25852      case $mdConstant.KEY_CODE.DOWN_ARROW:
25853        if (ctrl.loading) return;
25854        event.stopPropagation();
25855        event.preventDefault();
25856        ctrl.index   = Math.min(ctrl.index + 1, ctrl.matches.length - 1);
25857        updateScroll();
25858        reportMessages(false, ReportType.Selected);
25859        break;
25860      case $mdConstant.KEY_CODE.UP_ARROW:
25861        if (ctrl.loading) return;
25862        event.stopPropagation();
25863        event.preventDefault();
25864        ctrl.index   = ctrl.index < 0 ? ctrl.matches.length - 1 : Math.max(0, ctrl.index - 1);
25865        updateScroll();
25866        reportMessages(false, ReportType.Selected);
25867        break;
25868      case $mdConstant.KEY_CODE.TAB:
25869        // If we hit tab, assume that we've left the list so it will close
25870        onListLeave();
25871
25872        if (ctrl.hidden || ctrl.loading || ctrl.index < 0 || ctrl.matches.length < 1) return;
25873        select(ctrl.index);
25874        break;
25875      case $mdConstant.KEY_CODE.ENTER:
25876        if (ctrl.hidden || ctrl.loading || ctrl.index < 0 || ctrl.matches.length < 1) return;
25877        if (hasSelection()) return;
25878        event.stopPropagation();
25879        event.preventDefault();
25880        select(ctrl.index);
25881        break;
25882      case $mdConstant.KEY_CODE.ESCAPE:
25883        event.preventDefault(); // Prevent browser from always clearing input
25884        if (!shouldProcessEscape()) return;
25885        event.stopPropagation();
25886
25887        clearSelectedItem();
25888        if ($scope.searchText && hasEscapeOption('clear')) {
25889          clearSearchText();
25890        }
25891
25892        // Manually hide (needed for mdNotFound support)
25893        ctrl.hidden = true;
25894
25895        if (hasEscapeOption('blur')) {
25896          // Force the component to blur if they hit escape
25897          doBlur(true);
25898        }
25899
25900        break;
25901      default:
25902    }
25903  }
25904
25905  //-- getters
25906
25907  /**
25908   * Returns the minimum length needed to display the dropdown.
25909   * @returns {*}
25910   */
25911  function getMinLength () {
25912    return angular.isNumber($scope.minLength) ? $scope.minLength : 1;
25913  }
25914
25915  /**
25916   * Returns the display value for an item.
25917   * @param item
25918   * @returns {*}
25919   */
25920  function getDisplayValue (item) {
25921    return $q.when(getItemText(item) || item).then(function(itemText) {
25922      if (itemText && !angular.isString(itemText)) {
25923        $log.warn('md-autocomplete: Could not resolve display value to a string. ' +
25924          'Please check the `md-item-text` attribute.');
25925      }
25926
25927      return itemText;
25928    });
25929
25930    /**
25931     * Getter function to invoke user-defined expression (in the directive)
25932     * to convert your object to a single string.
25933     */
25934    function getItemText (item) {
25935      return (item && $scope.itemText) ? $scope.itemText(getItemAsNameVal(item)) : null;
25936    }
25937  }
25938
25939  /**
25940   * Returns the locals object for compiling item templates.
25941   * @param item
25942   * @returns {{}}
25943   */
25944  function getItemAsNameVal (item) {
25945    if (!item) return undefined;
25946
25947    var locals = {};
25948    if (ctrl.itemName) locals[ ctrl.itemName ] = item;
25949
25950    return locals;
25951  }
25952
25953  /**
25954   * Returns the default index based on whether or not autoselect is enabled.
25955   * @returns {number}
25956   */
25957  function getDefaultIndex () {
25958    return $scope.autoselect ? 0 : -1;
25959  }
25960
25961  /**
25962   * Sets the loading parameter and updates the hidden state.
25963   * @param value {boolean} Whether or not the component is currently loading.
25964   */
25965  function setLoading(value) {
25966    if (ctrl.loading != value) {
25967      ctrl.loading = value;
25968    }
25969
25970    // Always refresh the hidden variable as something else might have changed
25971    ctrl.hidden = shouldHide();
25972  }
25973
25974  /**
25975   * Determines if the menu should be hidden.
25976   * @returns {boolean}
25977   */
25978  function shouldHide () {
25979    if (!isSearchable()) return true;    // Hide when not able to query
25980    else return !shouldShow();            // Hide when the dropdown is not able to show.
25981  }
25982
25983  /**
25984   * Determines whether the autocomplete is able to query within the current state.
25985   * @returns {boolean}
25986   */
25987  function isSearchable() {
25988    if (ctrl.loading && !hasMatches()) return false; // No query when query is in progress.
25989    else if (hasSelection()) return false;           // No query if there is already a selection
25990    else if (!hasFocus) return false;                // No query if the input does not have focus
25991    return true;
25992  }
25993
25994  /**
25995   * Determines if the escape keydown should be processed
25996   * @returns {boolean}
25997   */
25998  function shouldProcessEscape() {
25999    return hasEscapeOption('blur') || !ctrl.hidden || ctrl.loading || hasEscapeOption('clear') && $scope.searchText;
26000  }
26001
26002  /**
26003   * Determines if an escape option is set
26004   * @returns {boolean}
26005   */
26006  function hasEscapeOption(option) {
26007    return !$scope.escapeOptions || $scope.escapeOptions.toLowerCase().indexOf(option) !== -1;
26008  }
26009
26010  /**
26011   * Determines if the menu should be shown.
26012   * @returns {boolean}
26013   */
26014  function shouldShow() {
26015    return (isMinLengthMet() && hasMatches()) || notFoundVisible();
26016  }
26017
26018  /**
26019   * Returns true if the search text has matches.
26020   * @returns {boolean}
26021   */
26022  function hasMatches() {
26023    return ctrl.matches.length ? true : false;
26024  }
26025
26026  /**
26027   * Returns true if the autocomplete has a valid selection.
26028   * @returns {boolean}
26029   */
26030  function hasSelection() {
26031    return ctrl.scope.selectedItem ? true : false;
26032  }
26033
26034  /**
26035   * Returns true if the loading indicator is, or should be, visible.
26036   * @returns {boolean}
26037   */
26038  function loadingIsVisible() {
26039    return ctrl.loading && !hasSelection();
26040  }
26041
26042  /**
26043   * Returns the display value of the current item.
26044   * @returns {*}
26045   */
26046  function getCurrentDisplayValue () {
26047    return getDisplayValue(ctrl.matches[ ctrl.index ]);
26048  }
26049
26050  /**
26051   * Determines if the minimum length is met by the search text.
26052   * @returns {*}
26053   */
26054  function isMinLengthMet () {
26055    return ($scope.searchText || '').length >= getMinLength();
26056  }
26057
26058  //-- actions
26059
26060  /**
26061   * Defines a public property with a handler and a default value.
26062   * @param key
26063   * @param handler
26064   * @param value
26065   */
26066  function defineProperty (key, handler, value) {
26067    Object.defineProperty(ctrl, key, {
26068      get: function () { return value; },
26069      set: function (newValue) {
26070        var oldValue = value;
26071        value        = newValue;
26072        handler(newValue, oldValue);
26073      }
26074    });
26075  }
26076
26077  /**
26078   * Selects the item at the given index.
26079   * @param index
26080   */
26081  function select (index) {
26082    //-- force form to update state for validation
26083    $mdUtil.nextTick(function () {
26084      getDisplayValue(ctrl.matches[ index ]).then(function (val) {
26085        var ngModel = elements.$.input.controller('ngModel');
26086        ngModel.$setViewValue(val);
26087        ngModel.$render();
26088      }).finally(function () {
26089        $scope.selectedItem = ctrl.matches[ index ];
26090        setLoading(false);
26091      });
26092    }, false);
26093  }
26094
26095  /**
26096   * Clears the searchText value and selected item.
26097   */
26098  function clearValue () {
26099    clearSelectedItem();
26100    clearSearchText();
26101  }
26102
26103  /**
26104   * Clears the selected item
26105   */
26106  function clearSelectedItem () {
26107    // Reset our variables
26108    ctrl.index = 0;
26109    ctrl.matches = [];
26110  }
26111
26112  /**
26113   * Clears the searchText value
26114   */
26115  function clearSearchText () {
26116    // Set the loading to true so we don't see flashes of content.
26117    // The flashing will only occur when an async request is running.
26118    // So the loading process will stop when the results had been retrieved.
26119    setLoading(true);
26120
26121    $scope.searchText = '';
26122
26123    // Normally, triggering the change / input event is unnecessary, because the browser detects it properly.
26124    // But some browsers are not detecting it properly, which means that we have to trigger the event.
26125    // Using the `input` is not working properly, because for example IE11 is not supporting the `input` event.
26126    // The `change` event is a good alternative and is supported by all supported browsers.
26127    var eventObj = document.createEvent('CustomEvent');
26128    eventObj.initCustomEvent('change', true, true, { value: '' });
26129    elements.input.dispatchEvent(eventObj);
26130
26131    // For some reason, firing the above event resets the value of $scope.searchText if
26132    // $scope.searchText has a space character at the end, so we blank it one more time and then
26133    // focus.
26134    elements.input.blur();
26135    $scope.searchText = '';
26136    elements.input.focus();
26137  }
26138
26139  /**
26140   * Fetches the results for the provided search text.
26141   * @param searchText
26142   */
26143  function fetchResults (searchText) {
26144    var items = $scope.$parent.$eval(itemExpr),
26145        term  = searchText.toLowerCase(),
26146        isList = angular.isArray(items),
26147        isPromise = !!items.then; // Every promise should contain a `then` property
26148
26149    if (isList) onResultsRetrieved(items);
26150    else if (isPromise) handleAsyncResults(items);
26151
26152    function handleAsyncResults(items) {
26153      if ( !items ) return;
26154
26155      items = $q.when(items);
26156      fetchesInProgress++;
26157      setLoading(true);
26158
26159      $mdUtil.nextTick(function () {
26160          items
26161            .then(onResultsRetrieved)
26162            .finally(function(){
26163              if (--fetchesInProgress === 0) {
26164                setLoading(false);
26165              }
26166            });
26167      },true, $scope);
26168    }
26169
26170    function onResultsRetrieved(matches) {
26171      cache[term] = matches;
26172
26173      // Just cache the results if the request is now outdated.
26174      // The request becomes outdated, when the new searchText has changed during the result fetching.
26175      if ((searchText || '') !== ($scope.searchText || '')) {
26176        return;
26177      }
26178
26179      handleResults(matches);
26180    }
26181  }
26182
26183
26184  /**
26185   * Reports given message types to supported screenreaders.
26186   * @param {boolean} isPolite Whether the announcement should be polite.
26187   * @param {!number} types Message flags to be reported to the screenreader.
26188   */
26189  function reportMessages(isPolite, types) {
26190
26191    var politeness = isPolite ? 'polite' : 'assertive';
26192    var messages = [];
26193
26194    if (types & ReportType.Selected && ctrl.index !== -1) {
26195      messages.push(getCurrentDisplayValue());
26196    }
26197
26198    if (types & ReportType.Count) {
26199      messages.push($q.resolve(getCountMessage()));
26200    }
26201
26202    $q.all(messages).then(function(data) {
26203      $mdLiveAnnouncer.announce(data.join(' '), politeness);
26204    });
26205
26206  }
26207
26208  /**
26209   * Returns the ARIA message for how many results match the current query.
26210   * @returns {*}
26211   */
26212  function getCountMessage () {
26213    switch (ctrl.matches.length) {
26214      case 0:
26215        return 'There are no matches available.';
26216      case 1:
26217        return 'There is 1 match available.';
26218      default:
26219        return 'There are ' + ctrl.matches.length + ' matches available.';
26220    }
26221  }
26222
26223  /**
26224   * Makes sure that the focused element is within view.
26225   */
26226  function updateScroll () {
26227    if (!elements.li[0]) return;
26228    var height = elements.li[0].offsetHeight,
26229        top = height * ctrl.index,
26230        bot = top + height,
26231        hgt = elements.scroller.clientHeight,
26232        scrollTop = elements.scroller.scrollTop;
26233    if (top < scrollTop) {
26234      scrollTo(top);
26235    } else if (bot > scrollTop + hgt) {
26236      scrollTo(bot - hgt);
26237    }
26238  }
26239
26240  function isPromiseFetching() {
26241    return fetchesInProgress !== 0;
26242  }
26243
26244  function scrollTo (offset) {
vendor: 1,762 bytes, lines 26245-26293
26245    elements.$.scrollContainer.controller('mdVirtualRepeatContainer').scrollTo(offset);
26246  }
26247
26248  function notFoundVisible () {
26249    var textLength = (ctrl.scope.searchText || '').length;
26250
26251    return ctrl.hasNotFound && !hasMatches() && (!ctrl.loading || isPromiseFetching()) && textLength >= getMinLength() && (hasFocus || noBlur) && !hasSelection();
26252  }
26253
26254  /**
26255   * Starts the query to gather the results for the current searchText.  Attempts to return cached
26256   * results first, then forwards the process to `fetchResults` if necessary.
26257   */
26258  function handleQuery () {
26259    var searchText = $scope.searchText || '';
26260    var term = searchText.toLowerCase();
26261
26262    // If caching is enabled and the current searchText is stored in the cache
26263    if (!$scope.noCache && cache[term]) {
26264      // The results should be handled as same as a normal un-cached request does.
26265      handleResults(cache[term]);
26266    } else {
26267      fetchResults(searchText);
26268    }
26269
26270    ctrl.hidden = shouldHide();
26271  }
26272
26273  /**
26274   * Handles the retrieved results by showing them in the autocompletes dropdown.
26275   * @param results Retrieved results
26276   */
26277  function handleResults(results) {
26278    ctrl.matches = results;
26279    ctrl.hidden  = shouldHide();
26280
26281    // If loading is in progress, then we'll end the progress. This is needed for example,
26282    // when the `clear` button was clicked, because there we always show the loading process, to prevent flashing.
26283    if (ctrl.loading) setLoading(false);
26284
26285    if ($scope.selectOnMatch) selectItemOnMatch();
26286
26287    positionDropdown();
26288    reportMessages(true, ReportType.Count);
26289  }
26290
26291  /**
26292   * If there is only one matching item and the search text matches its display value exactly,
26293   * automatically select that item.  Note: This function is only called 
26293if the user uses the
26294   * `md-select-on-match` flag.
26295   */
26296  function selectItemOnMatch () {
26297    var searchText = $scope.searchText,
26298        matches    = ctrl.matches,
26299        item       = matches[ 0 ];
26300    if (matches.length === 1) getDisplayValue(item).then(function (displayValue) {
26301      var isMatching = searchText == displayValue;
26302      if ($scope.matchInsensitive && !isMatching) {
26303        isMatching = searchText.toLowerCase() == displayValue.toLowerCase();
26304      }
26305
26306      if (isMatching) select(0);
26307    });
26308  }
26309
26310  /**
26311   * Evaluates an attribute expression against the parent scope.
26312   * @param {String} attr Name of the attribute to be evaluated.
26313   * @param {Object?} locals Properties to be injected into the evaluation context.
26314   */
26315 function evalAttr(attr, locals) {
26316    if ($attrs[attr]) {
26317      $scope.$parent.$eval($attrs[attr], locals || {});
26318    }
26319  }
26320
26321}
26322
26323})();
26324(function(){
26325"use strict";
26326
26327
26328MdAutocomplete.$inject = ["$$mdSvgRegistry"];angular
26329    .module('material.components.autocomplete')
26330    .directive('mdAutocomplete', MdAutocomplete);
26331
26332/**
26333 * @ngdoc directive
26334 * @name mdAutocomplete
26335 * @module material.components.autocomplete
26336 *
26337 * @description
26338 * `<md-autocomplete>` is a special input component with a drop-down of all possible matches to a
26339 *     custom query. This component allows you to provide real-time suggestions as the user types
26340 *     in the input area.
26341 *
26342 * To start, you will need to specify the required parameters and provide a template for your
26343 *     results. The content inside `md-autocomplete` will be treated as a template.
26344 *
26345 * In more complex cases, you may want to include other content such as a message to display when
26346 *     no matches were found.  You can do this by wrapping your template in `md-item-template` and
26347 *     adding a tag for `md-not-found`.  An example of this is shown below.
26348 *
26349 * To reset the displayed value you must clear both values for `md-search-text` and `md-selected-item`.
26350 *
26351 * ### Validation
26352 *
26353 * You can use `ng-messages` to include validation the same way that you would normally validate;
26354 *     however, if you want to replicate a standard input with a floating label, you will have to
26355 *     do the following:
26356 *
26357 * - Make sure that your template is wrapped in `md-item-template`
26358 * - Add your `ng-messages` code inside of `md-autocomplete`
26359 * - Add your validation properties to `md-autocomplete` (ie. `required`)
26360 * - Add a `name` to `md-autocomplete` (to be used on the generated `input`)
26361 *
26362 * There is an example below of how this should look.
26363 *
26364 * ### Snapping Drop-Down
26365 *
26366 * You can cause the autocomplete drop-down to snap to an ancestor element by applying the
26367 *     `md-autocomplete-snap` attribute to that element. You can also snap to the width of
26368 *     the `md-autocomplete-snap` element by setting the attribute's value to `width`
26369 *     (ie. `md-autocomplete-snap="width"`).
26370 *
26371 * ### Notes
26372 *
26373 * **Autocomplete Dropdown Items Rendering**
26374 *
26375 * The `md-autocomplete` uses the the <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeat</a>
26376 * directive for displaying the results inside of the dropdown.<br/>
26377 *
26378 * > When encountering issues regarding the item template please take a look at the
26379 *   <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeatContainer</a> documentation.
26380 *
26381 * **Autocomplete inside of a Virtual Repeat**
26382 *
26383 * When using the `md-autocomplete` directive inside of a
26384 * <a ng-href="api/directive/mdVirtualRepeatContainer">VirtualRepeatContainer</a> the dropdown items might
26385 * not update properly, because caching of the results is enabled by default.
26386 *
26387 * The autocomplete will then show invalid dropdown items, because the VirtualRepeat only updates the
26388 * scope bindings, rather than re-creating the `md-autocomplete` and the previous cached results will be used.
26389 *
26390 * > To avoid such problems ensure that the autocomplete does not cache any results.
26391 *
26392 * <hljs lang="html">
26393 *   <md-autocomplete
26394 *       md-no-cache="true"
26395 *       md-selected-item="selectedItem"
26396 *       md-items="item in items"
26397 *       md-search-text="searchText"
26398 *       md-item-text="item.display">
26399 *     <span>{{ item.display }}</span>
26400 *   </md-autocomplete>
26401 * </hljs>
26402 *
26403 *
26404 *
26405 * @param {expression} md-items An expression in the format of `item in results` to iterate over
26406 *     matches for your search.<br/><br/>
26407 *     The `results` expression can be also a function, which returns the results synchronously
26408 *     or asynchronously (per Promise)
26409 * @param {expression=} md-selected-item-change An expression to be run each time a new item is
26410 *     selected
26411 * @param {expression=} md-search-text-change An expression to be run each time the search text
26412 *     updates
26413 * @param {expression=} md-search-text A model to bind the search query text to
26414 * @param {object=} md-selected-item A model to bind the selected item to
26415 * @param {expression=} md-item-text An expression that will convert your object to a single str
26415ing.
26416 * @param {string=} placeholder Placeholder text that will be forwarded to the input.
26417 * @param {boolean=} md-no-cache Disables the internal caching that happens in autocomplete
26418 * @param {boolean=} ng-disabled Determines whether or not to disable the input field
26419 * @param {boolean=} md-require-match When set to true, the autocomplete will add a validator,
26420 *     which will evaluate to false, when no item is currently selected.
26421 * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will
26422 *     make suggestions
26423 * @param {number=} md-delay Specifies the amount of time (in milliseconds) to wait before looking
26424 *     for results
26425 * @param {boolean=} md-clear-button Whether the clear button for the autocomplete input should show up or not.
26426 * @param {boolean=} md-autofocus If true, the autocomplete will be automatically focused when a `$mdDialog`,
26427 *     `$mdBottomsheet` or `$mdSidenav`, which contains the autocomplete, is opening. <br/><br/>
26428 *     Also the autocomplete will immediately focus the input element.
26429 * @param {boolean=} md-no-asterisk When present, asterisk will not be appended to the floating label
26430 * @param {boolean=} md-autoselect If set to true, the first item will be automatically selected
26431 *     in the dropdown upon open.
26432 * @param {string=} md-menu-class This will be applied to the dropdown menu for styling
26433 * @param {string=} md-floating-label This will add a floating label to autocomplete and wrap it in
26434 *     `md-input-container`
26435 * @param {string=} md-input-name The name attribute given to the input element to be used with
26436 *     FormController
26437 * @param {string=} md-select-on-focus When present the inputs text will be automatically selected
26438 *     on focus.
26439 * @param {string=} md-input-id An ID to be added to the input element
26440 * @param {number=} md-input-minlength The minimum length for the input's value for validation
26441 * @param {number=} md-input-maxlength The maximum length for the input's value for validation
26442 * @param {boolean=} md-select-on-match When set, autocomplete will automatically select exact
26443 *     the item if the search text is an exact match. <br/><br/>
26444 *     Exact match means that there is only one match showing up.
26445 * @param {boolean=} md-match-case-insensitive When set and using `md-select-on-match`, autocomplete
26446 *     will select on case-insensitive match
26447 * @param {string=} md-escape-options Override escape key logic. Default is `blur clear`.<br/>
26448 *     Options: `blur | clear`, `none`
26449 * @param {string=} md-dropdown-items Specifies the maximum amount of items to be shown in
26450 *     the dropdown.<br/><br/>
26451 *     When the dropdown doesn't fit into the viewport, the dropdown will shrink
26452 *     as less as possible.
26453 * @param {string=} md-dropdown-position Overrides the default dropdown position. Options: `top`, `bottom`.
26454 * @param {string=} ng-trim If set to false, the search text will be not trimmed automatically.
26455 *     Defaults to true.
26456 * @param {string=} ng-pattern Adds the pattern validator to the ngModel of the search text.
26457 *     [ngPattern Directive](https://docs.angularjs.org/api/ng/directive/ngPattern)
26458 *
26459 * @usage
26460 * ### Basic Example
26461 * <hljs lang="html">
26462 *   <md-autocomplete
26463 *       md-selected-item="selectedItem"
26464 *       md-search-text="searchText"
26465 *       md-items="item in getMatches(searchText)"
26466 *       md-item-text="item.display">
26467 *     <span md-highlight-text="searchText">{{item.display}}</span>
26468 *   </md-autocomplete>
26469 * </hljs>
26470 *
26471 * ### Example with "not found" message
26472 * <hljs lang="html">
26473 * <md-autocomplete
26474 *     md-selected-item="selectedItem"
26475 *     md-search-text="searchText"
26476 *     md-items="item in getMatches(searchText)"
26477 *     md-item-text="item.display">
26478 *   <md-item-template>
26479 *     <span md-highlight-text="searchText">{{item.display}}</span>
26480 *   </md-item-template>
26481 *   <md-not-found>
26482 *     No matches found.
26483 *   </md-not-found>
26484 * </md-autocomplete>
26485 * </hljs>
26486 *
26487 * In this example, our code utilizes `md-item-template` and `md-not-found` to specify the
26488 *     different parts that make up our component.
26489 *
26490 * ### Clear button for the input
26491 * By default, for floating label autocomplete's the clear button is not showing up
26492 * ([See specs](https://material.google.com/components/text-fields.html#text-fields-auto-complete-text-field))
26493 *
26494 * Nevertheless, developers are able to explicitly toggle the clear button for all types of autocomplete's.
26495 *
26496 * <hljs lang="html">
26497 *   <md-autocomplete ... md-clear-button="true"></md-autocomplete>
26498 *   <md-autocomplete ... md-clear-button="false"></md-autocomplete>
26499 * </hljs>
26500 *
26501 * ### Example with validation
26502 * <hljs lang="html">
26503 * <form name="autocompleteForm">
26504 *   <md-autocomplete
26505 *       required
26506 *       md-input-name="autocomplete"
26507 *       md-selected-item="selectedItem"
26508 *       md-search-text="searchText"
26509 *       md-items="item in getMatches(searchText)"
26510 *       md-item-text="item.display">
26511 *     <md-item-template>
26512 *       <span md-highlight-text="searchText">{{item.display}}</span>
26513 *     </md-item-template>
26514 *     <div ng-messages="autocompleteForm.autocomplete.$error">
26515 *       <div ng-message="required">This field is required</div>
26516 *     </div>
26517 *   </md-autocomplete>
26518 * </form>
26519 * </hljs>
26520 *
26521 * In this example, our code utilizes `md-item-template` and `ng-messages` to specify
26522 *     input validation for the field.
26523 *
26524 * ### Asynchronous Results
26525 * The autocomplete items expression also supports promises, which will resolve with the query results.
26526 *
26527 * <hljs lang="js">
26528 *   function AppController($scope, $http) {
26529 *     $scope.query = function(searchText) {
26530 *       return $http
26531 *         .get(BACKEND_URL + '/items/' + searchText)
26532 *         .then(function(data) {
26533 *           // Map the response object to the data object.
26534 *           return data;
26535 *         });
26536 *     };
26537 *   }
26538 * </hljs>
26539 *
26540 * <hljs lang="html">
26541 *   <md-autocomplete
26542 *       md-selected-item="selectedItem"
26543 *       md-search-text="searchText"
26544 *       md-items="item in query(searchText)">
26545 *     <md-item-template>
26546 *       <span md-highlight-text="searchText">{{item}}</span>
26547 *     </md-item-template>
26548 * </md-autocomplete>
26549 * </hljs>
26550 *
26551 */
26552
26553function MdAutocomplete ($$mdSvgRegistry) {
26554
26555  return {
26556    controller:   'MdAutocompleteCtrl',
26557    controllerAs: '$mdAutocompleteCtrl',
26558    scope:        {
26559      inputName:        '@mdInputName',
26560      inputMinlength:   '@mdInputMinlength',
26561      inputMaxlength:   '@mdInputMaxlength',
26562      searchText:       '=?mdSearchText',
26563      selectedItem:     '=?mdSelectedItem',
26564      itemsExpr:        '@mdItems',
26565      itemText:         '&mdItemText',
26566      placeholder:      '@placeholder',
26567      noCache:          '=?mdNoCache',
26568      requireMatch:     '=?mdRequireMatch',
26569      selectOnMatch:    '=?mdSelectOnMatch',
26570      matchInsensitive: '=?mdMatchCaseInsensitive',
26571      itemChange:       '&?mdSelectedItemChange',
26572      textChange:       '&?mdSearchTextChange',
26573      minLength:        '=?mdMinLength',
26574      delay:            '=?mdDelay',
26575      autofocus:        '=?mdAutofocus',
26576      floatingLabel:    '@?mdFloatingLabel',
26577      autoselect:       '=?mdAutoselect',
26578      menuClass:        '@?mdMenuClass',
26579      inputId:          '@?mdInputId',
26580      escapeOptions:    '@?mdEscapeOptions',
26581      dropdownItems:    '=?mdDropdownItems',
26582      dropdownPosition: '@?mdDropdownPosition',
26583      clearButton:      '=?mdClearButton'
26584    },
vendor: 13,250 bytes, lines 26585-26965
26585    compile: function(tElement, tAttrs) {
26586      var attributes = ['md-select-on-focus', 'md-no-asterisk', 'ng-trim', 'ng-pattern'];
26587      var input = tElement.find('input');
26588
26589      attributes.forEach(function(attribute) {
26590        var attrValue = tAttrs[tAttrs.$normalize(attribute)];
26591
26592        if (attrValue !== null) {
26593          input.attr(attribute, attrValue);
26594        }
26595      });
26596
26597      return function(scope, element, attrs, ctrl) {
26598        // Retrieve the state of using a md-not-found template by using our attribute, which will
26599        // be added to the element in the template function.
26600        ctrl.hasNotFound = !!element.attr('md-has-not-found');
26601
26602        // By default the inset autocomplete should show the clear button when not explicitly overwritten.
26603        if (!angular.isDefined(attrs.mdClearButton) && !scope.floatingLabel) {
26604          scope.clearButton = true;
26605        }
26606      }
26607    },
26608    template:     function (element, attr) {
26609      var noItemsTemplate = getNoItemsTemplate(),
26610          itemTemplate    = getItemTemplate(),
26611          leftover        = element.html(),
26612          tabindex        = attr.tabindex;
26613
26614      // Set our attribute for the link function above which runs later.
26615      // We will set an attribute, because otherwise the stored variables will be trashed when
26616      // removing the element is hidden while retrieving the template. For example when using ngIf.
26617      if (noItemsTemplate) element.attr('md-has-not-found', true);
26618
26619      // Always set our tabindex of the autocomplete directive to -1, because our input
26620      // will hold the actual tabindex.
26621      element.attr('tabindex', '-1');
26622
26623      return '\
26624        <md-autocomplete-wrap\
26625            ng-class="{ \'md-whiteframe-z1\': !floatingLabel, \
26626                        \'md-menu-showing\': !$mdAutocompleteCtrl.hidden, \
26627                        \'md-show-clear-button\': !!clearButton }">\
26628          ' + getInputElement() + '\
26629          ' + getClearButton() + '\
26630          <md-progress-linear\
26631              class="' + (attr.mdFloatingLabel ? 'md-inline' : '') + '"\
26632              ng-if="$mdAutocompleteCtrl.loadingIsVisible()"\
26633              md-mode="indeterminate"></md-progress-linear>\
26634          <md-virtual-repeat-container\
26635              md-auto-shrink\
26636              md-auto-shrink-min="1"\
26637              ng-mouseenter="$mdAutocompleteCtrl.listEnter()"\
26638              ng-mouseleave="$mdAutocompleteCtrl.listLeave()"\
26639              ng-mouseup="$mdAutocompleteCtrl.mouseUp()"\
26640              ng-hide="$mdAutocompleteCtrl.hidden"\
26641              class="md-autocomplete-suggestions-container md-whiteframe-z1"\
26642              ng-class="{ \'md-not-found\': $mdAutocompleteCtrl.notFoundVisible() }"\
26643              role="presentation">\
26644            <ul class="md-autocomplete-suggestions"\
26645                ng-class="::menuClass"\
26646                id="ul-{{$mdAutocompleteCtrl.id}}">\
26647              <li md-virtual-repeat="item in $mdAutocompleteCtrl.matches"\
26648                  ng-class="{ selected: $index === $mdAutocompleteCtrl.index }"\
26649                  ng-click="$mdAutocompleteCtrl.select($index)"\
26650                  md-extra-name="$mdAutocompleteCtrl.itemName">\
26651                  ' + itemTemplate + '\
26652                  </li>' + noItemsTemplate + '\
26653            </ul>\
26654          </md-virtual-repeat-container>\
26655        </md-autocomplete-wrap>';
26656
26657      function getItemTemplate() {
26658        var templateTag = element.find('md-item-template').detach(),
26659            html = templateTag.length ? templateTag.html() : element.html();
26660        if (!templateTag.length) element.empty();
26661        return '<md-autocomplete-parent-scope md-autocomplete-replace>' + html + '</md-autocomplete-parent-scope>';
26662      }
26663
26664      function getNoItemsTemplate() {
26665        var templateTag = element.find('md-not-found').detach(),
26666            template = templateTag.length ? templateTag.html() : '';
26667        return template
26668            ? '<li ng-if="$mdAutocompleteCtrl.notFoundVisible()"\
26669                         md-autocomplete-parent-scope>' + template + '</li>'
26670            : '';
26671
26672      }
26673
26674      function getInputElement () {
26675        if (attr.mdFloatingLabel) {
26676          return '\
26677            <md-input-container ng-if="floatingLabel">\
26678              <label>{{floatingLabel}}</label>\
26679              <input type="search"\
26680                  ' + (tabindex != null ? 'tabindex="' + tabindex + '"' : '') + '\
26681                  id="{{ inputId || \'fl-input-\' + $mdAutocompleteCtrl.id }}"\
26682                  name="{{inputName}}"\
26683                  autocomplete="off"\
26684                  ng-required="$mdAutocompleteCtrl.isRequired"\
26685                  ng-readonly="$mdAutocompleteCtrl.isReadonly"\
26686                  ng-minlength="inputMinlength"\
26687                  ng-maxlength="inputMaxlength"\
26688                  ng-disabled="$mdAutocompleteCtrl.isDisabled"\
26689                  ng-model="$mdAutocompleteCtrl.scope.searchText"\
26690                  ng-model-options="{ allowInvalid: true }"\
26691                  ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
26692                  ng-blur="$mdAutocompleteCtrl.blur($event)"\
26693                  ng-focus="$mdAutocompleteCtrl.focus($event)"\
26694                  aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
26695                  aria-label="{{floatingLabel}}"\
26696                  aria-autocomplete="list"\
26697                  role="combobox"\
26698                  aria-haspopup="true"\
26699                  aria-activedescendant=""\
26700                  aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
26701              <div md-autocomplete-parent-scope md-autocomplete-replace>' + leftover + '</div>\
26702            </md-input-container>';
26703        } else {
26704          return '\
26705            <input type="search"\
26706                ' + (tabindex != null ? 'tabindex="' + tabindex + '"' : '') + '\
26707                id="{{ inputId || \'input-\' + $mdAutocompleteCtrl.id }}"\
26708                name="{{inputName}}"\
26709                ng-if="!floatingLabel"\
26710                autocomplete="off"\
26711                ng-required="$mdAutocompleteCtrl.isRequired"\
26712                ng-disabled="$mdAutocompleteCtrl.isDisabled"\
26713                ng-readonly="$mdAutocompleteCtrl.isReadonly"\
26714                ng-minlength="inputMinlength"\
26715                ng-maxlength="inputMaxlength"\
26716                ng-model="$mdAutocompleteCtrl.scope.searchText"\
26717                ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
26718                ng-blur="$mdAutocompleteCtrl.blur($event)"\
26719                ng-focus="$mdAutocompleteCtrl.focus($event)"\
26720                placeholder="{{placeholder}}"\
26721                aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
26722                aria-label="{{placeholder}}"\
26723                aria-autocomplete="list"\
26724                role="combobox"\
26725                aria-haspopup="true"\
26726                aria-activedescendant=""\
26727                aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>';
26728        }
26729      }
26730
26731      function getClearButton() {
26732        return '' +
26733          '<button ' +
26734              'type="button" ' +
26735              'aria-label="Clear Input" ' +
26736              'tabindex="-1" ' +
26737              'ng-if="clearButton && $mdAutocompleteCtrl.scope.searchText" ' +
26738              'ng-click="$mdAutocompleteCtrl.clear($event)">' +
26739            '<md-icon md-svg-src="' + $$mdSvgRegistry.mdClose + '"></md-icon>' +
26740          '</button>';
26741        }
26742    }
26743  };
26744}
26745
26746})();
26747(function(){
26748"use strict";
26749
26750
26751MdAutocompleteItemScopeDirective.$inject = ["$compile", "$mdUtil"];angular
26752  .module('material.components.autocomplete')
26753  .directive('mdAutocompleteParentScope', MdAutocompleteItemScopeDirective);
26754
26755function MdAutocompleteItemScopeDirective($compile, $mdUtil) {
26756  return {
26757    restrict: 'AE',
26758    compile: compile,
26759    terminal: true,
26760    transclude: 'element'
26761  };
26762
26763  function compile(tElement, tAttr, transclude) {
26764    return function postLink(scope, element, attr) {
26765      var ctrl = scope.$mdAutocompleteCtrl;
26766      var newScope = ctrl.parent.$new();
26767      var itemName = ctrl.itemName;
26768
26769      // Watch for changes to our scope's variables and copy them to the new scope
26770      watchVariable('$index', '$index');
26771      watchVariable('item', itemName);
26772
26773      // Ensure that $digest calls on our scope trigger $digest on newScope.
26774      connectScopes();
26775
26776      // Link the element against newScope.
26777      transclude(newScope, function(clone) {
26778        element.after(clone);
26779      });
26780
26781      /**
26782       * Creates a watcher for variables that are copied from the parent scope
26783       * @param variable
26784       * @param alias
26785       */
26786      function watchVariable(variable, alias) {
26787        newScope[alias] = scope[variable];
26788
26789        scope.$watch(variable, function(value) {
26790          $mdUtil.nextTick(function() {
26791            newScope[alias] = value;
26792          });
26793        });
26794      }
26795
26796      /**
26797       * Creates watchers on scope and newScope that ensure that for any
26798       * $digest of scope, newScope is also $digested.
26799       */
26800      function connectScopes() {
26801        var scopeDigesting = false;
26802        var newScopeDigesting = false;
26803
26804        scope.$watch(function() {
26805          if (newScopeDigesting || scopeDigesting) {
26806            return;
26807          }
26808
26809          scopeDigesting = true;
26810          scope.$$postDigest(function() {
26811            if (!newScopeDigesting) {
26812              newScope.$digest();
26813            }
26814
26815            scopeDigesting = newScopeDigesting = false;
26816          });
26817        });
26818
26819        newScope.$watch(function() {
26820          newScopeDigesting = true;
26821        });
26822      }
26823    };
26824  }
26825}
26826})();
26827(function(){
26828"use strict";
26829
26830
26831MdHighlightCtrl.$inject = ["$scope", "$element", "$attrs"];angular
26832    .module('material.components.autocomplete')
26833    .controller('MdHighlightCtrl', MdHighlightCtrl);
26834
26835function MdHighlightCtrl ($scope, $element, $attrs) {
26836  this.$scope = $scope;
26837  this.$element = $element;
26838  this.$attrs = $attrs;
26839
26840  // Cache the Regex to avoid rebuilding each time.
26841  this.regex = null;
26842}
26843
26844MdHighlightCtrl.prototype.init = function(unsafeTermFn, unsafeContentFn) {
26845
26846  this.flags = this.$attrs.mdHighlightFlags || '';
26847
26848  this.unregisterFn = this.$scope.$watch(function($scope) {
26849    return {
26850      term: unsafeTermFn($scope),
26851      contentText: unsafeContentFn($scope)
26852    };
26853  }.bind(this), this.onRender.bind(this), true);
26854
26855  this.$element.on('$destroy', this.unregisterFn);
26856};
26857
26858/**
26859 * Triggered once a new change has been recognized and the highlighted
26860 * text needs to be updated.
26861 */
26862MdHighlightCtrl.prototype.onRender = function(state, prevState) {
26863
26864  var contentText = state.contentText;
26865
26866  /* Update the regex if it's outdated, because we don't want to rebuilt it constantly. */
26867  if (this.regex === null || state.term !== prevState.term) {
26868    this.regex = this.createRegex(state.term, this.flags);
26869  }
26870
26871  /* If a term is available apply the regex to the content */
26872  if (state.term) {
26873    this.applyRegex(contentText);
26874  } else {
26875    this.$element.text(contentText);
26876  }
26877
26878};
26879
26880/**
26881 * Decomposes the specified text into different tokens (whether match or not).
26882 * Breaking down the string guarantees proper XSS protection due to the native browser
26883 * escaping of unsafe text.
26884 */
26885MdHighlightCtrl.prototype.applyRegex = function(text) {
26886  var tokens = this.resolveTokens(text);
26887
26888  this.$element.empty();
26889
26890  tokens.forEach(function (token) {
26891
26892    if (token.isMatch) {
26893      var tokenEl = angular.element('<span class="highlight">').text(token.text);
26894
26895      this.$element.append(tokenEl);
26896    } else {
26897      this.$element.append(document.createTextNode(token));
26898    }
26899
26900  }.bind(this));
26901
26902};
26903
26904  /**
26905 * Decomposes the specified text into different tokens by running the regex against the text.
26906 */
26907MdHighlightCtrl.prototype.resolveTokens = function(string) {
26908  var tokens = [];
26909  var lastIndex = 0;
26910
26911  // Use replace here, because it supports global and single regular expressions at same time.
26912  string.replace(this.regex, function(match, index) {
26913    appendToken(lastIndex, index);
26914
26915    tokens.push({
26916      text: match,
26917      isMatch: true
26918    });
26919
26920    lastIndex = index + match.length;
26921  });
26922
26923  // Append the missing text as a token.
26924  appendToken(lastIndex);
26925
26926  return tokens;
26927
26928  function appendToken(from, to) {
26929    var targetText = string.slice(from, to);
26930    targetText && tokens.push(targetText);
26931  }
26932};
26933
26934/** Creates a regex for the specified text with the given flags. */
26935MdHighlightCtrl.prototype.createRegex = function(term, flags) {
26936  var startFlag = '', endFlag = '';
26937  var regexTerm = this.sanitizeRegex(term);
26938
26939  if (flags.indexOf('^') >= 0) startFlag = '^';
26940  if (flags.indexOf('$') >= 0) endFlag = '$';
26941
26942  return new RegExp(startFlag + regexTerm + endFlag, flags.replace(/[$\^]/g, ''));
26943};
26944
26945/** Sanitizes a regex by removing all common RegExp identifiers */
26946MdHighlightCtrl.prototype.sanitizeRegex = function(term) {
26947  return term && term.toString().replace(/[\\\^\$\*\+\?\.\(\)\|\{}\[\]]/g, '\\$&');
26948};
26949
26950})();
26951(function(){
26952"use strict";
26953
26954
26955MdHighlight.$inject = ["$interpolate", "$parse"];angular
26956    .module('material.components.autocomplete')
26957    .directive('mdHighlightText', MdHighlight);
26958
26959/**
26960 * @ngdoc directive
26961 * @name mdHighlightText
26962 * @module material.components.autocomplete
26963 *
26964 * @description
26965 * The `md-highlight-text` directive allows you to specify text that should be highlighted 
26965within
26966 *     an element.  Highlighted text will be wrapped in `<span class="highlight"></span>` which can
26967 *     be styled through CSS.  Please note that child elements may not be used with this directive.
26968 *
26969 * @param {string} md-highlight-text A model to be searched for
26970 * @param {string=} md-highlight-flags A list of flags (loosely based on JavaScript RexExp flags).
26971 * #### **Supported flags**:
26972 * - `g`: Find all matches within the provided text
26973 * - `i`: Ignore case when searching for matches
26974 * - `$`: Only match if the text ends with the search term
26975 * - `^`: Only match if the text begins with the search term
26976 *
26977 * @usage
26978 * <hljs lang="html">
26979 * <input placeholder="Enter a search term..." ng-model="searchTerm" type="text" />
26980 * <ul>
26981 *   <li ng-repeat="result in results" md-highlight-text="searchTerm">
26982 *     {{result.text}}
26983 *   </li>
26984 * </ul>
26985 * </hljs>
26986 */
26987
26988function MdHighlight ($interpolate, $parse) {
26989  return {
26990    terminal: true,
26991    controller: 'MdHighlightCtrl',
26992    compile: function mdHighlightCompile(tElement, tAttr) {
26993      var termExpr = $parse(tAttr.mdHighlightText);
26994      var unsafeContentExpr = $interpolate(tElement.html());
26995
26996      return function mdHighlightLink(scope, element, attr, ctrl) {
26997        ctrl.init(termExpr, unsafeContentExpr);
26998      };
26999    }
27000  };
27001}
27002
27003})();
27004(function(){
27005"use strict";
27006
27007
27008MdChipCtrl.$inject = ["$scope", "$element", "$mdConstant", "$timeout", "$mdUtil"];angular
27009  .module('material.components.chips')
27010  .controller('MdChipCtrl', MdChipCtrl);
27011
27012/**
27013 * Controller for the MdChip component. Responsible for handling keyboard
27014 * events and editting the chip if needed.
27015 *
27016 * @param $scope
27017 * @param $element
27018 * @param $mdConstant
27019 * @param $timeout
27020 * @param $mdUtil
27021 * @constructor
27022 */
27023function MdChipCtrl ($scope, $element, $mdConstant, $timeout, $mdUtil) {
27024  /**
27025   * @type {$scope}
27026   */
27027  this.$scope = $scope;
27028
27029  /**
27030   * @type {$element}
27031   */
27032  this.$element = $element;
27033
27034  /**
27035   * @type {$mdConstant}
27036   */
27037  this.$mdConstant = $mdConstant;
27038
27039  /**
27040   * @type {$timeout}
27041   */
27042  this.$timeout = $timeout;
27043
27044  /**
27045   * @type {$mdUtil}
27046   */
27047  this.$mdUtil = $mdUtil;
27048
27049  /**
27050   * @type {boolean}
27051   */
27052  this.isEditting = false;
27053
27054  /**
27055   * @type {MdChipsCtrl}
27056   */
27057  this.parentController = undefined;
27058
27059  /**
27060   * @type {boolean}
27061   */
27062  this.enableChipEdit = false;
27063}
27064
27065
27066/**
27067 * @param {MdChipsCtrl} controller
27068 */
27069MdChipCtrl.prototype.init = function(controller) {
27070  this.parentController = controller;
27071  this.enableChipEdit = this.parentController.enableChipEdit;
27072
27073  if (this.enableChipEdit) {
27074    this.$element.on('keydown', this.chipKeyDown.bind(this));
27075    this.$element.on('mousedown', this.chipMouseDown.bind(this));
27076    this.getChipContent().addClass('_md-chip-content-edit-is-enabled');
27077  }
27078};
27079
27080
27081/**
27082 * @return {Object}
27083 */
27084MdChipCtrl.prototype.getChipContent = function() {
27085  var chipContents = this.$element[0].getElementsByClassName('md-chip-content');
27086  return angular.element(chipContents[0]);
27087};
27088
27089
27090/**
27091 * @return {Object}
27092 */
27093MdChipCtrl.prototype.getContentElement = function() {
27094  return angular.element(this.getChipContent().children()[0]);
27095};
27096
27097
27098/**
27099 * @return {number}
27100 */
27101MdChipCtrl.prototype.getChipIndex = function() {
27102  return parseInt(this.$element.attr('index'));
27103};
27104
27105
27106/**
27107 * Presents an input element to edit the contents of the chip.
27108 */
27109MdChipCtrl.prototype.goOutOfEditMode = function() {
27110  if (!this.isEditting) return;
27111
27112  this.isEditting = false;
27113  this.$element.removeClass('_md-chip-editing');
27114  this.getChipContent()[0].contentEditable = 'false';
27115  var chipIndex = this.getChipIndex();
27116
27117  var content = this.getContentElement().text();
27118  if (content) {
27119    this.parentController.updateChipContents(
27120        chipIndex,
27121        this.getContentElement().text()
27122    );
27123
27124    this.$mdUtil.nextTick(function() {
27125      if (this.parentController.selectedChip === chipIndex) {
27126        this.parentController.focusChip(chipIndex);
27127      }
27128    }.bind(this));
27129  } else {
27130    this.parentController.removeChipAndFocusInput(chipIndex);
27131  }
27132};
27133
27134
27135/**
27136 * Given an HTML element. Selects contents of it.
27137 * @param node
27138 */
27139MdChipCtrl.prototype.selectNodeContents = function(node) {
27140  var range, selection;
27141  if (document.body.createTextRange) {
27142    range = document.body.createTextRange();
27143    range.moveToElementText(node);
27144    range.select();
27145  } else if (window.getSelection) {
27146    selection = window.getSelection();
27147    range = document.createRange();
27148    range.selectNodeContents(node);
27149    selection.removeAllRanges();
27150    selection.addRange(range);
27151  }
27152};
27153
27154
27155/**
27156 * Presents an input element to edit the contents of the chip.
27157 */
27158MdChipCtrl.prototype.goInEditMode = function() {
27159  this.isEditting = true;
27160  this.$element.addClass('_md-chip-editing');
27161  this.getChipContent()[0].contentEditable = 'true';
27162  this.getChipContent().on('blur', function() {
27163    this.goOutOfEditMode();
27164  }.bind(this));
27165
27166  this.selectNodeContents(this.getChipContent()[0]);
27167};
27168
27169
27170/**
27171 * Handles the keydown event on the chip element. If enable-chip-edit attribute is
27172 * set to true, space or enter keys can trigger going into edit mode. Enter can also
27173 * trigger submitting if the chip is already being edited.
27174 * @param event
27175 */
27176MdChipCtrl.prototype.chipKeyDown = function(event) {
27177  if (!this.isEditting &&
27178    (event.keyCode === this.$mdConstant.KEY_CODE.ENTER ||
27179    event.keyCode === this.$mdConstant.KEY_CODE.SPACE)) {
27180    event.preventDefault();
27181    this.goInEditMode();
27182  } else if (this.isEditting &&
27183    event.keyCode === this.$mdConstant.KEY_CODE.ENTER) {
27184    event.preventDefault();
27185    this.goOutOfEditMode();
27186  }
27187};
27188
27189
27190/**
27191 * Handles the double click event
27192 */
27193MdChipCtrl.prototype.chipMouseDown = function() {
27194  if(this.getChipIndex() == this.parentController.selectedChip &&
27195    this.enableChipEdit &&
27196    !this.isEditting) {
27197    this.goInEditMode();
27198  }
27199};
27200
27201})();
27202(function(){
27203"use strict";
27204
27205
27206MdChip.$inject = ["$mdTheming", "$mdUtil", "$compile", "$timeout"];angular
27207  .module('material.components.chips')
27208  .directive('mdChip', MdChip);
27209
27210/**
27211 * @ngdoc directive
27212 * @name mdChip
27213 * @module material.components.chips
27214 *
27215 * @description
27216 * `<md-chip>` is a component used within `<md-chips>` and is responsible for rendering individual
27217 * chips.
27218 *
27219 *
27220 * @usage
27221 * <hljs lang="html">
27222 *   <md-chip>{{$chip}}</md-chip>
27223 * </hljs>
27224 *
27225 */
27226
27227// This hint text is hidden within a chip but used by screen readers to
27228// inform the user how they can interact with a chip.
27229var DELETE_HINT_TEMPLATE = '\
27230    <span ng-if="!$mdChipsCtrl.readonly" class="md-visually-hidden">\
27231      {{$mdChipsCtrl.deleteHint}}\
27232    </span>';
27233
27234/**
27235 * MDChip Directive Definition
27236 *
27237 * @param $mdTheming
27238 * @param $mdUtil
27239 * @ngInject
27240 */
27241function MdChip($mdTheming, $mdUtil, $compile, $timeout) {
27242  var deleteHintTemplate = $mdUtil.processTemplate(DELETE_HINT_TEMPLATE);
27243
27244  return {
27245    restrict: 'E',
27246    require: ['^?mdChips', 'mdChip'],
27247    link: postLink,
27248    controller: 'MdChipCtrl'
27249  };
27250
27251  function postLink(scope, element, attr, ctrls) {
27252    var chipsController = ctrls.shift();
27253    var chipController = ctrls.shift();
27254    var chipContentElement = angular.element(element[0].querySelector('.md-chip-content'));
27255
27256    $mdTheming(element);
27257
27258    if (chipsController) {
27259      chipController.init(chipsController);
27260
27261      // Append our delete hint to the div.md-chip-content (which does not exist at compile time)
27262      chipContentElement.append($compile(deleteHintTemplate)(scope));
27263
27264      // When a chip is blurred, make sure to unset (or reset) the selected chip so that tabbing
27265      // through elements works properly
27266      chipContentElement.on('blur', function() {
27267        chipsController.resetSelectedChip();
27268        chipsController.$scope.$applyAsync();
27269      });
27270    }
27271
27272    // Use $timeout to ensure we run AFTER the element has been added to the DOM so we can focus it.
27273    $timeout(function() {
27274      if (!chipsController) {
27275        return;
27276      }
27277
27278      if (chipsController.shouldFocusLastChip) {
27279        chipsController.focusLastChipThenInput();
27280      }
27281    });
27282  }
27283}
27284
27285})();
27286(function(){
27287"use strict";
27288
27289
27290MdChipRemove.$inject = ["$timeout"];angular
27291    .module('material.components.chips')
27292    .directive('mdChipRemove', MdChipRemove);
27293
27294/**
27295 * @ngdoc directive
27296 * @name mdChipRemove
27297 * @restrict A
27298 * @module material.components.chips
27299 *
27300 * @description
27301 * Designates an element to be used as the delete button for a chip. <br/>
27302 * This element is passed as a child of the `md-chips` element.
27303 *
27304 * The designated button will be just appended to the chip and removes the given chip on click.<br/>
27305 * By default the button is not being styled by the `md-chips` component.
27306 *
27307 * @usage
27308 * <hljs lang="html">
27309 *   <md-chips>
27310 *     <button md-chip-remove="">
27311 *       <md-icon md-svg-icon="md-close"></md-icon>
27312 *     </button>
27313 *   </md-chips>
27314 * </hljs>
27315 */
27316
27317
27318/**
27319 * MdChipRemove Directive Definition.
27320 * 
27321 * @param $timeout
27322 * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}
27323 * @constructor
27324 */
27325function MdChipRemove ($timeout) {
27326  return {
27327    restrict: 'A',
27328    require: '^mdChips',
27329    scope: false,
27330    link: postLink
27331  };
27332
27333  function postLink(scope, element, attr, ctrl) {
27334    element.on('click', function(event) {
27335      scope.$apply(function() {
27336        ctrl.removeChip(scope.$$replacedScope.$index);
27337      });
27338    });
27339
27340    // Child elements aren't available until after a $timeout tick as they are hidden by an
27341    // `ng-if`. see http://goo.gl/zIWfuw
27342    $timeout(function() {
27343      element.attr({ tabindex: -1, 'aria-hidden': true });
27344      element.find('button').attr('tabindex', '-1');
27345    });
27346  }
27347}
27348
27349})();
27350(function(){
27351"use strict";
27352
27353
27354MdChipTransclude.$inject = ["$compile"];angular
27355    .module('material.components.chips')
27356    .directive('mdChipTransclude', MdChipTransclude);
27357
27358function MdChipTransclude ($compile) {
27359  return {
27360    restrict: 'EA',
27361    terminal: true,
27362    link: link,
27363    scope: false
27364  };
27365  function link (scope, element, attr) {
27366    var ctrl = scope.$parent.$mdChipsCtrl,
27367        newScope = ctrl.parent.$new(false, ctrl.parent);
27368    newScope.$$replacedScope = scope;
27369    newScope.$chip = scope.$chip;
27370    newScope.$index = scope.$index;
27371    newScope.$mdChipsCtrl = ctrl;
27372
27373    var newHtml = ctrl.$scope.$eval(attr.mdChipTransclude);
27374
27375    element.html(newHtml);
27376    $compile(element.contents())(newScope);
27377  }
27378}
27379
27380})();
27381(function(){
27382"use strict";
27383
27384/**
27385 * The default chip append delay.
27386 *
27387 * @type {number}
27388 */
27389MdChipsCtrl.$inject = ["$scope", "$attrs", "$mdConstant", "$log", "$element", "$timeout", "$mdUtil"];
27390var DEFAULT_CHIP_APPEND_DELAY = 300;
27391
27392angular
27393    .module('material.components.chips')
27394    .controller('MdChipsCtrl', MdChipsCtrl);
27395
27396/**
27397 * Controller for the MdChips component. Responsible for adding to and
27398 * removing from the list of chips, marking chips as selected, and binding to
27399 * the models of various input components.
27400 *
27401 * @param $scope
27402 * @param $attrs
27403 * @param $mdConstant
27404 * @param $log
27405 * @param $element
27406 * @param $timeout
27407 * @param $mdUtil
27408 * @constructor
27409 */
27410function MdChipsCtrl ($scope, $attrs, $mdConstant, $log, $element, $timeout, $mdUtil) {
27411  /** @type {$timeout} **/
27412  this.$timeout = $timeout;
27413
27414  /** @type {Object} */
27415  this.$mdConstant = $mdConstant;
27416
27417  /** @type {angular.$scope} */
27418  this.$scope = $scope;
27419
27420  /** @type {angular.$scope} */
27421  this.parent = $scope.$parent;
27422
27423  /** @type {$mdUtil} */
27424  this.$mdUtil = $mdUtil;
27425
27426  /** @type {$log} */
27427  this.$log = $log;
27428
27429  /** @type {$element} */
27430  this.$element = $element;
27431
27432  /** @type {$attrs} */
27433  this.$attrs = $attrs;
27434
27435  /** @type {angular.NgModelController} */
27436  this.ngModelCtrl = null;
27437
27438  /** @type {angular.NgModelController} */
27439  this.userInputNgModelCtrl = null;
27440
27441  /** @type {MdAutocompleteCtrl} */
27442  this.autocompleteCtrl = null;
27443
27444  /** @type {Element} */
27445  this.userInputElement = null;
27446
27447  /** @type {Array.<Object>} */
27448  this.items = [];
27449
27450  /** @type {number} */
27451  this.selectedChip = -1;
27452
27453  /** @type {string} */
27454  this.enableChipEdit = $mdUtil.parseAttributeBoolean($attrs.mdEnableChipEdit);
27455
27456  /** @type {string} */
vendor: 12,207 bytes, lines 27457-27860
27457  this.addOnBlur = $mdUtil.parseAttributeBoolean($attrs.mdAddOnBlur);
27458
27459  /**
27460   * The text to be used as the aria-label for the input.
27461   * @type {string}
27462   */
27463  this.inputAriaLabel = 'Chips input.';
27464
27465  /**
27466   * Hidden hint text to describe the chips container. Used to give context to screen readers when
27467   * the chips are readonly and the input cannot be selected.
27468   *
27469   * @type {string}
27470   */
27471  this.containerHint = 'Chips container. Use arrow keys to select chips.';
27472
27473  /**
27474   * Hidden hint text for how to delete a chip. Used to give context to screen readers.
27475   * @type {string}
27476   */
27477  this.deleteHint = 'Press delete to remove this chip.';
27478
27479  /**
27480   * Hidden label for the delete button. Used to give context to screen readers.
27481   * @type {string}
27482   */
27483  this.deleteButtonLabel = 'Remove';
27484
27485  /**
27486   * Model used by the input element.
27487   * @type {string}
27488   */
27489  this.chipBuffer = '';
27490
27491  /**
27492   * Whether to use the transformChip expression to transform the chip buffer
27493   * before appending it to the list.
27494   * @type {boolean}
27495   */
27496  this.useTransformChip = false;
27497
27498  /**
27499   * Whether to use the onAdd expression to notify of chip additions.
27500   * @type {boolean}
27501   */
27502  this.useOnAdd = false;
27503
27504  /**
27505   * Whether to use the onRemove expression to notify of chip removals.
27506   * @type {boolean}
27507   */
27508  this.useOnRemove = false;
27509
27510  /**
27511   * The ID of the chips wrapper which is used to build unique IDs for the chips and the aria-owns
27512   * attribute.
27513   *
27514   * Defaults to '_md-chips-wrapper-' plus a unique number.
27515   *
27516   * @type {string}
27517   */
27518  this.wrapperId = '';
27519
27520  /**
27521   * Array of unique numbers which will be auto-generated any time the items change, and is used to
27522   * create unique IDs for the aria-owns attribute.
27523   *
27524   * @type {Array}
27525   */
27526  this.contentIds = [];
27527
27528  /**
27529   * The index of the chip that should have it's tabindex property set to 0 so it is selectable
27530   * via the keyboard.
27531   *
27532   * @type {int}
27533   */
27534  this.ariaTabIndex = null;
27535
27536  /**
27537   * After appending a chip, the chip will be focused for this number of milliseconds before the
27538   * input is refocused.
27539   *
27540   * **Note:** This is **required** for compatibility with certain screen readers in order for
27541   * them to properly allow keyboard access.
27542   *
27543   * @type {number}
27544   */
27545  this.chipAppendDelay = DEFAULT_CHIP_APPEND_DELAY;
27546
27547  this.init();
27548}
27549
27550/**
27551 * Initializes variables and sets up watchers
27552 */
27553MdChipsCtrl.prototype.init = function() {
27554  var ctrl = this;
27555
27556  // Set the wrapper ID
27557  ctrl.wrapperId = '_md-chips-wrapper-' + ctrl.$mdUtil.nextUid();
27558
27559  // Setup a watcher which manages the role and aria-owns attributes
27560  ctrl.$scope.$watchCollection('$mdChipsCtrl.items', function() {
27561    // Make sure our input and wrapper have the correct ARIA attributes
27562    ctrl.setupInputAria();
27563    ctrl.setupWrapperAria();
27564  });
27565
27566  ctrl.$attrs.$observe('mdChipAppendDelay', function(newValue) {
27567    ctrl.chipAppendDelay = parseInt(newValue) || DEFAULT_CHIP_APPEND_DELAY;
27568  });
27569};
27570
27571/**
27572 * If we have an input, ensure it has the appropriate ARIA attributes.
27573 */
27574MdChipsCtrl.prototype.setupInputAria = function() {
27575  var input = this.$element.find('input');
27576
27577  // If we have no input, just return
27578  if (!input) {
27579    return;
27580  }
27581
27582  input.attr('role', 'textbox');
27583  input.attr('aria-multiline', true);
27584};
27585
27586/**
27587 * Ensure our wrapper has the appropriate ARIA attributes.
27588 */
27589MdChipsCtrl.prototype.setupWrapperAria = function() {
27590  var ctrl = this,
27591      wrapper = this.$element.find('md-chips-wrap');
27592
27593  if (this.items && this.items.length) {
27594    // Dynamically add the listbox role on every change because it must be removed when there are
27595    // no items.
27596    wrapper.attr('role', 'listbox');
27597
27598    // Generate some random (but unique) IDs for each chip
27599    this.contentIds = this.items.map(function() {
27600      return ctrl.wrapperId + '-chip-' + ctrl.$mdUtil.nextUid();
27601    });
27602
27603    // Use the contentIDs above to generate the aria-owns attribute
27604    wrapper.attr('aria-owns', this.contentIds.join(' '));
27605  } else {
27606    // If we have no items, then the role and aria-owns attributes MUST be removed
27607    wrapper.removeAttr('role');
27608    wrapper.removeAttr('aria-owns');
27609  }
27610};
27611
27612/**
27613 * Handles the keydown event on the input element: by default <enter> appends
27614 * the buffer to the chip list, while backspace removes the last chip in the
27615 * list if the current buffer is empty.
27616 * @param event
27617 */
27618MdChipsCtrl.prototype.inputKeydown = function(event) {
27619  var chipBuffer = this.getChipBuffer();
27620
27621  // If we have an autocomplete, and it handled the event, we have nothing to do
27622  if (this.autocompleteCtrl && event.isDefaultPrevented && event.isDefaultPrevented()) {
27623    return;
27624  }
27625
27626  if (event.keyCode === this.$mdConstant.KEY_CODE.BACKSPACE) {
27627    // Only select and focus the previous chip, if the current caret position of the
27628    // input element is at the beginning.
27629    if (this.getCursorPosition(event.target) !== 0) {
27630      return;
27631    }
27632
27633    event.preventDefault();
27634    event.stopPropagation();
27635
27636    if (this.items.length) {
27637      this.selectAndFocusChipSafe(this.items.length - 1);
27638    }
27639
27640    return;
27641  }
27642
27643  // By default <enter> appends the buffer to the chip list.
27644  if (!this.separatorKeys || this.separatorKeys.length < 1) {
27645    this.separatorKeys = [this.$mdConstant.KEY_CODE.ENTER];
27646  }
27647
27648  // Support additional separator key codes in an array of `md-separator-keys`.
27649  if (this.separatorKeys.indexOf(event.keyCode) !== -1) {
27650    if ((this.autocompleteCtrl && this.requireMatch) || !chipBuffer) return;
27651    event.preventDefault();
27652
27653    // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
27654    if (this.hasMaxChipsReached()) return;
27655
27656    this.appendChip(chipBuffer.trim());
27657    this.resetChipBuffer();
27658
27659    return false;
27660  }
27661};
27662
27663/**
27664 * Returns the cursor position of the specified input element.
27665 * @param element HTMLInputElement
27666 * @returns {Number} Cursor Position of the input.
27667 */
27668MdChipsCtrl.prototype.getCursorPosition = function(element) {
27669  /*
27670   * Figure out whether the current input for the chips buffer is valid for using
27671   * the selectionStart / end property to retrieve the cursor position.
27672   * Some browsers do not allow the use of those attributes, on different input types.
27673   */
27674  try {
27675    if (element.selectionStart === element.selectionEnd) {
27676      return element.selectionStart;
27677    }
27678  } catch (e) {
27679    if (!element.value) {
27680      return 0;
27681    }
27682  }
27683};
27684
27685
27686/**
27687 * Updates the content of the chip at given index
27688 * @param chipIndex
27689 * @param chipContents
27690 */
27691MdChipsCtrl.prototype.updateChipContents = function(chipIndex, chipContents){
27692  if(chipIndex >= 0 && chipIndex < this.items.length) {
27693    this.items[chipIndex] = chipContents;
27694    this.ngModelCtrl.$setDirty();
27695  }
27696};
27697
27698
27699/**
27700 * Returns true if a chip is currently being edited. False otherwise.
27701 * @return {boolean}
27702 */
27703MdChipsCtrl.prototype.isEditingChip = function() {
27704  return !!this.$element[0].querySelector('._md-chip-editing');
27705};
27706
27707
27708MdChipsCtrl.prototype.isRemovable = function() {
27709  // Return false if we have static chips
27710  if (!this.ngModelCtrl) {
27711    return false;
27712  }
27713
27714  return this.readonly ? this.removable :
27715         angular.isDefined(this.removable) ? this.removable : true;
27716};
27717
27718/**
27719 * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
27720 * keys switch which chips is active
27721 * @param event
27722 */
27723MdChipsCtrl.prototype.chipKeydown = function (event) {
27724  if (this.getChipBuffer()) return;
27725  if (this.isEditingChip()) return;
27726
27727  switch (event.keyCode) {
27728    case this.$mdConstant.KEY_CODE.BACKSPACE:
27729    case this.$mdConstant.KEY_CODE.DELETE:
27730      if (this.selectedChip < 0) return;
27731      event.preventDefault();
27732      // Cancel the delete action only after the event cancel. Otherwise the page will go back.
27733      if (!this.isRemovable()) return;
27734      this.removeAndSelectAdjacentChip(this.selectedChip);
27735      break;
27736    case this.$mdConstant.KEY_CODE.LEFT_ARROW:
27737      event.preventDefault();
27738      // By default, allow selection of -1 which will focus the input; if we're readonly, don't go
27739      // below 0
27740      if (this.selectedChip < 0 || (this.readonly && this.selectedChip == 0)) {
27741        this.selectedChip = this.items.length;
27742      }
27743      if (this.items.length) this.selectAndFocusChipSafe(this.selectedChip - 1);
27744      break;
27745    case this.$mdConstant.KEY_CODE.RIGHT_ARROW:
27746      event.preventDefault();
27747      this.selectAndFocusChipSafe(this.selectedChip + 1);
27748      break;
27749    case this.$mdConstant.KEY_CODE.ESCAPE:
27750    case this.$mdConstant.KEY_CODE.TAB:
27751      if (this.selectedChip < 0) return;
27752      event.preventDefault();
27753      this.onFocus();
27754      break;
27755  }
27756};
27757
27758/**
27759 * Get the input's placeholder - uses `placeholder` when list is empty and `secondary-placeholder`
27760 * when the list is non-empty. If `secondary-placeholder` is not provided, `placeholder` is used
27761 * always.
27762 */
27763MdChipsCtrl.prototype.getPlaceholder = function() {
27764  // Allow `secondary-placeholder` to be blank.
27765  var useSecondary = (this.items && this.items.length &&
27766      (this.secondaryPlaceholder == '' || this.secondaryPlaceholder));
27767  return useSecondary ? this.secondaryPlaceholder : this.placeholder;
27768};
27769
27770/**
27771 * Removes chip at {@code index} and selects the adjacent chip.
27772 * @param index
27773 */
27774MdChipsCtrl.prototype.removeAndSelectAdjacentChip = function(index) {
27775  var self = this;
27776  var selIndex = self.getAdjacentChipIndex(index);
27777  var wrap = this.$element[0].querySelector('md-chips-wrap');
27778  var chip = this.$element[0].querySelector('md-chip[index="' + index + '"]');
27779
27780  self.removeChip(index);
27781
27782  // The dobule-timeout is currently necessary to ensure that the DOM has finalized and the select()
27783  // will find the proper chip since the selection is index-based.
27784  //
27785  // TODO: Investigate calling from within chip $scope.$on('$destroy') to reduce/remove timeouts
27786  self.$timeout(function() {
27787    self.$timeout(function() {
27788      self.selectAndFocusChipSafe(selIndex);
27789    });
27790  });
27791};
27792
27793/**
27794 * Sets the selected chip index to -1.
27795 */
27796MdChipsCtrl.prototype.resetSelectedChip = function() {
27797  this.selectedChip = -1;
27798  this.ariaTabIndex = null;
27799};
27800
27801/**
27802 * Gets the index of an adjacent chip to select after deletion. Adjacency is
27803 * determined as the next chip in the list, unless the target chip is the
27804 * last in the list, then it is the chip immediately preceding the target. If
27805 * there is only one item in the list, -1 is returned (select none).
27806 * The number returned is the index to select AFTER the target has been
27807 * removed.
27808 * If the current chip is not selected, then -1 is returned to select none.
27809 */
27810MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {
27811  var len = this.items.length - 1;
27812  return (len == 0) ? -1 :
27813      (index == len) ? index -1 : index;
27814};
27815
27816/**
27817 * Append the contents of the buffer to the chip list. This method will first
27818 * call out to the md-transform-chip method, if provided.
27819 *
27820 * @param newChip
27821 */
27822MdChipsCtrl.prototype.appendChip = function(newChip) {
27823  this.shouldFocusLastChip = true;
27824  if (this.useTransformChip && this.transformChip) {
27825    var transformedChip = this.transformChip({'$chip': newChip});
27826
27827    // Check to make sure the chip is defined before assigning it, otherwise, we'll just assume
27828    // they want the string version.
27829    if (angular.isDefined(transformedChip)) {
27830      newChip = transformedChip;
27831    }
27832  }
27833
27834  // If items contains an identical object to newChip, do not append
27835  if (angular.isObject(newChip)){
27836    var identical = this.items.some(function(item){
27837      return angular.equals(newChip, item);
27838    });
27839    if (identical) return;
27840  }
27841
27842  // Check for a null (but not undefined), or existing chip and cancel appending
27843  if (newChip == null || this.items.indexOf(newChip) + 1) return;
27844
27845  // Append the new chip onto our list
27846  var length = this.items.push(newChip);
27847  var index = length - 1;
27848
27849  // Update model validation
27850  this.ngModelCtrl.$setDirty();
27851  this.validateModel();
27852
27853  // If they provide the md-on-add attribute, notify them of the chip addition
27854  if (this.useOnAdd && this.onAdd) {
27855    this.onAdd({ '$chip': newChip, '$index': index });
27856  }
27857};
27858
27859/**
27860 * Sets whether to use the md-transform-chip expression. This expression is
27861 * bound to scope and controller in {@code MdChipsDirective} as
27862 * {@code transformChip}. Due to the nature of directive scope bindings, the
27863 * controller cannot know on its own/from the scope whether an expression was
27864 * actually provided.
27865 */
27866MdChipsCtrl.prototype.useTransformChipExpression = function() {
27867  this.useTransformChip = true;
27868};
27869
27870/**
27871 * Sets whether to use the md-on-add expression. This expression is
27872 * bound to scope and controller in {@code MdChipsDirective} as
27873 * {@code onAdd}. Due to the nature of directive scope bindings, the
27874 * controller cannot know on its own/from the scope whether an expression was
27875 * actually provided.
27876 */
27877MdChipsCtrl.prototype.useOnAddExpression = function() {
27878  this.useOnAdd = true;
27879};
27880
27881/**
27882 * Sets whether to use the md-on-remove expression. This expression is
27883 * bound to scope and controller in {@code MdChipsDirective} as
27884 * {@code onRemove}. Due to the nature of directive scope bindings, the
27885 * controller cannot know on its own/from the scope whether an expression was
27886 * actually provided.
27887 */
27888MdChipsCtrl.prototype.useOnRemoveExpression = function() {
27889  this.useOnRemove = true;
27890};
27891
27892/*
27893 * Sets whether to use the md-on-select expression. This expression is
27894 * bound to scope and controller in {@code MdChipsDirective} as
27895 * {@code onSelect}. Due to the nature of directive scope bindings, the
27896 * controller cannot know on its own/from the scope whether an expression was
27897 * actually provided.
27898 */
27899MdChipsCtrl.prototype.useOnSelectExpression = function() {
27900  this.useOnSelect = true;
27901};
27902
27903/**
27904 * Gets the input buffer. The input buffer can be the model bound to the
27905 * default input item {@code this.chipBuffer}, the {@code selectedItem}
27906 * model of an {@code md-autocomplete}, or, through some magic, the model
27907 * bound to any inpput or text area element found within a
27908 * {@code md-input-container} element.
27909 * @return {string}
27910 */
27911MdChipsCtrl.prototype.getChipBuffer = function() {
27912  var chipBuffer =  !this.userInputElement ? this.chipBuffer :
27913                     this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :
27914                     this.userInputElement[0].value;
27915
27916  // Ensure that the chip buffer is always a string. For example, the input element buffer might be falsy.
27917  return angular.isString(chipBuffer) ? chipBuffer : '';
27918};
27919
27920/**
27921 * Resets the input buffer for either the internal input or user provided input element.
27922 */
27923MdChipsCtrl.prototype.resetChipBuffer = function() {
27924  if (this.userInputElement) {
27925    if (this.userInputNgModelCtrl) {
27926      this.userInputNgModelCtrl.$setViewValue('');
27927      this.userInputNgModelCtrl.$render();
27928    } else {
27929      this.userInputElement[0].value = '';
27930    }
27931  } else {
27932    this.chipBuffer = '';
27933  }
27934};
27935
27936MdChipsCtrl.prototype.hasMaxChipsReached = function() {
27937  if (angular.isString(this.maxChips)) this.maxChips = parseInt(this.maxChips, 10) || 0;
27938
27939  return this.maxChips > 0 && this.items.length >= this.maxChips;
27940};
27941
27942/**
27943 * Updates the validity properties for the ngModel.
27944 */
27945MdChipsCtrl.prototype.validateModel = function() {
27946  this.ngModelCtrl.$setValidity('md-max-chips', !this.hasMaxChipsReached());
27947};
27948
27949/**
27950 * Removes the chip at the given index.
27951 * @param index
27952 */
27953MdChipsCtrl.prototype.removeChip = function(index) {
27954  var removed = this.items.splice(index, 1);
27955
27956  // Update model validation
27957  this.ngModelCtrl.$setDirty();
27958  this.validateModel();
27959
27960  if (removed && removed.length && this.useOnRemove && this.onRemove) {
27961    this.onRemove({ '$chip': removed[0], '$index': index });
27962  }
27963};
27964
27965MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {
27966  this.removeChip(index);
27967
27968  if (this.autocompleteCtrl) {
27969    // Always hide the autocomplete dropdown before focusing the autocomplete input.
27970    // Wait for the input to move horizontally, because the chip was removed.
27971    // This can lead to an incorrect dropdown position.
27972    this.autocompleteCtrl.hidden = true;
27973    this.$mdUtil.nextTick(this.onFocus.bind(this));
27974  } else {
27975    this.onFocus();
27976  }
27977
27978};
27979/**
27980 * Selects the chip at `index`,
27981 * @param index
27982 */
27983MdChipsCtrl.prototype.selectAndFocusChipSafe = function(index) {
27984  // If we have no chips, or are asked to select a chip before the first, just focus the input
27985  if (!this.items.length || index === -1) {
27986    return this.focusInput();
27987  }
27988
27989  // If we are asked to select a chip greater than the number of chips...
27990  if (index >= this.items.length) {
27991    if (this.readonly) {
27992      // If we are readonly, jump back to the start (because we have no input)
27993      index = 0;
27994    } else {
27995      // If we are not readonly, we should attempt to focus the input
27996      return this.onFocus();
27997    }
27998  }
27999
28000  index = Math.max(index, 0);
28001  index = Math.min(index, this.items.length - 1);
28002
28003  this.selectChip(index);
28004  this.focusChip(index);
28005};
28006
vendor: 4,438 bytes, lines 28007-28169
28007MdChipsCtrl.prototype.focusLastChipThenInput = function() {
28008  var ctrl = this;
28009
28010  ctrl.shouldFocusLastChip = false;
28011
28012  ctrl.focusChip(this.items.length - 1);
28013
28014  ctrl.$timeout(function() {
28015    ctrl.focusInput();
28016  }, ctrl.chipAppendDelay);
28017};
28018
28019MdChipsCtrl.prototype.focusInput = function() {
28020  this.selectChip(-1);
28021  this.onFocus();
28022};
28023
28024/**
28025 * Marks the chip at the given index as selected.
28026 * @param index
28027 */
28028MdChipsCtrl.prototype.selectChip = function(index) {
28029  if (index >= -1 && index <= this.items.length) {
28030    this.selectedChip = index;
28031
28032    // Fire the onSelect if provided
28033    if (this.useOnSelect && this.onSelect) {
28034      this.onSelect({'$chip': this.items[index] });
28035    }
28036  } else {
28037    this.$log.warn('Selected Chip index out of bounds; ignoring.');
28038  }
28039};
28040
28041/**
28042 * Selects the chip at `index` and gives it focus.
28043 * @param index
28044 */
28045MdChipsCtrl.prototype.selectAndFocusChip = function(index) {
28046  this.selectChip(index);
28047  if (index != -1) {
28048    this.focusChip(index);
28049  }
28050};
28051
28052/**
28053 * Call `focus()` on the chip at `index`
28054 */
28055MdChipsCtrl.prototype.focusChip = function(index) {
28056  var chipContent = this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content');
28057
28058  this.ariaTabIndex = index;
28059
28060  chipContent.focus();
28061};
28062
28063/**
28064 * Configures the required interactions with the ngModel Controller.
28065 * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.
28066 * @param ngModelCtrl
28067 */
28068MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {
28069  this.ngModelCtrl = ngModelCtrl;
28070
28071  var self = this;
28072  ngModelCtrl.$render = function() {
28073    // model is updated. do something.
28074    self.items = self.ngModelCtrl.$viewValue;
28075  };
28076};
28077
28078MdChipsCtrl.prototype.onFocus = function () {
28079  var input = this.$element[0].querySelector('input');
28080  input && input.focus();
28081  this.resetSelectedChip();
28082};
28083
28084MdChipsCtrl.prototype.onInputFocus = function () {
28085  this.inputHasFocus = true;
28086
28087  // Make sure we have the appropriate ARIA attributes
28088  this.setupInputAria();
28089
28090  // Make sure we don't have any chips selected
28091  this.resetSelectedChip();
28092};
28093
28094MdChipsCtrl.prototype.onInputBlur = function () {
28095  this.inputHasFocus = false;
28096
28097  if (this.shouldAddOnBlur()) {
28098    this.appendChip(this.getChipBuffer().trim());
28099    this.resetChipBuffer();
28100  }
28101};
28102
28103/**
28104 * Configure event bindings on a user-provided input element.
28105 * @param inputElement
28106 */
28107MdChipsCtrl.prototype.configureUserInput = function(inputElement) {
28108  this.userInputElement = inputElement;
28109
28110  // Find the NgModelCtrl for the input element
28111  var ngModelCtrl = inputElement.controller('ngModel');
28112  // `.controller` will look in the parent as well.
28113  if (ngModelCtrl != this.ngModelCtrl) {
28114    this.userInputNgModelCtrl = ngModelCtrl;
28115  }
28116
28117  var scope = this.$scope;
28118  var ctrl = this;
28119
28120  // Run all of the events using evalAsync because a focus may fire a blur in the same digest loop
28121  var scopeApplyFn = function(event, fn) {
28122    scope.$evalAsync(angular.bind(ctrl, fn, event));
28123  };
28124
28125  // Bind to keydown and focus events of input
28126  inputElement
28127      .attr({ tabindex: 0 })
28128      .on('keydown', function(event) { scopeApplyFn(event, ctrl.inputKeydown) })
28129      .on('focus', function(event) { scopeApplyFn(event, ctrl.onInputFocus) })
28130      .on('blur', function(event) { scopeApplyFn(event, ctrl.onInputBlur) })
28131};
28132
28133MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {
28134  if (ctrl) {
28135    this.autocompleteCtrl = ctrl;
28136
28137    ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {
28138      if (item) {
28139        // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
28140        if (this.hasMaxChipsReached()) return;
28141
28142        this.appendChip(item);
28143        this.resetChipBuffer();
28144      }
28145    }));
28146
28147    this.$element.find('input')
28148        .on('focus',angular.bind(this, this.onInputFocus) )
28149        .on('blur', angular.bind(this, this.onInputBlur) );
28150  }
28151};
28152
28153/**
28154 * Whether the current chip buffer should be added on input blur or not.
28155 * @returns {boolean}
28156 */
28157MdChipsCtrl.prototype.shouldAddOnBlur = function() {
28158
28159  // Update the custom ngModel validators from the chips component.
28160  this.validateModel();
28161
28162  var chipBuffer = this.getChipBuffer().trim();
28163  var isModelValid = this.ngModelCtrl.$valid;
28164  var isAutocompleteShowing = this.autocompleteCtrl && !this.autocompleteCtrl.hidden;
28165
28166  if (this.userInputNgModelCtrl) {
28167    isModelValid = isModelValid && this.userInputNgModelCtrl.$valid;
28168  }
28169
28170  return this.addOnBlur && !this.requireMatch && chipBuffer && isModelValid && !isAutocompleteShowing;
28171};
28172
28173MdChipsCtrl.prototype.hasFocus = function () {
28174  return this.inputHasFocus || this.selectedChip >= 0;
28175};
28176
28177MdChipsCtrl.prototype.contentIdFor = function(index) {
28178  return this.contentIds[index];
28179};
28180
28181})();
28182(function(){
28183"use strict";
28184
28185  
28186  MdChips.$inject = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout", "$$mdSvgRegistry"];angular
28187      .module('material.components.chips')
28188      .directive('mdChips', MdChips);
28189
28190  /**
28191   * @ngdoc directive
28192   * @name mdChips
28193   * @module material.components.chips
28194   *
28195   * @description
28196   * `<md-chips>` is an input component for building lists of strings or objects. The list items are
28197   * displayed as 'chips'. This component can make use of an `<input>` element or an 
28198   * `<md-autocomplete>` element.
28199   *
28200   * ### Custom templates
28201   * A custom template may be provided to render the content of each chip. This is achieved by
28202   * specifying an `<md-chip-template>` element containing the custom content as a child of
28203   * `<md-chips>`.
28204   *
28205   * Note: Any attributes on
28206   * `<md-chip-template>` will be dropped as only the innerHTML is used for the chip template. The
28207   * variables `$chip` and `$index` are available in the scope of `<md-chip-template>`, representing
28208   * the chip object and its index in the list of chips, respectively.
28209   * To override the chip delete control, include an element (ideally a button) with the attribute
28210   * `md-chip-remove`. A click listener to remove the chip will be added automatically. The element
28211   * is also placed as a sibling to the chip content (on which there are also click listeners) to
28212   * avoid a nested ng-click situation.
28213   *
28214   * <!-- Note: We no longer want to include this in the site docs; but it should remain here for
28215   * future developers and those looking at the documentation.
28216   *
28217   * <h3> Pending Features </h3>
28218   * <ul style="padding-left:20px;">
28219   *
28220   *   <ul>Style
28221   *     <li>Colours for hover, press states (ripple?).</li>
28222   *   </ul>
28223   *
28224   *   <ul>Validation
28225   *     <li>allow a validation callback</li>
28226   *     <li>highlighting style for invalid chips</li>
28227   *   </ul>
28228   *
28229   *   <ul>Item mutation
28230   *     <li>Support `
28231   *       <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double
28232   *       click?
28233   *     </li>
28234   *   </ul>
28235   *
28236   *   <ul>Truncation and Disambiguation (?)
28237   *     <li>Truncate chip text where possible, but do not truncate entries such that two are
28238   *     indistinguishable.</li>
28239   *   </ul>
28240   *
28241   *   <ul>Drag and Drop
28242   *     <li>Drag and drop chips between related `<md-chips>` elements.
28243   *     </li>
28244   *   </ul>
28245   * </ul>
28246   *
28247   * //-->
28248   *
28249   * Sometimes developers want to limit the amount of possible chips.<br/>
28250   * You can specify the maximum amount of chips by using the following markup.
28251   *
28252   * <hljs lang="html">
28253   *   <md-chips
28254   *       ng-model="myItems"
28255   *       placeholder="Add an item"
28256   *       md-max-chips="5">
28257   *   </md-chips>
28258   * </hljs>
28259   *
28260   * In some cases, you have an autocomplete inside of the `md-chips`.<br/>
28261   * When the maximum amount of chips has been reached, you can also disable the autocomplete selection.<br/>
28262   * Here is an example markup.
28263   *
28264   * <hljs lang="html">
28265   *   <md-chips ng-model="myItems" md-max-chips="5">
28266   *     <md-autocomplete ng-hide="myItems.length > 5" ...></md-autocomplete>
28267   *   </md-chips>
28268   * </hljs>
28269   *
28270   * ### Accessibility
28271   *
28272   * The `md-chips` component supports keyboard and screen reader users since Version 1.1.2. In
28273   * order to achieve this, we modified the chips behavior to select newly appended chips for
28274   * `300ms` before re-focusing the input and allowing the user to type.
28275   *
28276   * For most users, this delay is small enough that it will not be noticeable but allows certain
28277   * screen readers to function properly (JAWS and NVDA in particular).
28278   *
28279   * We introduced a new `md-chip-append-delay` option to allow developers to better control this
28280   * behavior.
28281   *
28282   * Please refer to the documentation of this option (below) for more information.
28283   *
28284   * @param {string=|object=} ng-model A model to which the list of items will be bound.
28285   * @param {string=} placeholder Placeholder text that will be forwarded to the input.
28286   * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
28287   *    displayed when there is at least one item in the list
28288   * @param {boolean=} md-removable Enables or disables the deletion of chips through the
28289   *    removal icon or the Delete/Backspace key. Defaults to true.
28290   * @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
28291   *    the input and delete buttons. If no `ng-model` is provided, the chips will automatically be
28292   *    marked as readonly.<br/><br/>
28293   *    When `md-removable` is not defined, the `md-remove` behavior will be overwritten and disabled.
28294   * @param {string=} md-enable-chip-edit Set this to "true" to enable editing of chip contents. The user can 
28295   *    go into edit mode with pressing "space", "enter", or double clicking on the chip. Chip edit is only
28296   *    supported for chips with basic template.
28297   * @param {number=} md-max-chips The maximum number of chips allowed to add through user input.
28298   *    <br/><br/>The validation property `md-max-chips` can be used when the max chips
28299   *    amount is reached.
28300   * @param {boolean=} md-add-on-blur When set to true, remaining text inside of the input will
28301   *    be converted into a new chip on blur.
28302   * @param {expression} md-transform-chip An expression of form `myFunction($chip)` that when called
28303   *    expects one of the following return values:
28304   *    - an object representing the `$chip` input string
28305   *    - `undefined` to simply add the `$chip` input string, or
28306   *    - `null` to prevent the chip from being appended
28307   * @param {expression=} md-on-add An expression which will be called when a chip has been
28308   *    added.
28309   * @param {expression=} md-on-remove An expression which will be called when a chip has been
28310   *    removed.
28311   * @param {expression=} md-on-select An expression which will be called when a chip is selected.
28312   * @param {boolean} md-require-match If true, and the chips template contains an autocomplete,
28313   *    only allow selection of pre-defined chips (i.e. you cannot add new ones).
28314   * @param {string=} input-aria-label A string read by screen readers to identify the input.
28315   * @param {string=} container-hint A string read by screen readers informing users of how to
28316   *    navigate the chips. Used in readonly mode.
28317   * @param {string=} delete-hint A string read by screen readers instructing users that pressing
28318   *    the delete key will remove the chip.
28319   * @param {string=} delete-button-label A label for the delete button. Also hidden and read by
28320   *    screen readers.
28321   * @param {expression=} md-separator-keys An array of key codes used to separate chips.
28322   * @param {string=} md-chip-append-delay The number of milliseconds that the component will select
28323   *    a newly appended chip before allowing a user to type into the input. This is **necessary**
28324   *    for keyboard accessibility for screen readers. It defaults to 300ms and any number less than
28325   *    300 can cause issues with screen readers (particularly JAWS and sometimes NVDA).
28326   *
28327   *    _Available since Version 1.1.2._
28328   *
28329   *    **Note:** You can safely set this to `0` in one of the following two instances:
28330   *
28331   *    1. You are targeting an iOS or Safari-only application (where users would use VoiceOver) or
28332   *    only ChromeVox users.
28333   *
28334   *    2. If you have utilized the `md-separator-keys` to disable the `enter` keystroke in
28335   *    favor of another one (such as `,` or `;`).
28336   *
28337   * @usage
28338   * <hljs lang="html">
28339   *   <md-chips
28340   *       ng-model="myItems"
28341   *       placeholder="Add an item"
28342   *       readonly="isReadOnly">
28343   *   </md-chips>
28344   * </hljs>
28345   *
28346   * <h3>Validation</h3>
28347   * When using [ngMessages](https://docs.angularjs.org/api/ngMessages), you can show errors based
28348   * on our custom validators.
28349   * <hljs lang="html">
28350   *   <form name="userForm">
28351   *     <md-chips
28352   *       name="fruits"
28353   *       ng-model="myItems"
28354   *       placeholder="Add an item"
28355   *       md-max-chips="5">
28356   *     </md-chips>
28357   *     <div ng-messages="userForm.fruits.$error" ng-if="userForm.$dirty">
28358   *       <div ng-message="md-max-chips">You reached the maximum amount of chips</div>
28359   *    </div>
28360   *   </form>
28361   * </hljs>
28362   *
28363   */
28364
28365  var MD_CHIPS_TEMPLATE = '\
28366      <md-chips-wrap\
28367          id="{{$mdChipsCtrl.wrapperId}}"\
28368          tabindex="{{$mdChipsCtrl.readonly ? 0 : -1}}"\
28369          ng-keydown="$mdChipsCtrl.chipKeydown($event)"\
28370          ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus(), \
28371                      \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly,\
28372                      \'md-removable\': $mdChipsCtrl.isRemovable() }"\
28373          aria-setsize="{{$mdChipsCtrl.items.length}}"\
28374          class="md-chips">\
28375        <span ng-if="$mdChipsCtrl.readonly" class="md-visually-hidden">\
28376          {{$mdChipsCtrl.containerHint}}\
28377        </span>\
28378        <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
28379            index="{{$index}}"\
28380            ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index, \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly}">\
28381          <div class="md-chip-content"\
28382              tabindex="{{$mdChipsCtrl.ariaTabIndex == $index ? 0 : -1}}"\
28383              id="{{$mdChipsCtrl.contentIdFor($index)}}"\
28384              role="option"\
28385              aria-selected="{{$mdChipsCtrl.selectedChip == $index}}" \
28386              aria-posinset="{{$index}}"\
28387              ng-click="!$mdChipsCtrl.readonly && $mdChipsCtrl.focusChip($index)"\
28388              ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)"\
28389              md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div>\
28390          <div ng-if="$mdChipsCtrl.isRemovable()"\
28391               class="md-chip-remove-container"\
28392               tabindex="-1"\
28393               md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\
28394        </md-chip>\
28395        <div class="md-chip-input-container" ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl">\
28396          <div md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\
28397        </div>\
28398      </md-chips-wrap>';
28399
28400  var CHIP_INPUT_TEMPLATE = '\
28401        <input\
28402            class="md-input"\
28403            tabindex="0"\
28404            aria-label="{{$mdChipsCtrl.inputAriaLabel}}" \
28405            placeholder="{{$mdChipsCtrl.getPlaceholder()}}"\
28406            ng-model="$mdChipsCtrl.chipBuffer"\
28407            ng-focus="$mdChipsCtrl.onInputFocus()"\
28408            ng-blur="$mdChipsCtrl.onInputBlur()"\
28409            ng-keydown="$mdChipsCtrl.inputKeydown($event)">';
28410
28411  var CHIP_DEFAULT_TEMPLATE = '\
vendor: 12,684 bytes, lines 28412-28745
28412      <span>{{$chip}}</span>';
28413
28414  var CHIP_REMOVE_TEMPLATE = '\
28415      <button\
28416          class="md-chip-remove"\
28417          ng-if="$mdChipsCtrl.isRemovable()"\
28418          ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
28419          type="button"\
28420          tabindex="-1">\
28421        <md-icon md-svg-src="{{ $mdChipsCtrl.mdCloseIcon }}"></md-icon>\
28422        <span class="md-visually-hidden">\
28423          {{$mdChipsCtrl.deleteButtonLabel}}\
28424        </span>\
28425      </button>';
28426
28427  /**
28428   * MDChips Directive Definition
28429   */
28430  function MdChips ($mdTheming, $mdUtil, $compile, $log, $timeout, $$mdSvgRegistry) {
28431    // Run our templates through $mdUtil.processTemplate() to allow custom start/end symbols
28432    var templates = getTemplates();
28433
28434    return {
28435      template: function(element, attrs) {
28436        // Clone the element into an attribute. By prepending the attribute
28437        // name with '$', AngularJS won't write it into the DOM. The cloned
28438        // element propagates to the link function via the attrs argument,
28439        // where various contained-elements can be consumed.
28440        attrs['$mdUserTemplate'] = element.clone();
28441        return templates.chips;
28442      },
28443      require: ['mdChips'],
28444      restrict: 'E',
28445      controller: 'MdChipsCtrl',
28446      controllerAs: '$mdChipsCtrl',
28447      bindToController: true,
28448      compile: compile,
28449      scope: {
28450        readonly: '=readonly',
28451        removable: '=mdRemovable',
28452        placeholder: '@',
28453        secondaryPlaceholder: '@',
28454        maxChips: '@mdMaxChips',
28455        transformChip: '&mdTransformChip',
28456        onAppend: '&mdOnAppend',
28457        onAdd: '&mdOnAdd',
28458        onRemove: '&mdOnRemove',
28459        onSelect: '&mdOnSelect',
28460        inputAriaLabel: '@',
28461        containerHint: '@',
28462        deleteHint: '@',
28463        deleteButtonLabel: '@',
28464        separatorKeys: '=?mdSeparatorKeys',
28465        requireMatch: '=?mdRequireMatch',
28466        chipAppendDelayString: '@?mdChipAppendDelay'
28467      }
28468    };
28469
28470    /**
28471     * Builds the final template for `md-chips` and returns the postLink function.
28472     *
28473     * Building the template involves 3 key components:
28474     * static chips
28475     * chip template
28476     * input control
28477     *
28478     * If no `ng-model` is provided, only the static chip work needs to be done.
28479     *
28480     * If no user-passed `md-chip-template` exists, the default template is used. This resulting
28481     * template is appended to the chip content element.
28482     *
28483     * The remove button may be overridden by passing an element with an md-chip-remove attribute.
28484     *
28485     * If an `input` or `md-autocomplete` element is provided by the caller, it is set aside for
28486     * transclusion later. The transclusion happens in `postLink` as the parent scope is required.
28487     * If no user input is provided, a default one is appended to the input container node in the
28488     * template.
28489     *
28490     * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for
28491     * transclusion in the `postLink` function.
28492     *
28493     *
28494     * @param element
28495     * @param attr
28496     * @returns {Function}
28497     */
28498    function compile(element, attr) {
28499      // Grab the user template from attr and reset the attribute to null.
28500      var userTemplate = attr['$mdUserTemplate'];
28501      attr['$mdUserTemplate'] = null;
28502
28503      var chipTemplate = getTemplateByQuery('md-chips>md-chip-template');
28504
28505      var chipRemoveSelector = $mdUtil
28506        .prefixer()
28507        .buildList('md-chip-remove')
28508        .map(function(attr) {
28509          return 'md-chips>*[' + attr + ']';
28510        })
28511        .join(',');
28512
28513      // Set the chip remove, chip contents and chip input templates. The link function will put
28514      // them on the scope for transclusion later.
28515      var chipRemoveTemplate   = getTemplateByQuery(chipRemoveSelector) || templates.remove,
28516          chipContentsTemplate = chipTemplate || templates.default,
28517          chipInputTemplate    = getTemplateByQuery('md-chips>md-autocomplete')
28518              || getTemplateByQuery('md-chips>input')
28519              || templates.input,
28520          staticChips = userTemplate.find('md-chip');
28521
28522      // Warn of malformed template. See #2545
28523      if (userTemplate[0].querySelector('md-chip-template>*[md-chip-remove]')) {
28524        $log.warn('invalid placement of md-chip-remove within md-chip-template.');
28525      }
28526
28527      function getTemplateByQuery (query) {
28528        if (!attr.ngModel) return;
28529        var element = userTemplate[0].querySelector(query);
28530        return element && element.outerHTML;
28531      }
28532
28533      /**
28534       * Configures controller and transcludes.
28535       */
28536      return function postLink(scope, element, attrs, controllers) {
28537        $mdUtil.initOptionalProperties(scope, attr);
28538
28539        $mdTheming(element);
28540        var mdChipsCtrl = controllers[0];
28541        if(chipTemplate) {
28542          // Chip editing functionality assumes we are using the default chip template.
28543          mdChipsCtrl.enableChipEdit = false;
28544        }
28545
28546        mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;
28547        mdChipsCtrl.chipRemoveTemplate   = chipRemoveTemplate;
28548        mdChipsCtrl.chipInputTemplate    = chipInputTemplate;
28549
28550        mdChipsCtrl.mdCloseIcon = $$mdSvgRegistry.mdClose;
28551
28552        element
28553            .attr({ tabindex: -1 })
28554            .on('focus', function () { mdChipsCtrl.onFocus(); });
28555
28556        if (attr.ngModel) {
28557          mdChipsCtrl.configureNgModel(element.controller('ngModel'));
28558
28559          // If an `md-transform-chip` attribute was set, tell the controller to use the expression
28560          // before appending chips.
28561          if (attrs.mdTransformChip) mdChipsCtrl.useTransformChipExpression();
28562
28563          // If an `md-on-append` attribute was set, tell the controller to use the expression
28564          // when appending chips.
28565          //
28566          // DEPRECATED: Will remove in official 1.0 release
28567          if (attrs.mdOnAppend) mdChipsCtrl.useOnAppendExpression();
28568
28569          // If an `md-on-add` attribute was set, tell the controller to use the expression
28570          // when adding chips.
28571          if (attrs.mdOnAdd) mdChipsCtrl.useOnAddExpression();
28572
28573          // If an `md-on-remove` attribute was set, tell the controller to use the expression
28574          // when removing chips.
28575          if (attrs.mdOnRemove) mdChipsCtrl.useOnRemoveExpression();
28576
28577          // If an `md-on-select` attribute was set, tell the controller to use the expression
28578          // when selecting chips.
28579          if (attrs.mdOnSelect) mdChipsCtrl.useOnSelectExpression();
28580
28581          // The md-autocomplete and input elements won't be compiled until after this directive
28582          // is complete (due to their nested nature). Wait a tick before looking for them to
28583          // configure the controller.
28584          if (chipInputTemplate != templates.input) {
28585            // The autocomplete will not appear until the readonly attribute is not true (i.e.
28586            // false or undefined), so we have to watch the readonly and then on the next tick
28587            // after the chip transclusion has run, we can configure the autocomplete and user
28588            // input.
28589            scope.$watch('$mdChipsCtrl.readonly', function(readonly) {
28590              if (!readonly) {
28591
28592                $mdUtil.nextTick(function(){
28593
28594                  if (chipInputTemplate.indexOf('<md-autocomplete') === 0) {
28595                    var autocompleteEl = element.find('md-autocomplete');
28596                    mdChipsCtrl.configureAutocomplete(autocompleteEl.controller('mdAutocomplete'));
28597                  }
28598
28599                  mdChipsCtrl.configureUserInput(element.find('input'));
28600                });
28601              }
28602            });
28603          }
28604
28605          // At the next tick, if we find an input, make sure it has the md-input class
28606          $mdUtil.nextTick(function() {
28607            var input = element.find('input');
28608
28609            input && input.toggleClass('md-input', true);
28610          });
28611        }
28612
28613        // Compile with the parent's scope and prepend any static chips to the wrapper.
28614        if (staticChips.length > 0) {
28615          var compiledStaticChips = $compile(staticChips.clone())(scope.$parent);
28616          $timeout(function() { element.find('md-chips-wrap').prepend(compiledStaticChips); });
28617        }
28618      };
28619    }
28620
28621    function getTemplates() {
28622      return {
28623        chips: $mdUtil.processTemplate(MD_CHIPS_TEMPLATE),
28624        input: $mdUtil.processTemplate(CHIP_INPUT_TEMPLATE),
28625        default: $mdUtil.processTemplate(CHIP_DEFAULT_TEMPLATE),
28626        remove: $mdUtil.processTemplate(CHIP_REMOVE_TEMPLATE)
28627      };
28628    }
28629  }
28630
28631})();
28632(function(){
28633"use strict";
28634
28635angular
28636    .module('material.components.chips')
28637    .controller('MdContactChipsCtrl', MdContactChipsCtrl);
28638
28639
28640
28641/**
28642 * Controller for the MdContactChips component
28643 * @constructor
28644 */
28645function MdContactChipsCtrl () {
28646  /** @type {Object} */
28647  this.selectedItem = null;
28648
28649  /** @type {string} */
28650  this.searchText = '';
28651}
28652
28653
28654MdContactChipsCtrl.prototype.queryContact = function(searchText) {
28655  return this.contactQuery({'$query': searchText});
28656};
28657
28658
28659MdContactChipsCtrl.prototype.itemName = function(item) {
28660  return item[this.contactName];
28661};
28662
28663})();
28664(function(){
28665"use strict";
28666
28667
28668MdContactChips.$inject = ["$mdTheming", "$mdUtil"];angular
28669  .module('material.components.chips')
28670  .directive('mdContactChips', MdContactChips);
28671
28672/**
28673 * @ngdoc directive
28674 * @name mdContactChips
28675 * @module material.components.chips
28676 *
28677 * @description
28678 * `<md-contact-chips>` is an input component based on `md-chips` and makes use of an
28679 * `md-autocomplete` element. The component allows the caller to supply a query expression which
28680 * returns  a list of possible contacts. The user can select one of these and add it to the list of
28681 * chips.
28682 *
28683 * You may also use the `md-highlight-text` directive along with its parameters to control the
28684 * appearance of the matched text inside of the contacts' autocomplete popup.
28685 *
28686 * @param {string=|object=} ng-model A model to bind the list of items to
28687 * @param {string=} placeholder Placeholder text that will be forwarded to the input.
28688 * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
28689 *    displayed when there is at least on item in the list
28690 * @param {expression} md-contacts An expression expected to return contacts matching the search
28691 *    test, `$query`. If this expression involves a promise, a loading bar is displayed while
28692 *    waiting for it to resolve.
28693 * @param {string} md-contact-name The field name of the contact object representing the
28694 *    contact's name.
28695 * @param {string} md-contact-email The field name of the contact object representing the
28696 *    contact's email address.
28697 * @param {string} md-contact-image The field name of the contact object representing the
28698 *    contact's image.
28699 * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will
28700 *    make suggestions
28701 *
28702 * @param {expression=} filter-selected Whether to filter selected contacts from the list of
28703 *    suggestions shown in the autocomplete.
28704 *
28705 *    ***Note:** This attribute has been removed but may come back.*
28706 *
28707 *
28708 *
28709 * @usage
28710 * <hljs lang="html">
28711 *   <md-contact-chips
28712 *       ng-model="ctrl.contacts"
28713 *       md-contacts="ctrl.querySearch($query)"
28714 *       md-contact-name="name"
28715 *       md-contact-image="image"
28716 *       md-contact-email="email"
28717 *       placeholder="To">
28718 *   </md-contact-chips>
28719 * </hljs>
28720 *
28721 */
28722
28723
28724var MD_CONTACT_CHIPS_TEMPLATE = '\
28725      <md-chips class="md-contact-chips"\
28726          ng-model="$mdContactChipsCtrl.contacts"\
28727          md-require-match="$mdContactChipsCtrl.requireMatch"\
28728          md-chip-append-delay="{{$mdContactChipsCtrl.chipAppendDelay}}" \
28729          md-autocomplete-snap>\
28730          <md-autocomplete\
28731              md-menu-class="md-contact-chips-suggestions"\
28732              md-selected-item="$mdContactChipsCtrl.selectedItem"\
28733              md-search-text="$mdContactChipsCtrl.searchText"\
28734              md-items="item in $mdContactChipsCtrl.queryContact($mdContactChipsCtrl.searchText)"\
28735              md-item-text="$mdContactChipsCtrl.itemName(item)"\
28736              md-no-cache="true"\
28737              md-min-length="$mdContactChipsCtrl.minLength"\
28738              md-autoselect\
28739              placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\
28740                  $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\
28741            <div class="md-contact-suggestion">\
28742              <img \
28743                  ng-src="{{item[$mdContactChipsCtrl.contactImage]}}"\
28744                  alt="{{item[$mdContactChipsCtrl.contactName]}}"\
28745                  ng-if="item[$mdContactChipsCtrl.contactImage]" />\
28746              <span class="md-contact-name" md-highlight-text="$mdContactChipsCtrl.searchText"\
28747                    md-highlight-flags="{{$mdContactChipsCtrl.highlightFlags}}">\
28748                {{item[$mdContactChipsCtrl.contactName]}}\
28749              </span>\
28750              <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\
28751            </div>\
28752          </md-autocomplete>\
28753          <md-chip-template>\
28754            <div class="md-contact-avatar">\
28755              <img \
28756                  ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\
28757                  alt="{{$chip[$mdContactChipsCtrl.contactName]}}"\
28758                  ng-if="$chip[$mdContactChipsCtrl.contactImage]" />\
28759            </div>\
28760            <div class="md-contact-name">\
28761              {{$chip[$mdContactChipsCtrl.contactName]}}\
28762            </div>\
28763          </md-chip-template>\
28764      </md-chips>';
28765
28766
28767/**
28768 * MDContactChips Directive Definition
28769 *
28770 * @param $mdTheming
28771 * @returns {*}
28772 * @ngInject
28773 */
28774function MdContactChips($mdTheming, $mdUtil) {
28775  return {
28776    template: function(element, attrs) {
28777      return MD_CONTACT_CHIPS_TEMPLATE;
28778    },
28779    restrict: 'E',
28780    controller: 'MdContactChipsCtrl',
28781    controllerAs: '$mdContactChipsCtrl',
28782    bindToController: true,
28783    compile: compile,
28784    scope: {
28785      contactQuery: '&mdContacts',
28786      placeholder: '@',
28787      secondaryPlaceholder: '@',
28788      contactName: '@mdContactName',
28789      contactImage: '@mdContactImage',
28790      contactEmail: '@mdContactEmail',
28791      contacts: '=ngModel',
28792      requireMatch: '=?mdRequireMatch',
28793      minLength: '=?mdMinLength',
28794      highlightFlags: '@?mdHighlightFlags',
28795      chipAppendDelay: '@?mdChipAppendDelay'
28796    }
28797  };
28798
28799  function compile(element, attr) {
28800    return function postLink(scope, element, attrs, controllers) {
28801      var contactChipsController = controllers;
28802
28803      $mdUtil.initOptionalProperties(scope, attr);
28804      $mdTheming(element);
28805
28806      element.attr('tabindex', '-1');
28807
28808      attrs.$observe('mdChipAppendDelay', function(newValue) {
28809        contactChipsController.chipAppendDelay = newValue;
28810      });
28811    };
28812  }
28813}
28814
28815})();
28816(function(){
28817"use strict";
28818
28819(function() {
28820  'use strict';
28821
28822  /**
28823   * @ngdoc directive
28824   * @name mdCalendar
28825   * @module material.components.datepicker
28826   *
28827   * @param {Date} ng-model The component's model. Should be a Date object.
28828   * @param {Date=} md-min-date Expression representing the minimum date.
28829   * @param {Date=} md-max-date Expression representing the maximum date.
28830   * @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not.
28831   *
28832   * @description
28833   * `<md-calendar>` is a component that renders a calendar that can be used to select a date.
28834   * It is a part of the `<md-datepicker>` pane, however it can also be used on it's own.
28835   *
28836   * @usage
28837   *
28838   * <hljs lang="html">
28839   *   <md-calendar ng-model="birthday"></md-calendar>
28840   * </hljs>
28841   */
28842  CalendarCtrl.$inject = ["$element", "$scope", "$$mdDateUtil", "$mdUtil", "$mdConstant", "$mdTheming", "$$rAF", "$attrs", "$mdDateLocale"];
28843  angular.module('material.components.datepicker')
28844    .directive('mdCalendar', calendarDirective);
28845
28846  // POST RELEASE
28847  // TODO(jelbourn): Mac Cmd + left / right == Home / End
28848  // TODO(jelbourn): Refactor month element creation to use cloneNode (performance).
28849  // TODO(jelbourn): Define virtual scrolling constants (compactness) users can override.
28850  // TODO(jelbourn): Animated month transition on ng-model change (virtual-repeat)
28851  // TODO(jelbourn): Scroll snapping (virtual repeat)
28852  // TODO(jelbourn): Remove superfluous row from short months (virtual-repeat)
28853  // TODO(jelbourn): Month headers stick to top when scrolling.
28854  // TODO(jelbourn): Previous month opacity is lowered when partially scrolled out of view.
28855  // TODO(jelbourn): Support md-calendar standalone on a page (as a tabstop w/ aria-live
28856  //     announcement and key handling).
28857  // Read-only calendar (not just date-picker).
28858
28859  function calendarDirective() {
28860    return {
28861      template: function(tElement, tAttr) {
28862        // TODO(crisbeto): This is a workaround that allows the calendar to work, without
28863        // a datepicker, until issue #8585 gets resolved. It can safely be removed
28864        // afterwards. This ensures that the virtual repeater scrolls to the proper place on load by
28865        // deferring the execution until the next digest. It's necessary only if the calendar is used
28866        // without a datepicker, otherwise it's already wrapped in an ngIf.
28867        var extraAttrs = tAttr.hasOwnProperty('ngIf') ? '' : 'ng-if="calendarCtrl.isInitialized"';
28868        var template = '' +
28869          '<div ng-switch="calendarCtrl.currentView" ' + extraAttrs + '>' +
28870            '<md-calendar-year ng-switch-when="year"></md-calendar-year>' +
28871            '<md-calendar-month ng-switch-default></md-calendar-month>' +
28872          '</div>';
28873
28874        return template;
28875      },
28876      scope: {
28877        minDate: '=mdMinDate',
28878        maxDate: '=mdMaxDate',
28879        dateFilter: '=mdDateFilter',
28880        _currentView: '@mdCurrentView'
28881      },
28882      require: ['ngModel', 'mdCalendar'],
28883      controller: CalendarCtrl,
28884      controllerAs: 'calendarCtrl',
28885      bindToController: true,
28886      link: function(scope, element, attrs, controllers) {
28887        var ngModelCtrl = controllers[0];
28888        var mdCalendarCtrl = controllers[1];
28889        mdCalendarCtrl.configureNgModel(ngModelCtrl);
28890      }
28891    };
28892  }
28893
28894  /**
28895   * Occasionally the hideVerticalScrollbar method might read an element's
28896   * width as 0, because it hasn't been laid out yet. This value will be used
28897   * as a fallback, in order to prevent scenarios where the element's width
28898   * would otherwise have been set to 0. This value is the "usual" width of a
28899   * calendar within a floating calendar pane.
28900   */
28901  var FALLBACK_WIDTH = 340;
28902
28903  /** Next identifier for calendar instance. */
28904  var nextUniqueId = 0;
28905
28906  /**
28907   * Controller for the mdCalendar component.
28908   * @ngInject @constructor
28909   */
28910  function CalendarCtrl($element, $scope, $$mdDateUtil, $mdUtil,
28911    $mdConstant, $mdTheming, $$rAF, $attrs, $mdDateLocale) {
28912
28913    $mdTheming($element);
28914
28915    /** @final {!angular.JQLite} */
28916    this.$element = $element;
28917
28918    /** @final {!angular.Scope} */
28919    this.$scope = $scope;
28920
28921    /** @final */
28922    this.dateUtil = $$mdDateUtil;
28923
28924    /** @final */
28925    this.$mdUtil = $mdUtil;
28926
28927    /** @final */
28928    this.keyCode = $mdConstant.KEY_CODE;
28929
28930    /** @final */
28931    this.$$rAF = $$rAF;
28932
28933    /** @final */
28934    this.$mdDateLocale = $mdDateLocale;
28935
28936    /** @final {Date} */
28937    this.today = this.dateUtil.createDateAtMidnight();
28938
28939    /** @type {!angular.NgModelController} */
28940    this.ngModelCtrl = null;
28941
28942    /** @type {String} Class applied to the selected date cell. */
28943    this.SELECTED_DATE_CLASS = 'md-calendar-selected-date';
28944
28945    /** @type {String} Class applied to the cell for today. */
28946    this.TODAY_CLASS = 'md-calendar-date-today';
28947
28948    /** @type {String} Class applied to the focused cell. */
28949    this.FOCUSED_DATE_CLASS = 'md-focus';
28950
28951    /** @final {number} Unique ID for this calendar instance. */
28952    this.id = nextUniqueId++;
28953
28954    /**
28955     * The date that is currently focused or showing in the calendar. This will initially be set
28956     * to the ng-model value if set, otherwise to today. It will be updated as the user navigates
28957     * to other months. The cell corresponding to the displayDate does not necesarily always have
28958     * focus in the document (such as for cases when the user is scrolling the calendar).
28959     * @type {Date}
28960     */
28961    this.displayDate = null;
28962
28963    /**
28964     * The selected date. Keep track of this separately from the ng-model value so that we
28965     * can know, when the ng-model value changes, what the previous value was before it's updated
28966     * in the component's UI.
28967     *
28968     * @type {Date}
28969     */
28970    this.selectedDate = null;
28971
28972    /**
28973     * The first date that can be rendered by the calendar. The default is taken
28974     * from the mdDateLocale provider and is limited by the mdMinDate.
28975     * @type {Date}
28976     */
28977    this.firstRenderableDate = null;
28978
28979    /**
28980     * The last date that can be rendered by the calendar. The default comes
28981     * from the mdDateLocale provider and is limited by the maxDate.
28982     * @type {Date}
28983     */
28984    this.lastRenderableDate = null;
28985
28986    /**
28987     * Used to toggle initialize the root element in the next digest.
28988     * @type {Boolean}
28989     */
28990    this.isInitialized = false;
28991
28992    /**
28993     * Cache for the  width of the element without a scrollbar. Used to hide the scrollbar later on
28994     * and to avoid extra reflows when switching between views.
28995     * @type {Number}
28996     */
28997    this.width = 0;
28998
28999    /**
29000     * Caches the width of the scrollbar in order to be used when hiding it and to avoid extra reflows.
29001     * @type {Number}
29002     */
29003    this.scrollbarWidth = 0;
29004
29005    // Unless the user specifies so, the calendar should not be a tab stop.
29006    // This is necessary because ngAria might add a tabindex to anything with an ng-model
29007    // (based on whether or not the user has turned that particular feature on/off).
29008    if (!$attrs.tabindex) {
29009      $element.attr('tabindex', '-1');
29010    }
29011
29012    var boundKeyHandler = angular.bind(this, this.handleKeyEvent);
29013
29014
29015
29016    // If use the md-calendar directly in the body without datepicker,
29017    // handleKeyEvent will disable other inputs on the page.
29018    // So only apply the handleKeyEvent on the body when the md-calendar inside datepicker,
29019    // otherwise apply on the calendar element only.
29020
29021    var handleKeyElement;
29022    if ($element.parent().hasClass('md-datepicker-calendar')) {
29023      handleKeyElement = angular.element(document.body);
29024    } else {
29025      handleKeyElement = $element;
29026    }
29027
29028    // Bind the keydown handler to the body, in order to handle cases where the focused
29029    // element gets removed from the DOM and stops propagating click events.
29030    handleKeyElement.on('keydown', boundKeyHandler);
29031
29032    $scope.$on('$destroy', function() {
29033      handleKeyElement.off('keydown', boundKeyHandler);
29034    });
29035
29036    // For AngularJS 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
29037    // manually call the $onInit hook.
29038    if (angular.version.major === 1 && angular.version.minor <= 4) {
29039      this.$onInit();
29040    }
29041
29042  }
29043
29044  /**
29045   * AngularJS Lifecycle hook for newer AngularJS versions.
29046   * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
29047   */
29048  CalendarCtrl.prototype.$onInit = function() {
29049
29050    /**
29051     * The currently visible calendar view. Note the prefix on the scope value,
29052     * which is necessary, because the datepicker seems to reset the real one value if the
29053     * calendar is open, but the value on the datepicker's scope is empty.
29054     * @type {String}
29055     */
29056    this.currentView = this._currentView || 'month';
29057
29058    var dateLocale = this.$mdDateLocale;
29059
29060    if (this.minDate && this.minDate >
29060 dateLocale.firstRenderableDate) {
29061      this.firstRenderableDate = this.minDate;
29062    } else {
29063      this.firstRenderableDate = dateLocale.firstRenderableDate;
29064    }
29065
29066    if (this.maxDate && this.maxDate < dateLocale.lastRenderableDate) {
29067      this.lastRenderableDate = this.maxDate;
29068    } else {
29069      this.lastRenderableDate = dateLocale.lastRenderableDate;
29070    }
29071  };
29072
29073  /**
29074   * Sets up the controller's reference to ngModelController.
29075   * @param {!angular.NgModelController} ngModelCtrl
29076   */
29077  CalendarCtrl.prototype.configureNgModel = function(ngModelCtrl) {
29078    var self = this;
29079
29080    self.ngModelCtrl = ngModelCtrl;
29081
29082    self.$mdUtil.nextTick(function() {
29083      self.isInitialized = true;
29084    });
29085
29086    ngModelCtrl.$render = function() {
29087      var value = this.$viewValue;
29088
29089      // Notify the child scopes of any changes.
29090      self.$scope.$broadcast('md-calendar-parent-changed', value);
29091
29092      // Set up the selectedDate if it hasn't been already.
29093      if (!self.selectedDate) {
29094        self.selectedDate = value;
29095      }
29096
29097      // Also set up the displayDate.
29098      if (!self.displayDate) {
29099        self.displayDate = self.selectedDate || self.today;
29100      }
29101    };
29102  };
29103
29104  /**
29105   * Sets the ng-model value for the calendar and emits a change event.
29106   * @param {Date} date
29107   */
29108  CalendarCtrl.prototype.setNgModelValue = function(date) {
29109    var value = this.dateUtil.createDateAtMidnight(date);
29110    this.focus(value);
29111    this.$scope.$emit('md-calendar-change', value);
29112    this.ngModelCtrl.$setViewValue(value);
29113    this.ngModelCtrl.$render();
29114    return value;
29115  };
29116
29117  /**
29118   * Sets the current view that should be visible in the calendar
29119   * @param {string} newView View name to be set.
29120   * @param {number|Date} time Date object or a timestamp for the new display date.
29121   */
29122  CalendarCtrl.prototype.setCurrentView = function(newView, time) {
29123    var self = this;
29124
29125    self.$mdUtil.nextTick(function() {
29126      self.currentView = newView;
29127
29128      if (time) {
29129        self.displayDate = angular.isDate(time) ? time : new Date(time);
29130      }
29131    });
29132  };
29133
29134  /**
29135   * Focus the cell corresponding to the given date.
29136   * @param {Date} date The date to be focused.
29137   */
29138  CalendarCtrl.prototype.focus = function(date) {
29139    if (this.dateUtil.isValidDate(date)) {
29140      var previousFocus = this.$element[0].querySelector('.md-focus');
29141      if (previousFocus) {
29142        previousFocus.classList.remove(this.FOCUSED_DATE_CLASS);
29143      }
29144
29145      var cellId = this.getDateId(date, this.currentView);
29146      var cell = document.getElementById(cellId);
29147      if (cell) {
29148        cell.classList.add(this.FOCUSED_DATE_CLASS);
29149        cell.focus();
29150        this.displayDate = date;
29151      }
29152    } else {
29153      var rootElement = this.$element[0].querySelector('[ng-switch]');
29154
29155      if (rootElement) {
29156        rootElement.focus();
29157      }
29158    }
29159  };
29160
29161  /**
29162   * Normalizes the key event into an action name. The action will be broadcast
29163   * to the child controllers.
29164   * @param {KeyboardEvent} event
29165   * @returns {String} The action that should be taken, or null if the key
29166   * does not match a calendar shortcut.
29167   */
29168  CalendarCtrl.prototype.getActionFromKeyEvent = function(event) {
29169    var keyCode = this.keyCode;
29170
29171    switch (event.which) {
29172      case keyCode.ENTER: return 'select';
29173
29174      case keyCode.RIGHT_ARROW: return 'move-right';
29175      case keyCode.LEFT_ARROW: return 'move-left';
29176
vendor: 41,975 bytes, lines 29177-30368
29177      // TODO(crisbeto): Might want to reconsider using metaKey, because it maps
29178      // to the "Windows" key on PC, which opens the start menu or resizes the browser.
29179      case keyCode.DOWN_ARROW: return event.metaKey ? 'move-page-down' : 'move-row-down';
29180      case keyCode.UP_ARROW: return event.metaKey ? 'move-page-up' : 'move-row-up';
29181
29182      case keyCode.PAGE_DOWN: return 'move-page-down';
29183      case keyCode.PAGE_UP: return 'move-page-up';
29184
29185      case keyCode.HOME: return 'start';
29186      case keyCode.END: return 'end';
29187
29188      default: return null;
29189    }
29190  };
29191
29192  /**
29193   * Handles a key event in the calendar with the appropriate action. The action will either
29194   * be to select the focused date or to navigate to focus a new date.
29195   * @param {KeyboardEvent} event
29196   */
29197  CalendarCtrl.prototype.handleKeyEvent = function(event) {
29198    var self = this;
29199
29200    this.$scope.$apply(function() {
29201      // Capture escape and emit back up so that a wrapping component
29202      // (such as a date-picker) can decide to close.
29203      if (event.which == self.keyCode.ESCAPE || event.which == self.keyCode.TAB) {
29204        self.$scope.$emit('md-calendar-close');
29205
29206        if (event.which == self.keyCode.TAB) {
29207          event.preventDefault();
29208        }
29209
29210        return;
29211      }
29212
29213      // Broadcast the action that any child controllers should take.
29214      var action = self.getActionFromKeyEvent(event);
29215      if (action) {
29216        event.preventDefault();
29217        event.stopPropagation();
29218        self.$scope.$broadcast('md-calendar-parent-action', action);
29219      }
29220    });
29221  };
29222
29223  /**
29224   * Hides the vertical scrollbar on the calendar scroller of a child controller by
29225   * setting the width on the calendar scroller and the `overflow: hidden` wrapper
29226   * around the scroller, and then setting a padding-right on the scroller equal
29227   * to the width of the browser's scrollbar.
29228   *
29229   * This will cause a reflow.
29230   *
29231   * @param {object} childCtrl The child controller whose scrollbar should be hidden.
29232   */
29233  CalendarCtrl.prototype.hideVerticalScrollbar = function(childCtrl) {
29234    var self = this;
29235    var element = childCtrl.$element[0];
29236    var scrollMask = element.querySelector('.md-calendar-scroll-mask');
29237
29238    if (self.width > 0) {
29239      setWidth();
29240    } else {
29241      self.$$rAF(function() {
29242        var scroller = childCtrl.calendarScroller;
29243
29244        self.scrollbarWidth = scroller.offsetWidth - scroller.clientWidth;
29245        self.width = element.querySelector('table').offsetWidth;
29246        setWidth();
29247      });
29248    }
29249
29250    function setWidth() {
29251      var width = self.width || FALLBACK_WIDTH;
29252      var scrollbarWidth = self.scrollbarWidth;
29253      var scroller = childCtrl.calendarScroller;
29254
29255      scrollMask.style.width = width + 'px';
29256      scroller.style.width = (width + scrollbarWidth) + 'px';
29257      scroller.style.paddingRight = scrollbarWidth + 'px';
29258    }
29259  };
29260
29261  /**
29262   * Gets an identifier for a date unique to the calendar instance for internal
29263   * purposes. Not to be displayed.
29264   * @param {Date} date The date for which the id is being generated
29265   * @param {string} namespace Namespace for the id. (month, year etc.)
29266   * @returns {string}
29267   */
29268  CalendarCtrl.prototype.getDateId = function(date, namespace) {
29269    if (!namespace) {
29270      throw new Error('A namespace for the date id has to be specified.');
29271    }
29272
29273    return [
29274      'md',
29275      this.id,
29276      namespace,
29277      date.getFullYear(),
29278      date.getMonth(),
29279      date.getDate()
29280    ].join('-');
29281  };
29282
29283  /**
29284   * Util to trigger an extra digest on a parent scope, in order to to ensure that
29285   * any child virtual repeaters have updated. This is necessary, because the virtual
29286   * repeater doesn't update the $index the first time around since the content isn't
29287   * in place yet. The case, in which this is an issue, is when the repeater has less
29288   * than a page of content (e.g. a month or year view has a min or max date).
29289   */
29290  CalendarCtrl.prototype.updateVirtualRepeat = function() {
29291    var scope = this.$scope;
29292    var virtualRepeatResizeListener = scope.$on('$md-resize-enable', function() {
29293      if (!scope.$$phase) {
29294        scope.$apply();
29295      }
29296
29297      virtualRepeatResizeListener();
29298    });
29299  };
29300})();
29301
29302})();
29303(function(){
29304"use strict";
29305
29306(function() {
29307  'use strict';
29308
29309  CalendarMonthCtrl.$inject = ["$element", "$scope", "$animate", "$q", "$$mdDateUtil", "$mdDateLocale"];
29310  angular.module('material.components.datepicker')
29311    .directive('mdCalendarMonth', calendarDirective);
29312
29313  /**
29314   * Height of one calendar month tbody. This must be made known to the virtual-repeat and is
29315   * subsequently used for scrolling to specific months.
29316   */
29317  var TBODY_HEIGHT = 265;
29318
29319  /**
29320   * Height of a calendar month with a single row. This is needed to calculate the offset for
29321   * rendering an extra month in virtual-repeat that only contains one row.
29322   */
29323  var TBODY_SINGLE_ROW_HEIGHT = 45;
29324
29325  /** Private directive that represents a list of months inside the calendar. */
29326  function calendarDirective() {
29327    return {
29328      template:
29329        '<table aria-hidden="true" class="md-calendar-day-header"><thead></thead></table>' +
29330        '<div class="md-calendar-scroll-mask">' +
29331        '<md-virtual-repeat-container class="md-calendar-scroll-container" ' +
29332              'md-offset-size="' + (TBODY_SINGLE_ROW_HEIGHT - TBODY_HEIGHT) + '">' +
29333            '<table role="grid" tabindex="0" class="md-calendar" aria-readonly="true">' +
29334              '<tbody ' +
29335                  'md-calendar-month-body ' +
29336                  'role="rowgroup" ' +
29337                  'md-virtual-repeat="i in monthCtrl.items" ' +
29338                  'md-month-offset="$index" ' +
29339                  'class="md-calendar-month" ' +
29340                  'md-start-index="monthCtrl.getSelectedMonthIndex()" ' +
29341                  'md-item-size="' + TBODY_HEIGHT + '">' +
29342
29343                // The <tr> ensures that the <tbody> will always have the
29344                // proper height, even if it's empty. If it's content is
29345                // compiled, the <tr> will be overwritten.
29346                '<tr aria-hidden="true" md-force-height="\'' + TBODY_HEIGHT + 'px\'"></tr>' +
29347              '</tbody>' +
29348            '</table>' +
29349          '</md-virtual-repeat-container>' +
29350        '</div>',
29351      require: ['^^mdCalendar', 'mdCalendarMonth'],
29352      controller: CalendarMonthCtrl,
29353      controllerAs: 'monthCtrl',
29354      bindToController: true,
29355      link: function(scope, element, attrs, controllers) {
29356        var calendarCtrl = controllers[0];
29357        var monthCtrl = controllers[1];
29358        monthCtrl.initialize(calendarCtrl);
29359      }
29360    };
29361  }
29362
29363  /**
29364   * Controller for the calendar month component.
29365   * @ngInject @constructor
29366   */
29367  function CalendarMonthCtrl($element, $scope, $animate, $q,
29368    $$mdDateUtil, $mdDateLocale) {
29369
29370    /** @final {!angular.JQLite} */
29371    this.$element = $element;
29372
29373    /** @final {!angular.Scope} */
29374    this.$scope = $scope;
29375
29376    /** @final {!angular.$animate} */
29377    this.$animate = $animate;
29378
29379    /** @final {!angular.$q} */
29380    this.$q = $q;
29381
29382    /** @final */
29383    this.dateUtil = $$mdDateUtil;
29384
29385    /** @final */
29386    this.dateLocale = $mdDateLocale;
29387
29388    /** @final {HTMLElement} */
29389    this.calendarScroller = $element[0].querySelector('.md-virtual-repeat-scroller');
29390
29391    /** @type {boolean} */
29392    this.isInitialized = false;
29393
29394    /** @type {boolean} */
29395    this.isMonthTransitionInProgress = false;
29396
29397    var self = this;
29398
29399    /**
29400     * Handles a click event on a date cell.
29401     * Created here so that every cell can use the same function instance.
29402     * @this {HTMLTableCellElement} The cell that was clicked.
29403     */
29404    this.cellClickHandler = function() {
29405      var timestamp = $$mdDateUtil.getTimestampFromNode(this);
29406      self.$scope.$apply(function() {
29407        self.calendarCtrl.setNgModelValue(timestamp);
29408      });
29409    };
29410
29411    /**
29412     * Handles click events on the month headers. Switches
29413     * the calendar to the year view.
29414     * @this {HTMLTableCellElement} The cell that was clicked.
29415     */
29416    this.headerClickHandler = function() {
29417      self.calendarCtrl.setCurrentView('year', $$mdDateUtil.getTimestampFromNode(this));
29418    };
29419  }
29420
29421  /*** Initialization ***/
29422
29423  /**
29424   * Initialize the controller by saving a reference to the calendar and
29425   * setting up the object that will be iterated by the virtual repeater.
29426   */
29427  CalendarMonthCtrl.prototype.initialize = function(calendarCtrl) {
29428    /**
29429     * Dummy array-like object for virtual-repeat to iterate over. The length is the total
29430     * number of months that can be viewed. We add 2 months: one to include the current month
29431     * and one for the last dummy month.
29432     *
29433     * This is shorter than ideal because of a (potential) Firefox bug
29434     * https://bugzilla.mozilla.org/show_bug.cgi?id=1181658.
29435     */
29436
29437    this.items = {
29438      length: this.dateUtil.getMonthDistance(
29439        calendarCtrl.firstRenderableDate,
29440        calendarCtrl.lastRenderableDate
29441      ) + 2
29442    };
29443
29444    this.calendarCtrl = calendarCtrl;
29445    this.attachScopeListeners();
29446    calendarCtrl.updateVirtualRepeat();
29447
29448    // Fire the initial render, since we might have missed it the first time it fired.
29449    calendarCtrl.ngModelCtrl && calendarCtrl.ngModelCtrl.$render();
29450  };
29451
29452  /**
29453   * Gets the "index" of the currently selected date as it would be in the virtual-repeat.
29454   * @returns {number}
29455   */
29456  CalendarMonthCtrl.prototype.getSelectedMonthIndex = function() {
29457    var calendarCtrl = this.calendarCtrl;
29458
29459    return this.dateUtil.getMonthDistance(
29460      calendarCtrl.firstRenderableDate,
29461      calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today
29462    );
29463  };
29464
29465  /**
29466   * Change the selected date in the calendar (ngModel value has already been changed).
29467   * @param {Date} date
29468   */
29469  CalendarMonthCtrl.prototype.changeSelectedDate = function(date) {
29470    var self = this;
29471    var calendarCtrl = self.calendarCtrl;
29472    var previousSelectedDate = calendarCtrl.selectedDate;
29473    calendarCtrl.selectedDate = date;
29474
29475    this.changeDisplayDate(date).then(function() {
29476      var selectedDateClass = calendarCtrl.SELECTED_DATE_CLASS;
29477      var namespace = 'month';
29478
29479      // Remove the selected class from the previously selected date, if any.
29480      if (previousSelectedDate) {
29481        var prevDateCell = document.getElementById(calendarCtrl.getDateId(previousSelectedDate, namespace));
29482        if (prevDateCell) {
29483          prevDateCell.classList.remove(selectedDateClass);
29484          prevDateCell.setAttribute('aria-selected', 'false');
29485        }
29486      }
29487
29488      // Apply the select class to the new selected date if it is set.
29489      if (date) {
29490        var dateCell = document.getElementById(calendarCtrl.getDateId(date, namespace));
29491        if (dateCell) {
29492          dateCell.classList.add(selectedDateClass);
29493          dateCell.setAttribute('aria-selected', 'true');
29494        }
29495      }
29496    });
29497  };
29498
29499  /**
29500   * Change the date that is being shown in the calendar. If the given date is in a different
29501   * month, the displayed month will be transitioned.
29502   * @param {Date} date
29503   */
29504  CalendarMonthCtrl.prototype.changeDisplayDate = function(date) {
29505    // Initialization is deferred until this function is called because we want to reflect
29506    // the starting value of ngModel.
29507    if (!this.isInitialized) {
29508      this.buildWeekHeader();
29509      this.calendarCtrl.hideVerticalScrollbar(this);
29510      this.isInitialized = true;
29511      return this.$q.when();
29512    }
29513
29514    // If trying to show an invalid date or a transition is in progress, do nothing.
29515    if (!this.dateUtil.isValidDate(date) || this.isMonthTransitionInProgress) {
29516      return this.$q.when();
29517    }
29518
29519    this.isMonthTransitionInProgress = true;
29520    var animationPromise = this.animateDateChange(date);
29521
29522    this.calendarCtrl.displayDate = date;
29523
29524    var self = this;
29525    animationPromise.then(function() {
29526      self.isMonthTransitionInProgress = false;
29527    });
29528
29529    return animationPromise;
29530  };
29531
29532  /**
29533   * Animates the transition from the calendar's current month to the given month.
29534   * @param {Date} date
29535   * @returns {angular.$q.Promise} The animation promise.
29536   */
29537  CalendarMonthCtrl.prototype.animateDateChange = function(date) {
29538    if (this.dateUtil.isValidDate(date)) {
29539      var monthDistance = this.dateUtil.getMonthDistance(this.calendarCtrl.firstRenderableDate, date);
29540      this.calendarScroller.scrollTop = monthDistance * TBODY_HEIGHT;
29541    }
29542
29543    return this.$q.when();
29544  };
29545
29546  /**
29547   * Builds and appends a day-of-the-week header to the calendar.
29548   * This should only need to be called once during initialization.
29549   */
29550  CalendarMonthCtrl.prototype.buildWeekHeader = function() {
29551    var firstDayOfWeek = this.dateLocale.firstDayOfWeek;
29552    var shortDays = this.dateLocale.shortDays;
29553
29554    var row = document.createElement('tr');
29555    for (var i = 0; i < 7; i++) {
29556      var th = document.createElement('th');
29557      th.textContent = shortDays[(i + firstDayOfWeek) % 7];
29558      row.appendChild(th);
29559    }
29560
29561    this.$element.find('thead').append(row);
29562  };
29563
29564  /**
29565   * Attaches listeners for the scope events that are broadcast by the calendar.
29566   */
29567  CalendarMonthCtrl.prototype.attachScopeListeners = function() {
29568    var self = this;
29569
29570    self.$scope.$on('md-calendar-parent-changed', function(event, value) {
29571      self.changeSelectedDate(value);
29572    });
29573
29574    self.$scope.$on('md-calendar-parent-action', angular.bind(this, this.handleKeyEvent));
29575  };
29576
29577  /**
29578   * Handles the month-specific keyboard interactions.
29579   * @param {Object} event Scope event object passed by the calendar.
29580   * @param {String} action Action, corresponding to the key that was pressed.
29581   */
29582  CalendarMonthCtrl.prototype.handleKeyEvent = function(event, action) {
29583    var calendarCtrl = this.calendarCtrl;
29584    var displayDate = calendarCtrl.displayDate;
29585
29586    if (action === 'select') {
29587      calendarCtrl.setNgModelValue(displayDate);
29588    } else {
29589      var date = null;
29590      var dateUtil = this.dateUtil;
29591
29592      switch (action) {
29593        case 'move-right': date = dateUtil.incrementDays(displayDate, 1); break;
29594        case 'move-left': date = dateUtil.incrementDays(displayDate, -1); break;
29595
29596        case 'move-page-down': date = dateUtil.incrementMonths(displayDate, 1); break;
29597        case 'move-page-up': date = dateUtil.incrementMonths(displayDate, -1); break;
29598
29599        case 'move-row-down': date = dateUtil.incrementDays(displayDate, 7); break;
29600        case 'move-row-up': date = dateUtil.incrementDays(displayDate, -7); break;
29601
29602        case 'start': date = dateUtil.getFirstDateOfMonth(displayDate); break;
29603        case 'end': date = dateUtil.getLastDateOfMonth(displayDate); break;
29604      }
29605
29606      if (date) {
29607        date = this.dateUtil.clampDate(date, calendarCtrl.minDate, calendarCtrl.maxDate);
29608
29609        this.changeDisplayDate(date).then(function() {
29610          calendarCtrl.focus(date);
29611        });
29612      }
29613    }
29614  };
29615})();
29616
29617})();
29618(function(){
29619"use strict";
29620
29621(function() {
29622  'use strict';
29623
29624  mdCalendarMonthBodyDirective.$inject = ["$compile", "$$mdSvgRegistry"];
29625  CalendarMonthBodyCtrl.$inject = ["$element", "$$mdDateUtil", "$mdDateLocale"];
29626  angular.module('material.components.datepicker')
29627      .directive('mdCalendarMonthBody', mdCalendarMonthBodyDirective);
29628
29629  /**
29630   * Private directive consumed by md-calendar-month. Having this directive lets the calender use
29631   * md-virtual-repeat and also cleanly separates the month DOM construction functions from
29632   * the rest of the calendar controller logic.
29633   * @ngInject
29634   */
29635  function mdCalendarMonthBodyDirective($compile, $$mdSvgRegistry) {
29636    var ARROW_ICON = $compile('<md-icon md-svg-src="' +
29637      $$mdSvgRegistry.mdTabsArrow + '"></md-icon>')({})[0];
29638
29639    return {
29640      require: ['^^mdCalendar', '^^mdCalendarMonth', 'mdCalendarMonthBody'],
29641      scope: { offset: '=mdMonthOffset' },
29642      controller: CalendarMonthBodyCtrl,
29643      controllerAs: 'mdMonthBodyCtrl',
29644      bindToController: true,
29645      link: function(scope, element, attrs, controllers) {
29646        var calendarCtrl = controllers[0];
29647        var monthCtrl = controllers[1];
29648        var monthBodyCtrl = controllers[2];
29649
29650        monthBodyCtrl.calendarCtrl = calendarCtrl;
29651        monthBodyCtrl.monthCtrl = monthCtrl;
29652        monthBodyCtrl.arrowIcon = ARROW_ICON.cloneNode(true);
29653
29654        // The virtual-repeat re-uses the same DOM elements, so there are only a limited number
29655        // of repeated items that are linked, and then those elements have their bindings updated.
29656        // Since the months are not generated by bindings, we simply regenerate the entire thing
29657        // when the binding (offset) changes.
29658        scope.$watch(function() { return monthBodyCtrl.offset; }, function(offset) {
29659          if (angular.isNumber(offset)) {
29660            monthBodyCtrl.generateContent();
29661          }
29662        });
29663      }
29664    };
29665  }
29666
29667  /**
29668   * Controller for a single calendar month.
29669   * @ngInject @constructor
29670   */
29671  function CalendarMonthBodyCtrl($element, $$mdDateUtil, $mdDateLocale) {
29672    /** @final {!angular.JQLite} */
29673    this.$element = $element;
29674
29675    /** @final */
29676    this.dateUtil = $$mdDateUtil;
29677
29678    /** @final */
29679    this.dateLocale = $mdDateLocale;
29680
29681    /** @type {Object} Reference to the month view. */
29682    this.monthCtrl = null;
29683
29684    /** @type {Object} Reference to the calendar. */
29685    this.calendarCtrl = null;
29686
29687    /**
29688     * Number of months from the start of the month "items" that the currently rendered month
29689     * occurs. Set via angular data binding.
29690     * @type {number}
29691     */
29692    this.offset = null;
29693
29694    /**
29695     * Date cell to focus after appending the month to the document.
29696     * @type {HTMLElement}
29697     */
29698    this.focusAfterAppend = null;
29699  }
29700
29701  /** Generate and append the content for this month to the directive element. */
29702  CalendarMonthBodyCtrl.prototype.generateContent = function() {
29703    var date = this.dateUtil.incrementMonths(this.calendarCtrl.firstRenderableDate, this.offset);
29704
29705    this.$element
29706      .empty()
29707      .append(this.buildCalendarForMonth(date));
29708
29709    if (this.focusAfterAppend) {
29710      this.focusAfterAppend.classList.add(this.calendarCtrl.FOCUSED_DATE_CLASS);
29711      this.focusAfterAppend.focus();
29712      this.focusAfterAppend = null;
29713    }
29714  };
29715
29716  /**
29717   * Creates a single cell to contain a date in the calendar with all appropriate
29718   * attributes and classes added. If a date is given, the cell content will be set
29719   * based on the date.
29720   * @param {Date=} opt_date
29721   * @returns {HTMLElement}
29722   */
29723  CalendarMonthBodyCtrl.prototype.buildDateCell = function(opt_date) {
29724    var monthCtrl = this.monthCtrl;
29725    var calendarCtrl = this.calendarCtrl;
29726
29727    // TODO(jelbourn): cloneNode is likely a faster way of doing this.
29728    var cell = document.createElement('td');
29729    cell.tabIndex = -1;
29730    cell.classList.add('md-calendar-date');
29731    cell.setAttribute('role', 'gridcell');
29732
29733    if (opt_date) {
29734      cell.setAttribute('tabindex', '-1');
29735      cell.setAttribute('aria-label', this.dateLocale.longDateFormatter(opt_date));
29736      cell.id = calendarCtrl.getDateId(opt_date, 'month');
29737
29738      // Use `data-timestamp` attribute because IE10 does not support the `dataset` property.
29739      cell.setAttribute('data-timestamp', opt_date.getTime());
29740
29741      // TODO(jelourn): Doing these comparisons for class addition during generation might be slow.
29742      // It may be better to finish the construction and then query the node and add the class.
29743      if (this.dateUtil.isSameDay(opt_date, calendarCtrl.today)) {
29744        cell.classList.add(calendarCtrl.TODAY_CLASS);
29745      }
29746
29747      if (this.dateUtil.isValidDate(calendarCtrl.selectedDate) &&
29748          this.dateUtil.isSameDay(opt_date, calendarCtrl.selectedDate)) {
29749        cell.classList.add(calendarCtrl.SELECTED_DATE_CLASS);
29750        cell.setAttribute('aria-selected', 'true');
29751      }
29752
29753      var cellText = this.dateLocale.dates[opt_date.getDate()];
29754
29755      if (this.isDateEnabled(opt_date)) {
29756        // Add a indicator for select, hover, and focus states.
29757        var selectionIndicator = document.createElement('span');
29758        selectionIndicator.classList.add('md-calendar-date-selection-indicator');
29759        selectionIndicator.textContent = cellText;
29760        cell.appendChild(selectionIndicator);
29761        cell.addEventListener('click', monthCtrl.cellClickHandler);
29762
29763        if (calendarCtrl.displayDate && this.dateUtil.isSameDay(opt_date, calendarCtrl.displayDate)) {
29764          this.focusAfterAppend = cell;
29765        }
29766      } else {
29767        cell.classList.add('md-calendar-date-disabled');
29768        cell.textContent = cellText;
29769      }
29770    }
29771
29772    return cell;
29773  };
29774
29775  /**
29776   * Check whether date is in range and enabled
29777   * @param {Date=} opt_date
29778   * @return {boolean} Whether the date is enabled.
29779   */
29780  CalendarMonthBodyCtrl.prototype.isDateEnabled = function(opt_date) {
29781    return this.dateUtil.isDateWithinRange(opt_date,
29782          this.calendarCtrl.minDate, this.calendarCtrl.maxDate) &&
29783          (!angular.isFunction(this.calendarCtrl.dateFilter)
29784           || this.calendarCtrl.dateFilter(opt_date));
29785  };
29786
29787  /**
29788   * Builds a `tr` element for the calendar grid.
29789   * @param rowNumber The week number within the month.
29790   * @returns {HTMLElement}
29791   */
29792  CalendarMonthBodyCtrl.prototype.buildDateRow = function(rowNumber) {
29793    var row = document.createElement('tr');
29794    row.setAttribute('role', 'row');
29795
29796    // Because of an NVDA bug (with Firefox), the row needs an aria-label in order
29797    // to prevent the entire row being read aloud when the user moves between rows.
29798    // See http://community.nvda-project.org/ticket/4643.
29799    row.setAttribute('aria-label', this.dateLocale.weekNumberFormatter(rowNumber));
29800
29801    return row;
29802  };
29803
29804  /**
29805   * Builds the <tbody> content for the given date's month.
29806   * @param {Date=} opt_dateInMonth
29807   * @returns {DocumentFragment} A document fragment containing the <tr> elements.
29808   */
29809  CalendarMonthBodyCtrl.prototype.buildCalendarForMonth = function(opt_dateInMonth) {
29810    var date = this.dateUtil.isValidDate(opt_dateInMonth) ? opt_dateInMonth : new Date();
29811
29812    var firstDayOfMonth = this.dateUtil.getFirstDateOfMonth(date);
29813    var firstDayOfTheWeek = this.getLocaleDay_(firstDayOfMonth);
29814    var numberOfDaysInMonth = this.dateUtil.getNumberOfDaysInMonth(date);
29815
29816    // Store rows for the month in a document fragment so that we can append them all at once.
29817    var monthBody = document.createDocumentFragment();
29818
29819    var rowNumber = 1;
29820    var row = this.buildDateRow(rowNumber);
29821    monthBody.appendChild(row);
29822
29823    // If this is the final month in the list of items, only the first week should render,
29824    // so we should return immediately after the first row is complete and has been
29825    // attached to the body.
29826    var isFinalMonth = this.offset === this.monthCtrl.items.length - 1;
29827
29828    // Add a label for the month. If the month starts on a Sun/Mon/Tues, the month label
29829    // goes on a row above the first of the month. Otherwise, the month label takes up the first
29830    // two cells of the first row.
29831    var blankCellOffset = 0;
29832    var monthLabelCell = document.createElement('td');
29833    var monthLabelCellContent = document.createElement('span');
29834
29835    monthLabelCellContent.textContent = this.dateLocale.monthHeaderFormatter(date);
29836    monthLabelCell.appendChild(monthLabelCellContent);
29837    monthLabelCell.classList.add('md-calendar-month-label');
29838    // If the entire month is after the max date, render the label as a disabled state.
29839    if (this.calendarCtrl.maxDate && firstDayOfMonth > this.calendarCtrl.maxDate) {
29840      monthLabelCell.classList.add('md-calendar-month-label-disabled');
29841    } else {
29842      monthLabelCell.addEventListener('click', this.monthCtrl.headerClickHandler);
29843      monthLabelCell.setAttribute('data-timestamp', firstDayOfMonth.getTime());
29844      monthLabelCell.setAttribute('aria-label', this.dateLocale.monthFormatter(date));
29845      monthLabelCell.appendChild(this.arrowIcon.cloneNode(true));
29846    }
29847
29848    if (firstDayOfTheWeek <= 2) {
29849      monthLabelCell.setAttribute('colspan', '7');
29850
29851      var monthLabelRow = this.buildDateRow();
29852      monthLabelRow.appendChild(monthLabelCell);
29853      monthBody.insertBefore(monthLabelRow, row);
29854
29855      if (isFinalMonth) {
29856        return monthBody;
29857      }
29858    } else {
29859      blankCellOffset = 3;
29860      monthLabelCell.setAttribute('colspan', '3');
29861      row.appendChild(monthLabelCell);
29862    }
29863
29864    // Add a blank cell for each day of the week that occurs before the first of the month.
29865    // For example, if the first day of the month is a Tuesday, add blank cells for Sun and Mon.
29866    // The blankCellOffset is needed in cases where the first N cells are used by the month label.
29867    for (var i = blankCellOffset; i < firstDayOfTheWeek; i++) {
29868      row.appendChild(this.buildDateCell());
29869    }
29870
29871    // Add a cell for each day of the month, keeping track of the day of the week so that
29872    // we know when to start a new row.
29873    var dayOfWeek = firstDayOfTheWeek;
29874    var iterationDate = firstDayOfMonth;
29875    for (var d = 1; d <= numberOfDaysInMonth; d++) {
29876      // If we've reached the end of the week, start a new row.
29877      if (dayOfWeek === 7) {
29878        // We've finished the first row, so we're done if this is the final month.
29879        if (isFinalMonth) {
29880          return monthBody;
29881        }
29882        dayOfWeek = 0;
29883        rowNumber++;
29884        row = this.buildDateRow(rowNumber);
29885        monthBody.appendChild(row);
29886      }
29887
29888      iterationDate.setDate(d);
29889      var cell = this.buildDateCell(iterationDate);
29890      row.appendChild(cell);
29891
29892      dayOfWeek++;
29893    }
29894
29895    // Ensure that the last row of the month has 7 cells.
29896    while (row.childNodes.length < 7) {
29897      row.appendChild(this.buildDateCell());
29898    }
29899
29900    // Ensure that all months have 6 rows. This is necessary for now because the virtual-repeat
29901    // requires that all items have exactly the same height.
29902    while (monthBody.childNodes.length < 6) {
29903      var whitespaceRow = this.buildDateRow();
29904      for (var j = 0; j < 7; j++) {
29905        whitespaceRow.appendChild(this.buildDateCell());
29906      }
29907      monthBody.appendChild(whitespaceRow);
29908    }
29909
29910    return monthBody;
29911  };
29912
29913  /**
29914   * Gets the day-of-the-week index for a date for the current locale.
29915   * @private
29916   * @param {Date} date
29917   * @returns {number} The column index of the date in the calendar.
29918   */
29919  CalendarMonthBodyCtrl.prototype.getLocaleDay_ = function(date) {
29920    return (date.getDay() + (7 - this.dateLocale.firstDayOfWeek)) % 7;
29921  };
29922})();
29923
29924})();
29925(function(){
29926"use strict";
29927
29928(function() {
29929  'use strict';
29930
29931  CalendarYearCtrl.$inject = ["$element", "$scope", "$animate", "$q", "$$mdDateUtil"];
29932  angular.module('material.components.datepicker')
29933    .directive('mdCalendarYear', calendarDirective);
29934
29935  /**
29936   * Height of one calendar year tbody. This must be made known to the virtual-repeat and is
29937   * subsequently used for scrolling to specific years.
29938   */
29939  var TBODY_HEIGHT = 88;
29940
29941  /** Private component, representing a list of years in the calendar. */
29942  function calendarDirective() {
29943    return {
29944      template:
29945        '<div class="md-calendar-scroll-mask">' +
29946          '<md-virtual-repeat-container class="md-calendar-scroll-container">' +
29947            '<table role="grid" tabindex="0" class="md-calendar" aria-readonly="true">' +
29948              '<tbody ' +
29949                  'md-calendar-year-body ' +
29950                  'role="rowgroup" ' +
29951                  'md-virtual-repeat="i in yearCtrl.items" ' +
29952                  'md-year-offset="$index" class="md-calendar-year" ' +
29953                  'md-start-index="yearCtrl.getFocusedYearIndex()" ' +
29954                  'md-item-size="' + TBODY_HEIGHT + '">' +
29955                // The <tr> ensures that the <tbody> will have the proper
29956                // height, even though it may be empty.
29957                '<tr aria-hidden="true" md-force-height="\'' + TBODY_HEIGHT + 'px\'"></tr>' +
29958              '</tbody>' +
29959            '</table>' +
29960          '</md-virtual-repeat-container>' +
29961        '</div>',
29962      require: ['^^mdCalendar', 'mdCalendarYear'],
29963      controller: CalendarYearCtrl,
29964      controllerAs: 'yearCtrl',
29965      bindToController: true,
29966      link: function(scope, element, attrs, controllers) {
29967        var calendarCtrl = controllers[0];
29968        var yearCtrl = controllers[1];
29969        yearCtrl.initialize(calendarCtrl);
29970      }
29971    };
29972  }
29973
29974  /**
29975   * Controller for the mdCalendar component.
29976   * @ngInject @constructor
29977   */
29978  function CalendarYearCtrl($element, $scope, $animate, $q, $$mdDateUtil) {
29979
29980    /** @final {!angular.JQLite} */
29981    this.$element = $element;
29982
29983    /** @final {!angular.Scope} */
29984    this.$scope = $scope;
29985
29986    /** @final {!angular.$animate} */
29987    this.$animate = $animate;
29988
29989    /** @final {!angular.$q} */
29990    this.$q = $q;
29991
29992    /** @final */
29993    this.dateUtil = $$mdDateUtil;
29994
29995    /** @final {HTMLElement} */
29996    this.calendarScroller = $element[0].querySelector('.md-virtual-repeat-scroller');
29997
29998    /** @type {boolean} */
29999    this.isInitialized = false;
30000
30001    /** @type {boolean} */
30002    this.isMonthTransitionInProgress = false;
30003
30004    var self = this;
30005
30006    /**
30007     * Handles a click event on a date cell.
30008     * Created here so that every cell can use the same function instance.
30009     * @this {HTMLTableCellElement} The cell that was clicked.
30010     */
30011    this.cellClickHandler = function() {
30012      self.calendarCtrl.setCurrentView('month', $$mdDateUtil.getTimestampFromNode(this));
30013    };
30014  }
30015
30016  /**
30017   * Initialize the controller by saving a reference to the calendar and
30018   * setting up the object that will be iterated by the virtual repeater.
30019   */
30020  CalendarYearCtrl.prototype.initialize = function(calendarCtrl) {
30021    /**
30022     * Dummy array-like object for virtual-repeat to iterate over. The length is the total
30023     * number of years that can be viewed. We add 1 extra in order to include the current year.
30024     */
30025    this.items = {
30026      length: this.dateUtil.getYearDistance(
30027        calendarCtrl.firstRenderableDate,
30028        calendarCtrl.lastRenderableDate
30029      ) + 1
30030    };
30031
30032    this.calendarCtrl = calendarCtrl;
30033    this.attachScopeListeners();
30034    calendarCtrl.updateVirtualRepeat();
30035
30036    // Fire the initial render, since we might have missed it the first time it fired.
30037    calendarCtrl.ngModelCtrl && calendarCtrl.ngModelCtrl.$render();
30038  };
30039
30040  /**
30041   * Gets the "index" of the currently selected date as it would be in the virtual-repeat.
30042   * @returns {number}
30043   */
30044  CalendarYearCtrl.prototype.getFocusedYearIndex = function() {
30045    var calendarCtrl = this.calendarCtrl;
30046
30047    return this.dateUtil.getYearDistance(
30048      calendarCtrl.firstRenderableDate,
30049      calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today
30050    );
30051  };
30052
30053  /**
30054   * Change the date that is highlighted in the calendar.
30055   * @param {Date} date
30056   */
30057  CalendarYearCtrl.prototype.changeDate = function(date) {
30058    // Initialization is deferred until this function is called because we want to reflect
30059    // the starting value of ngModel.
30060    if (!this.isInitialized) {
30061      this.calendarCtrl.hideVerticalScrollbar(this);
30062      this.isInitialized = true;
30063      return this.$q.when();
30064    } else if (this.dateUtil.isValidDate(date) && !this.isMonthTransitionInProgress) {
30065      var self = this;
30066      var animationPromise = this.animateDateChange(date);
30067
30068      self.isMonthTransitionInProgress = true;
30069      self.calendarCtrl.displayDate = date;
30070
30071      return animationPromise.then(function() {
30072        self.isMonthTransitionInProgress = false;
30073      });
30074    }
30075  };
30076
30077  /**
30078   * Animates the transition from the calendar's current month to the given month.
30079   * @param {Date} date
30080   * @returns {angular.$q.Promise} The animation promise.
30081   */
30082  CalendarYearCtrl.prototype.animateDateChange = function(date) {
30083    if (this.dateUtil.isValidDate(date)) {
30084      var monthDistance = this.dateUtil.getYearDistance(this.calendarCtrl.firstRenderableDate, date);
30085      this.calendarScroller.scrollTop = monthDistance * TBODY_HEIGHT;
30086    }
30087
30088    return this.$q.when();
30089  };
30090
30091  /**
30092   * Handles the year-view-specific keyboard interactions.
30093   * @param {Object} event Scope event object passed by the calendar.
30094   * @param {String} action Action, corresponding to the key that was pressed.
30095   */
30096  CalendarYearCtrl.prototype.handleKeyEvent = function(event, action) {
30097    var calendarCtrl = this.calendarCtrl;
30098    var displayDate = calendarCtrl.displayDate;
30099
30100    if (action === 'select') {
30101      this.changeDate(displayDate).then(function() {
30102        calendarCtrl.setCurrentView('month', displayDate);
30103        calendarCtrl.focus(displayDate);
30104      });
30105    } else {
30106      var date = null;
30107      var dateUtil = this.dateUtil;
30108
30109      switch (action) {
30110        case 'move-right': date = dateUtil.incrementMonths(displayDate, 1); break;
30111        case 'move-left': date = dateUtil.incrementMonths(displayDate, -1); break;
30112
30113        case 'move-row-down': date = dateUtil.incrementMonths(displayDate, 6); break;
30114        case 'move-row-up': date = dateUtil.incrementMonths(displayDate, -6); break;
30115      }
30116
30117      if (date) {
30118        var min = calendarCtrl.minDate ? dateUtil.getFirstDateOfMonth(calendarCtrl.minDate) : null;
30119        var max = calendarCtrl.maxDate ? dateUtil.getFirstDateOfMonth(calendarCtrl.maxDate) : null;
30120        date = dateUtil.getFirstDateOfMonth(this.dateUtil.clampDate(date, min, max));
30121
30122        this.changeDate(date).then(function() {
30123          calendarCtrl.focus(date);
30124        });
30125      }
30126    }
30127  };
30128
30129  /**
30130   * Attaches listeners for the scope events that are broadcast by the calendar.
30131   */
30132  CalendarYearCtrl.prototype.attachScopeListeners = function() {
30133    var self = this;
30134
30135    self.$scope.$on('md-calendar-parent-changed', function(event, value) {
30136      self.changeDate(value);
30137    });
30138
30139    self.$scope.$on('md-calendar-parent-action', angular.bind(self, self.handleKeyEvent));
30140  };
30141})();
30142
30143})();
30144(function(){
30145"use strict";
30146
30147(function() {
30148  'use strict';
30149
30150  CalendarYearBodyCtrl.$inject = ["$element", "$$mdDateUtil", "$mdDateLocale"];
30151  angular.module('material.components.datepicker')
30152      .directive('mdCalendarYearBody', mdCalendarYearDirective);
30153
30154  /**
30155   * Private component, consumed by the md-calendar-year, which separates the DOM construction logic
30156   * and allows for the year view to use md-virtual-repeat.
30157   */
30158  function mdCalendarYearDirective() {
30159    return {
30160      require: ['^^mdCalendar', '^^mdCalendarYear', 'mdCalendarYearBody'],
30161      scope: { offset: '=mdYearOffset' },
30162      controller: CalendarYearBodyCtrl,
30163      controllerAs: 'mdYearBodyCtrl',
30164      bindToController: true,
30165      link: function(scope, element, attrs, controllers) {
30166        var calendarCtrl = controllers[0];
30167        var yearCtrl = controllers[1];
30168        var yearBodyCtrl = controllers[2];
30169
30170        yearBodyCtrl.calendarCtrl = calendarCtrl;
30171        yearBodyCtrl.yearCtrl = yearCtrl;
30172
30173        scope.$watch(function() { return yearBodyCtrl.offset; }, function(offset) {
30174          if (angular.isNumber(offset)) {
30175            yearBodyCtrl.generateContent();
30176          }
30177        });
30178      }
30179    };
30180  }
30181
30182  /**
30183   * Controller for a single year.
30184   * @ngInject @constructor
30185   */
30186  function CalendarYearBodyCtrl($element, $$mdDateUtil, $mdDateLocale) {
30187    /** @final {!angular.JQLite} */
30188    this.$element = $element;
30189
30190    /** @final */
30191    this.dateUtil = $$mdDateUtil;
30192
30193    /** @final */
30194    this.dateLocale = $mdDateLocale;
30195
30196    /** @type {Object} Reference to the calendar. */
30197    this.calendarCtrl = null;
30198
30199    /** @type {Object} Reference to the year view. */
30200    this.yearCtrl = null;
30201
30202    /**
30203     * Number of months from the start of the month "items" that the currently rendered month
30204     * occurs. Set via angular data binding.
30205     * @type {number}
30206     */
30207    this.offset = null;
30208
30209    /**
30210     * Date cell to focus after appending the month to the document.
30211     * @type {HTMLElement}
30212     */
30213    this.focusAfterAppend = null;
30214  }
30215
30216  /** Generate and append the content for this year to the directive element. */
30217  CalendarYearBodyCtrl.prototype.generateContent = function() {
30218    var date = this.dateUtil.incrementYears(this.calendarCtrl.firstRenderableDate, this.offset);
30219
30220    this.$element
30221      .empty()
30222      .append(this.buildCalendarForYear(date));
30223
30224    if (this.focusAfterAppend) {
30225      this.focusAfterAppend.classList.add(this.calendarCtrl.FOCUSED_DATE_CLASS);
30226      this.focusAfterAppend.focus();
30227      this.focusAfterAppend = null;
30228    }
30229  };
30230
30231  /**
30232   * Creates a single cell to contain a year in the calendar.
30233   * @param {number} opt_year Four-digit year.
30234   * @param {number} opt_month Zero-indexed month.
30235   * @returns {HTMLElement}
30236   */
30237  CalendarYearBodyCtrl.prototype.buildMonthCell = function(year, month) {
30238    var calendarCtrl = this.calendarCtrl;
30239    var yearCtrl = this.yearCtrl;
30240    var cell = this.buildBlankCell();
30241
30242    // Represent this month/year as a date.
30243    var firstOfMonth = new Date(year, month, 1);
30244    cell.setAttribute('aria-label', this.dateLocale.monthFormatter(firstOfMonth));
30245    cell.id = calendarCtrl.getDateId(firstOfMonth, 'year');
30246
30247    // Use `data-timestamp` attribute because IE10 does not support the `dataset` property.
30248    cell.setAttribute('data-timestamp', firstOfMonth.getTime());
30249
30250    if (this.dateUtil.isSameMonthAndYear(firstOfMonth, calendarCtrl.today)) {
30251      cell.classList.add(calendarCtrl.TODAY_CLASS);
30252    }
30253
30254    if (this.dateUtil.isValidDate(calendarCtrl.selectedDate) &&
30255        this.dateUtil.isSameMonthAndYear(firstOfMonth, calendarCtrl.selectedDate)) {
30256      cell.classList.add(calendarCtrl.SELECTED_DATE_CLASS);
30257      cell.setAttribute('aria-selected', 'true');
30258    }
30259
30260    var cellText = this.dateLocale.shortMonths[month];
30261
30262    if (this.dateUtil.isMonthWithinRange(firstOfMonth,
30263        calendarCtrl.minDate, calendarCtrl.maxDate)) {
30264      var selectionIndicator = document.createElement('span');
30265      selectionIndicator.classList.add('md-calendar-date-selection-indicator');
30266      selectionIndicator.textContent = cellText;
30267      cell.appendChild(selectionIndicator);
30268      cell.addEventListener('click', yearCtrl.cellClickHandler);
30269
30270      if (calendarCtrl.displayDate && this.dateUtil.isSameMonthAndYear(firstOfMonth, calendarCtrl.displayDate)) {
30271        this.focusAfterAppend = cell;
30272      }
30273    } else {
30274      cell.classList.add('md-calendar-date-disabled');
30275      cell.textContent = cellText;
30276    }
30277
30278    return cell;
30279  };
30280
30281  /**
30282   * Builds a blank cell.
30283   * @return {HTMLTableCellElement}
30284   */
30285  CalendarYearBodyCtrl.prototype.buildBlankCell = function() {
30286    var cell = document.createElement('td');
30287    cell.tabIndex = -1;
30288    cell.classList.add('md-calendar-date');
30289    cell.setAttribute('role', 'gridcell');
30290
30291    cell.setAttribute('tabindex', '-1');
30292    return cell;
30293  };
30294
30295  /**
30296   * Builds the <tbody> content for the given year.
30297   * @param {Date} date Date for which the content should be built.
30298   * @returns {DocumentFragment} A document fragment containing the months within the year.
30299   */
30300  CalendarYearBodyCtrl.prototype.buildCalendarForYear = function(date) {
30301    // Store rows for the month in a document fragment so that we can append them all at once.
30302    var year = date.getFullYear();
30303    var yearBody = document.createDocumentFragment();
30304
30305    var monthCell, i;
30306    // First row contains label and Jan-Jun.
30307    var firstRow = document.createElement('tr');
30308    var labelCell = document.createElement('td');
30309    labelCell.className = 'md-calendar-month-label';
30310    labelCell.textContent = year;
30311    firstRow.appendChild(labelCell);
30312
30313    for (i = 0; i < 6; i++) {
30314      firstRow.appendChild(this.buildMonthCell(year, i));
30315    }
30316    yearBody.appendChild(firstRow);
30317
30318    // Second row contains a blank cell and Jul-Dec.
30319    var secondRow = document.createElement('tr');
30320    secondRow.appendChild(this.buildBlankCell());
30321    for (i = 6; i < 12; i++) {
30322      secondRow.appendChild(this.buildMonthCell(year, i));
30323    }
30324    yearBody.appendChild(secondRow);
30325
30326    return yearBody;
30327  };
30328})();
30329
30330})();
30331(function(){
30332"use strict";
30333
30334(function() {
30335  'use strict';
30336
30337  /**
30338   * @ngdoc service
30339   * @name $mdDateLocaleProvider
30340   * @module material.components.datepicker
30341   *
30342   * @description
30343   * The `$mdDateLocaleProvider` is the provider that creates the `$mdDateLocale` service.
30344   * This provider that allows the user to specify messages, formatters, and parsers for date
30345   * internationalization. The `$mdDateLocale` service itself is consumed by AngularJS Material
30346   * components that deal with dates.
30347   *
30348   * @property {(Array<string>)=} months Array of month names (in order).
30349   * @property {(Array<string>)=} shortMonths Array of abbreviated month names.
30350   * @property {(Array<string>)=} days Array of the days of the week (in order).
30351   * @property {(Array<string>)=} shortDays Array of abbreviated days of the week.
30352   * @property {(Array<string>)=} dates Array of dates of the month. Only necessary for locales
30353   *     using a numeral system other than [1, 2, 3...].
30354   * @property {(Array<string>)=} firstDayOfWeek The first day of the week. Sunday = 0, Monday = 1,
30355   *    etc.
30356   * @property {(function(string): Date)=} parseDate Function to parse a date object from a string.
30357   * @property {(function(Date, string): string)=} formatDate Function to format a date object to a
30358   *     string. The datepicker directive also provides the time zone, if it was specified.
30359   * @property {(function(Date): string)=} monthHeaderFormatter Function that returns the label for
30360   *     a month given a date.
30361   * @property {(function(Date): string)=} monthFormatter Function that returns the full name of a month
30362   *     for a given date.
30363   * @property {(function(number): string)=} weekNumberFormatter Function that returns a label for
30364   *     a week given the week number.
30365   * @property {(string)=} msgCalendar Translation of the label "Calendar" for the current locale.
30366   * @property {(string)=} msgOpenCalendar Translation of the button label "Open calendar" for the
30367   *     current locale.
30368   * @property {Date=}
30368 firstRenderableDate The date from which the datepicker calendar will begin
30369   * rendering. Note that this will be ignored if a minimum date is set. Defaults to January 1st 1880.
30370   * @property {Date=} lastRenderableDate The last date that will be rendered by the datepicker
30371   * calendar. Note that this will be ignored if a maximum date is set. Defaults to January 1st 2130.
30372   *
30373   * @usage
30374   * <hljs lang="js">
30375   * myAppModule.config(function($mdDateLocaleProvider) {
30376   *
30377   *     // Example of a French localization.
30378   *     $mdDateLocaleProvider.months = ['janvier', 'février', 'mars', ...];
30379   *     $mdDateLocaleProvider.shortMonths = ['janv', 'févr', 'mars', ...];
30380   *     $mdDateLocaleProvider.days = ['dimanche', 'lundi', 'mardi', ...];
30381   *     $mdDateLocaleProvider.shortDays = ['Di', 'Lu', 'Ma', ...];
30382   *
30383   *     // Can change week display to start on Monday.
30384   *     $mdDateLocaleProvider.firstDayOfWeek = 1;
30385   *
30386   *     // Optional.
30387   *     $mdDateLocaleProvider.dates = [1, 2, 3, 4, 5, 6, ...];
30388   *
30389   *     // Example uses moment.js to parse and format dates.
30390   *     $mdDateLocaleProvider.parseDate = function(dateString) {
30391   *       var m = moment(dateString, 'L', true);
30392   *       return m.isValid() ? m.toDate() : new Date(NaN);
30393   *     };
30394   *
30395   *     $mdDateLocaleProvider.formatDate = function(date) {
30396   *       var m = moment(date);
30397   *       return m.isValid() ? m.format('L') : '';
30398   *     };
30399   *
30400   *     $mdDateLocaleProvider.monthHeaderFormatter = function(date) {
30401   *       return myShortMonths[date.getMonth()] + ' ' + date.getFullYear();
30402   *     };
30403   *
30404   *     // In addition to date display, date components also need localized messages
30405   *     // for aria-labels for screen-reader users.
30406   *
30407   *     $mdDateLocaleProvider.weekNumberFormatter = function(weekNumber) {
30408   *       return 'Semaine ' + weekNumber;
30409   *     };
30410   *
30411   *     $mdDateLocaleProvider.msgCalendar = 'Calendrier';
30412   *     $mdDateLocaleProvider.msgOpenCalendar = 'Ouvrir le calendrier';
30413   *
30414   *     // You can also set when your calendar begins and ends.
30415   *     $mdDateLocaleProvider.firstRenderableDate = new Date(1776, 6, 4);
30416   *     $mdDateLocaleProvider.lastRenderableDate = new Date(2012, 11, 21);
30417   * });
30418   * </hljs>
30419   *
30420   */
30421  angular.module('material.components.datepicker').config(["$provide", function($provide) {
30422    // TODO(jelbourn): Assert provided values are correctly formatted. Need assertions.
30423
30424    /** @constructor */
30425    function DateLocaleProvider() {
30426      /** Array of full month names. E.g., ['January', 'Febuary', ...] */
30427      this.months = null;
30428
30429      /** Array of abbreviated month names. E.g., ['Jan', 'Feb', ...] */
30430      this.shortMonths = null;
30431
30432      /** Array of full day of the week names. E.g., ['Monday', 'Tuesday', ...] */
30433      this.days = null;
30434
30435      /** Array of abbreviated dat of the week names. E.g., ['M', 'T', ...] */
30436      this.shortDays = null;
30437
30438      /** Array of dates of a month (1 - 31). Characters might be different in some locales. */
30439      this.dates = null;
30440
30441      /** Index of the first day of the week. 0 = Sunday, 1 = Monday, etc. */
30442      this.firstDayOfWeek = 0;
30443
30444      /**
30445       * Function that converts the date portion of a Date to a string.
30446       * @type {(function(Date): string)}
30447       */
30448      this.formatDate = null;
30449
30450      /**
30451       * Function that converts a date string to a Date object (the date portion)
30452       * @type {function(string): Date}
30453       */
30454      this.parseDate = null;
30455
30456      /**
30457       * Function that formats a Date into a month header string.
30458       * @type {function(Date): string}
30459       */
30460      this.monthHeaderFormatter = null;
30461
30462      /**
30463       * Function that formats a week number into a label for the week.
30464       * @type {function(number): string}
30465       */
30466      this.weekNumberFormatter = null;
30467
30468      /**
30469       * Function that formats a date into a long aria-label that is read
30470       * when the focused date changes.
30471       * @type {function(Date): string}
30472       */
30473      this.longDateFormatter = null;
30474
30475      /**
30476       * ARIA label for the calendar "dialog" used in the datepicker.
30477       * @type {string}
30478       */
30479      this.msgCalendar = '';
30480
30481      /**
30482       * ARIA label for the datepicker's "Open calendar" buttons.
30483       * @type {string}
30484       */
30485      this.msgOpenCalendar = '';
30486    }
30487
30488    /**
30489     * Factory function that returns an instance of the dateLocale service.
30490     * @ngInject
30491     * @param $locale
30492     * @returns {DateLocale}
30493     */
30494    DateLocaleProvider.prototype.$get = function($locale, $filter) {
30495      /**
30496       * Default date-to-string formatting function.
30497       * @param {!Date} date
30498       * @param {string=} timezone
30499       * @returns {string}
30500       */
30501      function defaultFormatDate(date, timezone) {
30502        if (!date) {
30503          return '';
30504        }
30505
30506        // All of the dates created through ng-material *should* be set to midnight.
30507        // If we encounter a date where the localeTime shows at 11pm instead of midnight,
30508        // we have run into an issue with DST where we need to increment the hour by one:
30509        // var d = new Date(1992, 9, 8, 0, 0, 0);
vendor: 53,762 bytes, lines 30510-31914
30510        // d.toLocaleString(); // == "10/7/1992, 11:00:00 PM"
30511        var localeTime = date.toLocaleTimeString();
30512        var formatDate = date;
30513        if (date.getHours() === 0 &&
30514            (localeTime.indexOf('11:') !== -1 || localeTime.indexOf('23:') !== -1)) {
30515          formatDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 1, 0, 0);
30516        }
30517
30518        return $filter('date')(formatDate, 'dd/MM/yyyy', timezone);
30519      }
30520
30521      /**
30522       * Default string-to-date parsing function.
30523       * @param {string} dateString
30524       * @returns {!Date}
30525       */
30526      function defaultParseDate(dateString) {
30527        return new Date(dateString);
30528      }
30529
30530      /**
30531       * Default function to determine whether a string makes sense to be
30532       * parsed to a Date object.
30533       *
30534       * This is very permissive and is just a basic sanity check to ensure that
30535       * things like single integers aren't able to be parsed into dates.
30536       * @param {string} dateString
30537       * @returns {boolean}
30538       */
30539      function defaultIsDateComplete(dateString) {
30540        dateString = dateString.trim();
30541
30542        // Looks for three chunks of content (either numbers or text) separated
30543        // by delimiters.
30544        var re = /^(([a-zA-Z]{3,}|[0-9]{1,4})([ \.,]+|[\/\-])){2}([a-zA-Z]{3,}|[0-9]{1,4})$/;
30545        return re.test(dateString);
30546      }
30547
30548      /**
30549       * Default date-to-string formatter to get a month header.
30550       * @param {!Date} date
30551       * @returns {string}
30552       */
30553      function defaultMonthHeaderFormatter(date) {
30554        return service.shortMonths[date.getMonth()] + ' ' + date.getFullYear();
30555      }
30556
30557      /**
30558       * Default formatter for a month.
30559       * @param {!Date} date
30560       * @returns {string}
30561       */
30562      function defaultMonthFormatter(date) {
30563        return service.months[date.getMonth()] + ' ' + date.getFullYear();
30564      }
30565
30566      /**
30567       * Default week number formatter.
30568       * @param number
30569       * @returns {string}
30570       */
30571      function defaultWeekNumberFormatter(number) {
30572        return 'Week ' + number;
30573      }
30574
30575      /**
30576       * Default formatter for date cell aria-labels.
30577       * @param {!Date} date
30578       * @returns {string}
30579       */
30580      function defaultLongDateFormatter(date) {
30581        // Example: 'Thursday June 18 2015'
30582        return [
30583          service.days[date.getDay()],
30584          service.months[date.getMonth()],
30585          service.dates[date.getDate()],
30586          date.getFullYear()
30587        ].join(' ');
30588      }
30589
30590      // The default "short" day strings are the first character of each day,
30591      // e.g., "Monday" => "M".
30592      var defaultShortDays = $locale.DATETIME_FORMATS.SHORTDAY.map(function(day) {
30593        return day.substring(0, 1);
30594      });
30595
30596      // The default dates are simply the numbers 1 through 31.
30597      var defaultDates = Array(32);
30598      for (var i = 1; i <= 31; i++) {
30599        defaultDates[i] = i;
30600      }
30601
30602      // Default ARIA messages are in English (US).
30603      var defaultMsgCalendar = 'Calendar';
30604      var defaultMsgOpenCalendar = 'Open calendar';
30605
30606      // Default start/end dates that are rendered in the calendar.
30607      var defaultFirstRenderableDate = new Date(1880, 0, 1);
30608      var defaultLastRendereableDate = new Date(defaultFirstRenderableDate.getFullYear() + 250, 0, 1);
30609
30610      var service = {
30611        months: this.months || $locale.DATETIME_FORMATS.MONTH,
30612        shortMonths: this.shortMonths || $locale.DATETIME_FORMATS.SHORTMONTH,
30613        days: this.days || $locale.DATETIME_FORMATS.DAY,
30614        shortDays: this.shortDays || defaultShortDays,
30615        dates: this.dates || defaultDates,
30616        firstDayOfWeek: this.firstDayOfWeek || 0,
30617        formatDate: this.formatDate || defaultFormatDate,
30618        parseDate: this.parseDate || defaultParseDate,
30619        isDateComplete: this.isDateComplete || defaultIsDateComplete,
30620        monthHeaderFormatter: this.monthHeaderFormatter || defaultMonthHeaderFormatter,
30621        monthFormatter: this.monthFormatter || defaultMonthFormatter,
30622        weekNumberFormatter: this.weekNumberFormatter || defaultWeekNumberFormatter,
30623        longDateFormatter: this.longDateFormatter || defaultLongDateFormatter,
30624        msgCalendar: this.msgCalendar || defaultMsgCalendar,
30625        msgOpenCalendar: this.msgOpenCalendar || defaultMsgOpenCalendar,
30626        firstRenderableDate: this.firstRenderableDate || defaultFirstRenderableDate,
30627        lastRenderableDate: this.lastRenderableDate || defaultLastRendereableDate
30628      };
30629
30630      return service;
30631    };
30632    DateLocaleProvider.prototype.$get.$inject = ["$locale", "$filter"];
30633
30634    $provide.provider('$mdDateLocale', new DateLocaleProvider());
30635  }]);
30636})();
30637
30638})();
30639(function(){
30640"use strict";
30641
30642(function() {
30643  'use strict';
30644
30645  /**
30646   * Utility for performing date calculations to facilitate operation of the calendar and
30647   * datepicker.
30648   */
30649  angular.module('material.components.datepicker').factory('$$mdDateUtil', function() {
30650    return {
30651      getFirstDateOfMonth: getFirstDateOfMonth,
30652      getNumberOfDaysInMonth: getNumberOfDaysInMonth,
30653      getDateInNextMonth: getDateInNextMonth,
30654      getDateInPreviousMonth: getDateInPreviousMonth,
30655      isInNextMonth: isInNextMonth,
30656      isInPreviousMonth: isInPreviousMonth,
30657      getDateMidpoint: getDateMidpoint,
30658      isSameMonthAndYear: isSameMonthAndYear,
30659      getWeekOfMonth: getWeekOfMonth,
30660      incrementDays: incrementDays,
30661      incrementMonths: incrementMonths,
30662      getLastDateOfMonth: getLastDateOfMonth,
30663      isSameDay: isSameDay,
30664      getMonthDistance: getMonthDistance,
30665      isValidDate: isValidDate,
30666      setDateTimeToMidnight: setDateTimeToMidnight,
30667      createDateAtMidnight: createDateAtMidnight,
30668      isDateWithinRange: isDateWithinRange,
30669      incrementYears: incrementYears,
30670      getYearDistance: getYearDistance,
30671      clampDate: clampDate,
30672      getTimestampFromNode: getTimestampFromNode,
30673      isMonthWithinRange: isMonthWithinRange
30674    };
30675
30676    /**
30677     * Gets the first day of the month for the given date's month.
30678     * @param {Date} date
30679     * @returns {Date}
30680     */
30681    function getFirstDateOfMonth(date) {
30682      return new Date(date.getFullYear(), date.getMonth(), 1);
30683    }
30684
30685    /**
30686     * Gets the number of days in the month for the given date's month.
30687     * @param date
30688     * @returns {number}
30689     */
30690    function getNumberOfDaysInMonth(date) {
30691      return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
30692    }
30693
30694    /**
30695     * Get an arbitrary date in the month after the given date's month.
30696     * @param date
30697     * @returns {Date}
30698     */
30699    function getDateInNextMonth(date) {
30700      return new Date(date.getFullYear(), date.getMonth() + 1, 1);
30701    }
30702
30703    /**
30704     * Get an arbitrary date in the month before the given date's month.
30705     * @param date
30706     * @returns {Date}
30707     */
30708    function getDateInPreviousMonth(date) {
30709      return new Date(date.getFullYear(), date.getMonth() - 1, 1);
30710    }
30711
30712    /**
30713     * Gets whether two dates have the same month and year.
30714     * @param {Date} d1
30715     * @param {Date} d2
30716     * @returns {boolean}
30717     */
30718    function isSameMonthAndYear(d1, d2) {
30719      return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth();
30720    }
30721
30722    /**
30723     * Gets whether two dates are the same day (not not necesarily the same time).
30724     * @param {Date} d1
30725     * @param {Date} d2
30726     * @returns {boolean}
30727     */
30728    function isSameDay(d1, d2) {
30729      return d1.getDate() == d2.getDate() && isSameMonthAndYear(d1, d2);
30730    }
30731
30732    /**
30733     * Gets whether a date is in the month immediately after some date.
30734     * @param {Date} startDate The date from which to compare.
30735     * @param {Date} endDate The date to check.
30736     * @returns {boolean}
30737     */
30738    function isInNextMonth(startDate, endDate) {
30739      var nextMonth = getDateInNextMonth(startDate);
30740      return isSameMonthAndYear(nextMonth, endDate);
30741    }
30742
30743    /**
30744     * Gets whether a date is in the month immediately before some date.
30745     * @param {Date} startDate The date from which to compare.
30746     * @param {Date} endDate The date to check.
30747     * @returns {boolean}
30748     */
30749    function isInPreviousMonth(startDate, endDate) {
30750      var previousMonth = getDateInPreviousMonth(startDate);
30751      return isSameMonthAndYear(endDate, previousMonth);
30752    }
30753
30754    /**
30755     * Gets the midpoint between two dates.
30756     * @param {Date} d1
30757     * @param {Date} d2
30758     * @returns {Date}
30759     */
30760    function getDateMidpoint(d1, d2) {
30761      return createDateAtMidnight((d1.getTime() + d2.getTime()) / 2);
30762    }
30763
30764    /**
30765     * Gets the week of the month that a given date occurs in.
30766     * @param {Date} date
30767     * @returns {number} Index of the week of the month (zero-based).
30768     */
30769    function getWeekOfMonth(date) {
30770      var firstDayOfMonth = getFirstDateOfMonth(date);
30771      return Math.floor((firstDayOfMonth.getDay() + date.getDate() - 1) / 7);
30772    }
30773
30774    /**
30775     * Gets a new date incremented by the given number of days. Number of days can be negative.
30776     * @param {Date} date
30777     * @param {number} numberOfDays
30778     * @returns {Date}
30779     */
30780    function incrementDays(date, numberOfDays) {
30781      return new Date(date.getFullYear(), date.getMonth(), date.getDate() + numberOfDays);
30782    }
30783
30784    /**
30785     * Gets a new date incremented by the given number of months. Number of months can be negative.
30786     * If the date of the given month does not match the target month, the date will be set to the
30787     * last day of the month.
30788     * @param {Date} date
30789     * @param {number} numberOfMonths
30790     * @returns {Date}
30791     */
30792    function incrementMonths(date, numberOfMonths) {
30793      // If the same date in the target month does not actually exist, the Date object will
30794      // automatically advance *another* month by the number of missing days.
30795      // For example, if you try to go from Jan. 30 to Feb. 30, you'll end up on March 2.
30796      // So, we check if the month overflowed and go to the last day of the target month instead.
30797      var dateInTargetMonth = new Date(date.getFullYear(), date.getMonth() + numberOfMonths, 1);
30798      var numberOfDaysInMonth = getNumberOfDaysInMonth(dateInTargetMonth);
30799      if (numberOfDaysInMonth < date.getDate()) {
30800        dateInTargetMonth.setDate(numberOfDaysInMonth);
30801      } else {
30802        dateInTargetMonth.setDate(date.getDate());
30803      }
30804
30805      return dateInTargetMonth;
30806    }
30807
30808    /**
30809     * Get the integer distance between two months. This *only* considers the month and year
30810     * portion of the Date instances.
30811     *
30812     * @param {Date} start
30813     * @param {Date} end
30814     * @returns {number} Number of months between `start` and `end`. If `end` is before `start`
30815     *     chronologically, this number will be negative.
30816     */
30817    function getMonthDistance(start, end) {
30818      return (12 * (end.getFullYear() - start.getFullYear())) + (end.getMonth() - start.getMonth());
30819    }
30820
30821    /**
30822     * Gets the last day of the month for the given date.
30823     * @param {Date} date
30824     * @returns {Date}
30825     */
30826    function getLastDateOfMonth(date) {
30827      return new Date(date.getFullYear(), date.getMonth(), getNumberOfDaysInMonth(date));
30828    }
30829
30830    /**
30831     * Checks whether a date is valid.
30832     * @param {Date} date
30833     * @return {boolean} Whether the date is a valid Date.
30834     */
30835    function isValidDate(date) {
30836      return date && date.getTime && !isNaN(date.getTime());
30837    }
30838
30839    /**
30840     * Sets a date's time to midnight.
30841     * @param {Date} date
30842     */
30843    function setDateTimeToMidnight(date) {
30844      if (isValidDate(date)) {
30845        date.setHours(0, 0, 0, 0);
30846      }
30847    }
30848
30849    /**
30850     * Creates a date with the time set to midnight.
30851     * Drop-in replacement for two forms of the Date constructor:
30852     * 1. No argument for Date representing now.
30853     * 2. Single-argument value representing number of seconds since Unix Epoch
30854     * or a Date object.
30855     * @param {number|Date=} opt_value
30856     * @return {Date} New date with time set to midnight.
30857     */
30858    function createDateAtMidnight(opt_value) {
30859      var date;
30860      if (angular.isUndefined(opt_value)) {
30861        date = new Date();
30862      } else {
30863        date = new Date(opt_value);
30864      }
30865      setDateTimeToMidnight(date);
30866      return date;
30867    }
30868
30869     /**
30870      * Checks if a date is within a min and max range, ignoring the time component.
30871      * If minDate or maxDate are not dates, they are ignored.
30872      * @param {Date} date
30873      * @param {Date} minDate
30874      * @param {Date} maxDate
30875      */
30876     function isDateWithinRange(date, minDate, maxDate) {
30877       var dateAtMidnight = createDateAtMidnight(date);
30878       var minDateAtMidnight = isValidDate(minDate) ? createDateAtMidnight(minDate) : null;
30879       var maxDateAtMidnight = isValidDate(maxDate) ? createDateAtMidnight(maxDate) : null;
30880       return (!minDateAtMidnight || minDateAtMidnight <= dateAtMidnight) &&
30881           (!maxDateAtMidnight || maxDateAtMidnight >= dateAtMidnight);
30882     }
30883
30884    /**
30885     * Gets a new date incremented by the given number of years. Number of years can be negative.
30886     * See `incrementMonths` for notes on overflow for specific dates.
30887     * @param {Date} date
30888     * @param {number} numberOfYears
30889     * @returns {Date}
30890     */
30891     function incrementYears(date, numberOfYears) {
30892       return incrementMonths(date, numberOfYears * 12);
30893     }
30894
30895     /**
30896      * Get the integer distance between two years. This *only* considers the year portion of the
30897      * Date instances.
30898      *
30899      * @param {Date} start
30900      * @param {Date} end
30901      * @returns {number} Number of months between `start` and `end`. If `end` is before `start`
30902      *     chronologically, this number will be negative.
30903      */
30904     function getYearDistance(start, end) {
30905       return end.getFullYear() - start.getFullYear();
30906     }
30907
30908     /**
30909      * Clamps a date between a minimum and a maximum date.
30910      * @param {Date} date Date to be clamped
30911      * @param {Date=} minDate Minimum date
30912      * @param {Date=} maxDate Maximum date
30913      * @return {Date}
30914      */
30915     function clampDate(date, minDate, maxDate) {
30916       var boundDate = date;
30917       if (minDate && date < minDate) {
30918         boundDate = new Date(minDate.getTime());
30919       }
30920       if (maxDate && date > maxDate) {
30921         boundDate = new Date(maxDate.getTime());
30922       }
30923       return boundDate;
30924     }
30925
30926     /**
30927      * Extracts and parses the timestamp from a DOM node.
30928      * @param  {HTMLElement} node Node from which the timestamp will be extracted.
30929      * @return {number} Time since epoch.
30930      */
30931     function getTimestampFromNode(node) {
30932       if (node && node.hasAttribute('data-timestamp')) {
30933         return Number(node.getAttribute('data-timestamp'));
30934       }
30935     }
30936
30937     /**
30938      * Checks if a month is within a min and max range, ignoring the date and time components.
30939      * If minDate or maxDate are not dates, they are ignored.
30940      * @param {Date} date
30941      * @param {Date} minDate
30942      * @param {Date} maxDate
30943      */
30944     function isMonthWithinRange(date, minDate, maxDate) {
30945       var month = date.getMonth();
30946       var year = date.getFullYear();
30947
30948       return (!minDate || minDate.getFullYear() < year || minDate.getMonth() <= month) &&
30949        (!maxDate || maxDate.getFullYear() > year || maxDate.getMonth() >= month);
30950     }
30951  });
30952})();
30953
30954})();
30955(function(){
30956"use strict";
30957
30958(function() {
30959  'use strict';
30960
30961  // POST RELEASE
30962  // TODO(jelbourn): Demo that uses moment.js
30963  // TODO(jelbourn): make sure this plays well with validation and ngMessages.
30964  // TODO(jelbourn): calendar pane doesn't open up outside of visible viewport.
30965  // TODO(jelbourn): forward more attributes to the internal input (required, autofocus, etc.)
30966  // TODO(jelbourn): something better for mobile (calendar panel takes up entire screen?)
30967  // TODO(jelbourn): input behavior (masking? auto-complete?)
30968
30969
30970  DatePickerCtrl.$inject = ["$scope", "$element", "$attrs", "$window", "$mdConstant", "$mdTheming", "$mdUtil", "$mdDateLocale", "$$mdDateUtil", "$$rAF", "$filter"];
30971  datePickerDirective.$inject = ["$$mdSvgRegistry", "$mdUtil", "$mdAria", "inputDirective"];
30972  angular.module('material.components.datepicker')
30973      .directive('mdDatepicker', datePickerDirective);
30974
30975  /**
30976   * @ngdoc directive
30977   * @name mdDatepicker
30978   * @module material.components.datepicker
30979   *
30980   * @param {Date} ng-model The component's model. Expects either a JavaScript Date object or a value that can be parsed into one (e.g. a ISO 8601 string).
30981   * @param {Object=} ng-model-options Allows tuning of the way in which `ng-model` is being updated. Also allows
30982   * for a timezone to be specified. <a href="https://docs.angularjs.org/api/ng/directive/ngModelOptions#usage">Read more at the ngModelOptions docs.</a>
30983   * @param {expression=} ng-change Expression evaluated when the model value changes.
30984   * @param {expression=} ng-focus Expression evaluated when the input is focused or the calendar is opened.
30985   * @param {expression=} ng-blur Expression evaluated when focus is removed from the input or the calendar is closed.
30986   * @param {boolean=} ng-disabled Whether the datepicker is disabled.
30987   * @param {boolean=} ng-required Whether a value is required for the datepicker.
30988   * @param {Date=} md-min-date Expression representing a min date (inclusive).
30989   * @param {Date=} md-max-date Expression representing a max date (inclusive).
30990   * @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not.
30991   * @param {String=} md-placeholder The date input placeholder value.
30992   * @param {String=} md-open-on-focus When present, the calendar will be opened when the input is focused.
30993   * @param {Boolean=} md-is-open Expression that can be used to open the datepicker's calendar on-demand.
30994   * @param {String=} md-current-view Default open view of the calendar pane. Can be either "month" or "year".
30995   * @param {String=} md-hide-icons Determines which datepicker icons should be hidden. Note that this may cause the
30996   * datepicker to not align properly with other components. **Use at your own risk.** Possible values are:
30997   * * `"all"` - Hides all icons.
30998   * * `"calendar"` - Only hides the calendar icon.
30999   * * `"triangle"` - Only hides the triangle icon.
31000   * @param {Object=} md-date-locale Allows for the values from the `$mdDateLocaleProvider` to be
31001   * ovewritten on a per-element basis (e.g. `msgOpenCalendar` can be overwritten with
31002   * `md-date-locale="{ msgOpenCalendar: 'Open a special calendar' }"`).
31003   *
31004   * @description
31005   * `<md-datepicker>` is a component used to select a single date.
31006   * For information on how to configure internationalization for the date picker,
31007   * see `$mdDateLocaleProvider`.
31008   *
31009   * This component supports [ngMessages](https://docs.angularjs.org/api/ngMessages/directive/ngMessages).
31010   * Supported attributes are:
31011   * * `required`: whether a required date is not set.
31012   * * `mindate`: whether the selected date is before the minimum allowed date.
31013   * * `maxdate`: whether the selected date is after the maximum allowed date.
31014   * * `debounceInterval`: ms to delay input processing (since last debounce reset); default value 500ms
31015   *
31016   * @usage
31017   * <hljs lang="html">
31018   *   <md-datepicker ng-model="birthday"></md-datepicker>
31019   * </hljs>
31020   *
31021   */
31022
31023  function datePickerDirective($$mdSvgRegistry, $mdUtil, $mdAria, inputDirective) {
31024    return {
31025      template: function(tElement, tAttrs) {
31026        // Buttons are not in the tab order because users can open the calendar via keyboard
31027        // interaction on the text input, and multiple tab stops for one component (picker)
31028        // may be confusing.
31029        var hiddenIcons = tAttrs.mdHideIcons;
31030        var ariaLabelValue = tAttrs.ariaLabel || tAttrs.mdPlaceholder;
31031
31032        var calendarButton = (hiddenIcons === 'all' || hiddenIcons === 'calendar') ? '' :
31033          '<md-button class="md-datepicker-button md-icon-button" type="button" ' +
31034              'tabindex="-1" aria-hidden="true" ' +
31035              'ng-click="ctrl.openCalendarPane($event)">' +
31036            '<md-icon class="md-datepicker-calendar-icon" aria-label="md-calendar" ' +
31037                     'md-svg-src="' + $$mdSvgRegistry.mdCalendar + '"></md-icon>' +
31038          '</md-button>';
31039
31040        var triangleButton = '';
31041
31042        if (hiddenIcons !== 'all' && hiddenIcons !== 'triangle') {
31043          triangleButton = '' +
31044            '<md-button type="button" md-no-ink ' +
31045              'class="md-datepicker-triangle-button md-icon-button" ' +
31046              'ng-click="ctrl.openCalendarPane($event)" ' +
31047              'aria-label="{{::ctrl.locale.msgOpenCalendar}}">' +
31048            '<div class="md-datepicker-expand-triangle"></div>' +
31049          '</md-button>';
31050
31051          tElement.addClass(HAS_TRIANGLE_ICON_CLASS);
31052        }
31053
31054        return calendarButton +
31055        '<div class="md-datepicker-input-container" ng-class="{\'md-datepicker-focused\': ctrl.isFocused}">' +
31056          '<input ' +
31057            (ariaLabelValue ? 'aria-label="' + ariaLabelValue + '" ' : '') +
31058            'class="md-datepicker-input" ' +
31059            'aria-haspopup="true" ' +
31060            'aria-expanded="{{ctrl.isCalendarOpen}}" ' +
31061            'ng-focus="ctrl.setFocused(true)" ' +
31062            'ng-blur="ctrl.setFocused(false)"> ' +
31063            triangleButton +
31064        '</div>' +
31065
31066        // This pane will be detached from here and re-attached to the document body.
31067        '<div class="md-datepicker-calendar-pane md-whiteframe-z1" id="{{::ctrl.calendarPaneId}}">' +
31068          '<div class="md-datepicker-input-mask">' +
31069            '<div class="md-datepicker-input-mask-opaque"></div>' +
31070          '</div>' +
31071          '<div class="md-datepicker-calendar">' +
31072            '<md-calendar role="dialog" aria-label="{{::ctrl.locale.msgCalendar}}" ' +
31073                'md-current-view="{{::ctrl.currentView}}"' +
31074                'md-min-date="ctrl.minDate"' +
31075                'md-max-date="ctrl.maxDate"' +
31076                'md-date-filter="ctrl.dateFilter"' +
31077                'ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen">' +
31078            '</md-calendar>' +
31079          '</div>' +
31080        '</div>';
31081      },
31082      require: ['ngModel', 'mdDatepicker', '?^mdInputContainer', '?^form'],
31083      scope: {
31084        minDate: '=mdMinDate',
31085        maxDate: '=mdMaxDate',
31086        placeholder: '@mdPlaceholder',
31087        currentView: '@mdCurrentView',
31088        dateFilter: '=mdDateFilter',
31089        isOpen: '=?mdIsOpen',
31090        debounceInterval: '=mdDebounceInterval',
31091        dateLocale: '=mdDateLocale'
31092      },
31093      controller: DatePickerCtrl,
31094      controllerAs: 'ctrl',
31095      bindToController: true,
31096      link: function(scope, element, attr, controllers) {
31097        var ngModelCtrl = controllers[0];
31098        var mdDatePickerCtrl = controllers[1];
31099        var mdInputContainer = controllers[2];
31100        var parentForm = controllers[3];
31101        var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
31102
31103        mdDatePickerCtrl.configureNgModel(ngModelCtrl, mdInputContainer, inputDirective);
31104
31105        if (mdInputContainer) {
31106          // We need to move the spacer after the datepicker itself,
31107          // because md-input-container adds it after the
31108          // md-datepicker-input by default. The spacer gets wrapped in a
31109          // div, because it floats and gets aligned next to the datepicker.
31110          // There are easier ways of working around this with CSS (making the
31111          // datepicker 100% wide, change the `display` etc.), however they
31112          // break the alignment with any other form controls.
31113          var spacer = element[0].querySelector('.md-errors-spacer');
31114
31115          if (spacer) {
31116            element.after(angular.element('<div>').append(spacer));
31117          }
31118
31119          mdInputContainer.setHasPlaceholder(attr.mdPlaceholder);
31120          mdInputContainer.input = element;
31121          mdInputContainer.element
31122            .addClass(INPUT_CONTAINER_CLASS)
31123            .toggleClass(HAS_CALENDAR_ICON_CLASS, attr.mdHideIcons !== 'calendar' && attr.mdHideIcons !== 'all');
31124
31125          if (!mdInputContainer.label) {
31126            $mdAria.expect(element, 'aria-label', attr.mdPlaceholder);
31127          } else if(!mdNoAsterisk) {
31128            attr.$observe('required', function(value) {
31129              mdInputContainer.label.toggleClass('md-required', !!value);
31130            });
31131          }
31132
31133          scope.$watch(mdInputContainer.isErrorGetter || function() {
31134            return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
31135          }, mdInputContainer.setInvalid);
31136        } else if (parentForm) {
31137          // If invalid, highlights the input when the parent form is submitted.
31138          var parentSubmittedWatcher = scope.$watch(function() {
31139            return parentForm.$submitted;
31140          }, function(isSubmitted) {
31141            if (isSubmitted) {
31142              mdDatePickerCtrl.updateErrorState();
31143              parentSubmittedWatcher();
31144            }
31145          });
31146        }
31147      }
31148    };
31149  }
31150
31151  /** Additional offset for the input's `size` attribute, which is updated based on its content. */
31152  var EXTRA_INPUT_SIZE = 3;
31153
31154  /** Class applied to the container if the date is invalid. */
31155  var INVALID_CLASS = 'md-datepicker-invalid';
31156
31157  /** Class applied to the datepicker when it's open. */
31158  var OPEN_CLASS = 'md-datepicker-open';
31159
31160  /** Class applied to the md-input-container, if a datepicker is placed inside it */
31161  var INPUT_CONTAINER_CLASS = '_md-datepicker-floating-label';
31162
31163  /** Class to be applied when the calendar icon is enabled. */
31164  var HAS_CALENDAR_ICON_CLASS = '_md-datepicker-has-calendar-icon';
31165
31166  /** Class to be applied when the triangle icon is enabled. */
31167  var HAS_TRIANGLE_ICON_CLASS = '_md-datepicker-has-triangle-icon';
31168
31169  /** Default time in ms to debounce input event by. */
31170  var DEFAULT_DEBOUNCE_INTERVAL = 500;
31171
31172  /**
31173   * Height of the calendar pane used to check if the pane is going outside the boundary of
31174   * the viewport. See calendar.scss for how $md-calendar-height is computed; an extra 20px is
31175   * also added to space the pane away from the exact edge of the screen.
31176   *
31177   *  This is computed statically now, but can be changed to be measured if the circumstances
31178   *  of calendar sizing are changed.
31179   */
31180  var CALENDAR_PANE_HEIGHT = 368;
31181
31182  /**
31183   * Width of the calendar pane used to check if the pane is going outside the boundary of
31184   * the viewport. See calendar.scss for how $md-calendar-width is computed; an extra 20px is
31185   * also added to space the pane away from the exact edge of the screen.
31186   *
31187   *  This is computed statically now, but can be changed to be measured if the circumstances
31188   *  of calendar sizing are changed.
31189   */
31190  var CALENDAR_PANE_WIDTH = 360;
31191
31192  /** Used for checking whether the current user agent is on iOS or Android. */
31193  var IS_MOBILE_REGEX = /ipad|iphone|ipod|android/i;
31194
31195  /**
31196   * Controller for md-datepicker.
31197   *
31198   * @ngInject @constructor
31199   */
31200  function DatePickerCtrl($scope, $element, $attrs, $window, $mdConstant,
31201    $mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF, $filter) {
31202
31203    /** @final */
31204    this.$window = $window;
31205
31206    /** @final */
31207    this.dateUtil = $$mdDateUtil;
31208
31209    /** @final */
31210    this.$mdConstant = $mdConstant;
31211
31212    /* @final */
31213    this.$mdUtil = $mdUtil;
31214
31215    /** @final */
31216    this.$$rAF = $$rAF;
31217
31218    /** @final */
31219    this.$mdDateLocale = $mdDateLocale;
31220
31221    /**
31222     * The root document element. This is used for attaching a top-level click handler to
31223     * close the calendar panel when a click outside said panel occurs. We use `documentElement`
31224     * instead of body because, when scrolling is disabled, some browsers consider the body element
31225     * to be completely off the screen and propagate events directly to the html element.
31226     * @type {!angular.JQLite}
31227     */
31228    this.documentElement = angular.element(document.documentElement);
31229
31230    /** @type {!angular.NgModelController} */
31231    this.ngModelCtrl = null;
31232
31233    /** @type {HTMLInputElement} */
31234    this.inputElement = $element[0].querySelector('input');
31235
31236    /** @final {!angular.JQLite} */
31237    this.ngInputElement = angular.element(this.inputElement);
31238
31239    /** @type {HTMLElement} */
31240    this.inputContainer = $element[0].querySelector('.md-datepicker-input-container');
31241
31242    /** @type {HTMLElement} Floating calendar pane. */
31243    this.calendarPane = $element[0].querySelector('.md-datepicker-calendar-pane');
31244
31245    /** @type {HTMLElement} Calendar icon button. */
31246    this.calendarButton = $element[0].querySelector('.md-datepicker-button');
31247
31248    /**
31249     * Element covering everything but the input in the top of the floating calendar pane.
31250     * @type {!angular.JQLite}
31251     */
31252    this.inputMask = angular.element($element[0].querySelector('.md-datepicker-input-mask-opaque'));
31253
31254    /** @final {!angular.JQLite} */
31255    this.$element = $element;
31256
31257    /** @final {!angular.Attributes} */
31258    this.$attrs = $attrs;
31259
31260    /** @final {!angular.Scope} */
31261    this.$scope = $scope;
31262
31263    /** @type {Date} */
31264    this.date = null;
31265
31266    /** @type {boolean} */
31267    this.isFocused = false;
31268
31269    /** @type {boolean} */
31270    this.isDisabled;
31271    this.setDisabled($element[0].disabled || angular.isString($attrs.disabled));
31272
31273    /** @type {boolean} Whether the date-picker's calendar pane is open. */
31274    this.isCalendarOpen = false;
31275
31276    /** @type {boolean} Whether the calendar should open when the input is focused. */
31277    this.openOnFocus = $attrs.hasOwnProperty('mdOpenOnFocus');
31278
31279    /** @final */
31280    this.mdInputContainer = null;
31281
31282    /**
31283     * Element from which the calendar pane was opened. Keep track of this so that we can return
31284     * focus to it when the pane is closed.
31285     * @type {HTMLElement}
31286     */
31287    this.calendarPaneOpenedFrom = null;
31288
31289    /** @type {String} Unique id for the calendar pane. */
31290    this.calendarPaneId = 'md-date-pane-' + $mdUtil.nextUid();
31291
31292    /** Pre-bound click handler is saved so that the event listener can be removed. */
31293    this.bodyClickHandler = angular.bind(this, this.handleBodyClick);
31294
31295    /**
31296     * Name of the event that will trigger a close. Necessary to sniff the browser, because
31297     * the resize event doesn't make sense on mobile and can have a negative impact since it
31298     * triggers whenever the browser zooms in on a focused input.
31299     */
31300    this.windowEventName = IS_MOBILE_REGEX.test(
31301      navigator.userAgent || navigator.vendor || window.opera
31302    ) ? 'orientationchange' : 'resize';
31303
31304    /** Pre-bound close handler so that the event listener can be removed. */
31305    this.windowEventHandler = $mdUtil.debounce(angular.bind(this, this.closeCalendarPane), 100);
31306
31307    /** Pre-bound handler for the window blur event. Allows for it to be removed later. */
31308    this.windowBlurHandler = angular.bind(this, this.handleWindowBlur);
31309
31310    /** The built-in AngularJS date filter. */
31311    this.ngDateFilter = $filter('date');
31312
31313    /** @type {Number} Extra margin for the left side of the floating calendar pane. */
31314    this.leftMargin = 20;
31315
31316    /** @type {Number} Extra margin for the top of the floating calendar. Gets determined on the first open. */
31317    this.topMargin = null;
31318
31319    // Unless the user specifies so, the datepicker should not be a tab stop.
31320    // This is necessary because ngAria might add a tabindex to anything with an ng-model
31321    // (based on whether or not the user has turned that particular feature on/off).
31322    if ($attrs.tabindex) {
31323      this.ngInputElement.attr('tabindex', $attrs.tabindex);
31324      $attrs.$set('tabindex', null);
31325    } else {
31326      $attrs.$set('tabindex', '-1');
31327    }
31328
31329    $attrs.$set('aria-owns', this.calendarPaneId);
31330
31331    $mdTheming($element);
31332    $mdTheming(angular.element(this.calendarPane));
31333
31334    var self = this;
31335
31336    $scope.$on('$destroy', function() {
31337      self.detachCalendarPane();
31338    });
31339
31340    if ($attrs.mdIsOpen) {
31341      $scope.$watch('ctrl.isOpen', function(shouldBeOpen) {
31342        if (shouldBeOpen) {
31343          self.openCalendarPane({
31344            target: self.inputElement
31345          });
31346        } else {
31347          self.closeCalendarPane();
31348        }
31349      });
31350    }
31351
31352    // For AngularJS 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
31353    // manually call the $onInit hook.
31354    if (angular.version.major === 1 && angular.version.minor <= 4) {
31355      this.$onInit();
31356    }
31357
31358  }
31359
31360  /**
31361   * AngularJS Lifecycle hook for newer AngularJS versions.
31362   * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
31363   */
31364  DatePickerCtrl.prototype.$onInit = function() {
31365
31366    /**
31367     * Holds locale-specific formatters, parsers, labels etc. Allows
31368     * the user to override specific ones from the $mdDateLocale provider.
31369     * @type {!Object}
31370     */
31371    this.locale = this.dateLocale ? angular.extend({}, this.$mdDateLocale, this.dateLocale) : this.$mdDateLocale;
31372
31373    this.installPropertyInterceptors();
31374    this.attachChangeListeners();
31375    this.attachInteractionListeners();
31376  };
31377
31378  /**
31379   * Sets up the controller's reference to ngModelController and
31380   * applies AngularJS's `input[type="date"]` directive.
31381   * @param {!angular.NgModelController} ngModelCtrl Instance of the ngModel controller.
31382   * @param {Object} mdInputContainer Instance of the mdInputContainer controller.
31383   * @param {Object} inputDirective Config for AngularJS's `input` directive.
31384   */
31385  DatePickerCtrl.prototype.configureNgModel = function(ngModelCtrl, mdInputContainer, inputDirective) {
31386    this.ngModelCtrl = ngModelCtrl;
31387    this.mdInputContainer = mdInputContainer;
31388
31389    // The input needs to be [type="date"] in order to be picked up by AngularJS.
31390    this.$attrs.$set('type', 'date');
31391
31392    // Invoke the `input` directive link function, adding a stub for the element.
31393    // This allows us to re-use AngularJS's logic for setting the timezone via ng-model-options.
31394    // It works by calling the link function directly which then adds the proper `$parsers` and
31395    // `$formatters` to the ngModel controller.
31396    inputDirective[0].link.pre(this.$scope, {
31397      on: angular.noop,
31398      val: angular.noop,
31399      0: {}
31400    }, this.$attrs, [ngModelCtrl]);
31401
31402    var self = this;
31403
31404    // Responds to external changes to the model value.
31405    self.ngModelCtrl.$formatters.push(function(value) {
31406      var parsedValue = angular.isDefined(value) ? Date.parse(value) : null;
31407
31408      // `parsedValue` is the time since epoch if valid or `NaN` if invalid.
31409      if (!isNaN(parsedValue) && angular.isNumber(parsedValue)) {
31410        value = new Date(parsedValue);
31411      }
31412
31413      if (value && !(value instanceof Date)) {
31414        throw Error(
31415          'The ng-model for md-datepicker must be a Date instance or a value ' +
31416          'that can be parsed into a date. Currently the model is of type: ' + typeof value
31417        );
31418      }
31419
31420      self.onExternalChange(value);
31421
31422      return value;
31423    });
31424
31425    // Responds to external error state changes (e.g. ng-required based on another input).
31426    ngModelCtrl.$viewChangeListeners.unshift(angular.bind(this, this.updateErrorState));
31427
31428    // Forwards any events from the input to the root element. This is necessary to get `updateOn`
31429    // working for events that don't bubble (e.g. 'blur') since AngularJS binds the handlers to
31430    // the `<md-datepicker>`.
31431    var updateOn = self.$mdUtil.getModelOption(ngModelCtrl, 'updateOn');
31432
31433    if (updateOn) {
31434      this.ngInputElement.on(
31435        updateOn,
31436        angular.bind(this.$element, this.$element.triggerHandler, updateOn)
31437      );
31438    }
31439  };
31440
31441  /**
31442   * Attach event listeners for both the text input and the md-calendar.
31443   * Events are used instead of ng-model so that updates don't infinitely update the other
31444   * on a change. This should also be more performant than using a $watch.
31445   */
31446  DatePickerCtrl.prototype.attachChangeListeners = function() {
31447    var self = this;
31448
31449    self.$scope.$on('md-calendar-change', function(event, date) {
31450      self.setModelValue(date);
31451      self.onExternalChange(date);
31452      self.closeCalendarPane();
31453    });
31454
31455    self.ngInputElement.on('input', angular.bind(self, self.resizeInputElement));
31456
31457    var debounceInterval = angular.isDefined(this.debounceInterval) ?
31458        this.debounceInterval : DEFAULT_DEBOUNCE_INTERVAL;
31459    self.ngInputElement.on('input', self.$mdUtil.debounce(self.handleInputEvent,
31460        debounceInterval, self));
31461  };
31462
31463  /** Attach event listeners for user interaction. */
31464  DatePickerCtrl.prototype.attachInteractionListeners = function() {
31465    var self = this;
31466    var $scope = this.$scope;
31467    var keyCodes = this.$mdConstant.KEY_CODE;
31468
31469    // Add event listener through angular so that we can triggerHandler in unit tests.
31470    self.ngInputElement.on('keydown', function(event) {
31471      if (event.altKey && event.keyCode == keyCodes.DOWN_ARROW) {
31472        self.openCalendarPane(event);
31473        $scope.$digest();
31474      }
31475    });
31476
31477    if (self.openOnFocus) {
31478      self.ngInputElement.on('focus', angular.bind(self, self.openCalendarPane));
31479      angular.element(self.$window).on('blur', self.windowBlurHandler);
31480
31481      $scope.$on('$destroy', function() {
31482        angular.element(self.$window).off('blur', self.windowBlurHandler);
31483      });
31484    }
31485
31486    $scope.$on('md-calendar-close', function() {
31487      self.closeCalendarPane();
31488    });
31489  };
31490
31491  /**
31492   * Capture properties set to the date-picker and imperitively handle internal changes.
31493   * This is done to avoid setting up additional $watches.
31494   */
31495  DatePickerCtrl.prototype.installPropertyInterceptors = function() {
31496    var self = this;
31497
31498    if (this.$attrs.ngDisabled) {
31499      // The expression is to be evaluated against the directive element's scope and not
31500      // the directive's isolate scope.
31501      var scope = this.$scope.$parent;
31502
31503      if (scope) {
31504        scope.$watch(this.$attrs.ngDisabled, function(isDisabled) {
31505          self.setDisabled(isDisabled);
31506        });
31507      }
31508    }
31509
31510    Object.defineProperty(this, 'placeholder', {
31511      get: function() { return self.inputElement.placeholder; },
31512      set: function(value) { self.inputElement.placeholder = value || ''; }
31513    });
31514  };
31515
31516  /**
31517   * Sets whether the date-picker is disabled.
31518   * @param {boolean} isDisabled
31519   */
31520  DatePickerCtrl.prototype.setDisabled = function(isDisabled) {
31521    this.isDisabled = isDisabled;
31522    this.inputElement.disabled = isDisabled;
31523
31524    if (this.calendarButton) {
31525      this.calendarButton.disabled = isDisabled;
31526    }
31527  };
31528
31529  /**
31530   * Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are:
31531   *   - mindate: whether the selected date is before the minimum date.
31532   *   - maxdate: whether the selected flag is after the maximum date.
31533   *   - filtered: whether the selected date is allowed by the custom filtering function.
31534   *   - valid: whether the entered text input is a valid date
31535   *
31536   * The 'required' flag is handled automatically by ngModel.
31537   *
31538   * @param {Date=} opt_date Date to check. If not given, defaults to the datepicker's model value.
31539   */
31540  DatePickerCtrl.prototype.updateErrorState = function(opt_date) {
31541    var date = opt_date || this.date;
31542
31543    // Clear any existing errors to get rid of anything that's no longer relevant.
31544    this.clearErrorState();
31545
31546    if (this.dateUtil.isValidDate(date)) {
31547      // Force all dates to midnight in order to ignore the time portion.
31548      date = this.dateUtil.createDateAtMidnight(date);
31549
31550      if (this.dateUtil.isValidDate(this.minDate)) {
31551        var minDate = this.dateUtil.createDateAtMidnight(this.minDate);
31552        this.ngModelCtrl.$setValidity('mindate', date >= minDate);
31553      }
31554
31555      if (this.dateUtil.isValidDate(this.maxDate)) {
31556        var maxDate = this.dateUtil.createDateAtMidnight(this.maxDate);
31557        this.ngModelCtrl.$setValidity('maxdate', date <= maxDate);
31558      }
31559
31560      if (angular.isFunction(this.dateFilter)) {
31561        this.ngModelCtrl.$setValidity('filtered', this.dateFilter(date));
31562      }
31563    } else {
31564      // The date is seen as "not a valid date" if there is *something* set
31565      // (i.e.., not null or undefined), but that something isn't a valid date.
31566      this.ngModelCtrl.$setValidity('valid', date == null);
31567    }
31568
31569    angular.element(this.inputContainer).toggleClass(INVALID_CLASS, !this.ngModelCtrl.$valid);
31570  };
31571
31572  /** Clears any error flags set by `updateErrorState`. */
31573  DatePickerCtrl.prototype.clearErrorState = function() {
31574    this.inputContainer.classList.remove(INVALID_CLASS);
31575    ['mindate', 'maxdate', 'filtered', 'valid'].forEach(function(field) {
31576      this.ngModelCtrl.$setValidity(field, true);
31577    }, this);
31578  };
31579
31580  /** Resizes the input element based on the size of its content. */
31581  DatePickerCtrl.prototype.resizeInputElement = function() {
31582    this.inputElement.size = this.inputElement.value.length + EXTRA_INPUT_SIZE;
31583  };
31584
31585  /**
31586   * Sets the model value if the user input is a valid date.
31587   * Adds an invalid class to the input element if not.
31588   */
31589  DatePickerCtrl.prototype.handleInputEvent = function() {
31590    var inputString = this.inputElement.value;
31591    var parsedDate = inputString ? this.locale.parseDate(inputString) : null;
31592    this.dateUtil.setDateTimeToMidnight(parsedDate);
31593
31594    // An input string is valid if it is either empty (representing no date)
31595    // or if it parses to a valid date that the user is allowed to select.
31596    var isValidInput = inputString == '' || (
31597      this.dateUtil.isValidDate(parsedDate) &&
31598      this.locale.isDateComplete(inputString) &&
31599      this.isDateEnabled(parsedDate)
31600    );
31601
31602    // The datepicker's model is only updated when there is a valid input.
31603    if (isValidInput) {
31604      this.setModelValue(parsedDate);
31605      this.date = parsedDate;
31606    }
31607
31608    this.updateErrorState(parsedDate);
31609  };
31610
31611  /**
31612   * Check whether date is in range and enabled
31613   * @param {Date=} opt_date
31614   * @return {boolean} Whether the date is enabled.
31615   */
31616  DatePickerCtrl.prototype.isDateEnabled = function(opt_date) {
31617    return this.dateUtil.isDateWithinRange(opt_date, this.minDate, this.maxDate) &&
31618          (!angular.isFunction(this.dateFilter) || this.dateFilter(opt_date));
31619  };
31620
31621  /** Position and attach the floating calendar to the document. */
31622  DatePickerCtrl.prototype.attachCalendarPane = function() {
31623    var calendarPane = this.calendarPane;
31624    var body = document.body;
31625
31626    calendarPane.style.transform = '';
31627    this.$element.addClass(OPEN_CLASS);
31628    this.mdInputContainer && this.mdInputContainer.element.addClass(OPEN_CLASS);
31629    angular.element(body).addClass('md-datepicker-is-showing');
31630
31631    var elementRect = this.inputContainer.getBoundingClientRect();
31632    var bodyRect = body.getBoundingClientRect();
31633
31634    if (!this.topMargin || this.topMargin < 0) {
31635      this.topMargin = (this.inputMask.parent().prop('clientHeight') - this.ngInputElement.prop('clientHeight')) / 2;
31636    }
31637
31638    // Check to see if the calendar pane would go off the screen. If so, adjust position
31639    // accordingly to keep it within the viewport.
31640    var paneTop = elementRect.top - bodyRect.top - this.topMargin;
31641    var paneLeft = elementRect.left - bodyRect.left - this.leftMargin;
31642
31643    // If ng-material has disabled body scrolling (for example, if a dialog is open),
31644    // then it's possible that the already-scrolled body has a negative top/left. In this case,
31645    // we want to treat the "real" top as (0 - bodyRect.top). In a normal scrolling situation,
31646    // though, the top of the viewport should just be the body's scroll position.
31647    var viewportTop = (bodyRect.top < 0 && document.body.scrollTop == 0) ?
31648        -bodyRect.top :
31649        document.body.scrollTop;
31650
31651    var viewportLeft = (bodyRect.left < 0 && document.body.scrollLeft == 0) ?
31652        -bodyRect.left :
31653        document.body.scrollLeft;
31654
31655    var viewportBottom = viewportTop + this.$window.innerHeight;
31656    var viewportRight = viewportLeft + this.$window.innerWidth;
31657
31658    // Creates an overlay with a hole the same size as element. We remove a pixel or two
31659    // on each end to make it overlap slightly. The overlay's background is added in
31660    // the theme in the form of a box-shadow with a huge spread.
31661    this.inputMask.css({
31662      position: 'absolute',
31663      left: this.leftMargin + 'px',
31664      top: this.topMargin + 'px',
31665      width: (elementRect.width - 1) + 'px',
31666      height: (elementRect.height - 2) + 'px'
31667    });
31668
31669    // If the right edge of the pane would be off the screen and shifting it left by the
31670    // difference would not go past the left edge of the screen. If the calendar pane is too
31671    // big to fit on the screen at all, move it to the left of the screen and scale the entire
31672    // element down to fit.
31673    if (paneLeft + CALENDAR_PANE_WIDTH > viewportRight) {
31674      if (viewportRight - CALENDAR_PANE_WIDTH > 0) {
31675        paneLeft = viewportRight - CALENDAR_PANE_WIDTH;
31676      } else {
31677        paneLeft = viewportLeft;
31678        var scale = this.$window.innerWidth / CALENDAR_PANE_WIDTH;
31679        calendarPane.style.transform = 'scale(' + scale + ')';
31680      }
31681
31682      calendarPane.classList.add('md-datepicker-pos-adjusted');
31683    }
31684
31685    // If the bottom edge of the pane would be off the screen and shifting it up by the
31686    // difference would not go past the top edge of the screen.
31687    if (paneTop + CALENDAR_PANE_HEIGHT > viewportBottom &&
31688        viewportBottom - CALENDAR_PANE_HEIGHT > viewportTop) {
31689      paneTop = viewportBottom - CALENDAR_PANE_HEIGHT;
31690      calendarPane.classList.add('md-datepicker-pos-adjusted');
31691    }
31692
31693    calendarPane.style.left = paneLeft + 'px';
31694    calendarPane.style.top = paneTop + 'px';
31695    document.body.appendChild(calendarPane);
31696
31697    // Add CSS class after one frame to trigger open animation.
31698    this.$$rAF(function() {
31699      calendarPane.classList.add('md-pane-open');
31700    });
31701  };
31702
31703  /** Detach the floating calendar pane from the document. */
31704  DatePickerCtrl.prototype.detachCalendarPane = function() {
31705    this.$element.removeClass(OPEN_CLASS);
31706    this.mdInputContainer && this.mdInputContainer.element.removeClass(OPEN_CLASS);
31707    angular.element(document.body).removeClass('md-datepicker-is-showing');
31708    this.calendarPane.classList.remove('md-pane-open');
31709    this.calendarPane.classList.remove('md-datepicker-pos-adjusted');
31710
31711    if (this.isCalendarOpen) {
31712      this.$mdUtil.enableScrolling();
31713    }
31714
31715    if (this.calendarPane.parentNode) {
31716      // Use native DOM removal because we do not want any of the
31717      // angular state of this element to be disposed.
31718      this.calendarPane.parentNode.removeChild(this.calendarPane);
31719    }
31720  };
31721
31722  /**
31723   * Open the floating calendar pane.
31724   * @param {Event} event
31725   */
31726  DatePickerCtrl.prototype.openCalendarPane = function(event) {
31727    if (!this.isCalendarOpen && !this.isDisabled && !this.inputFocusedOnWindowBlur) {
31728      this.isCalendarOpen = this.isOpen = true;
31729      this.calendarPaneOpenedFrom = event.target;
31730
31731      // Because the calendar pane is attached directly to the body, it is possible that the
31732      // rest of the component (input, etc) is in a different scrolling container, such as
31733      // an md-content. This means that, if the container is scrolled, the pane would remain
31734      // stationary. To remedy this, we disable scrolling while the calendar pane is open, which
31735      // also matches the native behavior for things like `<select>` on Mac and Windows.
31736      this.$mdUtil.disableScrollAround(this.calendarPane);
31737
31738      this.attachCalendarPane();
31739      this.focusCalendar();
31740      this.evalAttr('ngFocus');
31741
31742      // Attach click listener inside of a timeout because, if this open call was triggered by a
31743      // click, we don't want it to be immediately propogated up to the body and handled.
31744      var self = this;
31745      this.$mdUtil.nextTick(function() {
31746        // Use 'touchstart` in addition to click in order to work on iOS Safari, where click
31747        // events aren't propogated under most circumstances.
31748        // See http://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
31749        self.documentElement.on('click touchstart', self.bodyClickHandler);
31750      }, false);
31751
31752      window.addEventListener(this.windowEventName, this.windowEventHandler);
31753    }
31754  };
31755
31756  /** Close the floating calendar pane. */
31757  DatePickerCtrl.prototype.closeCalendarPane = function() {
31758    if (this.isCalendarOpen) {
31759      var self = this;
31760
31761      self.detachCalendarPane();
31762      self.ngModelCtrl.$setTouched();
31763      self.evalAttr('ngBlur');
31764
31765      self.documentElement.off('click touchstart', self.bodyClickHandler);
31766      window.removeEventListener(self.windowEventName, self.windowEventHandler);
31767
31768      self.calendarPaneOpenedFrom.focus();
31769      self.calendarPaneOpenedFrom = null;
31770
31771      if (self.openOnFocus) {
31772        // Ensures that all focus events have fired before resetting
31773        // the calendar. Prevents the calendar from reopening immediately
31774        // in IE when md-open-on-focus is set. Also it needs to trigger
31775        // a digest, in order to prevent issues where the calendar wasn't
31776        // showing up on the next open.
31777        self.$mdUtil.nextTick(reset);
31778      } else {
31779        reset();
31780      }
31781    }
31782
31783    function reset(){
31784      self.isCalendarOpen = self.isOpen = false;
31785    }
31786  };
31787
31788  /** Gets the controller instance for the calendar in the floating pane. */
31789  DatePickerCtrl.prototype.getCalendarCtrl = function() {
31790    return angular.element(this.calendarPane.querySelector('md-calendar')).controller('mdCalendar');
31791  };
31792
31793  /** Focus the calendar in the floating pane. */
31794  DatePickerCtrl.prototype.focusCalendar = function() {
31795    // Use a timeout in order to allow the calendar to be rendered, as it is gated behind an ng-if.
31796    var self = this;
31797    this.$mdUtil.nextTick(function() {
31798      self.getCalendarCtrl().focus();
31799    }, false);
31800  };
31801
31802  /**
31803   * Sets whether the input is currently focused.
31804   * @param {boolean} isFocused
31805   */
31806  DatePickerCtrl.prototype.setFocused = function(isFocused) {
31807    if (!isFocused) {
31808      this.ngModelCtrl.$setTouched();
31809    }
31810
31811    // The ng* expressions shouldn't be evaluated when mdOpenOnFocus is on,
31812    // because they also get called when the calendar is opened/closed.
31813    if (!this.openOnFocus) {
31814      this.evalAttr(isFocused ? 'ngFocus' : 'ngBlur');
31815    }
31816
31817    this.isFocused = isFocused;
31818  };
31819
31820  /**
31821   * Handles a click on the document body when the floating calendar pane is open.
31822   * Closes the floating calendar pane if the click is not inside of it.
31823   * @param {MouseEvent} event
31824   */
31825  DatePickerCtrl.prototype.handleBodyClick = function(event) {
31826    if (this.isCalendarOpen) {
31827      var isInCalendar = this.$mdUtil.getClosest(event.target, 'md-calendar');
31828
31829      if (!isInCalendar) {
31830        this.closeCalendarPane();
31831      }
31832
31833      this.$scope.$digest();
31834    }
31835  };
31836
31837  /**
31838   * Handles the event when the user navigates away from the current tab. Keeps track of
31839   * whether the input was focused when the event happened, in order to prevent the calendar
31840   * from re-opening.
31841   */
31842  DatePickerCtrl.prototype.handleWindowBlur = function() {
31843    this.inputFocusedOnWindowBlur = document.activeElement === this.inputElement;
31844  };
31845
31846  /**
31847   * Evaluates an attribute expression against the parent scope.
31848   * @param {String} attr Name of the attribute to be evaluated.
31849   */
31850  DatePickerCtrl.prototype.evalAttr = function(attr) {
31851    if (this.$attrs[attr]) {
31852      this.$scope.$parent.$eval(this.$attrs[attr]);
31853    }
31854  };
31855
31856  /**
31857   * Sets the ng-model value by first converting the date object into a strng. Converting it
31858   * is necessary, in order to pass AngularJS's `input[type="date"]` validations. AngularJS turns
31859   * the value into a Date object afterwards, before setting it on the model.
31860   * @param {Date=} value Date to be set as the model value.
31861   */
31862  DatePickerCtrl.prototype.setModelValue = function(value) {
31863    var timezone = this.$mdUtil.getModelOption(this.ngModelCtrl, 'timezone');
31864    this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd', timezone));
31865  };
31866
31867  /**
31868   * Updates the datepicker when a model change occurred externally.
31869   * @param {Date=} value Value that was set to the model.
31870   */
31871  DatePickerCtrl.prototype.onExternalChange = function(value) {
31872    var timezone = this.$mdUtil.getModelOption(this.ngModelCtrl, 'timezone');
31873
31874    this.date = value;
31875    this.inputElement.value = this.locale.formatDate(value, timezone);
31876    this.mdInputContainer && this.mdInputContainer.setHasValue(!!value);
31877    this.resizeInputElement();
31878    this.updateErrorState();
31879  };
31880})();
31881
31882})();
31883(function(){
31884"use strict";
31885
31886angular
31887  .module('material.components.icon')
31888  .directive('mdIcon', ['$mdIcon', '$mdTheming', '$mdAria', '$sce', mdIconDirective]);
31889
31890/**
31891 * @ngdoc directive
31892 * @name mdIcon
31893 * @module material.components.icon
31894 *
31895 * @restrict E
31896 *
31897 * @description
31898 * The `md-icon` directive makes it easier to use vector-based icons in your app (as opposed to
31899 * raster-based icons types like PNG). The directive supports both icon fonts and SVG icons.
31900 *
31901 * Icons should be considered view-only elements that should not be used directly as buttons; instead nest a `<md-icon>`
31902 * inside a `md-button` to add hover and click features.
31903 *
31904 * ### Icon fonts
31905 * Icon fonts are a technique in which you use a font where the glyphs in the font are
31906 * your icons instead of text. Benefits include a straightforward way to bundle everything into a
31907 * single HTTP request, simple scaling, easy color changing, and more.
31908 *
31909 * `md-icon` lets you consume an icon font by letting you reference specific icons in that font
31910 * by name rather than character code.
31911 *
31912 * ### SVG
31913 * For SVGs, the problem with using `<img>` or a CSS `background-image` is that you can't take
31914 * advantage of some SVG features, such as styling specific parts of the icon 
31914with CSS or SVG
31915 * animation.
31916 *
31917 * `md-icon` makes it easier to use SVG icons by *inlining* the SVG into an `<svg>` element in the
31918 * document. The most straightforward way of referencing an SVG icon is via URL, just like a
31919 * traditional `<img>`. `$mdIconProvider`, as a convenience, lets you _name_ an icon so you can
31920 * reference it by name instead of URL throughout your templates.
31921 *
31922 * Additionally, you may not want to make separate HTTP requests for every icon, so you can bundle
31923 * your SVG icons together and pre-load them with $mdIconProvider as an icon set. An icon set can
31924 * also be given a name, which acts as a namespace for individual icons, so you can reference them
31925 * like `"social:cake"`.
31926 *
31927 * When using SVGs, both external SVGs (via URLs) or sets of SVGs [from icon sets] can be
31928 * easily loaded and used. When using font-icons, developers must follow three (3) simple steps:
31929 *
31930 * <ol>
31931 * <li>Load the font library. e.g.<br/>
31932 *    `<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
31933 *    rel="stylesheet">`
31934 * </li>
31935 * <li>
31936 *   Use either (a) font-icon class names or (b) a fontset and a font ligature to render the font glyph by
31937 *   using its textual name _or_ numerical character reference. Note that `material-icons` is the default fontset when
31938 *   none is specified.
31939 * </li>
31940 * <li> Use any of the following templates: <br/>
31941 *   <ul>
31942 *     <li>`<md-icon md-font-icon="classname"></md-icon>`</li>
31943 *     <li>`<md-icon md-font-set="font library classname or alias">textual_name</md-icon>`</li>
31944 *     <li>`<md-icon> numerical_character_reference </md-icon>`</li>
31945 *     <li>`<md-icon ng_bind="'textual_name'"></md-icon>`</li>
31946 *     <li>`<md-icon ng-bind="scopeVariable"></md-icon>`</li>
31947 *   </ul>
31948 * </li>
31949 * </ol>
31950 *
31951 * Full details for these steps can be found:
31952 *
31953 * <ul>
31954 * <li>http://google.github.io/material-design-icons/</li>
31955 * <li>http://google.github.io/material-design-icons/#icon-font-for-the-web</li>
31956 * </ul>
31957 *
31958 * The Material Design icon style <code>.material-icons</code> and the icon font references are published in
31959 * Material Design Icons:
31960 *
31961 * <ul>
31962 * <li>https://design.google.com/icons/</li>
31963 * <li>https://design.google.com/icons/#ic_accessibility</li>
31964 * </ul>
31965 *
31966 * ### Localization
31967 *
31968 * Because an `md-icon` element's text content is not intended to translated, it is recommended to declare the text
31969 * content for an `md-icon` element in its start tag. Instead of using the HTML text content, consider using `ng-bind`
31970 * with a scope variable or literal string.
31971 *
31972 * Examples:
31973 *
31974 * <ul>
31975 *   <li>`<md-icon ng-bind="myIconVariable"></md-icon>`</li>
31976 *   <li>`<md-icon ng-bind="'menu'"></md-icon>`
31977 * </ul>
31978 *
31979 * <h2 id="material_design_icons">Material Design Icons</h2>
31980 * Using the Material Design Icon-Selector, developers can easily and quickly search for a Material Design font-icon and
31981 * determine its textual name and character reference code. Click on any icon to see the slide-up information
31982 * panel with details regarding a SVG download or information on the font-icon usage.
31983 *
31984 * <a href="https://www.google.com/design/icons/#ic_accessibility" target="_blank" style="border-bottom:none;">
31985 * <img src="https://cloud.githubusercontent.com/assets/210413/7902490/fe8dd14c-0780-11e5-98fb-c821cc6475e6.png"
31986 *      aria-label="Material Design Icon-Selector" style="max-width:75%;padding-left:10%">
31987 * </a>
31988 *
31989 * <span class="image_caption">
31990 *  Click on the image above to link to the
31991 *  <a href="https://design.google.com/icons/#ic_accessibility" target="_blank">Material Design Icon-Selector</a>.
31992 * </span>
31993 *
31994 * @param {string} md-font-icon String name of CSS icon associated with the font-face will be used
31995 * to render the icon. Requires the fonts and the named CSS styles to be preloaded.
31996 * @param {string} md-font-set CSS style name associated with the font library; which will be assigned as
31997 * the class for the font-icon ligature. This value may also be an alias that is used to lookup the classname;
31998 * internally use `$mdIconProvider.fontSet(<alias>)` to determine the style name.
31999 * @param {string} md-svg-src String URL (or expression) used to load, cache, and display an
32000 *     external SVG.
32001 * @param {string} md-svg-icon md-svg-icon String name used for lookup of the icon from the internal 
32001cache;
32002 *     interpolated strings or expressions may also be used. Specific set names can be used with
32003 *     the syntax `<set name>:<icon name>`.<br/><br/>
32004 * To use icon sets, developers are required to pre-register the sets using the `$mdIconProvider` service.
32005 * @param {string=} aria-label Labels icon for accessibility. If an empty string is provided, icon
32006 * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no aria-label on the icon
32007 * nor a label on the parent element, a warning will be logged to the console.
32008 * @param {string=} alt Labels icon for accessibility. If an empty string is provided, icon
32009 * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no alt on the icon
32010 * nor a label on the parent element, a warning will be logged to the console.
32011 *
32012 * @usage
32013 * When using SVGs:
32014 * <hljs lang="html">
32015 *
32016 *  <!-- Icon ID; may contain optional icon set prefix; icons must registered using $mdIconProvider -->
32017 *  <md-icon md-svg-icon="social:android"    aria-label="android " ></md-icon>
32018 *
32019 *  <!-- Icon urls; may be preloaded in templateCache -->
32020 *  <md-icon md-svg-src="/android.svg"       aria-label="android " ></md-icon>
32021 *  <md-icon md-svg-src="{{ getAndroid() }}" aria-label="android " ></md-icon>
32022 *
32023 * </hljs>
32024 *
32025 * Use the <code>$mdIconProvider</code> to configure your application with
32026 * svg iconsets.
32027 *
32028 * <hljs lang="js">
32029 *  angular.module('appSvgIconSets', ['ngMaterial'])
32030 *    .controller('DemoCtrl', function($scope) {})
32031 *    .config(function($mdIconProvider) {
32032 *      $mdIconProvider
32033 *         .iconSet('social', 'img/icons/sets/social-icons.svg', 24)
32034 *         .defaultIconSet('img/icons/sets/core-icons.svg', 24);
32035 *     });
32036 * </hljs>
32037 *
32038 *
32039 * When using Font Icons with classnames:
32040 * <hljs lang="html">
32041 *
32042 *  <md-icon md-font-icon="android" aria-label="android" ></md-icon>
32043 *  <md-icon class="icon_home"      aria-label="Home"    ></md-icon>
32044 *
32045 * </hljs>
32046 *
32047 * When using Material Font Icons with ligatures:
32048 * <hljs lang="html">
32049 *  <!--
32050 *  For Material Design Icons
32051 *  The class '.material-icons' is auto-added if a style has NOT been specified
32052 *  since `material-icons` is the default fontset. So your markup:
32053 *  -->
32054 *  <md-icon> face </md-icon>
32055 *  <!-- becomes this at runtime: -->
32056 *  <md-icon md-font-set="material-icons"> face </md-icon>
32057 *  <!-- If the fontset does not support ligature names, then we need to use the ligature unicode.-->
32058 *  <md-icon> &#xE87C; </md-icon>
32059 *  <!-- The class '.material-icons' must be manually added if other styles are also specified-->
32060 *  <md-icon class="material-icons md-light md-48"> face </md-icon>
32061 * </hljs>
32062 *
32063 * When using other Font-Icon libraries:
32064 *
32065 * <hljs lang="js">
32066 *  // Specify a font-icon style alias
32067 *  angular.config(function($mdIconProvider) {
32068 *    $mdIconProvider.fontSet('md', 'material-icons');
32069 *  });
32070 * </hljs>
32071 *
32072 * <hljs lang="html">
32073 *  <md-icon md-font-set="md">favorite</md-icon>
32074 * </hljs>
32075 *
32076 */
32077function mdIconDirective($mdIcon, $mdTheming, $mdAria, $sce) {
32078
32079  return {
32080    restrict: 'E',
32081    link : postLink
32082  };
32083
32084
32085  /**
32086   * Directive postLink
32087   * Supports embedded SVGs, font-icons, & external SVGs
32088   */
32089  function postLink(scope, element, attr) {
32090    $mdTheming(element);
32091    var lastFontIcon = attr.mdFontIcon;
32092    var lastFontSet = $mdIcon.fontSet(attr.mdFontSet);
32093
32094    prepareForFontIcon();
32095
32096    attr.$observe('mdFontIcon', fontIconChanged);
32097    attr.$observe('mdFontSet', fontIconChanged);
32098
32099    // Keep track of the content of the svg src so we can compare against it later to see if the
32100    // attribute is static (and thus safe).
32101    var originalSvgSrc = element[0].getAttribute(attr.$attr.mdSvgSrc);
32102
32103    // If using a font-icon, then the textual name of the icon itself
32104    // provides the aria-label.
32105
32106    var attrName = attr.$normalize(attr.$attr.mdSvgIcon || attr.$attr.mdSvgSrc || '');
32107
32108    /* Provide a default accessibility role of img */
32109    if (!attr.role) {
32110      $mdAria.expect(element, 'role', 'img');
32111      /* manually update attr variable */
32112      attr.role = 'img';
32113    }
32114
32115    /* Don't process ARIA if already valid */
32116    if ( attr.role === "img" && !attr.ariaHidden && !$mdAria.hasAriaLabel(element) ) {
32117      var iconName;
32118      if (attr.alt) {
32119        /* Use alt text by default if available */
32120        $mdAria.expect(element, 'aria-label', attr.alt);
32121      } else if ($mdAria.parentHasAriaLabel(element, 2)) {
32122        /* Parent has ARIA so we will assume it will describe the image */
32123        $mdAria.expect(element, 'aria-hidden', 'true');
32124      } else if (iconName = (attr.mdFontIcon || attr.mdSvgIcon || element.text())) {
32125        /* Use icon name as aria-label */
32126        $mdAria.expect(element, 'aria-label', iconName);
32127      } else {
32128        /* No label found */
32129        $mdAria.expect(element, 'aria-hidden', 'true');
32130      }
32131    }
32132
32133    if (attrName) {
32134      // Use either pre-configured SVG or URL source, respectively.
32135      attr.$observe(attrName, function(attrVal) {
32136        element.empty();
32137        if (attrVal) {
32138          $mdIcon(attrVal)
32139            .then(function(svg) {
32140            element.empty();
32141            element.append(svg);
32142          });
32143        }
32144      });
32145    }
32146
32147    function prepareForFontIcon() {
32148      if (!attr.mdSvgIcon && !attr.mdSvgSrc) {
32149        if (attr.mdFontIcon) {
32150          element.addClass('md-font ' + attr.mdFontIcon);
32151        }
32152
32153        element.addClass(lastFontSet);
32154      }
32155    }
32156
32157    function fontIconChanged() {
32158      if (!attr.mdSvgIcon && !attr.mdSvgSrc) {
32159        if (attr.mdFontIcon) {
32160          element.removeClass(lastFontIcon);
32161          element.addClass(attr.mdFontIcon);
32162
32163          lastFontIcon = attr.mdFontIcon;
32164        }
32165
32166        var fontSet = $mdIcon.fontSet(attr.mdFontSet);
32167
32168        if (lastFontSet !== fontSet) {
32169          element.removeClass(lastFontSet);
32170          element.addClass(fontSet);
32171
32172          lastFontSet = fontSet;
32173        }
32174      }
32175    }
32176  }
32177}
32178
32179})();
32180(function(){
32181"use strict";
32182
32183  
32184MdIconService.$inject = ["config", "$templateRequest", "$q", "$log", "$mdUtil", "$sce"];angular
32185    .module('material.components.icon')
32186    .constant('$$mdSvgRegistry', {
32187        'mdTabsArrow':   'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwb2x5Z29uIHBvaW50cz0iMTUuNCw3LjQgMTQsNiA4LDEyIDE0LDE4IDE1LjQsMTYuNiAxMC44LDEyICIvPjwvZz48L3N2Zz4=',
32188        'mdClose':       'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xOSA2LjQxbC0xLjQxLTEuNDEtNS41OSA1LjU5LTUuNTktNS41OS0xLjQxIDEuNDEgNS41OSA1LjU5LTUuNTkgNS41OSAxLjQxIDEuNDEgNS41OS01LjU5IDUuNTkgNS41OSAxLjQxLTEuNDEtNS41OS01LjU5eiIvPjwvZz48L3N2Zz4=',
32189        'mdCancel':      'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xMiAyYy01LjUzIDAtMTAgNC40Ny0xMCAxMHM0LjQ3IDEwIDEwIDEwIDEwLTQuNDcgMTAtMTAtNC40Ny0xMC0xMC0xMHptNSAxMy41OWwtMS40MSAxLjQxLTMuNTktMy41OS0zLjU5IDMuNTktMS40MS0xLjQxIDMuNTktMy41OS0zLjU5LTMuNTkgMS40MS0xLjQxIDMuNTkgMy41OSAzLjU5LTMuNTkgMS40MSAxLjQxLTMuNTkgMy41OSAzLjU5IDMuNTl6Ii8+PC9nPjwvc3ZnPg==',
32190        'mdMenu':        'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zLDZIMjFWOEgzVjZNMywxMUgyMVYxM0gzVjExTTMsMTZIMjFWMThIM1YxNloiIC8+PC9zdmc+',
32191        'mdToggleArrow': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiPjxwYXRoIGQ9Ik0yNCAxNmwtMTIgMTIgMi44MyAyLjgzIDkuMTctOS4xNyA5LjE3IDkuMTcgMi44My0yLjgzeiIvPjxwYXRoIGQ9Ik0wIDBoNDh2NDhoLTQ4eiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==',
32192        'mdCalendar':    'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgM2gtMVYxaC0ydjJIOFYxSDZ2Mkg1Yy0xLjExIDAtMS45OS45LT
32192EuOTkgMkwzIDE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY4aDE0djExek03IDEwaDV2NUg3eiIvPjwvc3ZnPg==',
32193        'mdChecked':     'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiLz48L2c+PC9zdmc+'
32194    })
32195    .provider('$mdIcon', MdIconProvider);
32196
32197/**
32198 * @ngdoc service
32199 * @name $mdIconProvider
32200 * @module material.components.icon
32201 *
32202 * @description
32203 * `$mdIconProvider` is used only to register icon IDs with URLs. These configuration features allow
32204 * icons and icon sets to be pre-registered and associated with source URLs **before** the `<md-icon />`
32205 * directives are compiled.
32206 *
32207 * If using font-icons, the developer is responsible for loading the fonts.
32208 *
32209 * If using SVGs, loading of the actual svg files are deferred to on-demand requests and are loaded
32210 * internally by the `$mdIcon` service using the `$templateRequest` service. When an SVG is
32211 * requested by name/ID, the `$mdIcon` service searches its registry for the associated source URL;
32212 * that URL is used to on-demand load and parse the SVG dynamically.
32213 *
32214 * The `$templateRequest` service expects the icons source to be loaded over trusted URLs.<br/>
32215 * This means, when loading icons from an external URL, you have to trust the URL in the `$sceDelegateProvider`.
32216 *
32217 * <hljs lang="js">
32218 *   app.config(function($sceDelegateProvider) {
32219 *     $sceDelegateProvider.resourceUrlWhitelist([
32220 *       // Adding 'self' to the whitelist, will allow requests from the current origin.
32221 *       'self',
32222 *       // Using double asterisks here, will allow all URLs to load.
32223 *       // We recommend to only specify the given domain you want to allow.
32224 *       '**'
32225 *     ]);
32226 *   });
32227 * </hljs>
32228 *
32229 * Read more about the [$sceDelegateProvider](https://docs.angularjs.org/api/ng/provider/$sceDelegateProvider).
32230 *
32231 * **Notice:** Most font-icons libraries do not support ligatures (for example `fontawesome`).<br/>
32232 *  In such cases you are not able to use the icon's ligature name - Like so:
32233 *
32234 *  <hljs lang="html">
32235 *    <md-icon md-font-set="fa">fa-bell</md-icon>
32236 *  </hljs>
32237 *
32238 * You should instead use the given unicode, instead of the ligature name.
32239 *
32240 * <p ng-hide="true"> ##// Notice we can't use a hljs element here, because the characters will be escaped.</p>
32241 *  ```html
32242 *    <md-icon md-font-set="fa">&#xf0f3</md-icon>
32243 *  ```
32244 *
32245 * All unicode ligatures are prefixed with the `&#x` string.
32246 *
32247 * @usage
32248 * <hljs lang="js">
32249 *   app.config(function($mdIconProvider) {
32250    *
32251    *     // Configure URLs for icons specified by [set:]id.
32252    *
32253    *     $mdIconProvider
32254    *          .defaultFontSet( 'fa' )                   // This sets our default fontset className.
32255    *          .defaultIconSet('my/app/icons.svg')       // Register a default set of SVG icons
32256    *          .iconSet('social', 'my/app/social.svg')   // Register a named icon set of SVGs
32257    *          .icon('android', 'my/app/android.svg')    // Register a specific icon (by name)
32258    *          .icon('work:chair', 'my/app/chair.svg');  // Register icon in a specific set
32259    *   });
32260 * </hljs>
32261 *
32262 * SVG icons and icon sets can be easily pre-loaded and cached using either (a) a build process or (b) a runtime
32263 * **startup** process (shown below):
32264 *
32265 * <hljs lang="js">
32266 *   app.config(function($mdIconProvider) {
32267    *
32268    *     // Register a default set of SVG icon definitions
32269    *     $mdIconProvider.defaultIconSet('my/app/icons.svg')
32270    *
32271    *   })
32272 *   .run(function($templateRequest){
32273    *
32274    *     // Pre-fetch icons sources by URL and cache in the $templateCache...
32275    *     // subsequent $templateRequest calls will look there first.
32276    *
32277    *     var urls = [ 'imy/app/icons.svg', 'img/icons/android.svg'];
32278    *
32279    *     angular.forEach(urls, function(url) {
32280    *       $templateRequest(url);
32281    *     });
32282    *
32283    *   });
32284 *
32285 * </hljs>
32286 *
32287 * > <b>Note:</b> The loaded SVG data is subsequently cached internally for future requests.
32288 *
32289 */
32290
32291/**
32292 * @ngdoc method
32293 * @name $mdIconProvider#icon
32294 *
32295 * @description
32296 * Register a source URL for a specific icon name; the name may include optional 'icon set' name prefix.
32297 * These icons  will later be retrieved from the cache using `$mdIcon( <icon name> )`
32298 *
32299 * @param {string} id Icon name/id used to register the icon
32300 * @param {string} url specifies the external location for the data file. Used internally by
32301 * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
32302 * was configured.
32303 * @param {number=} viewBoxSize Sets the width and height the icon's viewBox.
32304 * It is ignored for icons with an existing viewBox. Default size is 24.
32305 *
32306 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
32307 *
32308 * @usage
32309 * <hljs lang="js">
32310 *   app.config(function($mdIconProvider) {
32311    *
32312    *     // Configure URLs for icons specified by [set:]id.
32313    *
32314    *     $mdIconProvider
32315    *          .icon('android', 'my/app/android.svg')    // Register a specific icon (by name)
32316    *          .icon('work:chair', 'my/app/chair.svg');  // Register icon in a specific set
32317    *   });
32318 * </hljs>
32319 *
32320 */
32321/**
32322 * @ngdoc method
32323 * @name $mdIconProvider#iconSet
32324 *
32325 * @description
32326 * Register a source URL for a 'named' set of icons; group of SVG definitions where each definition
32327 * has an icon id. Individual icons can be subsequently retrieved from this cached set using
32328 * `$mdIcon(<icon set name>:<icon name>)`
32329 *
32330 * @param {string} id Icon name/id used to register the iconset
32331 * @param {string} url specifies the external location for the data file. Used internally by
32332 * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
32333 * was configured.
32334 * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set.
32335 * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size.
32336 * Default value is 24.
32337 *
32338 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
32339 *
32340 *
32341 * @usage
32342 * <hljs lang="js">
32343 *   app.config(function($mdIconProvider) {
32344    *
32345    *     // Configure URLs for icons specified by [set:]id.
32346    *
32347    *     $mdIconProvider
32348    *          .iconSet('social', 'my/app/social.svg')   // Register a named icon set
32349    *   });
32350 * </hljs>
32351 *
32352 */
32353/**
32354 * @ngdoc method
32355 * @name $mdIconProvider#defaultIconSet
32356 *
32357 * @description
32358 * Register a source URL for the default 'named' set of icons. Unless explicitly registered,
32359 * subsequent lookups of icons will failover to search this 'default' icon set.
32360 * Icon can be retrieved from this cached, default set using `$mdIcon(<name>)`
32361 *
32362 * @param {string} url specifies the external location for the data file. Used internally by
32363 * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
32364 * was configured.
32365 * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set.
32366 * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size.
32367 * Default value is 24.
32368 *
32369 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
32370 *
32371 * @usage
32372 * <hljs lang="js">
32373 *   app.config(function($mdIconProvider) {
32374    *
32375    *     // Configure URLs for icons specified by [set:]id.
32376    *
32377    *     $mdIconProvider
32378    *          .defaultIconSet( 'my/app/social.svg' )   // Register a default icon set
32379    *   });
32380 * </hljs>
32381 *
32382 */
32383/**
32384 * @ngdoc method
32385 * @name $mdIconProvider#defaultFontSet
32386 *
32387 * @description
32388 * When using Font-Icons, AngularJS Material assumes the the Material Design icons will be used and automatically
32389 * configures the default font-set == 'material-icons'. Note that the font-set references the font-icon library
32390 * class style that should be applied to the `<md-icon>`.
32391 *
32392 * Configuring the default means that the attributes
32393 * `md-font-set="material-icons"` or `class="material-icons"` do not need to be explicitly declared on the
32394 * `<md-icon>` markup. For example:
32395 *
32396 *  `<md-icon> face </md-icon>`
32397 *  will render as
32398 *  `<span class="material-icons"> face </span>`, and
32399 *
32400 *  `<md-icon md-font-set="fa"> face </md-icon>`
32401 *  will render as
32402 *  `<span class="fa"> face </span>`
32403 *
32404 * @param {string} name of the font-library style that should be applied to the md-icon DOM element
32405 *
32406 * @usage
32407 * <hljs lang="js">
32408 *   app.config(function($mdIconProvider) {
32409   *     $mdIconProvider.defaultFontSet( 'fa' );
32410   *   });
32411 * </hljs>
32412 *
32413 */
32414
32415/**
32416 * @ngdoc method
32417 * @name $mdIconProvider#fontSet
32418 *
32419 * @description
32420 * When using a font set for `<md-icon>` you must specify the correct font classname in the `md-font-set`
32421 * attribute. If the fonset className is really long, your markup may become cluttered... an easy
32422 * solution is to define an `alias` for your fontset:
32423 *
32424 * @param {string} alias of the specified fontset.
32425 * @param {string} className of the fontset.
32426 *
32427 * @usage
32428 * <hljs lang="js">
32429 *   app.config(function($mdIconProvider) {
32430   *     // In this case, we set an alias for the `material-icons` fontset.
32431   *     $mdIconProvider.fontSet('md', 'material-icons');
32432   *   });
32433 * </hljs>
32434 *
32435 */
32436
32437/**
32438 * @ngdoc method
32439 * @name $mdIconProvider#defaultViewBoxSize
32440 *
32441 * @description
32442 * While `<md-icon />` markup can also be style with sizing CSS, this method configures
32443 * the default width **and** height used for all icons; unless overridden by specific CSS.
32444 * The default sizing is (24px, 24px).
32445 * @param {number=} viewBoxSize Sets the width and height of the viewBox for an icon or an icon set.
32446 * All icons in a set should be the same size. The default value is 24.
32447 *
32448 * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
32449 *
32450 * @usage
32451 * <hljs lang="js">
32452 *   app.config(function($mdIconProvider) {
32453    *
32454    *     // Configure URLs for icons specified by [set:]id.
32455    *
32456    *     $mdIconProvider
32457    *          .defaultViewBoxSize(36)   // Register a default icon size (width == height)
32458    *   });
32459 * </hljs>
32460 *
32461 */
32462
32463var config = {
32464  defaultViewBoxSize: 24,
32465  defaultFontSet: 'material-icons',
32466  fontSets: []
32467};
32468
32469function MdIconProvider() {
32470}
32471
32472MdIconProvider.prototype = {
32473  icon: function(id, url, viewBoxSize) {
32474    if (id.indexOf(':') == -1) id = '$default:' + id;
32475
32476    config[id] = new ConfigurationItem(url, viewBoxSize);
32477    return this;
32478  },
32479
32480  iconSet: function(id, url, viewBoxSize) {
32481    config[id] = new ConfigurationItem(url, viewBoxSize);
32482    return this;
32483  },
32484
32485  defaultIconSet: function(url, viewBoxSize) {
32486    var setName = '$default';
32487
32488    if (!config[setName]) {
32489      config[setName] = new ConfigurationItem(url, viewBoxSize);
32490    }
32491
32492    config[setName].viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
32493
32494    return this;
32495  },
32496
32497  defaultViewBoxSize: function(viewBoxSize) {
32498    config.defaultViewBoxSize = viewBoxSize;
32499    return this;
32500  },
32501
32502  /**
32503   * Register an alias name associated with a font-icon library style ;
32504   */
32505  fontSet: function fontSet(alias, className) {
32506    config.fontSets.push({
32507      alias: alias,
vendor: 17,625 bytes, lines 32508-33070
32508      fontSet: className || alias
32509    });
32510    return this;
32511  },
32512
32513  /**
32514   * Specify a default style name associated with a font-icon library
32515   * fallback to Material Icons.
32516   *
32517   */
32518  defaultFontSet: function defaultFontSet(className) {
32519    config.defaultFontSet = !className ? '' : className;
32520    return this;
32521  },
32522
32523  defaultIconSize: function defaultIconSize(iconSize) {
32524    config.defaultIconSize = iconSize;
32525    return this;
32526  },
32527
32528  $get: ['$templateRequest', '$q', '$log', '$mdUtil', '$sce', function($templateRequest, $q, $log, $mdUtil, $sce) {
32529    return MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce);
32530  }]
32531};
32532
32533/**
32534 *  Configuration item stored in the Icon registry; used for lookups
32535 *  to load if not already cached in the `loaded` cache
32536 */
32537function ConfigurationItem(url, viewBoxSize) {
32538  this.url = url;
32539  this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
32540}
32541
32542/**
32543 * @ngdoc service
32544 * @name $mdIcon
32545 * @module material.components.icon
32546 *
32547 * @description
32548 * The `$mdIcon` service is a function used to lookup SVG icons.
32549 *
32550 * @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
32551 * 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
32552 * performed. The Id may be a unique icon ID or may include an iconSet ID prefix.
32553 *
32554 * For the **id** query to work properly, this means that all id-to-URL mappings must have been previously configured
32555 * using the `$mdIconProvider`.
32556 *
32557 * @returns {angular.$q.Promise} A promise that gets resolved to a clone of the initial SVG DOM element; which was
32558 * created from the SVG markup in the SVG data file. If an error occurs (e.g. the icon cannot be found) the promise
32559 * will get rejected.
32560 *
32561 * @usage
32562 * <hljs lang="js">
32563 * function SomeDirective($mdIcon) {
32564  *
32565  *   // See if the icon has already been loaded, if not
32566  *   // then lookup the icon from the registry cache, load and cache
32567  *   // it for future requests.
32568  *   // NOTE: ID queries require configuration with $mdIconProvider
32569  *
32570  *   $mdIcon('android').then(function(iconEl)    { element.append(iconEl); });
32571  *   $mdIcon('work:chair').then(function(iconEl) { element.append(iconEl); });
32572  *
32573  *   // Load and cache the external SVG using a URL
32574  *
32575  *   $mdIcon('img/icons/android.svg').then(function(iconEl) {
32576  *     element.append(iconEl);
32577  *   });
32578  * };
32579 * </hljs>
32580 *
32581 * > <b>Note:</b> The `<md-icon>` directive internally uses the `$mdIcon` service to query, loaded,
32582 *   and instantiate SVG DOM elements.
32583 */
32584
32585/* @ngInject */
32586function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
32587  var iconCache = {};
32588  var svgCache = {};
32589  var urlRegex = /[-\w@:%\+.~#?&//=]{2,}\.[a-z]{2,4}\b(\/[-\w@:%\+.~#?&//=]*)?/i;
32590  var dataUrlRegex = /^data:image\/svg\+xml[\s*;\w\-\=]*?(base64)?,(.*)$/i;
32591
32592  Icon.prototype = {clone: cloneSVG, prepare: prepareAndStyle};
32593  getIcon.fontSet = findRegisteredFontSet;
32594
32595  // Publish service...
32596  return getIcon;
32597
32598  /**
32599   * Actual $mdIcon service is essentially a lookup function
32600   */
32601  function getIcon(id) {
32602    id = id || '';
32603
32604    // If the "id" provided is not a string, the only other valid value is a $sce trust wrapper
32605    // over a URL string. If the value is not trusted, this will intentionally throw an error
32606    // because the user is attempted to use an unsafe URL, potentially opening themselves up
32607    // to an XSS attack.
32608    if (!angular.isString(id)) {
32609      id = $sce.getTrustedUrl(id);
32610    }
32611
32612    // If already loaded and cached, use a clone of the cached icon.
32613    // Otherwise either load by URL, or lookup in the registry and then load by URL, and cache.
32614
32615    if (iconCache[id]) {
32616      return $q.when(transformClone(iconCache[id]));
32617    }
32618
32619    if (urlRegex.test(id) || dataUrlRegex.test(id)) {
32620      return loadByURL(id).then(cacheIcon(id));
32621    }
32622
32623    if (id.indexOf(':') == -1) {
32624      id = '$default:' + id;
32625    }
32626
32627    var load = config[id] ? loadByID : loadFromIconSet;
32628    return load(id)
32629      .then(cacheIcon(id));
32630  }
32631
32632  /**
32633   * Lookup registered fontSet style using its alias...
32634   * If not found,
32635   */
32636  function findRegisteredFontSet(alias) {
32637    var useDefault = angular.isUndefined(alias) || !(alias && alias.length);
32638    if (useDefault) return config.defaultFontSet;
32639
32640    var result = alias;
32641    angular.forEach(config.fontSets, function(it) {
32642      if (it.alias == alias) result = it.fontSet || result;
32643    });
32644
32645    return result;
32646  }
32647
32648  function transformClone(cacheElement) {
32649    var clone = cacheElement.clone();
32650    var cacheSuffix = '_cache' + $mdUtil.nextUid();
32651
32652    // We need to modify for each cached icon the id attributes.
32653    // This is needed because SVG id's are treated as normal DOM ids
32654    // and should not have a duplicated id.
32655    if (clone.id) clone.id += cacheSuffix;
32656    angular.forEach(clone.querySelectorAll('[id]'), function(item) {
32657      item.id += cacheSuffix;
32658    });
32659
32660    return clone;
32661  }
32662
32663  /**
32664   * Prepare and cache the loaded icon for the specified `id`
32665   */
32666  function cacheIcon(id) {
32667
32668    return function updateCache(icon) {
32669      iconCache[id] = isIcon(icon) ? icon : new Icon(icon, config[id]);
32670
32671      return iconCache[id].clone();
32672    };
32673  }
32674
32675  /**
32676   * Lookup the configuration in the registry, if !registered throw an error
32677   * otherwise load the icon [on-demand] using the registered URL.
32678   *
32679   */
32680  function loadByID(id) {
32681    var iconConfig = config[id];
32682    return loadByURL(iconConfig.url).then(function(icon) {
32683      return new Icon(icon, iconConfig);
32684    });
32685  }
32686
32687  /**
32688   *    Loads the file as XML and uses querySelector( <id> ) to find
32689   *    the desired node...
32690   */
32691  function loadFromIconSet(id) {
32692    var setName = id.substring(0, id.lastIndexOf(':')) || '$default';
32693    var iconSetConfig = config[setName];
32694
32695    return !iconSetConfig ? announceIdNotFound(id) : loadByURL(iconSetConfig.url).then(extractFromSet);
32696
32697    function extractFromSet(set) {
32698      var iconName = id.slice(id.lastIndexOf(':') + 1);
32699      var icon = set.querySelector('#' + iconName);
32700      return icon ? new Icon(icon, iconSetConfig) : announceIdNotFound(id);
32701    }
32702
32703    function announceIdNotFound(id) {
32704      var msg = 'icon ' + id + ' not found';
32705      $log.warn(msg);
32706
32707      return $q.reject(msg || id);
32708    }
32709  }
32710
32711  /**
32712   * Load the icon by URL (may use the $templateCache).
32713   * Extract the data for later conversion to Icon
32714   */
32715  function loadByURL(url) {
32716    /* Load the icon from embedded data URL. */
32717    function loadByDataUrl(url) {
32718      var results = dataUrlRegex.exec(url);
32719      var isBase64 = /base64/i.test(url);
32720      var data = isBase64 ? window.atob(results[2]) : results[2];
32721
32722      return $q.when(angular.element(data)[0]);
32723    }
32724
32725    /* Load the icon by URL using HTTP. */
32726    function loadByHttpUrl(url) {
32727      return $q(function(resolve, reject) {
32728        // Catch HTTP or generic errors not related to incorrect icon IDs.
32729        var announceAndReject = function(err) {
32730            var msg = angular.isString(err) ? err : (err.message || err.data || err.statusText);
32731            $log.warn(msg);
32732            reject(err);
32733          },
32734          extractSvg = function(response) {
32735            if (!svgCache[url]) {
32736              svgCache[url] = angular.element('<div>').append(response)[0].querySelector('svg');
32737            }
32738            resolve(svgCache[url]);
32739          };
32740
32741        $templateRequest(url, true).then(extractSvg, announceAndReject);
32742      });
32743    }
32744
32745    return dataUrlRegex.test(url)
32746      ? loadByDataUrl(url)
32747      : loadByHttpUrl(url);
32748  }
32749
32750  /**
32751   * Check target signature to see if it is an Icon instance.
32752   */
32753  function isIcon(target) {
32754    return angular.isDefined(target.element) && angular.isDefined(target.config);
32755  }
32756
32757  /**
32758   *  Define the Icon class
32759   */
32760  function Icon(el, config) {
32761    if (el && el.tagName != 'svg') {
32762      el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">').append(el.cloneNode(true))[0];
32763    }
32764
32765    // Inject the namespace if not available...
32766    if (!el.getAttribute('xmlns')) {
32767      el.setAttribute('xmlns', "http://www.w3.org/2000/svg");
32768    }
32769
32770    this.element = el;
32771    this.config = config;
32772    this.prepare();
32773  }
32774
32775  /**
32776   *  Prepare the DOM element that will be cached in the
32777   *  loaded iconCache store.
32778   */
32779  function prepareAndStyle() {
32780    var viewBoxSize = this.config ? this.config.viewBoxSize : config.defaultViewBoxSize;
32781    angular.forEach({
32782      'fit': '',
32783      'height': '100%',
32784      'width': '100%',
32785      'preserveAspectRatio': 'xMidYMid meet',
32786      'viewBox': this.element.getAttribute('viewBox') || ('0 0 ' + viewBoxSize + ' ' + viewBoxSize),
32787      'focusable': false // Disable IE11s default behavior to make SVGs focusable
32788    }, function(val, attr) {
32789      this.element.setAttribute(attr, val);
32790    }, this);
32791  }
32792
32793  /**
32794   * Clone the Icon DOM element.
32795   */
32796  function cloneSVG() {
32797    // If the element or any of its children have a style attribute, then a CSP policy without
32798    // 'unsafe-inline' in the style-src directive, will result in a violation.
32799    return this.element.cloneNode(true);
32800  }
32801
32802}
32803
32804})();
32805(function(){
32806"use strict";
32807
32808
32809
32810MenuController.$inject = ["$mdMenu", "$attrs", "$element", "$scope", "$mdUtil", "$timeout", "$rootScope", "$q", "$log"];
32811angular
32812    .module('material.components.menu')
32813    .controller('mdMenuCtrl', MenuController);
32814
32815/**
32816 * @ngInject
32817 */
32818function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout, $rootScope, $q, $log) {
32819
32820  var prefixer = $mdUtil.prefixer();
32821  var menuContainer;
32822  var self = this;
32823  var triggerElement;
32824
32825  this.nestLevel = parseInt($attrs.mdNestLevel, 10) || 0;
32826
32827  /**
32828   * Called by our linking fn to provide access to the menu-content
32829   * element removed during link
32830   */
32831  this.init = function init(setMenuContainer, opts) {
32832    opts = opts || {};
32833    menuContainer = setMenuContainer;
32834
32835    // Default element for ARIA attributes has the ngClick or ngMouseenter expression
32836    triggerElement = $element[0].querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter']));
32837    triggerElement.setAttribute('aria-expanded', 'false');
32838
32839    this.isInMenuBar = opts.isInMenuBar;
32840    this.nestedMenus = $mdUtil.nodesToArray(menuContainer[0].querySelectorAll('.md-nested-menu'));
32841
32842    menuContainer.on('$mdInterimElementRemove', function() {
32843      self.isOpen = false;
32844      $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
32845    });
32846    $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
32847
32848    var menuContainerId = 'menu_container_' + $mdUtil.nextUid();
32849    menuContainer.attr('id', menuContainerId);
32850    angular.element(triggerElement).attr({
32851      'aria-owns': menuContainerId,
32852      'aria-haspopup': 'true'
32853    });
32854
32855    $scope.$on('$destroy', angular.bind(this, function() {
32856      this.disableHoverListener();
32857      $mdMenu.destroy();
32858    }));
32859
32860    menuContainer.on('$destroy', function() {
32861      $mdMenu.destroy();
32862    });
32863  };
32864
32865  var openMenuTimeout, menuItems, deregisterScopeListeners = [];
32866  this.enableHoverListener = function() {
32867    deregisterScopeListeners.push($rootScope.$on('$mdMenuOpen', function(event, el) {
32868      if (menuContainer[0].contains(el[0])) {
32869        self.currentlyOpenMenu = el.controller('mdMenu');
32870        self.isAlreadyOpening = false;
32871        self.currentlyOpenMenu.registerContainerProxy(self.triggerContainerProxy.bind(self));
32872      }
32873    }));
32874    deregisterScopeListeners.push($rootScope.$on('$mdMenuClose', function(event, el) {
32875      if (menuContainer[0].contains(el[0])) {
32876        self.currentlyOpenMenu = undefined;
32877      }
32878    }));
32879    menuItems = angular.element($mdUtil.nodesToArray(menuContainer[0].children[0].children));
32880    menuItems.on('mouseenter', self.handleMenuItemHover);
32881    menuItems.on('mouseleave', self.handleMenuItemMouseLeave);
32882  };
32883
32884  this.disableHoverListener = function() {
32885    while (deregisterScopeListeners.length) {
32886      deregisterScopeListeners.shift()();
32887    }
32888    menuItems && menuItems.off('mouseenter', self.handleMenuItemHover);
32889    menuItems && menuItems.off('mouseleave', self.handleMenuItemMouseLeave);
32890  };
32891
32892  this.handleMenuItemHover = function(event) {
32893    if (self.isAlreadyOpening) return;
32894    var nestedMenu = (
32895      event.target.querySelector('md-menu')
32896        || $mdUtil.getClosest(event.target, 'MD-MENU')
32897    );
32898    openMenuTimeout = $timeout(function() {
32899      if (nestedMenu) {
32900        nestedMenu = angular.element(nestedMenu).controller('mdMenu');
32901      }
32902
32903      if (self.currentlyOpenMenu && self.currentlyOpenMenu != nestedMenu) {
32904        var closeTo = self.nestLevel + 1;
32905        self.currentlyOpenMenu.close(true, { closeTo: closeTo });
32906        self.isAlreadyOpening = !!nestedMenu;
32907        nestedMenu && nestedMenu.open();
32908      } else if (nestedMenu && !nestedMenu.isOpen && nestedMenu.open) {
32909        self.isAlreadyOpening = !!nestedMenu;
32910        nestedMenu && nestedMenu.open();
32911      }
32912    }, nestedMenu ? 100 : 250);
32913    var focusableTarget = event.currentTarget.querySelector('.md-button:not([disabled])');
32914    focusableTarget && focusableTarget.focus();
32915  };
32916
32917  this.handleMenuItemMouseLeave = function() {
32918    if (openMenuTimeout) {
32919      $timeout.cancel(openMenuTimeout);
32920      openMenuTimeout = undefined;
32921    }
32922  };
32923
32924
32925  /**
32926   * Uses the $mdMenu interim element service to open the menu contents
32927   */
32928  this.open = function openMenu(ev) {
32929    ev && ev.stopPropagation();
32930    ev && ev.preventDefault();
32931    if (self.isOpen) return;
32932    self.enableHoverListener();
32933    self.isOpen = true;
32934    $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
32935    triggerElement = triggerElement || (ev ? ev.target : $element[0]);
32936    triggerElement.setAttribute('aria-expanded', 'true');
32937    $scope.$emit('$mdMenuOpen', $element);
32938    $mdMenu.show({
32939      scope: $scope,
32940      mdMenuCtrl: self,
32941      nestLevel: self.nestLevel,
32942      element: menuContainer,
32943      target: triggerElement,
32944      preserveElement: true,
32945      parent: 'body'
32946    }).finally(function() {
32947      triggerElement.setAttribute('aria-expanded', 'false');
32948      self.disableHoverListener();
32949    });
32950  };
32951
32952  this.onIsOpenChanged = function(isOpen) {
32953    if (isOpen) {
32954      menuContainer.attr('aria-hidden', 'false');
32955      $element[0].classList.add('md-open');
32956      angular.forEach(self.nestedMenus, function(el) {
32957        el.classList.remove('md-open');
32958      });
32959    } else {
32960      menuContainer.attr('aria-hidden', 'true');
32961      $element[0].classList.remove('md-open');
32962    }
32963    $scope.$mdMenuIsOpen = self.isOpen;
32964  };
32965
32966  this.focusMenuContainer = function focusMenuContainer() {
32967    var focusTarget = menuContainer[0]
32968      .querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
32969
32970    if (!focusTarget) focusTarget = menuContainer[0].querySelector('.md-button:not([disabled])');
32971    focusTarget.focus();
32972  };
32973
32974  this.registerContainerProxy = function registerContainerProxy(handler) {
32975    this.containerProxy = handler;
32976  };
32977
32978  this.triggerContainerProxy = function triggerContainerProxy(ev) {
32979    this.containerProxy && this.containerProxy(ev);
32980  };
32981
32982  this.destroy = function() {
32983    return self.isOpen ? $mdMenu.destroy() : $q.when(false);
32984  };
32985
32986  // Use the $mdMenu interim element service to close the menu contents
32987  this.close = function closeMenu(skipFocus, closeOpts) {
32988    if ( !self.isOpen ) return;
32989    self.isOpen = false;
32990    $mdUtil.nextTick(function(){ self.onIsOpenChanged(self.isOpen);});
32991
32992    var eventDetails = angular.extend({}, closeOpts, { skipFocus: skipFocus });
32993    $scope.$emit('$mdMenuClose', $element, eventDetails);
32994    $mdMenu.hide(null, closeOpts);
32995
32996    if (!skipFocus) {
32997      var el = self.restoreFocusTo || $element.find('button')[0];
32998      if (el instanceof angular.element) el = el[0];
32999      if (el) el.focus();
33000    }
33001  };
33002
33003  /**
33004   * Build a nice object out of our string attribute which specifies the
33005   * target mode for left and top positioning
33006   */
33007  this.positionMode = function positionMode() {
33008    var attachment = ($attrs.mdPositionMode || 'target').split(' ');
33009
33010    // If attachment is a single item, duplicate it for our second value.
33011    // ie. 'target' -> 'target target'
33012    if (attachment.length == 1) {
33013      attachment.push(attachment[0]);
33014    }
33015
33016    return {
33017      left: attachment[0],
33018      top: attachment[1]
33019    };
33020  };
33021
33022  /**
33023   * Build a nice object out of our string attribute which specifies
33024   * the offset of top and left in pixels.
33025   */
33026  this.offsets = function offsets() {
33027    var position = ($attrs.mdOffset || '0 0').split(' ').map(parseFloat);
33028    if (position.length == 2) {
33029      return {
33030        left: position[0],
33031        top: position[1]
33032      };
33033    } else if (position.length == 1) {
33034      return {
33035        top: position[0],
33036        left: position[0]
33037      };
33038    } else {
33039      throw Error('Invalid offsets specified. Please follow format <x, y> or <n>');
33040    }
33041  };
33042
33043  // Functionality that is exposed in the view.
33044  $scope.$mdMenu = {
33045    open: this.open,
33046    close: this.close
33047  };
33048
33049  // Deprecated APIs
33050  $scope.$mdOpenMenu = angular.bind(this, function() {
33051    $log.warn('mdMenu: The $mdOpenMenu method is deprecated. Please use `$mdMenu.open`.');
33052    return this.open.apply(this, arguments);
33053  });
33054}
33055
33056})();
33057(function(){
33058"use strict";
33059
33060/**
33061 * @ngdoc directive
33062 * @name mdMenu
33063 * @module material.components.menu
33064 * @restrict E
33065 * @description
33066 *
33067 * Menus are elements that open when clicked. They are useful for displaying
33068 * additional options within the context of an action.
33069 *
33070 * Every `md-menu` must specify exactly two child elements. The first element is 
33070what is
33071 * left in the DOM and is used to open the menu. This element is called the trigger element.
33072 * The trigger element's scope has access to `$mdMenu.open($event)`
33073 * which it may call to open the menu. By passing $event as argument, the
33074 * corresponding event is stopped from propagating up the DOM-tree. Similarly, `$mdMenu.close()`
33075 * can be used to close the menu.
33076 *
33077 * The second element is the `md-menu-content` element which represents the
33078 * contents of the menu when it is open. Typically this will contain `md-menu-item`s,
33079 * but you can do custom content as well.
33080 *
33081 * <hljs lang="html">
33082 * <md-menu>
33083 *  <!-- Trigger element is a md-button with an icon -->
33084 *  <md-button ng-click="$mdMenu.open($event)" class="md-icon-button" aria-label="Open sample menu">
33085 *    <md-icon md-svg-icon="call:phone"></md-icon>
33086 *  </md-button>
33087 *  <md-menu-content>
33088 *    <md-menu-item><md-button ng-click="doSomething()">Do Something</md-button></md-menu-item>
33089 *  </md-menu-content>
33090 * </md-menu>
33091 * </hljs>
33092
33093 * ## Sizing Menus
33094 *
33095 * The width of the menu when it is open may be specified by specifying a `width`
33096 * attribute on the `md-menu-content` element.
33097 * See the [Material Design Spec](https://material.google.com/components/menus.html#menus-simple-menus)
33098 * for more information.
33099 *
33100 *
33101 * ## Aligning Menus
33102 *
33103 * When a menu opens, it is important that the content aligns with the trigger element.
33104 * Failure to align menus can result in jarring experiences for users as content
33105 * suddenly shifts. To help with this, `md-menu` provides serveral APIs to help
33106 * with alignment.
33107 *
33108 * ### Target Mode
33109 *
33110 * By default, `md-menu` will attempt to align the `md-menu-content` by aligning
33111 * designated child elements in both the trigger and the menu content.
33112 *
33113 * To specify the alignment element in the `trigger` you can use the `md-menu-origin`
33114 * attribute on a child element. If no `md-menu-origin` is specified, the `md-menu`
33115 * will be used as the origin element.
33116 *
33117 * Similarly, the `md-menu-content` may specify a `md-menu-align-target` for a
33118 * `md-menu-item` to specify the node that it should try and align with.
33119 *
33120 * In this example code, we specify an icon to be our origin element, and an
33121 * icon in our menu content to be our alignment target. This ensures that both
33122 * icons are aligned when the menu opens.
33123 *
33124 * <hljs lang="html">
33125 * <md-menu>
33126 *  <md-button ng-click="$mdMenu.open($event)" class="md-icon-button" aria-label="Open some menu">
33127 *    <md-icon md-menu-origin md-svg-icon="call:phone"></md-icon>
33128 *  </md-button>
33129 *  <md-menu-content>
33130 *    <md-menu-item>
33131 *      <md-button ng-click="doSomething()" aria-label="Do something">
33132 *        <md-icon md-menu-align-target md-svg-icon="call:phone"></md-icon>
33133 *        Do Something
33134 *      </md-button>
33135 *    </md-menu-item>
33136 *  </md-menu-content>
33137 * </md-menu>
33138 * </hljs>
33139 *
33140 * Sometimes we want to specify alignment on the right side of an element, for example
33141 * if we have a menu on the right side a toolbar, we want to right align our menu content.
33142 *
33143 * We can specify the origin by using the `md-position-mode` attribute on both
33144 * the `x` and `y` axis. Right now only the `x-axis` has more than one option.
33145 * You may specify the default mode of `target target` or
33146 * `target-right target` to specify a right-oriented alignment target. See the
33147 * position section of the demos for more examples.
33148 *
33149 * ### Menu Offsets
33150 *
33151 * It is sometimes unavoidable to need to have a deeper level of control for
33152 * the positioning of a menu to ensure perfect alignment. `md-menu` provides
33153 * the `md-offset` attribute to allow pixel level specificty of adjusting the
33154 * exact positioning.
33155 *
33156 * This offset is provided in the format of `x y` or `n` where `n` will be used
33157 * in both the `x` and `y` axis.
33158 *
33159 * For example, to move a menu by `2px` down from the top, we can use:
33160 * <hljs lang="html">
33161 * <md-menu md-offset="0 2">
33162 *   <!-- menu-content -->
33163 * </md-menu>
33164 * </hljs>
33165 *
33166 * ### Auto Focus
33167 * By default, when a menu opens, `md-menu` focuses the first button in the menu content.
33168 *
33169 * But sometimes you would like to focus another specific menu item instead of the first.<br/>
33170 * This can be done by applying the `md-autofocus` directive on the given element.
33171 *
33172 * <hljs lang="html">
33173 * <md-menu-item>
33174 *   <md-button md-autofocus ng-click="doSomething()">
33175 *     Auto Focus
33176 *   </md-button>
33177 * </md-menu-item>
33178 * </hljs>
33179 *
33180 *
33181 * ### Preventing close
33182 *
33183 * Sometimes you would like to be able to click on a menu item without having the menu
33184 * close. To do this, AngularJS Material exposes the `md-prevent-menu-close` attribute which
33185 * can be added to a button inside a menu to stop the menu from automatically closing.
33186 * You can then close the menu either by using `$mdMenu.close()` in the template,
33187 * or programatically by injecting `$mdMenu` and calling `$mdMenu.hide()`.
33188 *
33189 * <hljs lang="html">
33190 * <md-menu-content ng-mouseleave="$mdMenu.close()">
33191 *   <md-menu-item>
33192 *     <md-button ng-click="doSomething()" aria-label="Do something" md-prevent-menu-close="md-prevent-menu-close">
33193 *       <md-icon md-menu-align-target md-svg-icon="call:phone"></md-icon>
33194 *       Do Something
33195 *     </md-button>
33196 *   </md-menu-item>
33197 * </md-menu-content>
33198 * </hljs>
33199 *
33200 * @usage
33201 * <hljs lang="html">
33202 * <md-menu>
33203 *  <md-button ng-click="$mdMenu.open($event)" class="md-icon-button">
33204 *    <md-icon md-svg-icon="call:phone"></md-icon>
33205 *  </md-button>
33206 *  <md-menu-content>
33207 *    <md-menu-item><md-button ng-click="doSomething()">Do Something</md-button></md-menu-item>
33208 *  </md-menu-content>
33209 * </md-menu>
33210 * </hljs>
33211 *
33212 * @param {string} md-position-mode The position mode in the form of
33213 *           `x`, `y`. Default value is `target`,`target`. Right now the `x` axis
33214 *           also supports `target-right`.
33215 * @param {string} md-offset An offset to apply to the dropdown after positioning
33216 *           `x`, `y`. Default value is `0`,`0`.
33217 *
33218 */
33219
33220MenuDirective.$inject = ["$mdUtil"];
33221angular
33222    .module('material.components.menu')
33223    .directive('mdMenu', MenuDirective);
33224
33225/**
33226 * @ngInject
33227 */
33228function MenuDirective($mdUtil) {
33229  var INVALID_PREFIX = 'Invalid HTML for md-menu: ';
33230  return {
33231    restrict: 'E',
33232    require: ['mdMenu', '?^mdMenuBar'],
33233    controller: 'mdMenuCtrl', // empty function to be built by link
33234    scope: true,
33235    compile: compile
33236  };
33237
33238  function compile(templateElement) {
33239    templateElement.addClass('md-menu');
33240
33241    var triggerEl = templateElement.children()[0];
33242    var contentEl = templateElement.children()[1];
33243
33244    var prefixer = $mdUtil.prefixer();
33245
33246    if (!prefixer.hasAttribute(triggerEl, 'ng-click')) {
33247      triggerEl = triggerEl
33248          .querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter'])) || triggerEl;
33249    }
33250
33251    var isButtonTrigger = triggerEl.nodeName === 'MD-BUTTON' || triggerEl.nodeName === 'BUTTON';
33252
33253    if (triggerEl && isButtonTrigger && !triggerEl.hasAttribute('type')) {
33254      triggerEl.setAttribute('type', 'button');
33255    }
33256
33257    if (!triggerEl) {
33258      throw Error(INVALID_PREFIX + 'Expected the menu to have a trigger element.');
33259    }
33260
33261    if (!contentEl || contentEl.nodeName !== 'MD-MENU-CONTENT') {
33262      throw Error(INVALID_PREFIX + 'Expected the menu to contain a `md-menu-content` element.');
33263    }
33264
33265    // Default element for ARIA attributes has the ngClick or ngMouseenter expression
33266    triggerEl && triggerEl.setAttribute('aria-haspopup', 'true');
33267
33268    var nestedMenus = templateElement[0].querySelectorAll('md-menu');
33269    var nestingDepth = parseInt(templateElement[0].getAttribute('md-nest-level'), 10) || 0;
33270    if (nestedMenus) {
33271      angular.forEach($mdUtil.nodesToArray(nestedMenus), function(menuEl) {
33272        if (!menuEl.hasAttribute('md-position-mode')) {
33273          menuEl.setAttribute('md-position-mode', 'cascade');
33274        }
33275        menuEl.classList.add('_md-nested-menu');
33276        menuEl.setAttribute('md-nest-level', nestingDepth + 1);
33277      });
33278    }
33279    return link;
33280  }
33281
33282  function link(scope, element, attr, ctrls) {
33283    var mdMenuCtrl = ctrls[0];
33284    var isInMenuBar = !!ctrls[1];
33285    // Move everything into a md-menu-container and pass it to the controller
33286    var menuContainer = angular.element( '<div class="_md md-open-menu-container md-whiteframe-z2"></div>');
33287    var menuContents = element.children()[1];
33288
33289    element.addClass('_md');     // private md component indicator for styling
33290
33291    if (!menuContents.hasAttribute('role')) {
33292      menuContents.setAttribute('role', 'menu');
33293    }
33294    menuContainer.append(menuContents);
33295
33296    element.on('$destroy', function() {
33297      menuContainer.remove();
33298    });
33299
33300    element.append(menuContainer);
33301    menuContainer[0].style.display = 'none';
33302    mdMenuCtrl.init(menuContainer, { isInMenuBar: isInMenuBar });
33303
33304  }
33305}
33306
33307})();
33308(function(){
33309"use strict";
33310
33311
33312MenuProvider.$inject = ["$$interimElementProvider"];angular
33313  .module('material.components.menu')
33314  .provider('$mdMenu', MenuProvider);
33315
33316/*
33317 * Interim element provider for the menu.
33318 * Handles behavior for a menu while it is open, including:
33319 *    - handling animating the menu opening/closing
33320 *    - handling key/mouse events on the menu element
33321 *    - handling enabling/disabling scroll while the menu is open
33322 *    - handling redrawing during resizes and orientation changes
33323 *
33324 */
33325
33326function MenuProvider($$interimElementProvider) {
33327  menuDefaultOptions.$inject = ["$mdUtil", "$mdTheming", "$mdConstant", "$document", "$window", "$q", "$$rAF", "$animateCss", "$animate", "$log"];
33328  var MENU_EDGE_MARGIN = 8;
33329
33330  return $$interimElementProvider('$mdMenu')
33331    .setDefaults({
33332      methods: ['target'],
33333      options: menuDefaultOptions
33334    });
33335
33336  /* @ngInject */
33337  function menuDefaultOptions($mdUtil, $mdTheming, $mdConstant, $document, $window, $q, $$rAF,
33338                              $animateCss, $animate, $log) {
33339
33340    var prefixer = $mdUtil.prefixer();
33341    var animator = $mdUtil.dom.animator;
33342
33343    return {
33344      parent: 'body',
33345      onShow: onShow,
33346      onRemove: onRemove,
33347      hasBackdrop: true,
33348      disableParentScroll: true,
33349      skipCompile: true,
33350      preserveScope: true,
33351      multiple: true,
33352      themable: true
33353    };
33354
33355    /**
33356     * Show modal backdrop element...
33357     * @returns {function(): void} A function that removes this backdrop
33358     */
33359    function showBackdrop(scope, element, options) {
33360      if (options.nestLevel) return angular.noop;
33361
33362      // If we are not within a dialog...
33363      if (options.disableParentScroll && !$mdUtil.getClosest(options.target, 'MD-DIALOG')) {
33364        // !! DO this before creating the backdrop; since disableScrollAround()
33365        //    configures the scroll offset; which is used by mdBackDrop postLink()
33366        options.restoreScroll = $mdUtil.disableScrollAround(options.element, options.parent);
33367      } else {
33368        options.disableParentScroll = false;
vendor: 26,687 bytes, lines 33369-34176
33369      }
33370
33371      if (options.hasBackdrop) {
33372        options.backdrop = $mdUtil.createBackdrop(scope, "md-menu-backdrop md-click-catcher");
33373
33374        $animate.enter(options.backdrop, $document[0].body);
33375      }
33376
33377      /**
33378       * Hide and destroys the backdrop created by showBackdrop()
33379       */
33380      return function hideBackdrop() {
33381        if (options.backdrop) options.backdrop.remove();
33382        if (options.disableParentScroll) options.restoreScroll();
33383      };
33384    }
33385
33386    /**
33387     * Removing the menu element from the DOM and remove all associated event listeners
33388     * and backdrop
33389     */
33390    function onRemove(scope, element, opts) {
33391      opts.cleanupInteraction();
33392      opts.cleanupBackdrop();
33393      opts.cleanupResizing();
33394      opts.hideBackdrop();
33395
33396      // Before the menu is closing remove the clickable class.
33397      element.removeClass('md-clickable');
33398
33399      // For navigation $destroy events, do a quick, non-animated removal,
33400      // but for normal closes (from clicks, etc) animate the removal
33401
33402      return (opts.$destroy === true) ? detachAndClean() : animateRemoval().then( detachAndClean );
33403
33404      /**
33405       * For normal closes, animate the removal.
33406       * For forced closes (like $destroy events), skip the animations
33407       */
33408      function animateRemoval() {
33409        return $animateCss(element, {addClass: 'md-leave'}).start();
33410      }
33411
33412      /**
33413       * Detach the element
33414       */
33415      function detachAndClean() {
33416        element.removeClass('md-active');
33417        detachElement(element, opts);
33418        opts.alreadyOpen = false;
33419      }
33420
33421    }
33422
33423    /**
33424     * Inserts and configures the staged Menu element into the DOM, positioning it,
33425     * and wiring up various interaction events
33426     */
33427    function onShow(scope, element, opts) {
33428      sanitizeAndConfigure(opts);
33429
33430      if (opts.menuContentEl[0]) {
33431        // Inherit the theme from the target element.
33432        $mdTheming.inherit(opts.menuContentEl, opts.target);
33433      } else {
33434        $log.warn(
33435          '$mdMenu: Menu elements should always contain a `md-menu-content` element,' +
33436          'otherwise interactivity features will not work properly.',
33437          element
33438        );
33439      }
33440      
33441      // Register various listeners to move menu on resize/orientation change
33442      opts.cleanupResizing = startRepositioningOnResize();
33443      opts.hideBackdrop = showBackdrop(scope, element, opts);
33444
33445      // Return the promise for when our menu is done animating in
33446      return showMenu()
33447        .then(function(response) {
33448          opts.alreadyOpen = true;
33449          opts.cleanupInteraction = activateInteraction();
33450          opts.cleanupBackdrop = setupBackdrop();
33451
33452          // Since the menu finished its animation, mark the menu as clickable.
33453          element.addClass('md-clickable');
33454
33455          return response;
33456        });
33457
33458      /**
33459       * Place the menu into the DOM and call positioning related functions
33460       */
33461      function showMenu() {
33462        opts.parent.append(element);
33463        element[0].style.display = '';
33464
33465        return $q(function(resolve) {
33466          var position = calculateMenuPosition(element, opts);
33467
33468          element.removeClass('md-leave');
33469
33470          // Animate the menu scaling, and opacity [from its position origin (default == top-left)]
33471          // to normal scale.
33472          $animateCss(element, {
33473            addClass: 'md-active',
33474            from: animator.toCss(position),
33475            to: animator.toCss({transform: ''})
33476          })
33477          .start()
33478          .then(resolve);
33479
33480        });
33481      }
33482
33483      /**
33484       * Check for valid opts and set some sane defaults
33485       */
33486      function sanitizeAndConfigure() {
33487        if (!opts.target) {
33488          throw Error(
33489            '$mdMenu.show() expected a target to animate from in options.target'
33490          );
33491        }
33492        angular.extend(opts, {
33493          alreadyOpen: false,
33494          isRemoved: false,
33495          target: angular.element(opts.target), //make sure it's not a naked dom node
33496          parent: angular.element(opts.parent),
33497          menuContentEl: angular.element(element[0].querySelector('md-menu-content'))
33498        });
33499      }
33500
33501      /**
33502       * Configure various resize listeners for screen changes
33503       */
33504      function startRepositioningOnResize() {
33505
33506        var repositionMenu = (function(target, options) {
33507          return $$rAF.throttle(function() {
33508            if (opts.isRemoved) return;
33509            var position = calculateMenuPosition(target, options);
33510
33511            target.css(animator.toCss(position));
33512          });
33513        })(element, opts);
33514
33515        $window.addEventListener('resize', repositionMenu);
33516        $window.addEventListener('orientationchange', repositionMenu);
33517
33518        return function stopRepositioningOnResize() {
33519
33520          // Disable resizing handlers
33521          $window.removeEventListener('resize', repositionMenu);
33522          $window.removeEventListener('orientationchange', repositionMenu);
33523
33524        };
33525      }
33526
33527      /**
33528       * Sets up the backdrop and listens for click elements.
33529       * Once the backdrop will be clicked, the menu will automatically close.
33530       * @returns {!Function} Function to remove the backdrop.
33531       */
33532      function setupBackdrop() {
33533        if (!opts.backdrop) return angular.noop;
33534
33535        opts.backdrop.on('click', onBackdropClick);
33536
33537        return function() {
33538          opts.backdrop.off('click', onBackdropClick);
33539        }
33540      }
33541
33542      /**
33543       * Function to be called whenever the backdrop is clicked.
33544       * @param {!MouseEvent} event
33545       */
33546      function onBackdropClick(event) {
33547        event.preventDefault();
33548        event.stopPropagation();
33549
33550        scope.$apply(function() {
33551          opts.mdMenuCtrl.close(true, { closeAll: true });
33552        });
33553      }
33554
33555      /**
33556       * Activate interaction on the menu. Resolves the focus target and closes the menu on
33557       * escape or option click.
33558       * @returns {!Function} Function to deactivate the interaction listeners.
33559       */
33560      function activateInteraction() {
33561        if (!opts.menuContentEl[0]) return angular.noop;
33562
33563        // Wire up keyboard listeners.
33564        // - Close on escape,
33565        // - focus next item on down arrow,
33566        // - focus prev item on up
33567        opts.menuContentEl.on('keydown', onMenuKeyDown);
33568        opts.menuContentEl[0].addEventListener('click', captureClickListener, true);
33569
33570        // kick off initial focus in the menu on the first enabled element
33571        var focusTarget = opts.menuContentEl[0]
33572          .querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
33573
33574        if ( !focusTarget ) {
33575          var childrenLen = opts.menuContentEl[0].children.length;
33576          for(var childIndex = 0; childIndex < childrenLen; childIndex++) {
33577            var child = opts.menuContentEl[0].children[childIndex];
33578            focusTarget = child.querySelector('.md-button:not([disabled])');
33579            if (focusTarget) {
33580              break;
33581            }
33582            if (child.firstElementChild && !child.firstElementChild.disabled) {
33583              focusTarget = child.firstElementChild;
33584              break;
33585            }
33586          }
33587        }
33588
33589        focusTarget && focusTarget.focus();
33590
33591        return function cleanupInteraction() {
33592          opts.menuContentEl.off('keydown', onMenuKeyDown);
33593          opts.menuContentEl[0].removeEventListener('click', captureClickListener, true);
33594        };
33595
33596        // ************************************
33597        // internal functions
33598        // ************************************
33599
33600        function onMenuKeyDown(ev) {
33601          var handled;
33602          switch (ev.keyCode) {
33603            case $mdConstant.KEY_CODE.ESCAPE:
33604              opts.mdMenuCtrl.close(false, { closeAll: true });
33605              handled = true;
33606              break;
33607            case $mdConstant.KEY_CODE.UP_ARROW:
33608              if (!focusMenuItem(ev, opts.menuContentEl, opts, -1) && !opts.nestLevel) {
33609                opts.mdMenuCtrl.triggerContainerProxy(ev);
33610              }
33611              handled = true;
33612              break;
33613            case $mdConstant.KEY_CODE.DOWN_ARROW:
33614              if (!focusMenuItem(ev, opts.menuContentEl, opts, 1) && !opts.nestLevel) {
33615                opts.mdMenuCtrl.triggerContainerProxy(ev);
33616              }
33617              handled = true;
33618              break;
33619            case $mdConstant.KEY_CODE.LEFT_ARROW:
33620              if (opts.nestLevel) {
33621                opts.mdMenuCtrl.close();
33622              } else {
33623                opts.mdMenuCtrl.triggerContainerProxy(ev);
33624              }
33625              handled = true;
33626              break;
33627            case $mdConstant.KEY_CODE.RIGHT_ARROW:
33628              var parentMenu = $mdUtil.getClosest(ev.target, 'MD-MENU');
33629              if (parentMenu && parentMenu != opts.parent[0]) {
33630                ev.target.click();
33631              } else {
33632                opts.mdMenuCtrl.triggerContainerProxy(ev);
33633              }
33634              handled = true;
33635              break;
33636          }
33637          if (handled) {
33638            ev.preventDefault();
33639            ev.stopImmediatePropagation();
33640          }
33641        }
33642
33643        function onBackdropClick(e) {
33644          e.preventDefault();
33645          e.stopPropagation();
33646          scope.$apply(function() {
33647            opts.mdMenuCtrl.close(true, { closeAll: true });
33648          });
33649        }
33650
33651        // Close menu on menu item click, if said menu-item is not disabled
33652        function captureClickListener(e) {
33653          var target = e.target;
33654          // Traverse up the event until we get to the menuContentEl to see if
33655          // there is an ng-click and that the ng-click is not disabled
33656          do {
33657            if (target == opts.menuContentEl[0]) return;
33658            if ((hasAnyAttribute(target, ['ng-click', 'ng-href', 'ui-sref']) ||
33659                target.nodeName == 'BUTTON' || target.nodeName == 'MD-BUTTON') && !hasAnyAttribute(target, ['md-prevent-menu-close'])) {
33660              var closestMenu = $mdUtil.getClosest(target, 'MD-MENU');
33661              if (!target.hasAttribute('disabled') && (!closestMenu || closestMenu == opts.parent[0])) {
33662                close();
33663              }
33664              break;
33665            }
33666          } while (target = target.parentNode);
33667
33668          function close() {
33669            scope.$apply(function() {
33670              opts.mdMenuCtrl.close(true, { closeAll: true });
33671            });
33672          }
33673
33674          function hasAnyAttribute(target, attrs) {
33675            if (!target) return false;
33676
33677            for (var i = 0, attr; attr = attrs[i]; ++i) {
33678              if (prefixer.hasAttribute(target, attr)) {
33679                return true;
33680              }
33681            }
33682
33683            return false;
33684          }
33685        }
33686
33687      }
33688    }
33689
33690    /**
33691     * Takes a keypress event and focuses the next/previous menu
33692     * item from the emitting element
33693     * @param {event} e - The origin keypress event
33694     * @param {angular.element} menuEl - The menu element
33695     * @param {object} opts - The interim element options for the mdMenu
33696     * @param {number} direction - The direction to move in (+1 = next, -1 = prev)
33697     */
33698    function focusMenuItem(e, menuEl, opts, direction) {
33699      var currentItem = $mdUtil.getClosest(e.target, 'MD-MENU-ITEM');
33700
33701      var items = $mdUtil.nodesToArray(menuEl[0].children);
33702      var currentIndex = items.indexOf(currentItem);
33703
33704      // Traverse through our elements in the specified direction (+/-1) and try to
33705      // focus them until we find one that accepts focus
33706      var didFocus;
33707      for (var i = currentIndex + direction; i >= 0 && i < items.length; i = i + direction) {
33708        var focusTarget = items[i].querySelector('.md-button');
33709        didFocus = attemptFocus(focusTarget);
33710        if (didFocus) {
33711          break;
33712        }
33713      }
33714      return didFocus;
33715    }
33716
33717    /**
33718     * Attempts to focus an element. Checks whether that element is the currently
33719     * focused element after attempting.
33720     * @param {HTMLElement} el - the element to attempt focus on
33721     * @returns {bool} - whether the element was successfully focused
33722     */
33723    function attemptFocus(el) {
33724      if (el && el.getAttribute('tabindex') != -1) {
33725        el.focus();
33726        return ($document[0].activeElement == el);
33727      }
33728    }
33729
33730    /**
33731     * Use browser to remove this element without triggering a $destroy event
33732     */
33733    function detachElement(element, opts) {
33734      if (!opts.preserveElement) {
33735        if (toNode(element).parentNode === toNode(opts.parent)) {
33736          toNode(opts.parent).removeChild(toNode(element));
33737        }
33738      } else {
33739        toNode(element).style.display = 'none';
33740      }
33741    }
33742
33743    /**
33744     * Computes menu position and sets the style on the menu container
33745     * @param {HTMLElement} el - the menu container element
33746     * @param {object} opts - the interim element options object
33747     */
33748    function calculateMenuPosition(el, opts) {
33749
33750      var containerNode = el[0],
33751        openMenuNode = el[0].firstElementChild,
33752        openMenuNodeRect = openMenuNode.getBoundingClientRect(),
33753        boundryNode = $document[0].body,
33754        boundryNodeRect = boundryNode.getBoundingClientRect();
33755
33756      var menuStyle = $window.getComputedStyle(openMenuNode);
33757
33758      var originNode = opts.target[0].querySelector(prefixer.buildSelector('md-menu-origin')) || opts.target[0],
33759        originNodeRect = originNode.getBoundingClientRect();
33760
33761      var bounds = {
33762        left: boundryNodeRect.left + MENU_EDGE_MARGIN,
33763        top: Math.max(boundryNodeRect.top, 0) + MENU_EDGE_MARGIN,
33764        bottom: Math.max(boundryNodeRect.bottom, Math.max(boundryNodeRect.top, 0) + boundryNodeRect.height) - MENU_EDGE_MARGIN,
33765        right: boundryNodeRect.right - MENU_EDGE_MARGIN
33766      };
33767
33768      var alignTarget, alignTargetRect = { top:0, left : 0, right:0, bottom:0 }, existingOffsets  = { top:0, left : 0, right:0, bottom:0  };
33769      var positionMode = opts.mdMenuCtrl.positionMode();
33770
33771      if (positionMode.top == 'target' || positionMode.left == 'target' || positionMode.left == 'target-right') {
33772        alignTarget = firstVisibleChild();
33773        if ( alignTarget ) {
33774          // TODO: Allow centering on an arbitrary node, for now center on first menu-item's child
33775          alignTarget = alignTarget.firstElementChild || alignTarget;
33776          alignTarget = alignTarget.querySelector(prefixer.buildSelector('md-menu-align-target')) || alignTarget;
33777          alignTargetRect = alignTarget.getBoundingClientRect();
33778
33779          existingOffsets = {
33780            top: parseFloat(containerNode.style.top || 0),
33781            left: parseFloat(containerNode.style.left || 0)
33782          };
33783        }
33784      }
33785
33786      var position = {};
33787      var transformOrigin = 'top ';
33788
33789      switch (positionMode.top) {
33790        case 'target':
33791          position.top = existingOffsets.top + originNodeRect.top - alignTargetRect.top;
33792          break;
33793        case 'cascade':
33794          position.top = originNodeRect.top - parseFloat(menuStyle.paddingTop) - originNode.style.top;
33795          break;
33796        case 'bottom':
33797          position.top = originNodeRect.top + originNodeRect.height;
33798          break;
33799        default:
33800          throw new Error('Invalid target mode "' + positionMode.top + '" specified for md-menu on Y axis.');
33801      }
33802
33803      var rtl = ($mdUtil.bidi() == 'rtl');
33804
33805      switch (positionMode.left) {
33806        case 'target':
33807          position.left = existingOffsets.left + originNodeRect.left - alignTargetRect.left;
33808          transformOrigin += rtl ? 'right'  : 'left';
33809          break;
33810        case 'target-left':
33811          position.left = originNodeRect.left;
33812          transformOrigin += 'left';
33813          break;
33814        case 'target-right':
33815          position.left = originNodeRect.right - openMenuNodeRect.width + (openMenuNodeRect.right - alignTargetRect.right);
33816          transformOrigin += 'right';
33817          break;
33818        case 'cascade':
33819          var willFitRight = rtl ? (originNodeRect.left - openMenuNodeRect.width) < bounds.left : (originNodeRect.right + openMenuNodeRect.width) < bounds.right;
33820          position.left = willFitRight ? originNodeRect.right - originNode.style.left : originNodeRect.left - originNode.style.left - openMenuNodeRect.width;
33821          transformOrigin += willFitRight ? 'left' : 'right';
33822          break;
33823        case 'right':
33824          if (rtl) {
33825            position.left = originNodeRect.right - originNodeRect.width;
33826            transformOrigin += 'left';
33827          } else {
33828            position.left = originNodeRect.right - openMenuNodeRect.width;
33829            transformOrigin += 'right';
33830          }
33831          break;
33832        case 'left':
33833          if (rtl) {
33834            position.left = originNodeRect.right - openMenuNodeRect.width;
33835            transformOrigin += 'right';
33836          } else {
33837            position.left = originNodeRect.left;
33838            transformOrigin += 'left';
33839          }
33840          break;
33841        default:
33842          throw new Error('Invalid target mode "' + positionMode.left + '" specified for md-menu on X axis.');
33843      }
33844
33845      var offsets = opts.mdMenuCtrl.offsets();
33846      position.top += offsets.top;
33847      position.left += offsets.left;
33848
33849      clamp(position);
33850
33851      var scaleX = Math.round(100 * Math.min(originNodeRect.width / containerNode.offsetWidth, 1.0)) / 100;
33852      var scaleY = Math.round(100 * Math.min(originNodeRect.height / containerNode.offsetHeight, 1.0)) / 100;
33853
33854      return {
33855        top: Math.round(position.top),
33856        left: Math.round(position.left),
33857        // Animate a scale out if we aren't just repositioning
33858        transform: !opts.alreadyOpen ? $mdUtil.supplant('scale({0},{1})', [scaleX, scaleY]) : undefined,
33859        transformOrigin: transformOrigin
33860      };
33861
33862      /**
33863       * Clamps the repositioning of the menu within the confines of
33864       * bounding element (often the screen/body)
33865       */
33866      function clamp(pos) {
33867        pos.top = Math.max(Math.min(pos.top, bounds.bottom - containerNode.offsetHeight), bounds.top);
33868        pos.left = Math.max(Math.min(pos.left, bounds.right - containerNode.offsetWidth), bounds.left);
33869      }
33870
33871      /**
33872       * Gets the first visible child in the openMenuNode
33873       * Necessary incase menu nodes are being dynamically hidden
33874       */
33875      function firstVisibleChild() {
33876        for (var i = 0; i < openMenuNode.children.length; ++i) {
33877          if ($window.getComputedStyle(openMenuNode.children[i]).display != 'none') {
33878            return openMenuNode.children[i];
33879          }
33880        }
33881      }
33882    }
33883  }
33884  function toNode(el) {
33885    if (el instanceof angular.element) {
33886      el = el[0];
33887    }
33888    return el;
33889  }
33890}
33891
33892})();
33893(function(){
33894"use strict";
33895
33896
33897MenuBarController.$inject = ["$scope", "$rootScope", "$element", "$attrs", "$mdConstant", "$document", "$mdUtil", "$timeout"];
33898angular
33899  .module('material.components.menuBar')
33900  .controller('MenuBarController', MenuBarController);
33901
33902var BOUND_MENU_METHODS = ['handleKeyDown', 'handleMenuHover', 'scheduleOpenHoveredMenu', 'cancelScheduledOpen'];
33903
33904/**
33905 * @ngInject
33906 */
33907function MenuBarController($scope, $rootScope, $element, $attrs, $mdConstant, $document, $mdUtil, $timeout) {
33908  this.$element = $element;
33909  this.$attrs = $attrs;
33910  this.$mdConstant = $mdConstant;
33911  this.$mdUtil = $mdUtil;
33912  this.$document = $document;
33913  this.$scope = $scope;
33914  this.$rootScope = $rootScope;
33915  this.$timeout = $timeout;
33916
33917  var self = this;
33918  angular.forEach(BOUND_MENU_METHODS, function(methodName) {
33919    self[methodName] = angular.bind(self, self[methodName]);
33920  });
33921}
33922
33923MenuBarController.prototype.init = function() {
33924  var $element = this.$element;
33925  var $mdUtil = this.$mdUtil;
33926  var $scope = this.$scope;
33927
33928  var self = this;
33929  var deregisterFns = [];
33930  $element.on('keydown', this.handleKeyDown);
33931  this.parentToolbar = $mdUtil.getClosest($element, 'MD-TOOLBAR');
33932
33933  deregisterFns.push(this.$rootScope.$on('$mdMenuOpen', function(event, el) {
33934    if (self.getMenus().indexOf(el[0]) != -1) {
33935      $element[0].classList.add('md-open');
33936      el[0].classList.add('md-open');
33937      self.currentlyOpenMenu = el.controller('mdMenu');
33938      self.currentlyOpenMenu.registerContainerProxy(self.handleKeyDown);
33939      self.enableOpenOnHover();
33940    }
33941  }));
33942
33943  deregisterFns.push(this.$rootScope.$on('$mdMenuClose', function(event, el, opts) {
33944    var rootMenus = self.getMenus();
33945    if (rootMenus.indexOf(el[0]) != -1) {
33946      $element[0].classList.remove('md-open');
33947      el[0].classList.remove('md-open');
33948    }
33949
33950    if ($element[0].contains(el[0])) {
33951      var parentMenu = el[0];
33952      while (parentMenu && rootMenus.indexOf(parentMenu) == -1) {
33953        parentMenu = $mdUtil.getClosest(parentMenu, 'MD-MENU', true);
33954      }
33955      if (parentMenu) {
33956        if (!opts.skipFocus) parentMenu.querySelector('button:not([disabled])').focus();
33957        self.currentlyOpenMenu = undefined;
33958        self.disableOpenOnHover();
33959        self.setKeyboardMode(true);
33960      }
33961    }
33962  }));
33963
33964  $scope.$on('$destroy', function() {
33965    self.disableOpenOnHover();
33966    while (deregisterFns.length) {
33967      deregisterFns.shift()();
33968    }
33969  });
33970
33971
33972  this.setKeyboardMode(true);
33973};
33974
33975MenuBarController.prototype.setKeyboardMode = function(enabled) {
33976  if (enabled) this.$element[0].classList.add('md-keyboard-mode');
33977  else this.$element[0].classList.remove('md-keyboard-mode');
33978};
33979
33980MenuBarController.prototype.enableOpenOnHover = function() {
33981  if (this.openOnHoverEnabled) return;
33982
33983  var self = this;
33984
33985  self.openOnHoverEnabled = true;
33986
33987  if (self.parentToolbar) {
33988    self.parentToolbar.classList.add('md-has-open-menu');
33989
33990    // Needs to be on the next tick so it doesn't close immediately.
33991    self.$mdUtil.nextTick(function() {
33992      angular.element(self.parentToolbar).on('click', self.handleParentClick);
33993    }, false);
33994  }
33995
33996  angular
33997    .element(self.getMenus())
33998    .on('mouseenter', self.handleMenuHover);
33999};
34000
34001MenuBarController.prototype.handleMenuHover = function(e) {
34002  this.setKeyboardMode(false);
34003  if (this.openOnHoverEnabled) {
34004    this.scheduleOpenHoveredMenu(e);
34005  }
34006};
34007
34008MenuBarController.prototype.disableOpenOnHover = function() {
34009  if (!this.openOnHoverEnabled) return;
34010
34011  this.openOnHoverEnabled = false;
34012
34013  if (this.parentToolbar) {
34014    this.parentToolbar.classList.remove('md-has-open-menu');
34015    angular.element(this.parentToolbar).off('click', this.handleParentClick);
34016  }
34017
34018  angular
34019    .element(this.getMenus())
34020    .off('mouseenter', this.handleMenuHover);
34021};
34022
34023MenuBarController.prototype.scheduleOpenHoveredMenu = function(e) {
34024  var menuEl = angular.element(e.currentTarget);
34025  var menuCtrl = menuEl.controller('mdMenu');
34026  this.setKeyboardMode(false);
34027  this.scheduleOpenMenu(menuCtrl);
34028};
34029
34030MenuBarController.prototype.scheduleOpenMenu = function(menuCtrl) {
34031  var self = this;
34032  var $timeout = this.$timeout;
34033  if (menuCtrl != self.currentlyOpenMenu) {
34034    $timeout.cancel(self.pendingMenuOpen);
34035    self.pendingMenuOpen = $timeout(function() {
34036      self.pendingMenuOpen = undefined;
34037      if (self.currentlyOpenMenu) {
34038        self.currentlyOpenMenu.close(true, { closeAll: true });
34039      }
34040      menuCtrl.open();
34041    }, 200, false);
34042  }
34043};
34044
34045MenuBarController.prototype.handleKeyDown = function(e) {
34046  var keyCodes = this.$mdConstant.KEY_CODE;
34047  var currentMenu = this.currentlyOpenMenu;
34048  var wasOpen = currentMenu && currentMenu.isOpen;
34049  this.setKeyboardMode(true);
34050  var handled, newMenu, newMenuCtrl;
34051  switch (e.keyCode) {
34052    case keyCodes.DOWN_ARROW:
34053      if (currentMenu) {
34054        currentMenu.focusMenuContainer();
34055      } else {
34056        this.openFocusedMenu();
34057      }
34058      handled = true;
34059      break;
34060    case keyCodes.UP_ARROW:
34061      currentMenu && currentMenu.close();
34062      handled = true;
34063      break;
34064    case keyCodes.LEFT_ARROW:
34065      newMenu = this.focusMenu(-1);
34066      if (wasOpen) {
34067        newMenuCtrl = angular.element(newMenu).controller('mdMenu');
34068        this.scheduleOpenMenu(newMenuCtrl);
34069      }
34070      handled = true;
34071      break;
34072    case keyCodes.RIGHT_ARROW:
34073      newMenu = this.focusMenu(+1);
34074      if (wasOpen) {
34075        newMenuCtrl = angular.element(newMenu).controller('mdMenu');
34076        this.scheduleOpenMenu(newMenuCtrl);
34077      }
34078      handled = true;
34079      break;
34080  }
34081  if (handled) {
34082    e && e.preventDefault && e.preventDefault();
34083    e && e.stopImmediatePropagation && e.stopImmediatePropagation();
34084  }
34085};
34086
34087MenuBarController.prototype.focusMenu = function(direction) {
34088  var menus = this.getMenus();
34089  var focusedIndex = this.getFocusedMenuIndex();
34090
34091  if (focusedIndex == -1) { focusedIndex = this.getOpenMenuIndex(); }
34092
34093  var changed = false;
34094
34095  if (focusedIndex == -1) { focusedIndex = 0; changed = true; }
34096  else if (
34097    direction < 0 && focusedIndex > 0 ||
34098    direction > 0 && focusedIndex < menus.length - direction
34099  ) {
34100    focusedIndex += direction;
34101    changed = true;
34102  }
34103  if (changed) {
34104    menus[focusedIndex].querySelector('button').focus();
34105    return menus[focusedIndex];
34106  }
34107};
34108
34109MenuBarController.prototype.openFocusedMenu = function() {
34110  var menu = this.getFocusedMenu();
34111  menu && angular.element(menu).controller('mdMenu').open();
34112};
34113
34114MenuBarController.prototype.getMenus = function() {
34115  var $element = this.$element;
34116  return this.$mdUtil.nodesToArray($element[0].children)
34117    .filter(function(el) { return el.nodeName == 'MD-MENU'; });
34118};
34119
34120MenuBarController.prototype.getFocusedMenu = function() {
34121  return this.getMenus()[this.getFocusedMenuIndex()];
34122};
34123
34124MenuBarController.prototype.getFocusedMenuIndex = function() {
34125  var $mdUtil = this.$mdUtil;
34126  var focusedEl = $mdUtil.getClosest(
34127    this.$document[0].activeElement,
34128    'MD-MENU'
34129  );
34130  if (!focusedEl) return -1;
34131
34132  var focusedIndex = this.getMenus().indexOf(focusedEl);
34133  return focusedIndex;
34134};
34135
34136MenuBarController.prototype.getOpenMenuIndex = function() {
34137  var menus = this.getMenus();
34138  for (var i = 0; i < menus.length; ++i) {
34139    if (menus[i].classList.contains('md-open')) return i;
34140  }
34141  return -1;
34142};
34143
34144MenuBarController.prototype.handleParentClick = function(event) {
34145  var openMenu = this.querySelector('md-menu.md-open');
34146
34147  if (openMenu && !openMenu.contains(event.target)) {
34148    angular.element(openMenu).controller('mdMenu').close(true, {
34149      closeAll: true
34150    });
34151  }
34152};
34153
34154})();
34155(function(){
34156"use strict";
34157
34158/**
34159 * @ngdoc directive
34160 * @name mdMenuBar
34161 * @module material.components.menuBar
34162 * @restrict E
34163 * @description
34164 *
34165 * Menu bars are containers that hold multiple menus. They change the behavior and appearence
34166 * of the `md-menu` directive to behave similar to an operating system provided menu.
34167 *
34168 * @usage
34169 * <hljs lang="html">
34170 * <md-menu-bar>
34171 *   <md-menu>
34172 *     <button ng-click="$mdMenu.open()">
34173 *       File
34174 *     </button>
34175 *     <md-menu-content>
34176 *       <md-menu-item>
34177 *         <md-button ng-click="ctrl.sampleAction('share', $event)">
34178 *           Share...
34179 *         </md-button>
34180 *       </md-menu-item>
34181 *       <md-menu-divider></md-menu-divider>
34182 *       <md-menu-item>
34183 *       <md-menu-item>
34184 *         <md-menu>
34185 *           <md-button ng-click="$mdMenu.open()">New</md-button>
34186 *           <md-menu-content>
34187 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Document', $event)">Document</md-button></md-menu-item>
34188 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Spreadsheet', $event)">Spreadsheet</md-button></md-menu-item>
34189 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Presentation', $event)">Presentation</md-button></md-menu-item>
34190 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Form', $event)">Form</md-button></md-menu-item>
34191 *             <md-menu-item><md-button ng-click="ctrl.sampleAction('New Drawing', $event)">Drawing</md-button></md-menu-item>
34192 *           </md-menu-content>
34193 *         </md-menu>
34194 *       </md-menu-item>
34195 *     </md-menu-content>
34196 *   </md-menu>
34197 * </md-menu-bar>
34198 * </hljs>
34199 *
34200 * ## Menu Bar Controls
34201 *
34202 * You may place `md-menu-items` that function as controls within menu bars.
34203 * There are two modes that are exposed via the `type` attribute of the `md-menu-item`.
34204 * `type="checkbox"` will function as a boolean control for the `ng-model` attribute of the
34205 * `md-menu-item`. `type="radio"` will function like a radio button, setting the `ngModel`
34206 * to the `string` value of the `value` attribute. If you need non-string values, you can use
34207 * `ng-value` to provide an expression (this is similar to how angular's native `input[type=radio]` works.
34208 *
34209 * <hljs lang="html">
34210 * <md-menu-bar>
34211 *  <md-menu>
34212 *    <button ng-click="$mdMenu.open()">
34213 *      Sample Menu
34214 *    </button>
34215 *    <md-menu-content>
34216 *      <md-menu-item type="checkbox" ng-model="settings.allowChanges">Allow changes</md-menu-item>
34217 *      <md-menu-divider></md-menu-divider>
34218 *      <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 1</md-menu-item>
34219 *      <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 2</md-menu-item>
34220 *      <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 3</md-menu-item>
34221 *    </md-menu-content>
34222 *  </md-menu>
34223 * </md-menu-bar>
34224 * </hljs>
34225 *
34226 *
34227 * ### Nesting Menus
34228 *
34229 * Menus may be nested within menu bars. This is commonly called cascading menus.
34230 * To nest a menu place the nested menu inside the content of the `md-menu-item`.
34231 * <hljs lang="html">
34232 * <md-menu-item>
34233 *   <md-menu>
34234 *     <button ng-click="$mdMenu.open()">New</md-button>
34235 *     <md-menu-content>
34236 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Document', $event)">Document</md-button></md-menu-item>
34237 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Spreadsheet', $event)">Spreadsheet</md-button></md-menu-item>
34238 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Presentation', $event)">Presentation</md-button></md-menu-item>
34239 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Form', $event)">Form</md-button></md-menu-item>
34240 *       <md-menu-item><md-button ng-click="ctrl.sampleAction('New Drawing', $event)">Drawing</md-button></md-menu-item>
34241 *     </md-menu-content>
34242 *   </md-menu>
34243 * </md-menu-item>
34244 * </hljs>
34245 *
34246 */
34247
34248MenuBarDirective.$inject = ["$mdUtil", "$mdTheming"];
34249angular
34250  .module('material.components.menuBar')
34251  .directive('mdMenuBar', MenuBarDirective);
34252
34253/* @ngInject */
34254function MenuBarDirective($mdUtil, $mdTheming) {
34255  return {
34256    restrict: 'E',
34257    require: 'mdMenuBar',
34258    controller: 'MenuBarController',
34259
34260    compile: function compile(templateEl, templateAttrs) {
34261      if (!templateAttrs.ariaRole) {
34262        templateEl[0].setAttribute('role', 'menubar');
34263      }
34264      angular.forEach(templateEl[0].children, function(menuEl) {
34265        if (menuEl.nodeName == 'MD-MENU') {
34266          if (!menuEl.hasAttribute('md-position-mode')) {
34267            menuEl.setAttribute('md-position-mode', 'left bottom');
34268
34269            // Since we're in the compile function and actual `md-buttons` are not compiled yet,
34270            // we need to query for possible `md-buttons` as well.
34271            menuEl.querySelector('button, a, md-button').setAttribute('role', 'menuitem');
34272          }
34273          var contentEls = $mdUtil.nodesToArray(menuEl.querySelectorAll('md-menu-content'));
34274          angular.forEach(contentEls, function(contentEl) {
34275            contentEl.classList.add('md-menu-bar-menu');
34276            contentEl.classList.add('md-dense');
34277            if (!contentEl.hasAttribute('width')) {
34278              contentEl.setAttribute('width', 5);
34279            }
34280          });
34281        }
34282      });
34283
34284      // Mark the child menu items that they're inside a menu bar. This is necessary,
34285      // because mnMenuItem has special behaviour during compilation, depending on
34286      // whether it is inside a mdMenuBar. We can usually figure this out via the DOM,
vendor: 6,915 bytes, lines 34287-34529
34287      // however if a directive that uses documentFragment is applied to the child (e.g. ngRepeat),
34288      // the element won't have a parent and won't compile properly.
34289      templateEl.find('md-menu-item').addClass('md-in-menu-bar');
34290
34291      return function postLink(scope, el, attr, ctrl) {
34292        el.addClass('_md');     // private md component indicator for styling
34293        $mdTheming(scope, el);
34294        ctrl.init();
34295      };
34296    }
34297  };
34298
34299}
34300
34301})();
34302(function(){
34303"use strict";
34304
34305
34306angular
34307  .module('material.components.menuBar')
34308  .directive('mdMenuDivider', MenuDividerDirective);
34309
34310
34311function MenuDividerDirective() {
34312  return {
34313    restrict: 'E',
34314    compile: function(templateEl, templateAttrs) {
34315      if (!templateAttrs.role) {
34316        templateEl[0].setAttribute('role', 'separator');
34317      }
34318    }
34319  };
34320}
34321
34322})();
34323(function(){
34324"use strict";
34325
34326
34327MenuItemController.$inject = ["$scope", "$element", "$attrs"];
34328angular
34329  .module('material.components.menuBar')
34330  .controller('MenuItemController', MenuItemController);
34331
34332
34333/**
34334 * @ngInject
34335 */
34336function MenuItemController($scope, $element, $attrs) {
34337  this.$element = $element;
34338  this.$attrs = $attrs;
34339  this.$scope = $scope;
34340}
34341
34342MenuItemController.prototype.init = function(ngModel) {
34343  var $element = this.$element;
34344  var $attrs = this.$attrs;
34345
34346  this.ngModel = ngModel;
34347  if ($attrs.type == 'checkbox' || $attrs.type == 'radio') {
34348    this.mode  = $attrs.type;
34349    this.iconEl = $element[0].children[0];
34350    this.buttonEl = $element[0].children[1];
34351    if (ngModel) {
34352      // Clear ngAria set attributes
34353      this.initClickListeners();
34354    }
34355  }
34356};
34357
34358// ngAria auto sets attributes on a menu-item with a ngModel.
34359// We don't want this because our content (buttons) get the focus
34360// and set their own aria attributes appropritately. Having both
34361// breaks NVDA / JAWS. This undeoes ngAria's attrs.
34362MenuItemController.prototype.clearNgAria = function() {
34363  var el = this.$element[0];
34364  var clearAttrs = ['role', 'tabindex', 'aria-invalid', 'aria-checked'];
34365  angular.forEach(clearAttrs, function(attr) {
34366    el.removeAttribute(attr);
34367  });
34368};
34369
34370MenuItemController.prototype.initClickListeners = function() {
34371  var self = this;
34372  var ngModel = this.ngModel;
34373  var $scope = this.$scope;
34374  var $attrs = this.$attrs;
34375  var $element = this.$element;
34376  var mode = this.mode;
34377
34378  this.handleClick = angular.bind(this, this.handleClick);
34379
34380  var icon = this.iconEl;
34381  var button = angular.element(this.buttonEl);
34382  var handleClick = this.handleClick;
34383
34384  $attrs.$observe('disabled', setDisabled);
34385  setDisabled($attrs.disabled);
34386
34387  ngModel.$render = function render() {
34388    self.clearNgAria();
34389    if (isSelected()) {
34390      icon.style.display = '';
34391      button.attr('aria-checked', 'true');
34392    } else {
34393      icon.style.display = 'none';
34394      button.attr('aria-checked', 'false');
34395    }
34396  };
34397
34398  $scope.$$postDigest(ngModel.$render);
34399
34400  function isSelected() {
34401    if (mode == 'radio') {
34402      var val = $attrs.ngValue ? $scope.$eval($attrs.ngValue) : $attrs.value;
34403      return ngModel.$modelValue == val;
34404    } else {
34405      return ngModel.$modelValue;
34406    }
34407  }
34408
34409  function setDisabled(disabled) {
34410    if (disabled) {
34411      button.off('click', handleClick);
34412    } else {
34413      button.on('click', handleClick);
34414    }
34415  }
34416};
34417
34418MenuItemController.prototype.handleClick = function(e) {
34419  var mode = this.mode;
34420  var ngModel = this.ngModel;
34421  var $attrs = this.$attrs;
34422  var newVal;
34423  if (mode == 'checkbox') {
34424    newVal = !ngModel.$modelValue;
34425  } else if (mode == 'radio') {
34426    newVal = $attrs.ngValue ? this.$scope.$eval($attrs.ngValue) : $attrs.value;
34427  }
34428  ngModel.$setViewValue(newVal);
34429  ngModel.$render();
34430};
34431
34432})();
34433(function(){
34434"use strict";
34435
34436
34437MenuItemDirective.$inject = ["$mdUtil", "$mdConstant", "$$mdSvgRegistry"];
34438angular
34439  .module('material.components.menuBar')
34440  .directive('mdMenuItem', MenuItemDirective);
34441
34442 /* @ngInject */
34443function MenuItemDirective($mdUtil, $mdConstant, $$mdSvgRegistry) {
34444  return {
34445    controller: 'MenuItemController',
34446    require: ['mdMenuItem', '?ngModel'],
34447    priority: $mdConstant.BEFORE_NG_ARIA,
34448    compile: function(templateEl, templateAttrs) {
34449      var type = templateAttrs.type;
34450      var inMenuBarClass = 'md-in-menu-bar';
34451
34452      // Note: This allows us to show the `check` icon for the md-menu-bar items.
34453      // The `md-in-menu-bar` class is set by the mdMenuBar directive.
34454      if ((type == 'checkbox' || type == 'radio') && templateEl.hasClass(inMenuBarClass)) {
34455        var text = templateEl[0].textContent;
34456        var buttonEl = angular.element('<md-button type="button"></md-button>');
34457        var iconTemplate = '<md-icon md-svg-src="' + $$mdSvgRegistry.mdChecked + '"></md-icon>';
34458
34459        buttonEl.html(text);
34460        buttonEl.attr('tabindex', '0');
34461
34462        templateEl.html('');
34463        templateEl.append(angular.element(iconTemplate));
34464        templateEl.append(buttonEl);
34465        templateEl.addClass('md-indent').removeClass(inMenuBarClass);
34466
34467        setDefault('role', type == 'checkbox' ? 'menuitemcheckbox' : 'menuitemradio', buttonEl);
34468        moveAttrToButton('ng-disabled');
34469
34470      } else {
34471        setDefault('role', 'menuitem', templateEl[0].querySelector('md-button, button, a'));
34472      }
34473
34474
34475      return function(scope, el, attrs, ctrls) {
34476        var ctrl = ctrls[0];
34477        var ngModel = ctrls[1];
34478        ctrl.init(ngModel);
34479      };
34480
34481      function setDefault(attr, val, el) {
34482        el = el || templateEl;
34483        if (el instanceof angular.element) {
34484          el = el[0];
34485        }
34486        if (!el.hasAttribute(attr)) {
34487          el.setAttribute(attr, val);
34488        }
34489      }
34490
34491      function moveAttrToButton(attribute) {
34492        var attributes = $mdUtil.prefixer(attribute);
34493
34494        angular.forEach(attributes, function(attr) {
34495          if (templateEl[0].hasAttribute(attr)) {
34496            var val = templateEl[0].getAttribute(attr);
34497            buttonEl[0].setAttribute(attr, val);
34498            templateEl[0].removeAttribute(attr);
34499          }
34500        });
34501      }
34502    }
34503  };
34504}
34505
34506})();
34507(function(){
34508"use strict";
34509
34510/**
34511 * @ngdoc directive
34512 * @name mdProgressCircular
34513 * @module material.components.progressCircular
34514 * @restrict E
34515 *
34516 * @description
34517 * The circular progress directive is used to make loading content in your app as delightful and
34518 * painless as possible by minimizing the amount of visual change a user sees before they can view
34519 * and interact with content.
34520 *
34521 * For operations where the percentage of the operation completed can be determined, use a
34522 * determinate indicator. They give users a quick sense of how long an operation will take.
34523 *
34524 * For operations where the user is asked to wait a moment while something finishes up, and it’s
34525 * not necessary to expose what's happening behind the scenes and how long it will take, use an
34526 * indeterminate indicator.
34527 *
34528 * @param {string} md-mode Select from one of two modes: **'determinate'** and **'indeterminate'**.
34529 *
34530 * Note: if the `md-mode` value is set as undefined or specified as not 1 of the two (2) valid modes, then **'indeterminate'**
34531 * will be auto-applied as the mode.
34532 *
34533 * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute.
34534 * If `value=""` is also specified, however, then `md-mode="determinate"` would be auto-injected instead.
34535 * @param {number=} value In determinate mode, this number represents the percentage of the
34536 *     circular progress. Default: 0
34537 * @param {number=} md-diameter This specifies the diameter of the circular progress. The value
34538 * should be a pixel-size value (eg '100'). If this attribute is
34539 * not present then a default value of '50px' is assumed.
34540 *
34541 * @param {boolean=} ng-disabled Determines whether to disable the progress element.
34542 *
34543 * @usage
34544 * <hljs lang="html">
34545 * <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
34546 *
34547 * <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
34548 *
34549 * <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
34550 *
34551 * <md-progress-circular md-mode="indeterminate"></md-progress-circular>
34552 * </hljs>
34553 */
34554
34555MdProgressCircularDirective.$inject = ["$window", "$mdProgressCircular", "$mdTheming", "$mdUtil", "$interval", "$log"];
34556angular
34557  .module('material.components.progressCircular')
34558  .directive('mdProgressCircular', MdProgressCircularDirective);
34559
34560/* @ngInject */
34561function MdProgressCircularDirective($window, $mdProgressCircular, $mdTheming,
34562                                     $mdUtil, $interval, $log) {
34563
34564  // Note that this shouldn't use use $$rAF, because it can cause an infinite loop
34565  // in any tests that call $animate.flush.
34566  var rAF = $window.requestAnimationFrame ||
34567            $window.webkitRequestAnimationFrame ||
34568            angular.noop;
34569
34570  var cAF = $window.cancelAnimationFrame ||
34571            $window.webkitCancelAnimationFrame ||
34572            $window.webkitCancelRequestAnimationFrame ||
34573            angular.noop;
34574
34575  var MODE_DETERMINATE = 'determinate';
34576  var MODE_INDETERMINATE = 'indeterminate';
34577  var DISABLED_CLASS = '_md-progress-circular-disabled';
34578  var INDETERMINATE_CLASS = 'md-mode-indeterminate';
34579
34580  return {
34581    restrict: 'E',
34582    scope: {
34583      value: '@',
34584      mdDiameter: '@',
34585      mdMode: '@'
34586    },
34587    template:
34588      '<svg xmlns="http://www.w3.org/2000/svg">' +
34589        '<path fill="none"/>' +
34590      '</svg>',
34591    compile: function(element, attrs) {
34592      element.attr({
34593        'aria-valuemin': 0,
34594        'aria-valuemax': 100,
34595        'role': 'progressbar'
34596      });
34597
34598      if (angular.isUndefined(attrs.mdMode)) {
34599        var mode = attrs.hasOwnProperty('value') ? MODE_DETERMINATE : MODE_INDETERMINATE;
34600        attrs.$set('mdMode', mode);
34601      } else {
34602        attrs.$set('mdMode', attrs.mdMode.trim());
34603      }
34604
34605      return MdProgressCircularLink;
34606    }
34607  };
34608
34609  function MdProgressCircularLink(scope, element, attrs) {
34610    var node = element[0];
34611    var svg = angular.element(node.querySelector('svg'));
34612    var path = angular.element(node.querySelector('path'));
34613    var startIndeterminate = $mdProgressCircular.startIndeterminate;
34614    var endIndeterminate = $mdProgressCircular.endIndeterminate;
34615    var iterationCount = 0;
34616    var lastAnimationId = 0;
34617    var lastDrawFrame;
34618    var interval;
34619
34620    $mdTheming(element);
34621    element.toggleClass(DISABLED_CLASS, attrs.hasOwnProperty('disabled'));
34622
34623    // If the mode is indeterminate, it doesn't need to
34624    // wait for the next digest. It can start right away.
34625    if(scope.mdMode === MODE_INDETERMINATE){
34626      startIndeterminateAnimation();
34627    }
34628
34629    scope.$on('$destroy', function(){
34630      cleanupIndeterminateAnimation();
34631
34632      if (lastDrawFrame) {
34633        cAF(lastDrawFrame);
34634      }
34635    });
34636
34637    scope.$watchGroup(['value', 'mdMode', function() {
34638      var isDisabled = node.disabled;
34639
34640      // Sometimes the browser doesn't return a boolean, in
34641      // which case we should check whether the attribute is
34642      // present.
34643      if (isDisabled === true || isDisabled === false){
34644        return isDisabled;
34645      }
34646
34647      return angular.isDefined(element.attr('disabled'));
34648    }], function(newValues, oldValues) {
34649      var mode = newValues[1];
34650      var isDisabled = newValues[2];
34651      var wasDisabled = oldValues[2];
34652
34653      if (isDisabled !== wasDisabled) {
34654        element.toggleClass(DISABLED_CLASS, !!isDisabled);
34655      }
34656
34657      if (isDisabled) {
34658        cleanupIndeterminateAnimation();
34659      } else {
34660        if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
34661          mode = MODE_INDETERMINATE;
34662          attrs.$set('mdMode', mode);
34663        }
34664
34665        if (mode === MODE_INDETERMINATE) {
34666          startIndeterminateAnimation();
34667        } else {
34668          var newValue = clamp(newValues[0]);
34669
34670          cleanupIndeterminateAnimation();
34671
34672          element.attr('aria-valuenow', newValue);
34673          renderCircle(clamp(oldValues[0]), newValue);
34674        }
34675      }
34676
34677    });
34678
34679    // This is in a separate watch in order to avoid layout, unless
34680    // the value has actually changed.
34681    scope.$watch('mdDiameter', function(newValue) {
34682      var diameter = getSize(newValue);
34683      var strokeWidth = getStroke(diameter);
34684      var value = clamp(scope.value);
34685      var transformOrigin = (diameter / 2) + 'px';
34686      var dimensions = {
34687        width: diameter + 'px',
34688        height: diameter + 'px'
34689      };
34690
34691      // The viewBox has to be applied via setAttribute, because it is
34692      // case-sensitive. If jQuery is included in the page, `.attr` lowercases
34693      // all attribute names.
34694      svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);
34695
34696      // Usually viewBox sets the dimensions for the SVG, however that doesn't
34697      // seem to be the case on IE10.
34698      // Important! The transform origin has to be set from here and it has to
34699      // be in the format of "Ypx Ypx Ypx", otherwise the rotation wobbles in
34700      // IE and Edge, because they don't account for the stroke width when
34701      // rotating. Also "center" doesn't help in this case, it has to be a
34702      // precise value.
34703      svg
34704        .css(dimensions)
34705        .css('transform-origin', transformOrigin + ' ' + transformOrigin + ' ' + transformOrigin);
34706
34707      element.css(dimensions);
34708
34709      path.attr('stroke-width', strokeWidth);
34710      path.attr('stroke-linecap', 'square');
34711      if (scope.mdMode == MODE_INDETERMINATE) {
34712        path.attr('d', getSvgArc(diameter, strokeWidth, true));
34713        path.attr('stroke-dasharray', (diameter - strokeWidth) * $window.Math.PI * 0.75);
34714        path.attr('stroke-dashoffset', getDashLength(diameter, strokeWidth, 1, 75));
34715      } else {
34716        path.attr('d', getSvgArc(diameter, strokeWidth, false));
34717        path.attr('stroke-dasharray', (diameter - strokeWidth) * $window.Math.PI);
34718        path.attr('stroke-dashoffset', getDashLength(diameter, strokeWidth, 0, 100));
34719        renderCircle(value, value);
34720      }
34721
34722    });
34723
34724    function renderCircle(animateFrom, animateTo, easing, duration, iterationCount, maxValue) {
34725      var id = ++lastAnimationId;
34726      var startTime = $mdUtil.now();
34727      var changeInValue = animateTo - animateFrom;
34728      var diameter = getSize(scope.mdDiameter);
34729      var strokeWidth = getStroke(diameter);
34730      var ease = easing || $mdProgressCircular.easeFn;
34731      var animationDuration = duration || $mdProgressCircular.duration;
34732      var rotation = -90 * (iterationCount || 0);
34733      var dashLimit = maxValue || 100;
34734
34735      // No need to animate it if the values are the same
34736      if (animateTo === animateFrom) {
34737        renderFrame(animateTo);
34738      } else {
34739        lastDrawFrame = rAF(function animation() {
34740          var currentTime = $window.Math.max(0, $window.Math.min($mdUtil.now() - startTime, animationDuration));
34741
34742          renderFrame(ease(currentTime, animateFrom, changeInValue, animationDuration));
34743
vendor: 36,121 bytes, lines 34744-35883
34744          // Do not allow overlapping animations
34745          if (id === lastAnimationId && currentTime < animationDuration) {
34746            lastDrawFrame = rAF(animation);
34747          }
34748        });
34749      }
34750
34751      function renderFrame(value) {
34752        path.attr('stroke-dashoffset', getDashLength(diameter, strokeWidth, value, dashLimit));
34753        path.attr('transform','rotate(' + (rotation) + ' ' + diameter/2 + ' ' + diameter/2 + ')');
34754      }
34755    }
34756
34757    function animateIndeterminate() {
34758      renderCircle(
34759        startIndeterminate,
34760        endIndeterminate,
34761        $mdProgressCircular.easeFnIndeterminate,
34762        $mdProgressCircular.durationIndeterminate,
34763        iterationCount,
34764        75
34765      );
34766
34767      // The %4 technically isn't necessary, but it keeps the rotation
34768      // under 360, instead of becoming a crazy large number.
34769      iterationCount = ++iterationCount % 4;
34770
34771    }
34772
34773    function startIndeterminateAnimation() {
34774      if (!interval) {
34775        // Note that this interval isn't supposed to trigger a digest.
34776        interval = $interval(
34777          animateIndeterminate,
34778          $mdProgressCircular.durationIndeterminate,
34779          0,
34780          false
34781        );
34782
34783        animateIndeterminate();
34784
34785        element
34786          .addClass(INDETERMINATE_CLASS)
34787          .removeAttr('aria-valuenow');
34788      }
34789    }
34790
34791    function cleanupIndeterminateAnimation() {
34792      if (interval) {
34793        $interval.cancel(interval);
34794        interval = null;
34795        element.removeClass(INDETERMINATE_CLASS);
34796      }
34797    }
34798  }
34799
34800  /**
34801   * Returns SVG path data for progress circle
34802   * Syntax spec: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
34803   *
34804   * @param {number} diameter Diameter of the container.
34805   * @param {number} strokeWidth Stroke width to be used when drawing circle
34806   * @param {boolean} indeterminate Use if progress circle will be used for indeterminate
34807   *
34808   * @returns {string} String representation of an SVG arc.
34809   */
34810  function getSvgArc(diameter, strokeWidth, indeterminate) {
34811    var radius = diameter / 2;
34812    var offset = strokeWidth / 2;
34813    var start = radius + ',' + offset; // ie: (25, 2.5) or 12 o'clock
34814    var end = offset + ',' + radius;   // ie: (2.5, 25) or  9 o'clock
34815    var arcRadius = radius - offset;
34816    return 'M' + start
34817         + 'A' + arcRadius + ',' + arcRadius + ' 0 1 1 ' + end // 75% circle
34818         + (indeterminate ? '' : 'A' + arcRadius + ',' + arcRadius + ' 0 0 1 ' + start); // loop to start
34819  }
34820
34821  /**
34822   * Return stroke length for progress circle
34823   *
34824   * @param {number} diameter Diameter of the container.
34825   * @param {number} strokeWidth Stroke width to be used when drawing circle
34826   * @param {number} value Percentage of circle (between 0 and 100)
34827   * @param {number} limit Max percentage for circle
34828   *
34829   * @returns {number} Stroke length for progres circle
34830   */
34831  function getDashLength(diameter, strokeWidth, value, limit) {
34832    return (diameter - strokeWidth) * $window.Math.PI * ( (3 * (limit || 100) / 100) - (value/100) );
34833  }
34834
34835  /**
34836   * Limits a value between 0 and 100.
34837   */
34838  function clamp(value) {
34839    return $window.Math.max(0, $window.Math.min(value || 0, 100));
34840  }
34841
34842  /**
34843   * Determines the size of a progress circle, based on the provided
34844   * value in the following formats: `X`, `Ypx`, `Z%`.
34845   */
34846  function getSize(value) {
34847    var defaultValue = $mdProgressCircular.progressSize;
34848
34849    if (value) {
34850      var parsed = parseFloat(value);
34851
34852      if (value.lastIndexOf('%') === value.length - 1) {
34853        parsed = (parsed / 100) * defaultValue;
34854      }
34855
34856      return parsed;
34857    }
34858
34859    return defaultValue;
34860  }
34861
34862  /**
34863   * Determines the circle's stroke width, based on
34864   * the provided diameter.
34865   */
34866  function getStroke(diameter) {
34867    return $mdProgressCircular.strokeWidth / 100 * diameter;
34868  }
34869
34870}
34871
34872})();
34873(function(){
34874"use strict";
34875
34876/**
34877 * @ngdoc service
34878 * @name $mdProgressCircular
34879 * @module material.components.progressCircular
34880 *
34881 * @description
34882 * Allows the user to specify the default options for the `progressCircular` directive.
34883 *
34884 * @property {number} progressSize Diameter of the progress circle in pixels.
34885 * @property {number} strokeWidth Width of the circle's stroke as a percentage of the circle's size.
34886 * @property {number} duration Length of the circle animation in milliseconds.
34887 * @property {function} easeFn Default easing animation function.
34888 * @property {object} easingPresets Collection of pre-defined easing functions.
34889 *
34890 * @property {number} durationIndeterminate Duration of the indeterminate animation.
34891 * @property {number} startIndeterminate Indeterminate animation start point.
34892 * @property {number} endIndeterminate Indeterminate animation end point.
34893 * @property {function} easeFnIndeterminate Easing function to be used when animating
34894 * between the indeterminate values.
34895 *
34896 * @property {(function(object): object)} configure Used to modify the default options.
34897 *
34898 * @usage
34899 * <hljs lang="js">
34900 *   myAppModule.config(function($mdProgressCircularProvider) {
34901 *
34902 *     // Example of changing the default progress options.
34903 *     $mdProgressCircularProvider.configure({
34904 *       progressSize: 100,
34905 *       strokeWidth: 20,
34906 *       duration: 800
34907 *     });
34908 * });
34909 * </hljs>
34910 *
34911 */
34912
34913angular
34914  .module('material.components.progressCircular')
34915  .provider("$mdProgressCircular", MdProgressCircularProvider);
34916
34917function MdProgressCircularProvider() {
34918  var progressConfig = {
34919    progressSize: 50,
34920    strokeWidth: 10,
34921    duration: 100,
34922    easeFn: linearEase,
34923
34924    durationIndeterminate: 1333,
34925    startIndeterminate: 1,
34926    endIndeterminate: 149,
34927    easeFnIndeterminate: materialEase,
34928
34929    easingPresets: {
34930      linearEase: linearEase,
34931      materialEase: materialEase
34932    }
34933  };
34934
34935  return {
34936    configure: function(options) {
34937      progressConfig = angular.extend(progressConfig, options || {});
34938      return progressConfig;
34939    },
34940    $get: function() { return progressConfig; }
34941  };
34942
34943  function linearEase(t, b, c, d) {
34944    return c * t / d + b;
34945  }
34946
34947  function materialEase(t, b, c, d) {
34948    // via http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
34949    // with settings of [0, 0, 1, 1]
34950    var ts = (t /= d) * t;
34951    var tc = ts * t;
34952    return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
34953  }
34954}
34955
34956})();
34957(function(){
34958"use strict";
34959
34960/**
34961 * @ngdoc directive
34962 * @name mdTab
34963 * @module material.components.tabs
34964 *
34965 * @restrict E
34966 *
34967 * @description
34968 * The `<md-tab>` is a nested directive used within `<md-tabs>` to specify a tab with a **label** and optional *view content*.
34969 *
34970 * If the `label` attribute is not specified, then an optional `<md-tab-label>` tag can be used to specify more
34971 * complex tab header markup. If neither the **label** nor the **md-tab-label** are specified, then the nested
34972 * markup of the `<md-tab>` is used as the tab header markup.
34973 *
34974 * Please note that if you use `<md-tab-label>`, your content **MUST** be wrapped in the `<md-tab-body>` tag.  This
34975 * is to define a clear separation between the tab content and the tab label.
34976 *
34977 * This container is used by the TabsController to show/hide the active tab's content view. This synchronization is
34978 * automatically managed by the internal TabsController whenever the tab selection changes. Selection changes can
34979 * be initiated via data binding changes, programmatic invocation, or user gestures.
34980 *
34981 * @param {string=} label Optional attribute to specify a simple string as the tab label
34982 * @param {boolean=} ng-disabled If present and expression evaluates to truthy, disabled tab selection.
34983 * @param {expression=} md-on-deselect Expression to be evaluated after the tab has been de-selected.
34984 * @param {expression=} md-on-select Expression to be evaluated after the tab has been selected.
34985 * @param {boolean=} md-active When true, sets the active tab.  Note: There can only be one active tab at a time.
34986 *
34987 *
34988 * @usage
34989 *
34990 * <hljs lang="html">
34991 * <md-tab label="" ng-disabled md-on-select="" md-on-deselect="" >
34992 *   <h3>My Tab content</h3>
34993 * </md-tab>
34994 *
34995 * <md-tab >
34996 *   <md-tab-label>
34997 *     <h3>My Tab content</h3>
34998 *   </md-tab-label>
34999 *   <md-tab-body>
35000 *     <p>
35001 *       Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
35002 *       totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
35003 *       dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
35004 *       sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
35005 *     </p>
35006 *   </md-tab-body>
35007 * </md-tab>
35008 * </hljs>
35009 *
35010 */
35011angular
35012    .module('material.components.tabs')
35013    .directive('mdTab', MdTab);
35014
35015function MdTab () {
35016  return {
35017    require:  '^?mdTabs',
35018    terminal: true,
35019    compile:  function (element, attr) {
35020      var label = firstChild(element, 'md-tab-label'),
35021          body  = firstChild(element, 'md-tab-body');
35022
35023      if (label.length === 0) {
35024        label = angular.element('<md-tab-label></md-tab-label>');
35025        if (attr.label) label.text(attr.label);
35026        else label.append(element.contents());
35027
35028        if (body.length === 0) {
35029          var contents = element.contents().detach();
35030          body         = angular.element('<md-tab-body></md-tab-body>');
35031          body.append(contents);
35032        }
35033      }
35034
35035      element.append(label);
35036      if (body.html()) element.append(body);
35037
35038      return postLink;
35039    },
35040    scope:    {
35041      active:   '=?mdActive',
35042      disabled: '=?ngDisabled',
35043      select:   '&?mdOnSelect',
35044      deselect: '&?mdOnDeselect'
35045    }
35046  };
35047
35048  function postLink (scope, element, attr, ctrl) {
35049    if (!ctrl) return;
35050    var index = ctrl.getTabElementIndex(element),
35051        body  = firstChild(element, 'md-tab-body').remove(),
35052        label = firstChild(element, 'md-tab-label').remove(),
35053        data  = ctrl.insertTab({
35054          scope:    scope,
35055          parent:   scope.$parent,
35056          index:    index,
35057          element:  element,
35058          template: body.html(),
35059          label:    label.html()
35060        }, index);
35061
35062    scope.select   = scope.select || angular.noop;
35063    scope.deselect = scope.deselect || angular.noop;
35064
35065    scope.$watch('active', function (active) { if (active) ctrl.select(data.getIndex(), true); });
35066    scope.$watch('disabled', function () { ctrl.refreshIndex(); });
35067    scope.$watch(
35068        function () {
35069          return ctrl.getTabElementIndex(element);
35070        },
35071        function (newIndex) {
35072          data.index = newIndex;
35073          ctrl.updateTabOrder();
35074        }
35075    );
35076    scope.$on('$destroy', function () { ctrl.removeTab(data); });
35077  }
35078
35079  function firstChild (element, tagName) {
35080    var children = element[0].children;
35081    for (var i = 0, len = children.length; i < len; i++) {
35082      var child = children[i];
35083      if (child.tagName === tagName.toUpperCase()) return angular.element(child);
35084    }
35085    return angular.element();
35086  }
35087}
35088
35089})();
35090(function(){
35091"use strict";
35092
35093angular
35094    .module('material.components.tabs')
35095    .directive('mdTabItem', MdTabItem);
35096
35097function MdTabItem () {
35098  return {
35099    require: '^?mdTabs',
35100    link:    function link (scope, element, attr, ctrl) {
35101      if (!ctrl) return;
35102      ctrl.attachRipple(scope, element);
35103    }
35104  };
35105}
35106
35107})();
35108(function(){
35109"use strict";
35110
35111angular
35112    .module('material.components.tabs')
35113    .directive('mdTabLabel', MdTabLabel);
35114
35115function MdTabLabel () {
35116  return { terminal: true };
35117}
35118
35119
35120})();
35121(function(){
35122"use strict";
35123
35124
35125MdTabScroll.$inject = ["$parse"];angular.module('material.components.tabs')
35126    .directive('mdTabScroll', MdTabScroll);
35127
35128function MdTabScroll ($parse) {
35129  return {
35130    restrict: 'A',
35131    compile: function ($element, attr) {
35132      var fn = $parse(attr.mdTabScroll, null, true);
35133      return function ngEventHandler (scope, element) {
35134        element.on('mousewheel', function (event) {
35135          scope.$apply(function () { fn(scope, { $event: event }); });
35136        });
35137      };
35138    }
35139  };
35140}
35141
35142})();
35143(function(){
35144"use strict";
35145
35146
35147MdTabsController.$inject = ["$scope", "$element", "$window", "$mdConstant", "$mdTabInkRipple", "$mdUtil", "$animateCss", "$attrs", "$compile", "$mdTheming", "$mdInteraction", "MdTabsPaginationService"];angular
35148    .module('material.components.tabs')
35149    .controller('MdTabsController', MdTabsController);
35150
35151/**
35152 * @ngInject
35153 */
35154function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipple, $mdUtil,
35155                           $animateCss, $attrs, $compile, $mdTheming, $mdInteraction,
35156                           MdTabsPaginationService) {
35157  // define private properties
35158  var ctrl      = this,
35159      locked    = false,
35160      elements  = getElements(),
35161      queue     = [],
35162      destroyed = false,
35163      loaded    = false;
35164
35165
35166  // Define public methods
35167  ctrl.$onInit            = $onInit;
35168  ctrl.updatePagination   = $mdUtil.debounce(updatePagination, 100);
35169  ctrl.redirectFocus      = redirectFocus;
35170  ctrl.attachRipple       = attachRipple;
35171  ctrl.insertTab          = insertTab;
35172  ctrl.removeTab          = removeTab;
35173  ctrl.select             = select;
35174  ctrl.scroll             = scroll;
35175  ctrl.nextPage           = nextPage;
35176  ctrl.previousPage       = previousPage;
35177  ctrl.keydown            = keydown;
35178  ctrl.canPageForward     = canPageForward;
35179  ctrl.canPageBack        = canPageBack;
35180  ctrl.refreshIndex       = refreshIndex;
35181  ctrl.incrementIndex     = incrementIndex;
35182  ctrl.getTabElementIndex = getTabElementIndex;
35183  ctrl.updateInkBarStyles = $mdUtil.debounce(updateInkBarStyles, 100);
35184  ctrl.updateTabOrder     = $mdUtil.debounce(updateTabOrder, 100);
35185  ctrl.getFocusedTabId    = getFocusedTabId;
35186
35187  // For AngularJS 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
35188  // manually call the $onInit hook.
35189  if (angular.version.major === 1 && angular.version.minor <= 4) {
35190    this.$onInit();
35191  }
35192
35193  /**
35194   * AngularJS Lifecycle hook for newer AngularJS versions.
35195   * Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
35196   */
35197  function $onInit() {
35198    // Define one-way bindings
35199    defineOneWayBinding('stretchTabs', handleStretchTabs);
35200
35201    // Define public properties with change handlers
35202    defineProperty('focusIndex', handleFocusIndexChange, ctrl.selectedIndex || 0);
35203    defineProperty('offsetLeft', handleOffsetChange, 0);
35204    defineProperty('hasContent', handleHasContent, false);
35205    defineProperty('maxTabWidth', handleMaxTabWidth, getMaxTabWidth());
35206    defineProperty('shouldPaginate', handleShouldPaginate, false);
35207
35208    // Define boolean attributes
35209    defineBooleanAttribute('noInkBar', handleInkBar);
35210    defineBooleanAttribute('dynamicHeight', handleDynamicHeight);
35211    defineBooleanAttribute('noPagination');
35212    defineBooleanAttribute('swipeContent');
35213    defineBooleanAttribute('noDisconnect');
35214    defineBooleanAttribute('autoselect');
35215    defineBooleanAttribute('noSelectClick');
35216    defineBooleanAttribute('centerTabs', handleCenterTabs, false);
35217    defineBooleanAttribute('enableDisconnect');
35218
35219    // Define public properties
35220    ctrl.scope             = $scope;
35221    ctrl.parent            = $scope.$parent;
35222    ctrl.tabs              = [];
35223    ctrl.lastSelectedIndex = null;
35224    ctrl.hasFocus          = false;
35225    ctrl.styleTabItemFocus = false;
35226    ctrl.shouldCenterTabs  = shouldCenterTabs();
35227    ctrl.tabContentPrefix  = 'tab-content-';
35228
35229    // Setup the tabs controller after all bindings are available.
35230    setupTabsController();
35231  }
35232
35233  /**
35234   * Perform setup for the controller, setup events and watcher(s)
35235   */
35236  function setupTabsController () {
35237    ctrl.selectedIndex = ctrl.selectedIndex || 0;
35238    compileTemplate();
35239    configureWatchers();
35240    bindEvents();
35241    $mdTheming($element);
35242    $mdUtil.nextTick(function () {
35243      // Note that the element references need to be updated, because certain "browsers"
35244      // (IE/Edge) lose them and start throwing "Invalid calling object" errors, when we
35245      // compile the element contents down in `compileElement`.
35246      elements = getElements();
35247      updateHeightFromContent();
35248      adjustOffset();
35249      updateInkBarStyles();
35250      ctrl.tabs[ ctrl.selectedIndex ] && ctrl.tabs[ ctrl.selectedIndex ].scope.select();
35251      loaded = true;
35252      updatePagination();
35253    });
35254  }
35255
35256  /**
35257   * Compiles the template provided by the user.  This is passed as an attribute from the tabs
35258   * directive's template function.
35259   */
35260  function compileTemplate () {
35261    var template = $attrs.$mdTabsTemplate,
35262        element  = angular.element($element[0].querySelector('md-tab-data'));
35263
35264    element.html(template);
35265    $compile(element.contents())(ctrl.parent);
35266    delete $attrs.$mdTabsTemplate;
35267  }
35268
35269  /**
35270   * Binds events used by the tabs component.
35271   */
35272  function bindEvents () {
35273    angular.element($window).on('resize', handleWindowResize);
35274    $scope.$on('$destroy', cleanup);
35275  }
35276
35277  /**
35278   * Configure watcher(s) used by Tabs
35279   */
35280  function configureWatchers () {
35281    $scope.$watch('$mdTabsCtrl.selectedIndex', handleSelectedIndexChange);
35282  }
35283
35284  /**
35285   * Creates a one-way binding manually rather than relying on AngularJS's isolated scope
35286   * @param key
35287   * @param handler
35288   */
35289  function defineOneWayBinding (key, handler) {
35290    var attr = $attrs.$normalize('md-' + key);
35291    if (handler) defineProperty(key, handler);
35292    $attrs.$observe(attr, function (newValue) { ctrl[ key ] = newValue; });
35293  }
35294
35295  /**
35296   * Defines boolean attributes with default value set to true.  (ie. md-stretch-tabs with no value
35297   * will be treated as being truthy)
35298   * @param key
35299   * @param handler
35300   */
35301  function defineBooleanAttribute (key, handler) {
35302    var attr = $attrs.$normalize('md-' + key);
35303    if (handler) defineProperty(key, handler);
35304    if ($attrs.hasOwnProperty(attr)) updateValue($attrs[attr]);
35305    $attrs.$observe(attr, updateValue);
35306    function updateValue (newValue) {
35307      ctrl[ key ] = newValue !== 'false';
35308    }
35309  }
35310
35311  /**
35312   * Remove any events defined by this controller
35313   */
35314  function cleanup () {
35315    destroyed = true;
35316    angular.element($window).off('resize', handleWindowResize);
35317  }
35318
35319  // Change handlers
35320
35321  /**
35322   * Toggles stretch tabs class and updates inkbar when tab stretching changes
35323   * @param stretchTabs
35324   */
35325  function handleStretchTabs (stretchTabs) {
35326    var elements = getElements();
35327    angular.element(elements.wrapper).toggleClass('md-stretch-tabs', shouldStretchTabs());
35328    updateInkBarStyles();
35329  }
35330
35331  function handleCenterTabs (newValue) {
35332    ctrl.shouldCenterTabs = shouldCenterTabs();
35333  }
35334
35335  function handleMaxTabWidth (newWidth, oldWidth) {
35336    if (newWidth !== oldWidth) {
35337      var elements = getElements();
35338
35339      // Set the max width for the real tabs
35340      angular.forEach(elements.tabs, function(tab) {
35341        tab.style.maxWidth = newWidth + 'px';
35342      });
35343
35344      // Set the max width for the dummy tabs too
35345      angular.forEach(elements.dummies, function(tab) {
35346        tab.style.maxWidth = newWidth + 'px';
35347      });
35348
35349      $mdUtil.nextTick(ctrl.updateInkBarStyles);
35350    }
35351  }
35352
35353  function handleShouldPaginate (newValue, oldValue) {
35354    if (newValue !== oldValue) {
35355      ctrl.maxTabWidth      = getMaxTabWidth();
35356      ctrl.shouldCenterTabs = shouldCenterTabs();
35357      $mdUtil.nextTick(function () {
35358        ctrl.maxTabWidth = getMaxTabWidth();
35359        adjustOffset(ctrl.selectedIndex);
35360      });
35361    }
35362  }
35363
35364  /**
35365   * Add/remove the `md-no-tab-content` class depending on `ctrl.hasContent`
35366   * @param hasContent
35367   */
35368  function handleHasContent (hasContent) {
35369    $element[ hasContent ? 'removeClass' : 'addClass' ]('md-no-tab-content');
35370  }
35371
35372  /**
35373   * Apply ctrl.offsetLeft to the paging element when it changes
35374   * @param left
35375   */
35376  function handleOffsetChange (left) {
35377    var elements = getElements();
35378    var newValue = ((ctrl.shouldCenterTabs || isRtl() ? '' : '-') + left + 'px');
35379
35380    // Fix double-negative which can happen with RTL support
35381    newValue = newValue.replace('--', '');
35382
35383    angular.element(elements.paging).css($mdConstant.CSS.TRANSFORM, 'translate3d(' + newValue + ', 0, 0)');
35384    $scope.$broadcast('$mdTabsPaginationChanged');
35385  }
35386
35387  /**
35388   * Update the UI whenever `ctrl.focusIndex` is updated
35389   * @param newIndex
35390   * @param oldIndex
35391   */
35392  function handleFocusIndexChange (newIndex, oldIndex) {
35393    if (newIndex === oldIndex) return;
35394    if (!getElements().tabs[ newIndex ]) return;
35395    adjustOffset();
35396    redirectFocus();
35397  }
35398
35399  /**
35400   * Update the UI whenever the selected index changes. Calls user-defined select/deselect methods.
35401   * @param newValue
35402   * @param oldValue
35403   */
35404  function handleSelectedIndexChange (newValue, oldValue) {
35405    if (newValue === oldValue) return;
35406
35407    ctrl.selectedIndex     = getNearestSafeIndex(newValue);
35408    ctrl.lastSelectedIndex = oldValue;
35409    ctrl.updateInkBarStyles();
35410    updateHeightFromContent();
35411    adjustOffset(newValue);
35412    $scope.$broadcast('$mdTabsChanged');
35413    ctrl.tabs[ oldValue ] && ctrl.tabs[ oldValue ].scope.deselect();
35414    ctrl.tabs[ newValue ] && ctrl.tabs[ newValue ].scope.select();
35415  }
35416
35417  function getTabElementIndex(tabEl){
35418    var tabs = $element[0].getElementsByTagName('md-tab');
35419    return Array.prototype.indexOf.call(tabs, tabEl[0]);
35420  }
35421
35422  /**
35423   * Queues up a call to `handleWindowResize` when a resize occurs while the tabs component is
35424   * hidden.
35425   */
35426  function handleResizeWhenVisible () {
35427    // if there is already a watcher waiting for resize, do nothing
35428    if (handleResizeWhenVisible.watcher) return;
35429    // otherwise, we will abuse the $watch function to check for visible
35430    handleResizeWhenVisible.watcher = $scope.$watch(function () {
35431      // since we are checking for DOM size, we use $mdUtil.nextTick() to wait for after the DOM updates
35432      $mdUtil.nextTick(function () {
35433        // if the watcher has already run (ie. multiple digests in one cycle), do nothing
35434        if (!handleResizeWhenVisible.watcher) return;
35435
35436        if ($element.prop('offsetParent')) {
35437          handleResizeWhenVisible.watcher();
35438          handleResizeWhenVisible.watcher = null;
35439
35440          handleWindowResize();
35441        }
35442      }, false);
35443    });
35444  }
35445
35446  // Event handlers / actions
35447
35448  /**
35449   * Handle user keyboard interactions
35450   * @param event
35451   */
35452  function keydown (event) {
35453    switch (event.keyCode) {
35454      case $mdConstant.KEY_CODE.LEFT_ARROW:
35455        event.preventDefault();
35456        incrementIndex(-1, true);
35457        break;
35458      case $mdConstant.KEY_CODE.RIGHT_ARROW:
35459        event.preventDefault();
35460        incrementIndex(1, true);
35461        break;
35462      case $mdConstant.KEY_CODE.SPACE:
35463      case $mdConstant.KEY_CODE.ENTER:
35464        event.preventDefault();
35465        if (!locked) select(ctrl.focusIndex);
35466        break;
35467      case $mdConstant.KEY_CODE.TAB:
35468        // On tabbing out of the tablist, reset hasFocus to reset ng-focused and
35469        // its md-focused class if the focused tab is not the active tab.
35470        if (ctrl.focusIndex !== ctrl.selectedIndex) {
35471          ctrl.focusIndex = ctrl.selectedIndex;
35472        }
35473        break;
35474    }
35475  }
35476
35477  /**
35478   * Update the selected index. Triggers a click event on the original `md-tab` element in order
35479   * to fire user-added click events if canSkipClick or `md-no-select-click` are false.
35480   * @param index
35481   * @param canSkipClick Optionally allow not firing the click event if `md-no-select-click` is also true.
35482   */
35483  function select (index, canSkipClick) {
35484    if (!locked) ctrl.focusIndex = ctrl.selectedIndex = index;
35485    // skip the click event if noSelectClick is enabled
35486    if (canSkipClick && ctrl.noSelectClick) return;
35487    // nextTick is required to prevent errors in user-defined click events
35488    $mdUtil.nextTick(function () {
35489      ctrl.tabs[ index ].element.triggerHandler('click');
35490    }, false);
35491  }
35492
35493  /**
35494   * When pagination is on, this makes sure the selected index is in view.
35495   * @param event
35496   */
35497  function scroll (event) {
35498    if (!ctrl.shouldPaginate) return;
35499    event.preventDefault();
35500    ctrl.offsetLeft = fixOffset(ctrl.offsetLeft - event.wheelDelta);
35501  }
35502
35503  /**
35504   * Slides the tabs over approximately one page forward.
35505   */
35506  function nextPage () {
35507    if (!ctrl.canPageForward()) { return }
35508
35509    var newOffset = MdTabsPaginationService.increasePageOffset(getElements(), ctrl.offsetLeft);
35510
35511    ctrl.offsetLeft = fixOffset(newOffset);
35512  }
35513
35514  /**
35515   * Slides the tabs over approximately one page backward.
35516   */
35517  function previousPage () {
35518    if (!ctrl.canPageBack()) { return }
35519
35520    var newOffset = MdTabsPaginationService.decreasePageOffset(getElements(), ctrl.offsetLeft);
35521
35522    // Set the new offset
35523    ctrl.offsetLeft = fixOffset(newOffset);
35524  }
35525
35526  /**
35527   * Update size calculations when the window is resized.
35528   */
35529  function handleWindowResize () {
35530    ctrl.lastSelectedIndex = ctrl.selectedIndex;
35531    ctrl.offsetLeft        = fixOffset(ctrl.offsetLeft);
35532
35533    $mdUtil.nextTick(function () {
35534      ctrl.updateInkBarStyles();
35535      updatePagination();
35536    });
35537  }
35538
35539  function handleInkBar (hide) {
35540    angular.element(getElements().inkBar).toggleClass('ng-hide', hide);
35541  }
35542
35543  /**
35544   * Toggle dynamic height class when value changes
35545   * @param value
35546   */
35547  function handleDynamicHeight (value) {
35548    $element.toggleClass('md-dynamic-height', value);
35549  }
35550
35551  /**
35552   * Remove a tab from the data and select the nearest valid tab.
35553   * @param tabData
35554   */
35555  function removeTab (tabData) {
35556    if (destroyed) return;
35557    var selectedIndex = ctrl.selectedIndex,
35558        tab           = ctrl.tabs.splice(tabData.getIndex(), 1)[ 0 ];
35559    refreshIndex();
35560    // when removing a tab, if the selected index did not change, we have to manually trigger the
35561    //   tab select/deselect events
35562    if (ctrl.selectedIndex === selectedIndex) {
35563      tab.scope.deselect();
35564      ctrl.tabs[ ctrl.selectedIndex ] && ctrl.tabs[ ctrl.selectedIndex ].scope.select();
35565    }
35566    $mdUtil.nextTick(function () {
35567      updatePagination();
35568      ctrl.offsetLeft = fixOffset(ctrl.offsetLeft);
35569    });
35570  }
35571
35572  /**
35573   * Create an entry in the tabs array for a new tab at the specified index.
35574   * @param tabData
35575   * @param index
35576   * @returns {*}
35577   */
35578  function insertTab (tabData, index) {
35579    var hasLoaded = loaded;
35580    var proto     = {
35581          getIndex:     function () { return ctrl.tabs.indexOf(tab); },
35582          isActive:     function () { return this.getIndex() === ctrl.selectedIndex; },
35583          isLeft:       function () { return this.getIndex() < ctrl.selectedIndex; },
35584          isRight:      function () { return this.getIndex() > ctrl.selectedIndex; },
35585          shouldRender: function () { return !ctrl.noDisconnect || this.isActive(); },
35586          hasFocus:     function () {
35587            return ctrl.styleTabItemFocus
35588                && ctrl.hasFocus && this.getIndex() === ctrl.focusIndex;
35589          },
35590          id:           $mdUtil.nextUid(),
35591          hasContent: !!(tabData.template && tabData.template.trim())
35592        },
35593        tab       = angular.extend(proto, tabData);
35594    if (angular.isDefined(index)) {
35595      ctrl.tabs.splice(index, 0, tab);
35596    } else {
35597      ctrl.tabs.push(tab);
35598    }
35599
35600    processQueue();
35601    updateHasContent();
35602    $mdUtil.nextTick(function () {
35603      updatePagination();
35604      setAriaControls(tab);
35605
35606      // if autoselect is enabled, select the newly added tab
35607      if (hasLoaded && ctrl.autoselect) $mdUtil.nextTick(function () {
35608        $mdUtil.nextTick(function () { select(ctrl.tabs.indexOf(tab)); });
35609      });
35610    });
35611    return tab;
35612  }
35613
35614  // Getter methods
35615
35616  /**
35617   * Gathers references to all of the DOM elements used by this controller.
35618   * @returns {{}}
35619   */
35620  function getElements () {
35621    var elements = {};
35622    var node = $element[0];
35623
35624    // gather tab bar elements
35625    elements.wrapper = node.querySelector('md-tabs-wrapper');
35626    elements.canvas  = elements.wrapper.querySelector('md-tabs-canvas');
35627    elements.paging  = elements.canvas.querySelector('md-pagination-wrapper');
35628    elements.inkBar  = elements.paging.querySelector('md-ink-bar');
35629    elements.nextButton = node.querySelector('md-next-button');
35630    elements.prevButton = node.querySelector('md-prev-button');
35631
35632    elements.contents = node.querySelectorAll('md-tabs-content-wrapper > md-tab-content');
35633    elements.tabs    = elements.paging.querySelectorAll('md-tab-item');
35634    elements.dummies = elements.canvas.querySelectorAll('md-dummy-tab');
35635
35636    return elements;
35637  }
35638
35639  /**
35640   * Determines whether or not the left pagination arrow should be enabled.
35641   * @returns {boolean}
35642   */
35643  function canPageBack () {
35644    // This works for both LTR and RTL
35645    return ctrl.offsetLeft > 0;
35646  }
35647
35648  /**
35649   * Determines whether or not the right pagination arrow should be enabled.
35650   * @returns {*|boolean}
35651   */
35652  function canPageForward () {
35653    var elements = getElements();
35654    var lastTab = elements.tabs[ elements.tabs.length - 1 ];
35655
35656    if (isRtl()) {
35657      return ctrl.offsetLeft < elements.paging.offsetWidth - elements.canvas.offsetWidth;
35658    }
35659
35660    return lastTab && lastTab.offsetLeft + lastTab.offsetWidth > elements.canvas.clientWidth +
35661        ctrl.offsetLeft;
35662  }
35663
35664  /**
35665   * Returns currently focused tab item's element ID
35666   */
35667  function getFocusedTabId() {
35668    var focusedTab = ctrl.tabs[ctrl.focusIndex];
35669    if (!focusedTab || !focusedTab.id) {
35670      return null;
35671    }
35672    return 'tab-item-' + focusedTab.id;
35673  }
35674
35675  /**
35676   * Determines if the UI should stretch the tabs to fill the available space.
35677   * @returns {*}
35678   */
35679  function shouldStretchTabs () {
35680    switch (ctrl.stretchTabs) {
35681      case 'always':
35682        return true;
35683      case 'never':
35684        return false;
35685      default:
35686        return !ctrl.shouldPaginate
35687            && $window.matchMedia('(max-width: 600px)').matches;
35688    }
35689  }
35690
35691  /**
35692   * Determines if the tabs should appear centered.
35693   * @returns {string|boolean}
35694   */
35695  function shouldCenterTabs () {
35696    return ctrl.centerTabs && !ctrl.shouldPaginate;
35697  }
35698
35699  /**
35700   * Determines if pagination is necessary to display the tabs within the available space.
35701   * @returns {boolean}
35702   */
35703  function shouldPaginate () {
35704    if (ctrl.noPagination || !loaded) return false;
35705    var canvasWidth = $element.prop('clientWidth');
35706
35707    angular.forEach(getElements().tabs, function (tab) {
35708      canvasWidth -= tab.offsetWidth;
35709    });
35710
35711    return canvasWidth < 0;
35712  }
35713
35714  /**
35715   * Finds the nearest tab index that is available.  This is primarily used for when the active
35716   * tab is removed.
35717   * @param newIndex
35718   * @returns {*}
35719   */
35720  function getNearestSafeIndex (newIndex) {
35721    if (newIndex === -1) return -1;
35722    var maxOffset = Math.max(ctrl.tabs.length - newIndex, newIndex),
35723        i, tab;
35724    for (i = 0; i <= maxOffset; i++) {
35725      tab = ctrl.tabs[ newIndex + i ];
35726      if (tab && (tab.scope.disabled !== true)) return tab.getIndex();
35727      tab = ctrl.tabs[ newIndex - i ];
35728      if (tab && (tab.scope.disabled !== true)) return tab.getIndex();
35729    }
35730    return newIndex;
35731  }
35732
35733  // Utility methods
35734
35735  /**
35736   * Defines a property using a getter and setter in order to trigger a change handler without
35737   * using `$watch` to observe changes.
35738   * @param key
35739   * @param handler
35740   * @param value
35741   */
35742  function defineProperty (key, handler, value) {
35743    Object.defineProperty(ctrl, key, {
35744      get: function () { return value; },
35745      set: function (newValue) {
35746        var oldValue = value;
35747        value        = newValue;
35748        handler && handler(newValue, oldValue);
35749      }
35750    });
35751  }
35752
35753  /**
35754   * Updates whether or not pagination should be displayed.
35755   */
35756  function updatePagination () {
35757    ctrl.maxTabWidth = getMaxTabWidth();
35758    ctrl.shouldPaginate = shouldPaginate();
35759  }
35760
35761  /**
35762   * Calculates the width of the pagination wrapper by summing the widths of the dummy tabs.
35763   * @returns {number}
35764   */
35765  function calcPagingWidth () {
35766    return calcTabsWidth(getElements().tabs);
35767  }
35768
35769  function calcTabsWidth(tabs) {
35770    var width = 0;
35771
35772    angular.forEach(tabs, function (tab) {
35773      //-- Uses the larger value between `getBoundingClientRect().width` and `offsetWidth`.  This
35774      //   prevents `offsetWidth` value from being rounded down and causing wrapping issues, but
35775      //   also handles scenarios where `getBoundingClientRect()` is inaccurate (ie. tabs inside
35776      //   of a dialog)
35777      width += Math.max(tab.offsetWidth, tab.getBoundingClientRect().width);
35778    });
35779
35780    return Math.ceil(width);
35781  }
35782
35783  function getMaxTabWidth () {
35784    var elements = getElements(),
35785        containerWidth = elements.canvas.clientWidth,
35786
35787        // See https://material.google.com/components/tabs.html#tabs-specs
35788        specMax = 264;
35789
35790    // Do the spec maximum, or the canvas width; whichever is *smaller* (tabs larger than the canvas
35791    // width can break the pagination) but not less than 0
35792    return Math.max(0, Math.min(containerWidth - 1, specMax));
35793  }
35794
35795  function getMinTabWidth() {
35796    var elements = getElements(),
35797        containerWidth = elements.canvas.clientWidth,
35798        xsBreakpoint = 600,
35799
35800        // See https://material.google.com/components/tabs.html#tabs-specs
35801        specMin = containerWidth > xsBreakpoint ? 160 : 72;
35802
35803    // Do the spec minimum, or the canvas width; whichever is *smaller* (tabs larger than the canvas
35804    // width can break the pagination) but not less than 0
35805    return Math.max(0, Math.min(containerWidth - 1, specMin));
35806  }
35807
35808  /**
35809   * Re-orders the tabs and updates the selected and focus indexes to their new positions.
35810   * This is triggered by `tabDirective.js` when the user's tabs have been re-ordered.
35811   */
35812  function updateTabOrder () {
35813    var selectedItem   = ctrl.tabs[ ctrl.selectedIndex ],
35814        focusItem      = ctrl.tabs[ ctrl.focusIndex ];
35815    ctrl.tabs          = ctrl.tabs.sort(function (a, b) {
35816      return a.index - b.index;
35817    });
35818    ctrl.selectedIndex = ctrl.tabs.indexOf(selectedItem);
35819    ctrl.focusIndex    = ctrl.tabs.indexOf(focusItem);
35820  }
35821
35822  /**
35823   * This moves the selected or focus index left or right.  This is used by the keydown handler.
35824   * @param inc
35825   */
35826  function incrementIndex (inc, focus) {
35827    var newIndex,
35828        key   = focus ? 'focusIndex' : 'selectedIndex',
35829        index = ctrl[ key ];
35830    for (newIndex = index + inc;
35831         ctrl.tabs[ newIndex ] && ctrl.tabs[ newIndex ].scope.disabled;
35832         newIndex += inc) {}
35833
35834    newIndex = (index + inc + ctrl.tabs.length) % ctrl.tabs.length;
35835
35836    if (ctrl.tabs[ newIndex ]) {
35837      ctrl[ key ] = newIndex;
35838    }
35839  }
35840
35841  /**
35842   * This is used to forward focus to tab container elements.  This method is necessary to avoid
35843   * animation issues when attempting to focus an item that is out of view.
35844   */
35845  function redirectFocus () {
35846    ctrl.styleTabItemFocus = ($mdInteraction.getLastInteractionType() === 'keyboard');
35847    getElements().tabs[ ctrl.focusIndex ].focus();
35848  }
35849
35850  /**
35851   * Forces the pagination to move the focused tab into view.
35852   */
35853  function adjustOffset (index) {
35854    var elements = getElements();
35855
35856    if (!angular.isNumber(index)) index = ctrl.focusIndex;
35857    if (!elements.tabs[ index ]) return;
35858    if (ctrl.shouldCenterTabs) return;
35859    var tab         = elements.tabs[ index ],
35860        left        = tab.offsetLeft,
35861        right       = tab.offsetWidth + left,
35862        extraOffset = 32;
35863
35864    // If we are selecting the first tab (in LTR and RTL), always set the offset to 0
35865    if (index == 0) {
35866      ctrl.offsetLeft = 0;
35867      return;
35868    }
35869
35870    if (isRtl()) {
35871      var tabWidthsBefore = calcTabsWidth(Array.prototype.slice.call(elements.tabs, 0, index));
35872      var tabWidthsIncluding = calcTabsWidth(Array.prototype.slice.call(elements.tabs, 0, index + 1));
35873
35874      ctrl.offsetLeft = Math.min(ctrl.offsetLeft, fixOffset(tabWidthsBefore));
35875      ctrl.offsetLeft = Math.max(ctrl.offsetLeft, fixOffset(tabWidthsIncluding - elements.canvas.clientWidth));
35876    } else {
35877      ctrl.offsetLeft = Math.max(ctrl.offsetLeft, fixOffset(right - elements.canvas.clientWidth + extraOffset));
35878      ctrl.offsetLeft = Math.min(ctrl.offsetLeft, fixOffset(left));
35879    }
35880  }
35881
35882  /**
35883   * Iterates through all queued functions and clears the queue.  This is used for functions that
35884   * are called before the UI is ready, such as size calculations.
35885   */
35886  function processQueue () {
35887    queue.forEach(function (func) { $mdUtil.nextTick(func); });
35888    queue = [];
35889  }
35890
35891  /**
35892   * Determines if the tab content area is needed.
35893   */
35894  function updateHasContent () {
35895    var hasContent  = false;
35896
35897    for (var i = 0; i < ctrl.tabs.length; i++) {
35898      if (ctrl.tabs[i].hasContent) {
35899        hasContent = true;
35900        break;
35901      }
35902    }
35903
35904    ctrl.hasContent = hasContent;
35905  }
35906
35907  /**
35908   * Moves the indexes to their nearest valid values.
35909   */
35910  function refreshIndex () {
35911    ctrl.selectedIndex = getNearestSafeIndex(ctrl.selectedIndex);
35912    ctrl.focusIndex    = getNearestSafeIndex(ctrl.focusIndex);
35913  }
35914
35915  /**
35916   * Calculates the content height of the current tab.
35917   * @returns {*}
35918   */
35919  function updateHeightFromContent () {
35920    if (!ctrl.dynamicHeight) return $element.css('height', '');
35921    if (!ctrl.tabs.length) return queue.push(updateHeightFromContent);
35922
35923    var elements = getElements();
35924
35925    var tabContent    = elements.contents[ ctrl.selectedIndex ],
35926        contentHeight = tabContent ? tabContent.offsetHeight : 0,
35927        tabsHeight    = elements.wrapper.offsetHeight,
35928        newHeight     = contentHeight + tabsHeight,
35929        currentHeight = $element.prop('clientHeight');
35930
35931    if (currentHeight === newHeight) return;
35932
35933    // Adjusts calculations for when the buttons are bottom-aligned since this relies on absolute
35934    // positioning.  This should probably be cleaned up if a cleaner solution is possible.
35935    if ($element.attr('md-align-tabs') === 'bottom') {
35936      currentHeight -= tabsHeight;
35937      newHeight -= tabsHeight;
35938      // Need to include bottom border in these calculations
35939      if ($element.attr('md-border-bottom') !== undefined) ++currentHeight;
35940    }
35941
35942    // Lock during animation so the user can't change tabs
35943    locked = true;
35944
35945    var fromHeight = { height: currentHeight + 'px' },
35946        toHeight = { height: newHeight + 'px' };
35947
35948    // Set the height to the current, specific pixel height to fix a bug on iOS where the height
35949    // first animates to 0, then back to the proper height causing a visual glitch
35950    $element.css(fromHeight);
35951
35952    // Animate the height from the old to the new
35953    $animateCss($element, {
35954      from: fromHeight,
35955      to: toHeight,
35956      easing: 'cubic-bezier(0.35, 0, 0.25, 1)',
35957      duration: 0.5
35958    }).start().done(function () {
35959      // Then (to fix the same iOS issue as above), disable transitions and remove the specific
35960      // pixel height so the height can size with browser width/content changes, etc.
35961      $element.css({
35962        transition: 'none',
35963        height: ''
35964      });
35965
35966      // In the next tick, re-allow transitions (if we do it all at once, $element.css is "smart"
35967      // enough to batch it for us instead of doing it immediately, which undoes the original
35968      // transition: none)
35969      $mdUtil.nextTick(function() {
35970        $element.css('transition', '');
35971      });
35972
35973      // And unlock so tab changes can occur
35974      locked = false;
35975    });
35976  }
35977
35978  /**
35979   * Repositions the ink bar to the selected tab.
35980   * @returns {*}
35981   */
35982  function updateInkBarStyles () {
35983    var elements = getElements();
35984
35985    if (!elements.tabs[ ctrl.selectedIndex ]) {
35986      angular.element(elements.inkBar).css({ left: 'auto', right: 'auto' });
35987      return;
35988    }
35989
35990    if (!ctrl.tabs.length) return queue.push(ctrl.updateInkBarStyles);
35991    // if the element is not visible, we will not be able to calculate sizes until it is
35992    // we should treat that as a resize event rather than just updating the ink bar
35993    if (!$element.prop('offsetParent')) return handleResizeWhenVisible();
35994
35995    var index      = ctrl.selectedIndex,
35996        totalWidth = elements.paging.offsetWidth,
35997        tab        = elements.tabs[ index ],
35998        left       = tab.offsetLeft,
35999        right      = totalWidth - left - tab.offsetWidth;
36000
36001    if (ctrl.shouldCenterTabs) {
36002      // We need to use the same calculate process as in the pagination wrapper, to avoid rounding deviations.
36003      var tabWidth = calcTabsWidth(elements.tabs);
36004
36005      if (totalWidth > tabWidth) {
36006        $mdUtil.nextTick(updateInkBarStyles, false);
36007      }
36008    }
36009    updateInkBarClassName();
36010    angular.element(elements.inkBar).css({ left: left + 'px', right: right + 'px' });
36011  }
36012
36013  /**
36014   * Adds left/right classes so that the ink bar will animate properly.
36015   */
36016  function updateInkBarClassName () {
36017    var elements = getElements();
36018    var newIndex = ctrl.selectedIndex,
36019        oldIndex = ctrl.lastSelectedIndex,
36020        ink      = angular.element(elements.inkBar);
36021    if (!angular.isNumber(oldIndex)) return;
36022    ink
36023        .toggleClass('md-left', newIndex < oldIndex)
36024        .toggleClass('md-right', newIndex > oldIndex);
36025  }
36026
36027  /**
vendor: 22,548 bytes, lines 36028-36400
36028   * Takes an offset value and makes sure that it is within the min/max allowed values.
36029   * @param value
36030   * @returns {*}
36031   */
36032  function fixOffset (value) {
36033    var elements = getElements();
36034
36035    if (!elements.tabs.length || !ctrl.shouldPaginate) return 0;
36036
36037    var lastTab    = elements.tabs[ elements.tabs.length - 1 ],
36038        totalWidth = lastTab.offsetLeft + lastTab.offsetWidth;
36039
36040    if (isRtl()) {
36041      value = Math.min(elements.paging.offsetWidth - elements.canvas.clientWidth, value);
36042      value = Math.max(0, value);
36043    } else {
36044      value = Math.max(0, value);
36045      value = Math.min(totalWidth - elements.canvas.clientWidth, value);
36046    }
36047
36048    return value;
36049  }
36050
36051  /**
36052   * Attaches a ripple to the tab item element.
36053   * @param scope
36054   * @param element
36055   */
36056  function attachRipple (scope, element) {
36057    var elements = getElements();
36058    var options = { colorElement: angular.element(elements.inkBar) };
36059    $mdTabInkRipple.attach(scope, element, options);
36060  }
36061
36062  /**
36063   * Sets the `aria-controls` attribute to the elements that
36064   * correspond to the passed-in tab.
36065   * @param tab
36066   */
36067  function setAriaControls (tab) {
36068    if (tab.hasContent) {
36069      var nodes = $element[0].querySelectorAll('[md-tab-id="' + tab.id + '"]');
36070      angular.element(nodes).attr('aria-controls', ctrl.tabContentPrefix + tab.id);
36071    }
36072  }
36073
36074  function isRtl() {
36075    return ($mdUtil.bidi() == 'rtl');
36076  }
36077}
36078
36079})();
36080(function(){
36081"use strict";
36082
36083/**
36084 * @ngdoc directive
36085 * @name mdTabs
36086 * @module material.components.tabs
36087 *
36088 * @restrict E
36089 *
36090 * @description
36091 * The `<md-tabs>` directive serves as the container for 1..n `<md-tab>` child directives to
36092 * produces a Tabs components. In turn, the nested `<md-tab>` directive is used to specify a tab
36093 * label for the **header button** and a [optional] tab view content that will be associated with
36094 * each tab button.
36095 *
36096 * Below is the markup for its simplest usage:
36097 *
36098 *  <hljs lang="html">
36099 *  <md-tabs>
36100 *    <md-tab label="Tab #1"></md-tab>
36101 *    <md-tab label="Tab #2"></md-tab>
36102 *    <md-tab label="Tab #3"></md-tab>
36103 *  </md-tabs>
36104 *  </hljs>
36105 *
36106 * Tabs supports three (3) usage scenarios:
36107 *
36108 *  1. Tabs (buttons only)
36109 *  2. Tabs with internal view content
36110 *  3. Tabs with external view content
36111 *
36112 * **Tab-only** support is useful when tab buttons are used for custom navigation regardless of any
36113 * other components, content, or views.
36114 *
36115 * <i><b>Note:</b> If you are using the Tabs component for page-level navigation, please take a look
36116 * at the <a ng-href="./api/directive/mdNavBar">NavBar component</a> instead as it can handle this
36117 * case a bit more natively.</i>
36118 *
36119 * **Tabs with internal views** are the traditional usages where each tab has associated view
36120 * content and the view switching is managed internally by the Tabs component.
36121 *
36122 * **Tabs with external view content** is often useful when content associated with each tab is
36123 * independently managed and data-binding notifications announce tab selection changes.
36124 *
36125 * Additional features also include:
36126 *
36127 * *  Content can include any markup.
36128 * *  If a tab is disabled while active/selected, then the next tab will be auto-selected.
36129 *
36130 * ### Explanation of tab stretching
36131 *
36132 * 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.
36133 *
36134 * On mobile devices, tabs will be expanded to fill the available horizontal space.  When this happens, all tabs will become the same size.
36135 *
36136 * On desktops, by default, stretching will never occur.
36137 *
36138 * This default behavior can be overridden through the `md-stretch-tabs` attribute.  Here is a table showing when stretching will occur:
36139 *
36140 * `md-stretch-tabs` | mobile    | desktop
36141 * ------------------|-----------|--------
36142 * `auto`            | stretched | ---
36143 * `always`          | stretched | stretched
36144 * `never`           | ---       | ---
36145 *
36146 * @param {integer=} md-selected Index of the active/selected tab
36147 * @param {boolean=} md-no-ink If present, disables ink ripple effects.
36148 * @param {boolean=} md-no-ink-bar If present, disables the selection ink bar.
36149 * @param {string=}  md-align-tabs Attribute to indicate position of tab buttons: `bottom` or `top`; default is `top`
36150 * @param {string=} md-stretch-tabs Attribute to indicate whether or not to stretch tabs: `auto`, `always`, or `never`; default is `auto`
36151 * @param {boolean=} md-dynamic-height When enabled, the tab wrapper will resize based on the contents of the selected tab
36152 * @param {boolean=} md-border-bottom If present, shows a solid `1px` border between the tabs and their content
36153 * @param {boolean=} md-center-tabs When enabled, tabs will be centered provided there is no need for pagination
36154 * @param {boolean=} md-no-pagination When enabled, pagination will remain off
36155 * @param {boolean=} md-swipe-content When enabled, swipe gestures will be enabled for the content area to jump between tabs
36156 * @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.
36157 * @param {boolean=} md-autoselect When present, any tabs added after the initial load will be automatically selected
36158 * @param {boolean=} md-no-select-click When enabled, click events will not be fired when selecting tabs
36159 *
36160 * @usage
36161 * <hljs lang="html">
36162 * <md-tabs md-selected="selectedIndex" >
36163 *   <img ng-src="img/angular.png" class="centered">
36164 *   <md-tab
36165 *       ng-repeat="tab in tabs | orderBy:predicate:reversed"
36166 *       md-on-select="onTabSelected(tab)"
36167 *       md-on-deselect="announceDeselected(tab)"
36168 *       ng-disabled="tab.disabled">
36169 *     <md-tab-label>
36170 *       {{tab.title}}
36171 *       <img src="img/removeTab.png" ng-click="removeTab(tab)" class="delete">
36172 *     </md-tab-label>
36173 *     <md-tab-body>
36174 *       {{tab.content}}
36175 *     </md-tab-body>
36176 *   </md-tab>
36177 * </md-tabs>
36178 * </hljs>
36179 *
36180 */
36181MdTabs.$inject = ["$$mdSvgRegistry"];
36182angular
36183    .module('material.components.tabs')
36184    .directive('mdTabs', MdTabs);
36185
36186function MdTabs ($$mdSvgRegistry) {
36187  return {
36188    scope:            {
36189      selectedIndex: '=?mdSelected'
36190    },
36191    template:         function (element, attr) {
36192      attr.$mdTabsTemplate = element.html();
36193      return '' +
36194        '<md-tabs-wrapper> ' +
36195          '<md-tab-data></md-tab-data> ' +
36196          '<md-prev-button ' +
36197              'tabindex="-1" ' +
36198              'role="button" ' +
36199              'aria-label="Previous Page" ' +
36200              'aria-disabled="{{!$mdTabsCtrl.canPageBack()}}" ' +
36201              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageBack() }" ' +
36202              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
36203              'ng-click="$mdTabsCtrl.previousPage()"> ' +
36204            '<md-icon md-svg-src="'+ $$mdSvgRegistry.mdTabsArrow +'"></md-icon> ' +
36205          '</md-prev-button> ' +
36206          '<md-next-button ' +
36207              'tabindex="-1" ' +
36208              'role="button" ' +
36209              'aria-label="Next Page" ' +
36210              'aria-disabled="{{!$mdTabsCtrl.canPageForward()}}" ' +
36211              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageForward() }" ' +
36212              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
36213              'ng-click="$mdTabsCtrl.nextPage()"> ' +
36214            '<md-icon md-svg-src="'+ $$mdSvgRegistry.mdTabsArrow +'"></md-icon> ' +
36215          '</md-next-button> ' +
36216          '<md-tabs-canvas ' +
36217              'ng-focus="$mdTabsCtrl.redirectFocus()" ' +
36218              'ng-class="{ ' +
36219                  '\'md-paginated\': $mdTabsCtrl.shouldPaginate, ' +
36220                  '\'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs ' +
36221              '}" ' +
36222              'ng-keydown="$mdTabsCtrl.keydown($event)"> ' +
36223            '<md-pagination-wrapper ' +
36224                'ng-class="{ \'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs }" ' +
36225                'md-tab-scroll="$mdTabsCtrl.scroll($event)" ' +
36226                'role="tablist"> ' +
36227              '<md-tab-item ' +
36228                  'tabindex="{{ tab.isActive() ? 0 : -1 }}" ' +
36229                  'class="md-tab" ' +
36230                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
36231                  'role="tab" ' +
36232                  'id="tab-item-{{::tab.id}}" ' +
36233                  'md-tab-id="{{::tab.id}}"' +
36234                  'aria-selected="{{tab.isActive()}}" ' +
36235                  'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
36236                  'ng-click="$mdTabsCtrl.select(tab.getIndex())" ' +
36237                  'ng-focus="$mdTabsCtrl.hasFocus = true" ' +
36238                  'ng-blur="$mdTabsCtrl.hasFocus = false" ' +
36239                  'ng-class="{ ' +
36240                      '\'md-active\':    tab.isActive(), ' +
36241                      '\'md-focused\':   tab.hasFocus(), ' +
36242                      '\'md-disabled\':  tab.scope.disabled ' +
36243                  '}" ' +
36244                  'ng-disabled="tab.scope.disabled" ' +
36245                  'md-swipe-left="$mdTabsCtrl.nextPage()" ' +
36246                  'md-swipe-right="$mdTabsCtrl.previousPage()" ' +
36247                  'md-tabs-template="::tab.label" ' +
36248                  'md-scope="::tab.parent"></md-tab-item> ' +
36249              '<md-ink-bar></md-ink-bar> ' +
36250            '</md-pagination-wrapper> ' +
36251            '<md-tabs-dummy-wrapper aria-hidden="true" class="md-visually-hidden md-dummy-wrapper"> ' +
36252              '<md-dummy-tab ' +
36253                  'class="md-tab" ' +
36254                  'tabindex="-1" ' +
36255                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
36256                  'md-tabs-template="::tab.label" ' +
36257                  'md-scope="::tab.parent"></md-dummy-tab> ' +
36258            '</md-tabs-dummy-wrapper> ' +
36259          '</md-tabs-canvas> ' +
36260        '</md-tabs-wrapper> ' +
36261        '<md-tabs-content-wrapper ng-show="$mdTabsCtrl.hasContent && $mdTabsCtrl.selectedIndex >= 0" class="_md"> ' +
36262          '<md-tab-content ' +
36263              'id="{{:: $mdTabsCtrl.tabContentPrefix + tab.id}}" ' +
36264              'class="_md" ' +
36265              'role="tabpanel" ' +
36266              'aria-labelledby="tab-item-{{::tab.id}}" ' +
36267              'md-swipe-left="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(1)" ' +
36268              'md-swipe-right="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(-1)" ' +
36269              'ng-if="tab.hasContent" ' +
36270              'ng-repeat="(index, tab) in $mdTabsCtrl.tabs" ' +
36271              'ng-class="{ ' +
36272                '\'md-no-transition\': $mdTabsCtrl.lastSelectedIndex == null, ' +
36273                '\'md-active\':        tab.isActive(), ' +
36274                '\'md-left\':          tab.isLeft(), ' +
36275                '\'md-right\':         tab.isRight(), ' +
36276                '\'md-no-scroll\':     $mdTabsCtrl.dynamicHeight ' +
36277              '}"> ' +
36278            '<div ' +
36279                'md-tabs-template="::tab.template" ' +
36280                'md-connected-if="tab.isActive()" ' +
36281                'md-scope="::tab.parent" ' +
36282                'ng-if="$mdTabsCtrl.enableDisconnect || tab.shouldRender()"></div> ' +
36283          '</md-tab-content> ' +
36284        '</md-tabs-content-wrapper>';
36285    },
36286    controller:       'MdTabsController',
36287    controllerAs:     '$mdTabsCtrl',
36288    bindToController: true
36289  };
36290}
36291
36292})();
36293(function(){
36294"use strict";
36295
36296
36297MdTabsDummyWrapper.$inject = ["$mdUtil", "$window"];angular
36298  .module('material.components.tabs')
36299  .directive('mdTabsDummyWrapper', MdTabsDummyWrapper);
36300
36301/**
36302 * @private
36303 *
36304 * @param $mdUtil
36305 * @param $window
36306 * @returns {{require: string, link: link}}
36307 * @constructor
36308 *
36309 * @ngInject
36310 */
36311function MdTabsDummyWrapper ($mdUtil, $window) {
36312  return {
36313    require: '^?mdTabs',
36314    link:    function link (scope, element, attr, ctrl) {
36315      if (!ctrl) return;
36316
36317      var observer;
36318      var disconnect;
36319
36320      var mutationCallback = function() {
36321        ctrl.updatePagination();
36322        ctrl.updateInkBarStyles();
36323      };
36324
36325      if('MutationObserver' in $window) {
36326        var config = {
36327          childList: true,
36328          subtree: true,
36329          // Per https://bugzilla.mozilla.org/show_bug.cgi?id=1138368, browsers will not fire
36330          // the childList mutation, once a <span> element's innerText changes.
36331          // The characterData of the <span> element will change.
36332          characterData: true
36333        };
36334
36335        observer = new MutationObserver(mutationCallback);
36336        observer.observe(element[0], config);
36337        disconnect = observer.disconnect.bind(observer);
36338      } else {
36339        var debounced = $mdUtil.debounce(mutationCallback, 15, null, false);
36340        
36341        element.on('DOMSubtreeModified', debounced);
36342        disconnect = element.off.bind(element, 'DOMSubtreeModified', debounced);
36343      }
36344
36345      // Disconnect the observer
36346      scope.$on('$destroy', function() {
36347        disconnect();
36348      });
36349    }
36350  };
36351}
36352
36353})();
36354(function(){
36355"use strict";
36356
36357
36358MdTabsTemplate.$inject = ["$compile", "$mdUtil"];angular
36359    .module('material.components.tabs')
36360    .directive('mdTabsTemplate', MdTabsTemplate);
36361
36362function MdTabsTemplate ($compile, $mdUtil) {
36363  return {
36364    restrict: 'A',
36365    link:     link,
36366    scope:    {
36367      template:     '=mdTabsTemplate',
36368      connected:    '=?mdConnectedIf',
36369      compileScope: '=mdScope'
36370    },
36371    require:  '^?mdTabs'
36372  };
36373  function link (scope, element, attr, ctrl) {
36374    if (!ctrl) return;
36375
36376    var compileScope = ctrl.enableDisconnect ? scope.compileScope.$new() : scope.compileScope;
36377
36378    element.html(scope.template);
36379    $compile(element.contents())(compileScope);
36380
36381    return $mdUtil.nextTick(handleScope);
36382
36383    function handleScope () {
36384      scope.$watch('connected', function (value) { value === false ? disconnect() : reconnect(); });
36385      scope.$on('$destroy', reconnect);
36386    }
36387
36388    function disconnect () {
36389      if (ctrl.enableDisconnect) $mdUtil.disconnectScope(compileScope);
36390    }
36391
36392    function reconnect () {
36393      if (ctrl.enableDisconnect) $mdUtil.reconnectScope(compileScope);
36394    }
36395  }
36396}
36397
36398})();
36399(function(){ 
36400angular.module("material.core").constant("$MD_THEME_CSS", "md-autocomplete.md-THEME_NAME-theme{background:\"{{background-A100}}\"}md-autocomplete.md-THEME_NAME-theme[disabled]:not([md-floating-label]){background:\"{{background-100}}\"}md-autocomplete.md-THEME_NAME-theme button md-icon path{fill:\"{{background-600}}\"}md-autocomplete.md-THEME_NAME-theme button:after{background:\"{{background-600-0.3}}\"}.md-autocomplete-suggestions-container.md-THEME_NAME-theme{background:\"{{background-A100}}\"}.md-autocomplete-suggestions-container.md-THEME_NAME-theme li{color:\"{{background-900}}\"}.md-autocomplete-suggestions-container.md-THEME_NAME-theme li .highlight{color:\"{{background-600}}\"}.md-autocomplete-suggestions-container.md-THEME_NAME-theme li.selected,.md-autocomplete-suggestions-container.md-THEME_NAME-theme li:hover{background:\"{{background-200}}\"}md-backdrop{background-color:\"{{background-900-0.0}}\"}md-backdrop.md-opaque.md-THEME_NAME-theme{background-color:\"{{background-900-1.0}}\"}md-bottom-sheet.md-THEME_NAME-theme{background-color:\"{{background-50}}\";border-top-color:\"{{background-300}}\"}md-bottom-sheet.md-THEME_NAME-theme.md-list md-list-item{color:\"{{foreground-1}}\"}md-bottom-sheet.md-THEME_NAME-theme .md-subheader{background-color:\"{{background-50}}\";color:\"{{foreground-1}}\"}.md-button.md-THEME_NAME-theme:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme:not([disabled]):hover{background-color:\"{{background-500-0.2}}\"}.md-button.md-THEME_NAME-theme:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-THEME_NAME-theme.md-fab md-icon{color:\"{{accent-contrast}}\"}.md-button.md-THEME_NAME-theme.md-primary{color:\"{{primary-color}}\"}.md-button.md-THEME_NAME-theme.md-primary.md-fab,.md-button.md-THEME_NAME-theme.md-primary.md-raised{color:\"{{primary-contrast}}\";background-color:\"{{primary-color}}\"}.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]) md-icon{color:\"{{primary-contrast}}\"}.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]):hover,.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]):hover{background-color:\"{{primary-600}}\"}.md-button.md-THEME_NAME-theme.md-primary:not([disabled]) md-icon{color:\"{{primary-color}}\"}.md-button.md-THEME_NAME-theme.md-fab{background-color:\"{{accent-color}}\";color:\"{{accent-contrast}}\"}.md-button.md-THEME_NAME-theme.md-fab:not([disabled]) .md-icon{color:\"{{accent-contrast}}\"}.md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover{background-color:\"{{accent-A700}}\"}.md-button.md-THEME_NAME-theme.md-raised{color:\"{{background-900}}\";background-color:\"{{background-50}}\"}.md-button.md-THEME_NAME-theme.md-raised:not([disabled]) md-icon{color:\"{{background-900}}\"}.md-button.md-THEME_NAME-theme.md-raised:not([disabled]):hover{background-color:\"{{background-50}}\"}.md-button.md-THEME_NAME-theme.md-raised:not([disabled]).md-focused{background-color:\"{{background-200}}\"}.md-button.md-THEME_NAME-theme.md-warn{color:\"{{warn-color}}\"}.md-button.md-THEME_NAME-theme.md-warn.md-fab,.md-button.md-THEME_NAME-theme.md-warn.md-raised{color:\"{{warn-contrast}}\";background-color:\"{{warn-color}}\"}.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]) md-icon{color:\"{{warn-contrast}}\"}.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]):hover,.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]):hover{background-color:\"{{warn-600}}\"}.md-button.md-THEME_NAME-theme.md-warn:not([disabled]) md-icon{color:\"{{warn-color}}\"}.md-button.md-THEME_NAME-theme.md-accent{color:\"{{accent-color}}\"}.md-button.md-THEME_NAME-theme.md-accent.md-fab,.md-button.md-THEME_NAME-theme.md-accent.md-raised{color:\"{{accent-contrast}}\";background-color:\"{{accent-color}}\"}.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]) md-icon{color:\"{{accent-contrast}}\"}.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]):hover,.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]):hover{background-color:\"{{accent-A700}}\"}.md-button.md-THEME_NAME-theme.md-accent:not([disabled]) md-icon{color:\"{{accent-color}}\"}.md-button.md-THEME_NAME-theme.md-accent[disabled],.md-button.md-THEME_NAME-theme.md-fab[disabled],.md-button.md-THEME_NAME-theme.md-raised[disabled],.md-button.md-THEME_NAME-theme.md-warn[disabled],.md-button.md-THEME_NAME-theme[disabled]{color:\"{{foreground-3}}\";cursor:default}.md-button.md-THEME_NAME-theme.md-accent[disabled] md-icon,.md-button.md-THEME_NAME-theme.md-fab[disabled] md-icon,.md-button.md-THEME_NAME-theme.md-raised[disabled] md-icon,.md-button.md-THEME_NAME-theme.md-warn[disabled] md-icon,.md-button.md-THEME_NAME-theme[disabled] md-icon{color:\"{{foreground-3}}\"}.md-button.md-THEME_NAME-theme.md-fab[disabled],.md-button.md-THEME_NAME-theme.md-raised[disabled]{background-color:\"{{foreground-4}}\"}.md-button.md-THEME_NAME-theme[disabled]{background-color:transparent}._md a.md-THEME_NAME-theme:not(.md-button).md-primary{color:\"{{primary-color}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-primary:hover{color:\"{{primary-700}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-accent{color:\"{{accent-color}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-accent:hover{color:\"{{accent-A700}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-warn{color:\"{{warn-color}}\"}._md a.md-THEME_NAME-theme:not(.md-button).md-warn:hover{color:\"{{warn-700}}\"}md-card.md-THEME_NAME-theme{color:\"{{foreground-1}}\";background-color:\"{{background-hue-1}}\";border-radius:2px}md-card.md-THEME_NAME-theme .md-card-image{border-radius:2px 2px 0 0}md-card.md-THEME_NAME-theme md-card-header md-card-avatar md-icon{color:\"{{background-color}}\";background-color:\"{{foreground-3}}\"}md-card.md-THEME_NAME-theme md-card-header md-card-header-text .md-subhead,md-card.md-THEME_NAME-theme md-card-title md-card-title-text:not(:only-child) .md-subhead{color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme .md-ripple{color:\"{{accent-A700}}\"}md-checkbox.md-THEME_NAME-theme.md-checked .md-ripple{color:\"{{background-600}}\"}md-checkbox.md-THEME_NAME-theme.md-checked.md-focused .md-container:before{background-color:\"{{accent-color-0.26}}\"}md-checkbox.md-THEME_NAME-theme .md-ink-ripple{color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme.md-checked .md-ink-ripple{color:\"{{accent-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not(.md-checked) .md-icon{border-color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme.md-checked .md-icon{background-color:\"{{accent-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme.md-checked .md-icon:after{border-color:\"{{accent-contrast-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-ripple{color:\"{{primary-600}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ripple{color:\"{{background-600}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-ink-ripple{color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple{color:\"{{primary-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-icon{background-color:\"{{primary-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:\"{{primary-color-0.26}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-icon:after{border-color:\"{{primary-contrast-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{c
36400olor:\"{{foreground-3}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-ripple{color:\"{{warn-600}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-ink-ripple{color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple{color:\"{{warn-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:\"{{foreground-2}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-icon{background-color:\"{{warn-color-0.87}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:\"{{warn-color-0.26}}\"}md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-icon:after{border-color:\"{{background-200}}\"}md-checkbox.md-THEME_NAME-theme[disabled]:not(.md-checked) .md-icon{border-color:\"{{foreground-3}}\"}md-checkbox.md-THEME_NAME-theme[disabled].md-checked .md-icon{background-color:\"{{foreground-3}}\"}md-checkbox.md-THEME_NAME-theme[disabled].md-checked .md-icon:after{border-color:\"{{background-200}}\"}md-checkbox.md-THEME_NAME-theme[disabled] .md-icon:after{border-color:\"{{foreground-3}}\"}md-checkbox.md-THEME_NAME-theme[disabled] .md-label{color:\"{{foreground-3}}\"}md-chips.md-THEME_NAME-theme .md-chips{box-shadow:0 1px \"{{foreground-4}}\"}md-chips.md-THEME_NAME-theme .md-chips.md-focused{box-shadow:0 2px \"{{primary-color}}\"}md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input{color:\"{{foreground-1}}\"}md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input::-moz-placeholder{color:\"{{foreground-3}}\"}md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input:-ms-input-placeholder{color:\"{{foreground-3}}\"}md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:\"{{foreground-3}}\"}md-chips.md-THEME_NAME-theme md-chip{background:\"{{background-300}}\";color:\"{{background-800}}\"}md-chips.md-THEME_NAME-theme md-chip md-icon{color:\"{{background-700}}\"}md-chips.md-THEME_NAME-theme md-chip.md-focused{background:\"{{primary-color}}\";color:\"{{primary-contrast}}\"}md-chips.md-THEME_NAME-theme md-chip.md-focused md-icon{color:\"{{primary-contrast}}\"}md-chips.md-THEME_NAME-theme md-chip._md-chip-editing{background:transparent;color:\"{{background-800}}\"}md-chips.md-THEME_NAME-theme md-chip-remove .md-button md-icon path{fill:\"{{background-500}}\"}.md-contact-suggestion span.md-contact-email{color:\"{{background-400}}\"}md-content.md-THEME_NAME-theme{color:\"{{foreground-1}}\";background-color:\"{{background-default}}\"}.md-calendar.md-THEME_NAME-theme{background:\"{{background-A100}}\";color:\"{{background-A200-0.87}}\"}.md-calendar.md-THEME_NAME-theme tr:last-child td{border-bottom-color:\"{{background-200}}\"}.md-THEME_NAME-theme .md-calendar-day-header{background:\"{{background-300}}\";color:\"{{background-A200-0.87}}\"}.md-THEME_NAME-theme .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid \"{{primary-500}}\"}.md-THEME_NAME-theme .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:\"{{primary-500-0.6}}\"}.md-calendar-date.md-focus .md-THEME_NAME-theme .md-calendar-date-selection-indicator,.md-THEME_NAME-theme .md-calendar-date-selection-indicator:hover{background:\"{{background-300}}\"}.md-THEME_NAME-theme .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-THEME_NAME-theme .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:\"{{primary-500}}\";color:\"{{primary-500-contrast}}\";border-color:transparent}.md-THEME_NAME-theme .md-calendar-date-disabled,.md-THEME_NAME-theme .md-calendar-month-label-disabled{color:\"{{background-A200-0.435}}\"}.md-THEME_NAME-theme .md-datepicker-input{color:\"{{foreground-1}}\"}.md-THEME_NAME-theme .md-datepicker-input:-moz-placeholder,.md-THEME_NAME-theme .md-datepicker-input::-moz-placeholder{color:\"{{foreground-3}}\"}.md-THEME_NAME-theme .md-datepicker-input:-ms-input-placeholder{color:\"{{foreground-3}}\"}.md-THEME_NAME-theme .md-datepicker-input::-webkit-input-placeholder{color:\"{{foreground-3}}\"}.md-THEME_NAME-theme .md-datepicker-input-container{border-bottom-color:\"{{foreground-4}}\"}.md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:\"{{primary-color}}\"}.md-accent .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:\"{{accent-color}}\"}.md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:\"{{warn-A700}}\"}.md-THEME_NAME-theme .md-datepicker-calendar-pane{border-color:\"{{background-hue-1}}\"}.md-THEME_NAME-theme .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:\"{{foreground-2}}\"}.md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon{color:\"{{primary-color}}\"}.md-accent .md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon,.md-THEME_NAME-theme .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:\"{{accent-color}}\"}.md-THEME_NAME-theme .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon{color:\"{{warn-A700}}\"}.md-THEME_NAME-theme .md-datepicker-calendar{background:\"{{background-A100}}\"}.md-THEME_NAME-theme .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px \"{{background-hue-1}}\"}.md-THEME_NAME-theme .md-datepicker-open .md-datepicker-input-container{background:\"{{background-hue-1}}\"}md-dialog.md-THEME_NAME-theme{border-radius:4px;background-color:\"{{background-hue-1}}\";color:\"{{foreground-1}}\"}md-dialog.md-THEME_NAME-theme.md-content-overflow .md-actions,md-dialog.md-THEME_NAME-theme.md-content-overflow md-dialog-actions,md-div
36400ider.md-THEME_NAME-theme{border-top-color:\"{{foreground-4}}\"}.layout-gt-lg-row>md-divider.md-THEME_NAME-theme,.layout-gt-md-row>md-divider.md-THEME_NAME-theme,.layout-gt-sm-row>md-divider.md-THEME_NAME-theme,.layout-gt-xs-row>md-divider.md-THEME_NAME-theme,.layout-lg-row>md-divider.md-THEME_NAME-theme,.layout-md-row>md-divider.md-THEME_NAME-theme,.layout-row>md-divider.md-THEME_NAME-theme,.layout-sm-row>md-divider.md-THEME_NAME-theme,.layout-xl-row>md-divider.md-THEME_NAME-theme,.layout-xs-row>md-divider.md-THEME_NAME-theme{border-right-color:\"{{foreground-4}}\"}md-icon.md-THEME_NAME-theme{color:\"{{foreground-2}}\"}md-icon.md-THEME_NAME-theme.md-primary{color:\"{{primary-color}}\"}md-icon.md-THEME_NAME-theme.md-accent{color:\"{{accent-color}}\"}md-icon.md-THEME_NAME-theme.md-warn{color:\"{{warn-color}}\"}md-input-container.md-THEME_NAME-theme .md-input{color:\"{{foreground-1}}\";border-color:\"{{foreground-4}}\"}md-input-container.md-THEME_NAME-theme .md-input:-moz-placeholder,md-input-container.md-THEME_NAME-theme .md-input::-moz-placeholder{color:\"{{foreground-3}}\"}md-input-container.md-THEME_NAME-theme .md-input:-ms-input-placeholder{color:\"{{foreground-3}}\"}md-input-container.md-THEME_NAME-theme .md-input::-webkit-input-placeholder{color:\"{{foreground-3}}\"}md-input-container.md-THEME_NAME-theme>md-icon{color:\"{{foreground-1}}\"}md-input-container.md-THEME_NAME-theme .md-placeholder,md-input-container.md-THEME_NAME-theme label{color:\"{{foreground-3}}\"}md-input-container.md-THEME_NAME-theme label.md-required:after{color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme .md-input-message-animation,md-input-container.md-THEME_NAME-theme .md-input-messages-animation{color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme .md-input-message-animation .md-char-counter,md-input-container.md-THEME_NAME-theme .md-input-messages-animation .md-char-counter{color:\"{{foreground-1}}\"}md-input-container.md-THEME_NAME-theme.md-input-focused .md-input:-moz-placeholder,md-input-container.md-THEME_NAME-theme.md-input-focused .md-input::-moz-placeholder{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme.md-input-focused .md-input:-ms-input-placeholder{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme.md-input-focused .md-input::-webkit-input-placeholder{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-has-value label{color:\"{{foreground-2}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-resized .md-input{border-color:\"{{primary-color}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused label,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused md-icon{color:\"{{primary-color}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:\"{{accent-color}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:\"{{accent-color}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input{border-color:\"{{warn-A700}}\"}md-input-container.md-THEME_NAME-theme.md-input-invalid .md-char-counter,md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input-message-animation,md-input-container.md-THEME_NAME-theme.md-input-invalid label{color:\"{{warn-A700}}\"}[disabled] md-input-container.md-THEME_NAME-theme .md-input,md-input-container.md-THEME_NAME-theme .md-input[disabled]{border-bottom-color:transparent;color:\"{{foreground-3}}\";background-image:linear-gradient(90deg,\"{{foreground-3}}\" 0,\"{{foreground-3}}\" 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,\"{{foreground-3}}\" 100%)}md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h3,md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h4,md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h3,md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h4{color:\"{{foreground-1}}\"}md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text p,md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text p{color:\"{{foreground-2}}\"}
36400md-list.md-THEME_NAME-theme .md-proxy-focus.md-focused div.md-no-style{background-color:\"{{background-100}}\"}md-list.md-THEME_NAME-theme md-list-item .md-avatar-icon{background-color:\"{{foreground-3}}\";color:\"{{background-color}}\"}md-list.md-THEME_NAME-theme md-list-item>md-icon{color:\"{{foreground-2}}\"}md-list.md-THEME_NAME-theme md-list-item>md-icon.md-highlight{color:\"{{primary-color}}\"}md-list.md-THEME_NAME-theme md-list-item>md-icon.md-highlight.md-accent{color:\"{{accent-color}}\"}md-menu-content.md-THEME_NAME-theme{background-color:\"{{background-A100}}\"}md-menu-content.md-THEME_NAME-theme md-menu-item{color:\"{{background-A200-0.87}}\"}md-menu-content.md-THEME_NAME-theme md-menu-item md-icon{color:\"{{background-A200-0.54}}\"}md-menu-content.md-THEME_NAME-theme md-menu-item .md-button[disabled],md-menu-content.md-THEME_NAME-theme md-menu-item .md-button[disabled] md-icon{color:\"{{background-A200-0.25}}\"}md-menu-content.md-THEME_NAME-theme md-menu-divider{background-color:\"{{background-A200-0.11}}\"}md-menu-bar.md-THEME_NAME-theme>button.md-button{color:\"{{foreground-2}}\";border-radius:2px}md-menu-bar.md-THEME_NAME-theme md-menu.md-open>button,md-menu-bar.md-THEME_NAME-theme md-menu>button:focus{outline:none;background:\"{{background-200}}\"}md-menu-bar.md-THEME_NAME-theme.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:\"{{ background-500-0.2}}\"}md-menu-bar.md-THEME_NAME-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-THEME_NAME-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-THEME_NAME-theme .md-menu>.md-button:after{color:\"{{background-A200-0.54}}\"}md-menu-content.md-THEME_NAME-theme .md-menu.md-open>.md-button{background-color:\"{{ background-500-0.2}}\"}md-toolbar.md-THEME_NAME-theme.md-menu-toolbar{background-color:\"{{background-A100}}\";color:\"{{background-A200}}\"}md-toolbar.md-THEME_NAME-theme.md-menu-toolbar md-toolbar-filler{background-color:\"{{primary-color}}\";color:\"{{background-A100-0.87}}\"}md-toolbar.md-THEME_NAME-theme.md-menu-toolbar md-toolbar-filler md-icon{color:\"{{background-A100-0.87}}\"}md-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-nav-bar.md-THEME_NAME-theme.md-accent>.md-nav-bar{background-color:\"{{accent-color}}\"}md-nav-bar.md-THEME_NAME-theme.md-accent>.md-nav-bar .md-button._md-nav-button{color:\"{{accent-A100}}\"}md-nav-bar.md-THEME_NAME-theme.md-accent>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-THEME_NAME-theme.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{color:\"{{accent-contrast}}\"}md-nav-bar.md-THEME_NAME-theme.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{background:\"{{accent-contrast-0.1}}\"}md-nav-bar.md-THEME_NAME-theme.md-accent>.md-nav-bar md-nav-ink-bar{color:\"{{primary-600-1}}\";background:\"{{primary-600-1}}\"}md-nav-bar.md-THEME_NAME-theme.md-warn>.md-nav-bar{background-color:\"{{warn-color}}\"}md-nav-bar.md-THEME_NAME-theme.md-warn>.md-nav-bar .md-button._md-nav-button{color:\"{{warn-100}}\"}md-nav-bar.md-THEME_NAME-theme.md-warn>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-THEME_NAME-theme.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{color:\"{{warn-contrast}}\"}md-nav-bar.md-THEME_NAME-theme.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{background:\"{{warn-contrast-0.1}}\"}md-nav-bar.md-THEME_NAME-theme.md-primary>.md-nav-bar{background-color:\"{{primary-color}}\"}md-nav-bar.md-THEME_NAME-theme.md-primary>.md-nav-bar .md-button._md-nav-button{color:\"{{primary-100}}\"}md-nav-bar.md-THEME_NAME-theme.md-primary>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-THEME_NAME-theme.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{color:\"{{primary-contrast}}\"}md-nav-bar.md-THEME_NAME-theme.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{background:\"{{primary-contrast-0.1}}\"}md-toolbar>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar{background-color:\"{{primary-color}}\"}md-toolbar>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button{color:\"{{primary-100}}\"}md-toolbar>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button.md-focused{color:\"{{primary-contrast}}\"}md-toolbar>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button.md-focused{background:\"{{primary-contrast-0.1}}\"}md-toolbar.md-accent>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar{background-color:\"{{accent-color}}\"}md-toolbar.md-accent>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button{color:\"{{accent-A100}}\"}md-toolbar.md-accent>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-accent>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button.md-focused{color:\"{{accent-contrast}}\"}md-toolbar.md-accent>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button.md-focused{background:\"{{accent-contrast-0.1}}\"}md-toolbar.md-accent>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar md-nav-ink-bar{color:\"{{primary-600-1}}\";background:\"{{primary-600-1}}\"}md-toolbar.md-warn>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar{background-color:\"{{warn-color}}\"}md-toolbar.md-warn>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button{color:\"{{warn-100}}\"}md-toolbar.md-warn>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-warn>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button.md-focused{color:\"{{warn-contrast}}\"}md-toolbar.md-warn>md-nav-bar.md-THEME_NAME-theme>.md-nav-bar .md-button._md-nav-button.md-focused{background:\"{{warn-contrast-0.1}}\"}._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-primary .md-bar
364001{background-color:\"{{primary-100}}\"}md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-primary .md-dashed:before{background:radial-gradient(\"{{primary-100}}\" 0,\"{{primary-100}}\" 16%,transparent 42%)}md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-bar1{background-color:\"{{warn-100}}\"}md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(\"{{warn-100}}\" 0,\"{{warn-100}}\" 16%,transparent 42%)}md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-bar1{background-color:\"{{accent-100}}\"}md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(\"{{accent-100}}\" 0,\"{{accent-100}}\" 16%,transparent 42%)}md-radio-button.md-THEME_NAME-theme .md-off{border-color:\"{{foreground-2}}\"}md-radio-button.md-THEME_NAME-theme .md-on{background-color:\"{{accent-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme.md-checked .md-off{border-color:\"{{accent-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme.md-checked .md-ink-ripple{color:\"{{accent-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme .md-container .md-ripple{color:\"{{accent-A700}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-on,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-on,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on{background-color:\"{{primary-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off{border-color:\"{{primary-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:\"{{primary-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple{color:\"{{primary-600}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-on,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-on,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on{background-color:\"{{warn-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off{border-color:\"{{warn-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:\"{{warn-color-0.87}}\"}md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple{color:\"{{warn-600}}\"}md-radio-button.md-THEME_NAME-theme[disabled],md-radio-group.md-THEME_NAME-theme[disabled]{color:\"{{foreground-3}}\"}md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-off,md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-on,md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-off,md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-on{border-color:\"{{foreground-3}}\"}md-radio-group.md-THEME_NAME-theme .md-checked .md-ink-ripple{color:\"{{accent-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-THEME_NAME-theme.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:\"{{primary-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme .md-checked.md-primary .md-ink-ripple{color:\"{{warn-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked .md-container:before{background-color:\"{{accent-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:\"{{primary-color-0.26}}\"}md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:\"{{warn-color-0.26}}\"}md-input-container md-select.md-THEME_NAME-theme .md-select-value span:first-child:after{color:\"{{warn-A700}}\"}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-THEME_NAME-theme .md-select-value span:first-child:after{color:\"{{foreground-3}}\"}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-THEME_NAME-theme .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-THEME_NAME-theme .md-select-value.md-select-placeholder{color:\"{{primary-color}}\"}md-input-container.md-input-invalid md-select.md-THEME_NAME-theme .md-select-value{color:\"{{warn-A700}}\"!important;border-bottom-color:\"{{warn-A700}}\"!important}md-input-container.md-input-invalid md-select.md-THEME_NAME-theme.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-THEME_NAME-theme[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,\"{{foreground-3}}\" 0,\"{{foreground-3}}\" 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,\"{{foreground-3}}\" 100%)}md-select.md-THEME_NAME-theme .md-select-value{border-bottom-color:\"{{foreground-4}}\"}md-select.md-THEME_NAME-theme .md-select-value.md-select-placeholder{color:\"{{foreground-3}}\"}md-select.md-THEME_NAME-theme .md-select-value span:first-child:after{color:\"{{warn-A700}}\"}md-select.md-THEME_NAME-theme.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-THEME_NAME-theme.ng-invalid.ng-touched .md-select-value{color:\"{{warn-A700}}\"!important;border-bottom-color:\"{{warn-A700}}\"!important}md-select.md-THEME_NAME-theme.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-value{border-bottom-color:\"{{primary-color}}\";color:\"{{ foreground-1 }}\"}md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-value.md-select-placeholder{color:\"{{ foreground-1 }}\"}md-select.md-THEME_NAME-theme:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-THEME_NAME-theme:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:\"{{accent-color}}\"}md-select.md-THEME_NAME-theme:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:\"{{warn-color}}\"}md-select.md-THEME_NAME-theme[disabled] .md-select-icon,md-select.md-THEME_NAME-theme[disabled] .md-select-value,md-select.md-THEME_NAME-theme[disabled] .md-select-value.md-select-placeholder{color:\"{{foreground-3}}\"}md-select.md-THEME_NAME-theme .md-select-icon{color:\"{{foreground-2}}\"}md-select-menu.md-THEME_NAME-theme md-content{background:\"{{background-A100}}\"}md-select-menu.md-THEME_NAME-theme md-content md-optgroup{color:\"{{background-600-0.87}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option{color:\"{{background-900-0.87}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[disabled] .md-text{color:\"{{background-400-0.87}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option:not([disabled]):focus,md-select-menu.md-THEME_NAME-theme md-content md-option:not([disabled]):hover{background:\"{{background-200}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[selected]{color:\"{{primary-500}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[selected]:focus{color:\"{{primary-600}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[selected].md-accent{color:\"{{accent-color}}\"}md-select-menu.md-THEME_NAME-theme md-content md-option[selected].md-accent:focus{color:\"{{accent-A700}}\"}.md-checkbox-enabled.md-THEME_NAME-theme .md-ripple{color:\"{{primary-600}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-ripple{color:\"{{background-600}}\"}.md-checkbox-enabled.md-THEME_NAME-theme .md-ink-ripple{color:\"{{foreground-2}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-ink-ripple{color:\"{{primary-color-0.87}}\"}.md-checkbox-enabled.md-THEME_NAME-theme:not(.md-checked) .md-icon{border-color:\"{{foreground-2}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-icon{background-color:\"{{primary-color-0.87}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected].md-focused .md-container:before{background-color:\"{{primary-color-0.26}}\"}.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-icon:after{border-color:\"{{primary-contrast-0.87}}\"}.md-checkbox-enabled.md-THEME_NAME-theme .md-indeterminate[disabled] .md-container{c
36400olor:\"{{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}}\"}
36400md-tabs.md-THEME_NAME-theme .md-paginator md-icon{color:\"{{primary-color}}\"}md-tabs.md-THEME_NAME-theme md-ink-bar{color:\"{{accent-color}}\";background:\"{{accent-color}}\"}md-tabs.md-THEME_NAME-theme .md-tab{color:\"{{foreground-2}}\"}md-tabs.md-THEME_NAME-theme .md-tab[disabled],md-tabs.md-THEME_NAME-theme .md-tab[disabled] md-icon{color:\"{{foreground-3}}\"}md-tabs.md-THEME_NAME-theme .md-tab.md-active,md-tabs.md-THEME_NAME-theme .md-tab.md-active md-icon,md-tabs.md-THEME_NAME-theme .md-tab.md-focused,md-tabs.md-THEME_NAME-theme .md-tab.md-focused md-icon{color:\"{{primary-color}}\"}md-tabs.md-THEME_NAME-theme .md-tab.md-focused{background:\"{{primary-color-0.1}}\"}md-tabs.md-THEME_NAME-theme .md-tab .md-ripple-container{color:\"{{accent-A100}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper{background-color:\"{{accent-color}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{accent-A100}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{accent-contrast}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{accent-contrast-0.1}}\"}md-tabs.md-THEME_NAME-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:\"{{primary-600-1}}\";background:\"{{primary-600-1}}\"}md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper{background-color:\"{{primary-color}}\"}md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{primary-100}}\"}md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{primary-contrast}}\"}md-tabs.md-THEME_NAME-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{primary-contrast-0.1}}\"}md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper{background-color:\"{{warn-color}}\"}md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{warn-100}}\"}md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{warn-contrast}}\"}md-tabs.md-THEME_NAME-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{warn-contrast-0.1}}\"}md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper{background-color:\"{{primary-color}}\"}md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{primary-100}}\"}md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{primary-contrast}}\"}md-toolbar>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{primary-contrast-0.1}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper{background-color:\"{{accent-color}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{accent-A100}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{accent-contrast}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{accent-contrast-0.1}}\"}md-toolbar.md-accent>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:\"{{primary-600-1}}\";background:\"{{primary-600-1}}\"}md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper{background-color:\"{{warn-color}}\"}md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:\"{{warn-100}}\"}md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:\"{{warn-contrast}}\"}md-toolbar.md-warn>md-tabs.md-THEME_NAME-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:\"{{warn-contrast-0.1}}\"}md-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-panel.md-tooltip.md-THEME_NAME-theme{color:\"{{background-700-contrast}}\";background-color:\"{{background-700}}\"}body.md-THEME_NAME-theme,html.md-THEME_NAME-theme{color:\"{{foreground-1}}\";background-color:\"{{background-color}}\"}"); 
36401})();
36402
36403
36404})(window, window.angular);;window.ngMaterial={version:{full: "1.1.4-master-baa869a"}};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.