nexus site path corrected
[portal.git] / ecomp-portal-FE / client / bower_components / angular-material / angular-material.js
1 /*!
2  * Angular Material Design
3  * https://github.com/angular/material
4  * @license MIT
5  * v0.9.8
6  */
7 (function( window, angular, undefined ){
8 "use strict";
9
10 (function(){
11 "use strict";
12
13 angular.module('ngMaterial', ["ng","ngAnimate","ngAria","material.core","material.core.gestures","material.core.theming.palette","material.core.theming","material.components.autocomplete","material.components.backdrop","material.components.bottomSheet","material.components.button","material.components.card","material.components.checkbox","material.components.chips","material.components.content","material.components.dialog","material.components.divider","material.components.gridList","material.components.icon","material.components.input","material.components.list","material.components.progressCircular","material.components.progressLinear","material.components.radioButton","material.components.select","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.whiteframe"]);
14 })();
15 (function(){
16 "use strict";
17
18
19 /**
20  * Initialization function that validates environment
21  * requirements.
22  */
23 angular
24   .module('material.core', [ 'material.core.gestures', 'material.core.theming' ])
25   .config( MdCoreConfigure );
26
27
28 function MdCoreConfigure($provide, $mdThemingProvider) {
29
30   $provide.decorator('$$rAF', ["$delegate", rAFDecorator]);
31
32   $mdThemingProvider.theme('default')
33     .primaryPalette('indigo')
34     .accentPalette('pink')
35     .warnPalette('red')
36     .backgroundPalette('grey');
37 }
38 MdCoreConfigure.$inject = ["$provide", "$mdThemingProvider"];
39
40 function rAFDecorator( $delegate ) {
41   /**
42    * Use this to throttle events that come in often.
43    * The throttled function will always use the *last* invocation before the
44    * coming frame.
45    *
46    * For example, window resize events that fire many times a second:
47    * If we set to use an raf-throttled callback on window resize, then
48    * our callback will only be fired once per frame, with the last resize
49    * event that happened before that frame.
50    *
51    * @param {function} callback function to debounce
52    */
53   $delegate.throttle = function(cb) {
54     var queueArgs, alreadyQueued, queueCb, context;
55     return function debounced() {
56       queueArgs = arguments;
57       context = this;
58       queueCb = cb;
59       if (!alreadyQueued) {
60         alreadyQueued = true;
61         $delegate(function() {
62           queueCb.apply(context, queueArgs);
63           alreadyQueued = false;
64         });
65       }
66     };
67   };
68   return $delegate;
69 }
70
71 })();
72 (function(){
73 "use strict";
74
75 angular.module('material.core')
76 .factory('$mdConstant', MdConstantFactory);
77
78 function MdConstantFactory($$rAF, $sniffer) {
79
80   var webkit = /webkit/i.test($sniffer.vendorPrefix);
81   function vendorProperty(name) {
82     return webkit ?  ('webkit' + name.charAt(0).toUpperCase() + name.substring(1)) : name;
83   }
84
85   return {
86     KEY_CODE: {
87       ENTER: 13,
88       ESCAPE: 27,
89       SPACE: 32,
90       LEFT_ARROW : 37,
91       UP_ARROW : 38,
92       RIGHT_ARROW : 39,
93       DOWN_ARROW : 40,
94       TAB : 9,
95       BACKSPACE: 8,
96       DELETE: 46
97     },
98     CSS: {
99       /* Constants */
100       TRANSITIONEND: 'transitionend' + (webkit ? ' webkitTransitionEnd' : ''),
101       ANIMATIONEND: 'animationend' + (webkit ? ' webkitAnimationEnd' : ''),
102
103       TRANSFORM: vendorProperty('transform'),
104       TRANSFORM_ORIGIN: vendorProperty('transformOrigin'),
105       TRANSITION: vendorProperty('transition'),
106       TRANSITION_DURATION: vendorProperty('transitionDuration'),
107       ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'),
108       ANIMATION_DURATION: vendorProperty('animationDuration'),
109       ANIMATION_NAME: vendorProperty('animationName'),
110       ANIMATION_TIMING: vendorProperty('animationTimingFunction'),
111       ANIMATION_DIRECTION: vendorProperty('animationDirection')
112     },
113     MEDIA: {
114       'sm': '(max-width: 600px)',
115       'gt-sm': '(min-width: 600px)',
116       'md': '(min-width: 600px) and (max-width: 960px)',
117       'gt-md': '(min-width: 960px)',
118       'lg': '(min-width: 960px) and (max-width: 1200px)',
119       'gt-lg': '(min-width: 1200px)'
120     },
121     MEDIA_PRIORITY: [
122       'gt-lg',
123       'lg',
124       'gt-md',
125       'md',
126       'gt-sm',
127       'sm'
128     ]
129   };
130 }
131 MdConstantFactory.$inject = ["$$rAF", "$sniffer"];
132
133 })();
134 (function(){
135 "use strict";
136
137   angular
138     .module('material.core')
139     .config( ["$provide", function($provide){
140        $provide.decorator('$mdUtil', ['$delegate', function ($delegate){
141            /**
142             * Inject the iterator facade to easily support iteration and accessors
143             * @see iterator below
144             */
145            $delegate.iterator = MdIterator;
146
147            return $delegate;
148          }
149        ]);
150      }]);
151
152   /**
153    * iterator is a list facade to easily support iteration and accessors
154    *
155    * @param items Array list which this iterator will enumerate
156    * @param reloop Boolean enables iterator to consider the list as an endless reloop
157    */
158   function MdIterator(items, reloop) {
159     var trueFn = function() { return true; };
160
161     if (items && !angular.isArray(items)) {
162       items = Array.prototype.slice.call(items);
163     }
164
165     reloop = !!reloop;
166     var _items = items || [ ];
167
168     // Published API
169     return {
170       items: getItems,
171       count: count,
172
173       inRange: inRange,
174       contains: contains,
175       indexOf: indexOf,
176       itemAt: itemAt,
177
178       findBy: findBy,
179
180       add: add,
181       remove: remove,
182
183       first: first,
184       last: last,
185       next: angular.bind(null, findSubsequentItem, false),
186       previous: angular.bind(null, findSubsequentItem, true),
187
188       hasPrevious: hasPrevious,
189       hasNext: hasNext
190
191     };
192
193     /**
194      * Publish copy of the enumerable set
195      * @returns {Array|*}
196      */
197     function getItems() {
198       return [].concat(_items);
199     }
200
201     /**
202      * Determine length of the list
203      * @returns {Array.length|*|number}
204      */
205     function count() {
206       return _items.length;
207     }
208
209     /**
210      * Is the index specified valid
211      * @param index
212      * @returns {Array.length|*|number|boolean}
213      */
214     function inRange(index) {
215       return _items.length && ( index > -1 ) && (index < _items.length );
216     }
217
218     /**
219      * Can the iterator proceed to the next item in the list; relative to
220      * the specified item.
221      *
222      * @param item
223      * @returns {Array.length|*|number|boolean}
224      */
225     function hasNext(item) {
226       return item ? inRange(indexOf(item) + 1) : false;
227     }
228
229     /**
230      * Can the iterator proceed to the previous item in the list; relative to
231      * the specified item.
232      *
233      * @param item
234      * @returns {Array.length|*|number|boolean}
235      */
236     function hasPrevious(item) {
237       return item ? inRange(indexOf(item) - 1) : false;
238     }
239
240     /**
241      * Get item at specified index/position
242      * @param index
243      * @returns {*}
244      */
245     function itemAt(index) {
246       return inRange(index) ? _items[index] : null;
247     }
248
249     /**
250      * Find all elements matching the key/value pair
251      * otherwise return null
252      *
253      * @param val
254      * @param key
255      *
256      * @return array
257      */
258     function findBy(key, val) {
259       return _items.filter(function(item) {
260         return item[key] === val;
261       });
262     }
263
264     /**
265      * Add item to list
266      * @param item
267      * @param index
268      * @returns {*}
269      */
270     function add(item, index) {
271       if ( !item ) return -1;
272
273       if (!angular.isNumber(index)) {
274         index = _items.length;
275       }
276
277       _items.splice(index, 0, item);
278
279       return indexOf(item);
280     }
281
282     /**
283      * Remove item from list...
284      * @param item
285      */
286     function remove(item) {
287       if ( contains(item) ){
288         _items.splice(indexOf(item), 1);
289       }
290     }
291
292     /**
293      * Get the zero-based index of the target item
294      * @param item
295      * @returns {*}
296      */
297     function indexOf(item) {
298       return _items.indexOf(item);
299     }
300
301     /**
302      * Boolean existence check
303      * @param item
304      * @returns {boolean}
305      */
306     function contains(item) {
307       return item && (indexOf(item) > -1);
308     }
309
310     /**
311      * Return first item in the list
312      * @returns {*}
313      */
314     function first() {
315       return _items.length ? _items[0] : null;
316     }
317
318     /**
319      * Return last item in the list...
320      * @returns {*}
321      */
322     function last() {
323       return _items.length ? _items[_items.length - 1] : null;
324     }
325
326     /**
327      * Find the next item. If reloop is true and at the end of the list, it will go back to the
328      * first item. If given, the `validate` callback will be used to determine whether the next item
329      * is valid. If not valid, it will try to find the next item again.
330      *
331      * @param {boolean} backwards Specifies the direction of searching (forwards/backwards)
332      * @param {*} item The item whose subsequent item we are looking for
333      * @param {Function=} validate The `validate` function
334      * @param {integer=} limit The recursion limit
335      *
336      * @returns {*} The subsequent item or null
337      */
338     function findSubsequentItem(backwards, item, validate, limit) {
339       validate = validate || trueFn;
340
341       var curIndex = indexOf(item);
342       while (true) {
343         if (!inRange(curIndex)) return null;
344
345         var nextIndex = curIndex + (backwards ? -1 : 1);
346         var foundItem = null;
347         if (inRange(nextIndex)) {
348           foundItem = _items[nextIndex];
349         } else if (reloop) {
350           foundItem = backwards ? last() : first();
351           nextIndex = indexOf(foundItem);
352         }
353
354         if ((foundItem === null) || (nextIndex === limit)) return null;
355         if (validate(foundItem)) return foundItem;
356
357         if (angular.isUndefined(limit)) limit = nextIndex;
358
359         curIndex = nextIndex;
360       }
361     }
362   }
363
364
365 })();
366 (function(){
367 "use strict";
368
369 angular.module('material.core')
370 .factory('$mdMedia', mdMediaFactory);
371
372 /**
373  * @ngdoc service
374  * @name $mdMedia
375  * @module material.core
376  *
377  * @description
378  * `$mdMedia` is used to evaluate whether a given media query is true or false given the
379  * current device's screen / window size. The media query will be re-evaluated on resize, allowing
380  * you to register a watch.
381  *
382  * `$mdMedia` also has pre-programmed support for media queries that match the layout breakpoints.
383  *  (`sm`, `gt-sm`, `md`, `gt-md`, `lg`, `gt-lg`).
384  *
385  * @returns {boolean} a boolean representing whether or not the given media query is true or false.
386  *
387  * @usage
388  * <hljs lang="js">
389  * app.controller('MyController', function($mdMedia, $scope) {
390  *   $scope.$watch(function() { return $mdMedia('lg'); }, function(big) {
391  *     $scope.bigScreen = big;
392  *   });
393  *
394  *   $scope.screenIsSmall = $mdMedia('sm');
395  *   $scope.customQuery = $mdMedia('(min-width: 1234px)');
396  *   $scope.anotherCustom = $mdMedia('max-width: 300px');
397  * });
398  * </hljs>
399  */
400
401 function mdMediaFactory($mdConstant, $rootScope, $window) {
402   var queries = {};
403   var mqls = {};
404   var results = {};
405   var normalizeCache = {};
406
407   $mdMedia.getResponsiveAttribute = getResponsiveAttribute;
408   $mdMedia.getQuery = getQuery;
409   $mdMedia.watchResponsiveAttributes = watchResponsiveAttributes;
410
411   return $mdMedia;
412
413   function $mdMedia(query) {
414     var validated = queries[query];
415     if (angular.isUndefined(validated)) {
416       validated = queries[query] = validate(query);
417     }
418
419     var result = results[validated];
420     if (angular.isUndefined(result)) {
421       result = add(validated);
422     }
423
424     return result;
425   }
426
427   function validate(query) {
428     return $mdConstant.MEDIA[query] ||
429            ((query.charAt(0) !== '(') ? ('(' + query + ')') : query);
430   }
431
432   function add(query) {
433     var result = mqls[query] = $window.matchMedia(query);
434     result.addListener(onQueryChange);
435     return (results[result.media] = !!result.matches);
436   }
437
438   function onQueryChange(query) {
439     $rootScope.$evalAsync(function() {
440       results[query.media] = !!query.matches;
441     });
442   }
443
444   function getQuery(name) {
445     return mqls[name];
446   }
447
448   function getResponsiveAttribute(attrs, attrName) {
449     for (var i = 0; i < $mdConstant.MEDIA_PRIORITY.length; i++) {
450       var mediaName = $mdConstant.MEDIA_PRIORITY[i];
451       if (!mqls[queries[mediaName]].matches) {
452         continue;
453       }
454
455       var normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
456       if (attrs[normalizedName]) {
457         return attrs[normalizedName];
458       }
459     }
460
461     // fallback on unprefixed
462     return attrs[getNormalizedName(attrs, attrName)];
463   }
464
465   function watchResponsiveAttributes(attrNames, attrs, watchFn) {
466     var unwatchFns = [];
467     attrNames.forEach(function(attrName) {
468       var normalizedName = getNormalizedName(attrs, attrName);
469       if (attrs[normalizedName]) {
470         unwatchFns.push(
471             attrs.$observe(normalizedName, angular.bind(void 0, watchFn, null)));
472       }
473
474       for (var mediaName in $mdConstant.MEDIA) {
475         normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
476         if (!attrs[normalizedName]) {
477           return;
478         }
479
480         unwatchFns.push(attrs.$observe(normalizedName, angular.bind(void 0, watchFn, mediaName)));
481       }
482     });
483
484     return function unwatch() {
485       unwatchFns.forEach(function(fn) { fn(); })
486     };
487   }
488
489   // Improves performance dramatically
490   function getNormalizedName(attrs, attrName) {
491     return normalizeCache[attrName] ||
492         (normalizeCache[attrName] = attrs.$normalize(attrName));
493   }
494 }
495 mdMediaFactory.$inject = ["$mdConstant", "$rootScope", "$window"];
496
497 })();
498 (function(){
499 "use strict";
500
501 /*
502  * This var has to be outside the angular factory, otherwise when
503  * there are multiple material apps on the same page, each app
504  * will create its own instance of this array and the app's IDs
505  * will not be unique.
506  */
507 var nextUniqueId = 0;
508
509 angular.module('material.core')
510 .factory('$mdUtil', ["$cacheFactory", "$document", "$timeout", "$q", "$window", "$mdConstant", function($cacheFactory, $document, $timeout, $q, $window, $mdConstant) {
511   var Util;
512
513   function getNode(el) {
514     return el[0] || el;
515   }
516
517   return Util = {
518     now: window.performance ?
519       angular.bind(window.performance, window.performance.now) :
520       Date.now,
521
522     clientRect: function(element, offsetParent, isOffsetRect) {
523       var node = getNode(element);
524       offsetParent = getNode(offsetParent || node.offsetParent || document.body);
525       var nodeRect = node.getBoundingClientRect();
526
527       // The user can ask for an offsetRect: a rect relative to the offsetParent,
528       // or a clientRect: a rect relative to the page
529       var offsetRect = isOffsetRect ?
530         offsetParent.getBoundingClientRect() :
531         { left: 0, top: 0, width: 0, height: 0 };
532       return {
533         left: nodeRect.left - offsetRect.left,
534         top: nodeRect.top - offsetRect.top,
535         width: nodeRect.width,
536         height: nodeRect.height
537       };
538     },
539     offsetRect: function(element, offsetParent) {
540       return Util.clientRect(element, offsetParent, true);
541     },
542     // Disables scroll around the passed element. Goes up the DOM to find a
543     // disableTarget (a md-content that is scrolling, or the body as a fallback)
544     // and uses CSS/JS to prevent it from scrolling
545     disableScrollAround: function(element) {
546       element = element instanceof angular.element ? element[0] : element;
547       var parentEl = element;
548       var disableTarget;
549
550       // Find the highest level scrolling md-content
551       while (parentEl = this.getClosest(parentEl, 'MD-CONTENT', true)) {
552         if (isScrolling(parentEl)) {
553           disableTarget = angular.element(parentEl)[0];
554         }
555       }
556
557       // Default to the body if no scrolling md-content
558       if (!disableTarget) {
559         disableTarget = $document[0].body;
560         if (!isScrolling(disableTarget)) return angular.noop;
561       }
562
563       if (disableTarget.nodeName == 'BODY') {
564         return disableBodyScroll();
565       } else {
566         return disableElementScroll();
567       }
568
569       // Creates a virtual scrolling mask to absorb touchmove, keyboard, scrollbar clicking, and wheel events
570       function disableElementScroll() {
571         var scrollMask = angular.element('<div class="md-scroll-mask"><div class="md-scroll-mask-bar"></div></div>');
572         var computedStyle = $window.getComputedStyle(disableTarget);
573         var disableRect = disableTarget.getBoundingClientRect();
574         var scrollWidth = disableRect.width - disableTarget.clientWidth;
575         applyStyles(scrollMask[0], {
576           zIndex: computedStyle.zIndex == 'auto' ? 2 : computedStyle.zIndex + 1,
577           width: disableRect.width + 'px',
578           height: disableRect.height + 'px',
579           top: disableRect.top + 'px',
580           left: disableRect.left + 'px'
581         });
582         scrollMask[0].firstElementChild.style.width = scrollWidth + 'px';
583         $document[0].body.appendChild(scrollMask[0]);
584
585         scrollMask.on('wheel', preventDefault);
586         scrollMask.on('touchmove', preventDefault);
587         $document.on('keydown', disableKeyNav);
588
589         return function restoreScroll() {
590           scrollMask.off('wheel');
591           scrollMask.off('touchmove');
592           scrollMask[0].parentNode.removeChild(scrollMask[0]);
593           $document.off('keydown', disableKeyNav);
594         };
595
596         // Prevent keypresses from elements inside the disableTarget
597         // used to stop the keypresses that could cause the page to scroll
598         // (arrow keys, spacebar, tab, etc).
599         function disableKeyNav(e) {
600           if (disableTarget.contains(e.target)) {
601             e.preventDefault();
602             e.stopImmediatePropagation();
603           }
604         }
605
606         function preventDefault(e) {
607           e.preventDefault();
608         }
609       }
610
611       // Converts the disableTarget (body) to a position fixed block and translate it to the propper scroll position
612       function disableBodyScroll() {
613         var restoreStyle = disableTarget.getAttribute('style') || '';
614         var scrollOffset = disableTarget.scrollTop;
615
616         applyStyles(disableTarget, {
617           position: 'fixed',
618           width: '100%',
619           overflowY: 'scroll',
620           top: -scrollOffset + 'px'
621         });
622
623         return function restoreScroll() {
624           disableTarget.setAttribute('style', restoreStyle);
625           disableTarget.scrollTop = scrollOffset;
626         };
627       }
628
629       function applyStyles (el, styles) {
630         for (var key in styles) {
631           el.style[key] = styles[key];
632         }
633       }
634
635       function isScrolling(el) {
636         if (el instanceof angular.element) el = el[0];
637         return el.scrollHeight > el.offsetHeight;
638       }
639     },
640
641     floatingScrollbars: function() {
642       if (this.floatingScrollbars.cached === undefined) {
643         var tempNode = angular.element('<div style="width: 100%; z-index: -1; position: absolute; height: 35px; overflow-y: scroll"><div style="height: 60;"></div></div>');
644         $document[0].body.appendChild(tempNode[0]);
645         this.floatingScrollbars.cached = (tempNode[0].offsetWidth == tempNode[0].childNodes[0].offsetWidth);
646         tempNode.remove();
647       }
648       return this.floatingScrollbars.cached;
649     },
650
651     // Mobile safari only allows you to set focus in click event listeners...
652     forceFocus: function(element) {
653       var node = element[0] || element;
654
655       document.addEventListener('click', function focusOnClick(ev) {
656         if (ev.target === node && ev.$focus) {
657           node.focus();
658           ev.stopImmediatePropagation();
659           ev.preventDefault();
660           node.removeEventListener('click', focusOnClick);
661         }
662       }, true);
663
664       var newEvent = document.createEvent('MouseEvents');
665       newEvent.initMouseEvent('click', false, true, window, {}, 0, 0, 0, 0,
666                        false, false, false, false, 0, null);
667       newEvent.$material = true;
668       newEvent.$focus = true;
669       node.dispatchEvent(newEvent);
670     },
671
672     transitionEndPromise: function(element, opts) {
673       opts = opts || {};
674       var deferred = $q.defer();
675       element.on($mdConstant.CSS.TRANSITIONEND, finished);
676       function finished(ev) {
677         // Make sure this transitionend didn't bubble up from a child
678         if (!ev || ev.target === element[0]) {
679           element.off($mdConstant.CSS.TRANSITIONEND, finished);
680           deferred.resolve();
681         }
682       }
683       if (opts.timeout) $timeout(finished, opts.timeout);
684       return deferred.promise;
685     },
686
687     fakeNgModel: function() {
688       return {
689         $fake: true,
690         $setTouched: angular.noop,
691         $setViewValue: function(value) {
692           this.$viewValue = value;
693           this.$render(value);
694           this.$viewChangeListeners.forEach(function(cb) { cb(); });
695         },
696         $isEmpty: function(value) {
697           return ('' + value).length === 0;
698         },
699         $parsers: [],
700         $formatters: [],
701         $viewChangeListeners: [],
702         $render: angular.noop
703       };
704     },
705
706     // Returns a function, that, as long as it continues to be invoked, will not
707     // be triggered. The function will be called after it stops being called for
708     // N milliseconds.
709     // @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs
710     // @param invokeApply should the $timeout trigger $digest() dirty checking
711     debounce: function (func, wait, scope, invokeApply) {
712       var timer;
713
714       return function debounced() {
715         var context = scope,
716           args = Array.prototype.slice.call(arguments);
717
718         $timeout.cancel(timer);
719         timer = $timeout(function() {
720
721           timer = undefined;
722           func.apply(context, args);
723
724         }, wait || 10, invokeApply );
725       };
726     },
727
728     // Returns a function that can only be triggered every `delay` milliseconds.
729     // In other words, the function will not be called unless it has been more
730     // than `delay` milliseconds since the last call.
731     throttle: function throttle(func, delay) {
732       var recent;
733       return function throttled() {
734         var context = this;
735         var args = arguments;
736         var now = Util.now();
737
738         if (!recent || (now - recent > delay)) {
739           func.apply(context, args);
740           recent = now;
741         }
742       };
743     },
744
745     /**
746      * Measures the number of milliseconds taken to run the provided callback
747      * function. Uses a high-precision timer if available.
748      */
749     time: function time(cb) {
750       var start = Util.now();
751       cb();
752       return Util.now() - start;
753     },
754
755     /**
756      * Get a unique ID.
757      *
758      * @returns {string} an unique numeric string
759      */
760     nextUid: function() {
761       return '' + nextUniqueId++;
762     },
763
764     // Stop watchers and events from firing on a scope without destroying it,
765     // by disconnecting it from its parent and its siblings' linked lists.
766     disconnectScope: function disconnectScope(scope) {
767       if (!scope) return;
768
769       // we can't destroy the root scope or a scope that has been already destroyed
770       if (scope.$root === scope) return;
771       if (scope.$$destroyed ) return;
772
773       var parent = scope.$parent;
774       scope.$$disconnected = true;
775
776       // See Scope.$destroy
777       if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling;
778       if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling;
779       if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
780       if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
781
782       scope.$$nextSibling = scope.$$prevSibling = null;
783
784     },
785
786     // Undo the effects of disconnectScope above.
787     reconnectScope: function reconnectScope(scope) {
788       if (!scope) return;
789
790       // we can't disconnect the root node or scope already disconnected
791       if (scope.$root === scope) return;
792       if (!scope.$$disconnected) return;
793
794       var child = scope;
795
796       var parent = child.$parent;
797       child.$$disconnected = false;
798       // See Scope.$new for this logic...
799       child.$$prevSibling = parent.$$childTail;
800       if (parent.$$childHead) {
801         parent.$$childTail.$$nextSibling = child;
802         parent.$$childTail = child;
803       } else {
804         parent.$$childHead = parent.$$childTail = child;
805       }
806     },
807
808     /*
809      * getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName
810      *
811      * @param el Element to start walking the DOM from
812      * @param tagName Tag name to find closest to el, such as 'form'
813      */
814     getClosest: function getClosest(el, tagName, onlyParent) {
815       if (el instanceof angular.element) el = el[0];
816       tagName = tagName.toUpperCase();
817       if (onlyParent) el = el.parentNode;
818       if (!el) return null;
819       do {
820         if (el.nodeName === tagName) {
821           return el;
822         }
823       } while (el = el.parentNode);
824       return null;
825     },
826
827     /**
828      * Functional equivalent for $element.filter(‘md-bottom-sheet’)
829      * useful with interimElements where the element and its container are important...
830      */
831     extractElementByName: function (element, nodeName) {
832       for (var i = 0, len = element.length; i < len; i++) {
833         if (element[i].nodeName.toLowerCase() === nodeName){
834           return angular.element(element[i]);
835         }
836       }
837       return element;
838     },
839
840     /**
841      * Give optional properties with no value a boolean true by default
842      */
843     initOptionalProperties: function (scope, attr, defaults ) {
844        defaults = defaults || { };
845        angular.forEach(scope.$$isolateBindings, function (binding, key) {
846          if (binding.optional && angular.isUndefined(scope[key])) {
847            var hasKey = attr.hasOwnProperty(attr.$normalize(binding.attrName));
848
849            scope[key] = angular.isDefined(defaults[key]) ? defaults[key] : hasKey;
850          }
851        });
852     }
853
854   };
855
856 }]);
857
858 /*
859  * Since removing jQuery from the demos, some code that uses `element.focus()` is broken.
860  *
861  * We need to add `element.focus()`, because it's testable unlike `element[0].focus`.
862  *
863  * TODO(ajoslin): This should be added in a better place later.
864  */
865
866 angular.element.prototype.focus = angular.element.prototype.focus || function() {
867   if (this.length) {
868     this[0].focus();
869   }
870   return this;
871 };
872 angular.element.prototype.blur = angular.element.prototype.blur || function() {
873   if (this.length) {
874     this[0].blur();
875   }
876   return this;
877 };
878
879 })();
880 (function(){
881 "use strict";
882
883
884 angular.module('material.core')
885   .service('$mdAria', AriaService);
886
887 /*
888  * @ngInject
889  */
890 function AriaService($$rAF, $log, $window) {
891
892   return {
893     expect: expect,
894     expectAsync: expectAsync,
895     expectWithText: expectWithText
896   };
897
898   /**
899    * Check if expected attribute has been specified on the target element or child
900    * @param element
901    * @param attrName
902    * @param {optional} defaultValue What to set the attr to if no value is found
903    */
904   function expect(element, attrName, defaultValue) {
905     var node = element[0] || element;
906
907     // if node exists and neither it nor its children have the attribute
908     if (node &&
909        ((!node.hasAttribute(attrName) ||
910         node.getAttribute(attrName).length === 0) &&
911         !childHasAttribute(node, attrName))) {
912
913       defaultValue = angular.isString(defaultValue) ? defaultValue.trim() : '';
914       if (defaultValue.length) {
915         element.attr(attrName, defaultValue);
916       } else {
917         $log.warn('ARIA: Attribute "', attrName, '", required for accessibility, is missing on node:', node);
918       }
919
920     }
921   }
922
923   function expectAsync(element, attrName, defaultValueGetter) {
924     // Problem: when retrieving the element's contents synchronously to find the label,
925     // the text may not be defined yet in the case of a binding.
926     // There is a higher chance that a binding will be defined if we wait one frame.
927     $$rAF(function() {
928       expect(element, attrName, defaultValueGetter());
929     });
930   }
931
932   function expectWithText(element, attrName) {
933     expectAsync(element, attrName, function() {
934       return getText(element);
935     });
936   }
937
938   function getText(element) {
939     return element.text().trim();
940   }
941
942   function childHasAttribute(node, attrName) {
943     var hasChildren = node.hasChildNodes(),
944         hasAttr = false;
945
946     function isHidden(el) {
947       var style = el.currentStyle ? el.currentStyle : $window.getComputedStyle(el);
948       return (style.display === 'none');
949     }
950
951     if(hasChildren) {
952       var children = node.childNodes;
953       for(var i=0; i<children.length; i++){
954         var child = children[i];
955         if(child.nodeType === 1 && child.hasAttribute(attrName)) {
956           if(!isHidden(child)){
957             hasAttr = true;
958           }
959         }
960       }
961     }
962     return hasAttr;
963   }
964 }
965 AriaService.$inject = ["$$rAF", "$log", "$window"];
966
967 })();
968 (function(){
969 "use strict";
970
971 angular.module('material.core')
972   .service('$mdCompiler', mdCompilerService);
973
974 function mdCompilerService($q, $http, $injector, $compile, $controller, $templateCache) {
975   /* jshint validthis: true */
976
977   /*
978    * @ngdoc service
979    * @name $mdCompiler
980    * @module material.core
981    * @description
982    * The $mdCompiler service is an abstraction of angular's compiler, that allows the developer
983    * to easily compile an element with a templateUrl, controller, and locals.
984    *
985    * @usage
986    * <hljs lang="js">
987    * $mdCompiler.compile({
988    *   templateUrl: 'modal.html',
989    *   controller: 'ModalCtrl',
990    *   locals: {
991    *     modal: myModalInstance;
992    *   }
993    * }).then(function(compileData) {
994    *   compileData.element; // modal.html's template in an element
995    *   compileData.link(myScope); //attach controller & scope to element
996    * });
997    * </hljs>
998    */
999
1000    /*
1001     * @ngdoc method
1002     * @name $mdCompiler#compile
1003     * @description A helper to compile an HTML template/templateUrl with a given controller,
1004     * locals, and scope.
1005     * @param {object} options An options object, with the following properties:
1006     *
1007     *    - `controller` - `{(string=|function()=}` Controller fn that should be associated with
1008     *      newly created scope or the name of a registered controller if passed as a string.
1009     *    - `controllerAs` - `{string=}` A controller alias name. If present the controller will be
1010     *      published to scope under the `controllerAs` name.
1011     *    - `template` - `{string=}` An html template as a string.
1012     *    - `templateUrl` - `{string=}` A path to an html template.
1013     *    - `transformTemplate` - `{function(template)=}` A function which transforms the template after
1014     *      it is loaded. It will be given the template string as a parameter, and should
1015     *      return a a new string representing the transformed template.
1016     *    - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
1017     *      be injected into the controller. If any of these dependencies are promises, the compiler
1018     *      will wait for them all to be resolved, or if one is rejected before the controller is
1019     *      instantiated `compile()` will fail..
1020     *      * `key` - `{string}`: a name of a dependency to be injected into the controller.
1021     *      * `factory` - `{string|function}`: If `string` then it is an alias for a service.
1022     *        Otherwise if function, then it is injected and the return value is treated as the
1023     *        dependency. If the result is a promise, it is resolved before its value is 
1024     *        injected into the controller.
1025     *
1026     * @returns {object=} promise A promise, which will be resolved with a `compileData` object.
1027     * `compileData` has the following properties: 
1028     *
1029     *   - `element` - `{element}`: an uncompiled element matching the provided template.
1030     *   - `link` - `{function(scope)}`: A link function, which, when called, will compile
1031     *     the element and instantiate the provided controller (if given).
1032     *   - `locals` - `{object}`: The locals which will be passed into the controller once `link` is
1033     *     called. If `bindToController` is true, they will be coppied to the ctrl instead
1034     *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
1035     */
1036   this.compile = function(options) {
1037     var templateUrl = options.templateUrl;
1038     var template = options.template || '';
1039     var controller = options.controller;
1040     var controllerAs = options.controllerAs;
1041     var resolve = options.resolve || {};
1042     var locals = options.locals || {};
1043     var transformTemplate = options.transformTemplate || angular.identity;
1044     var bindToController = options.bindToController;
1045
1046     // Take resolve values and invoke them.  
1047     // Resolves can either be a string (value: 'MyRegisteredAngularConst'),
1048     // or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {})
1049     angular.forEach(resolve, function(value, key) {
1050       if (angular.isString(value)) {
1051         resolve[key] = $injector.get(value);
1052       } else {
1053         resolve[key] = $injector.invoke(value);
1054       }
1055     });
1056     //Add the locals, which are just straight values to inject
1057     //eg locals: { three: 3 }, will inject three into the controller
1058     angular.extend(resolve, locals);
1059
1060     if (templateUrl) {
1061       resolve.$template = $http.get(templateUrl, {cache: $templateCache})
1062         .then(function(response) {
1063           return response.data;
1064         });
1065     } else {
1066       resolve.$template = $q.when(template);
1067     }
1068
1069     // Wait for all the resolves to finish if they are promises
1070     return $q.all(resolve).then(function(locals) {
1071
1072       var template = transformTemplate(locals.$template);
1073       var element = options.element || angular.element('<div>').html(template.trim()).contents();
1074       var linkFn = $compile(element);
1075
1076       //Return a linking function that can be used later when the element is ready
1077       return {
1078         locals: locals,
1079         element: element,
1080         link: function link(scope) {
1081           locals.$scope = scope;
1082
1083           //Instantiate controller if it exists, because we have scope
1084           if (controller) {
1085             var invokeCtrl = $controller(controller, locals, true);
1086             if (bindToController) {
1087               angular.extend(invokeCtrl.instance, locals);
1088             }
1089             var ctrl = invokeCtrl();
1090             //See angular-route source for this logic
1091             element.data('$ngControllerController', ctrl);
1092             element.children().data('$ngControllerController', ctrl);
1093
1094             if (controllerAs) {
1095               scope[controllerAs] = ctrl;
1096             }
1097           }
1098           return linkFn(scope);
1099         }
1100       };
1101     });
1102
1103   };
1104 }
1105 mdCompilerService.$inject = ["$q", "$http", "$injector", "$compile", "$controller", "$templateCache"];
1106
1107 })();
1108 (function(){
1109 "use strict";
1110
1111   var HANDLERS = {};
1112   /* The state of the current 'pointer'
1113    * The pointer represents the state of the current touch.
1114    * It contains normalized x and y coordinates from DOM events,
1115    * as well as other information abstracted from the DOM.
1116    */
1117   var pointer, lastPointer, forceSkipClickHijack = false;
1118
1119   // Used to attach event listeners once when multiple ng-apps are running.
1120   var isInitialized = false;
1121   
1122   angular
1123     .module('material.core.gestures', [ ])
1124     .provider('$mdGesture', MdGestureProvider)
1125     .factory('$$MdGestureHandler', MdGestureHandler)
1126     .run( attachToDocument );
1127
1128   /**
1129      * @ngdoc service
1130      * @name $mdGestureProvider
1131      * @module material.core.gestures
1132      *
1133      * @description
1134      * In some scenarios on Mobile devices (without jQuery), the click events should NOT be hijacked.
1135      * `$mdGestureProvider` is used to configure the Gesture module to ignore or skip click hijacking on mobile
1136      * devices.
1137      *
1138      * <hljs lang="js">
1139      *   app.config(function($mdGestureProvider) {
1140      *
1141      *     // For mobile devices without jQuery loaded, do not
1142      *     // intercept click events during the capture phase.
1143      *     $mdGestureProvider.skipClickHijack();
1144      *
1145      *   });
1146      * </hljs>
1147      *
1148      */
1149   function MdGestureProvider() { }
1150
1151   MdGestureProvider.prototype = {
1152
1153     // Publish access to setter to configure a variable  BEFORE the
1154     // $mdGesture service is instantiated...
1155     skipClickHijack: function() {
1156       return forceSkipClickHijack = true;
1157     },
1158
1159     /**
1160      * $get is used to build an instance of $mdGesture
1161      * @ngInject
1162      */
1163     $get : ["$$MdGestureHandler", "$$rAF", "$timeout", function($$MdGestureHandler, $$rAF, $timeout) {
1164          return new MdGesture($$MdGestureHandler, $$rAF, $timeout);
1165     }]
1166   };
1167
1168
1169
1170   /**
1171    * MdGesture factory construction function
1172    * @ngInject
1173    */
1174   function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
1175     var userAgent = navigator.userAgent || navigator.vendor || window.opera;
1176     var isIos = userAgent.match(/ipad|iphone|ipod/i);
1177     var isAndroid = userAgent.match(/android/i);
1178     var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
1179
1180     var self = {
1181       handler: addHandler,
1182       register: register,
1183       // On mobile w/out jQuery, we normally intercept clicks. Should we skip that?
1184       isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack
1185     };
1186
1187     if (self.isHijackingClicks) {
1188       self.handler('click', {
1189         options: {
1190           maxDistance: 6
1191         },
1192         onEnd: function (ev, pointer) {
1193           if (pointer.distance < this.state.options.maxDistance) {
1194             this.dispatchEvent(ev, 'click');
1195           }
1196         }
1197       });
1198     }
1199
1200     /*
1201      * Register an element to listen for a handler.
1202      * This allows an element to override the default options for a handler.
1203      * Additionally, some handlers like drag and hold only dispatch events if
1204      * the domEvent happens inside an element that's registered to listen for these events.
1205      *
1206      * @see GestureHandler for how overriding of default options works.
1207      * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horziontal: false })
1208      */
1209     function register(element, handlerName, options) {
1210       var handler = HANDLERS[handlerName.replace(/^\$md./, '')];
1211       if (!handler) {
1212         throw new Error('Failed to register element with handler ' + handlerName + '. ' +
1213         'Available handlers: ' + Object.keys(HANDLERS).join(', '));
1214       }
1215       return handler.registerElement(element, options);
1216     }
1217
1218     /*
1219      * add a handler to $mdGesture. see below.
1220      */
1221     function addHandler(name, definition) {
1222       var handler = new $$MdGestureHandler(name);
1223       angular.extend(handler, definition);
1224       HANDLERS[name] = handler;
1225
1226       return self;
1227     }
1228
1229     /*
1230      * Register handlers. These listen to touch/start/move events, interpret them,
1231      * and dispatch gesture events depending on options & conditions. These are all
1232      * instances of GestureHandler.
1233      * @see GestureHandler 
1234      */
1235     return self
1236       /*
1237        * The press handler dispatches an event on touchdown/touchend.
1238        * It's a simple abstraction of touch/mouse/pointer start and end.
1239        */
1240       .handler('press', {
1241         onStart: function (ev, pointer) {
1242           this.dispatchEvent(ev, '$md.pressdown');
1243         },
1244         onEnd: function (ev, pointer) {
1245           this.dispatchEvent(ev, '$md.pressup');
1246         }
1247       })
1248
1249       /*
1250        * The hold handler dispatches an event if the user keeps their finger within
1251        * the same <maxDistance> area for <delay> ms.
1252        * The hold handler will only run if a parent of the touch target is registered
1253        * to listen for hold events through $mdGesture.register()
1254        */
1255       .handler('hold', {
1256         options: {
1257           maxDistance: 6,
1258           delay: 500
1259         },
1260         onCancel: function () {
1261           $timeout.cancel(this.state.timeout);
1262         },
1263         onStart: function (ev, pointer) {
1264           // For hold, require a parent to be registered with $mdGesture.register()
1265           // Because we prevent scroll events, this is necessary.
1266           if (!this.state.registeredParent) return this.cancel();
1267
1268           this.state.pos = {x: pointer.x, y: pointer.y};
1269           this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() {
1270             this.dispatchEvent(ev, '$md.hold');
1271             this.cancel(); //we're done!
1272           }), this.state.options.delay, false);
1273         },
1274         onMove: function (ev, pointer) {
1275           // Don't scroll while waiting for hold.
1276           // If we don't preventDefault touchmove events here, Android will assume we don't
1277           // want to listen to anymore touch events. It will start scrolling and stop sending
1278           // touchmove events.
1279           ev.preventDefault();
1280
1281           // If the user moves greater than <maxDistance> pixels, stop the hold timer
1282           // set in onStart
1283           var dx = this.state.pos.x - pointer.x;
1284           var dy = this.state.pos.y - pointer.y;
1285           if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) {
1286             this.cancel();
1287           }
1288         },
1289         onEnd: function () {
1290           this.onCancel();
1291         }
1292       })
1293
1294       /*
1295        * The drag handler dispatches a drag event if the user holds and moves his finger greater than
1296        * <minDistance> px in the x or y direction, depending on options.horizontal.
1297        * The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in
1298        * the perpindicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>
1299        * pixels vertically, this handler won't consider the move part of a drag.
1300        */
1301       .handler('drag', {
1302         options: {
1303           minDistance: 6,
1304           horizontal: true,
1305           cancelMultiplier: 1.5
1306         },
1307         onStart: function (ev) {
1308           // For drag, require a parent to be registered with $mdGesture.register()
1309           if (!this.state.registeredParent) this.cancel();
1310         },
1311         onMove: function (ev, pointer) {
1312           var shouldStartDrag, shouldCancel;
1313           // Don't scroll while deciding if this touchmove qualifies as a drag event.
1314           // If we don't preventDefault touchmove events here, Android will assume we don't
1315           // want to listen to anymore touch events. It will start scrolling and stop sending
1316           // touchmove events.
1317           ev.preventDefault();
1318
1319           if (!this.state.dragPointer) {
1320             if (this.state.options.horizontal) {
1321               shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance;
1322               shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier;
1323             } else {
1324               shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance;
1325               shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier;
1326             }
1327
1328             if (shouldStartDrag) {
1329               // Create a new pointer representing this drag, starting at this point where the drag started.
1330               this.state.dragPointer = makeStartPointer(ev);
1331               updatePointerState(ev, this.state.dragPointer);
1332               this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer);
1333
1334             } else if (shouldCancel) {
1335               this.cancel();
1336             }
1337           } else {
1338             this.dispatchDragMove(ev);
1339           }
1340         },
1341         // Only dispatch dragmove events every frame; any more is unnecessray
1342         dispatchDragMove: $$rAF.throttle(function (ev) {
1343           // Make sure the drag didn't stop while waiting for the next frame
1344           if (this.state.isRunning) {
1345             updatePointerState(ev, this.state.dragPointer);
1346             this.dispatchEvent(ev, '$md.drag', this.state.dragPointer);
1347           }
1348         }),
1349         onEnd: function (ev, pointer) {
1350           if (this.state.dragPointer) {
1351             updatePointerState(ev, this.state.dragPointer);
1352             this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer);
1353           }
1354         }
1355       })
1356
1357       /*
1358        * The swipe handler will dispatch a swipe event if, on the end of a touch,
1359        * the velocity and distance were high enough.
1360        * TODO: add vertical swiping with a `horizontal` option similar to the drag handler.
1361        */
1362       .handler('swipe', {
1363         options: {
1364           minVelocity: 0.65,
1365           minDistance: 10
1366         },
1367         onEnd: function (ev, pointer) {
1368           if (Math.abs(pointer.velocityX) > this.state.options.minVelocity &&
1369             Math.abs(pointer.distanceX) > this.state.options.minDistance) {
1370             var eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight';
1371             this.dispatchEvent(ev, eventType);
1372           }
1373         }
1374       });
1375
1376   }
1377   MdGesture.$inject = ["$$MdGestureHandler", "$$rAF", "$timeout"];
1378
1379   /**
1380    * MdGestureHandler
1381    * A GestureHandler is an object which is able to dispatch custom dom events
1382    * based on native dom {touch,pointer,mouse}{start,move,end} events.
1383    *
1384    * A gesture will manage its lifecycle through the start,move,end, and cancel
1385    * functions, which are called by native dom events.
1386    *
1387    * A gesture has the concept of 'options' (eg a swipe's required velocity), which can be
1388    * overridden by elements registering through $mdGesture.register()
1389    */
1390   function GestureHandler (name) {
1391     this.name = name;
1392     this.state = {};
1393   }
1394
1395   function MdGestureHandler() {
1396     var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
1397
1398     GestureHandler.prototype = {
1399       options: {},
1400       // jQuery listeners don't work with custom DOMEvents, so we have to dispatch events
1401       // differently when jQuery is loaded
1402       dispatchEvent: hasJQuery ?  jQueryDispatchEvent : nativeDispatchEvent,
1403
1404       // These are overridden by the registered handler
1405       onStart: angular.noop,
1406       onMove: angular.noop,
1407       onEnd: angular.noop,
1408       onCancel: angular.noop,
1409
1410       // onStart sets up a new state for the handler, which includes options from the
1411       // nearest registered parent element of ev.target.
1412       start: function (ev, pointer) {
1413         if (this.state.isRunning) return;
1414         var parentTarget = this.getNearestParent(ev.target);
1415         // Get the options from the nearest registered parent
1416         var parentTargetOptions = parentTarget && parentTarget.$mdGesture[this.name] || {};
1417
1418         this.state = {
1419           isRunning: true,
1420           // Override the default options with the nearest registered parent's options
1421           options: angular.extend({}, this.options, parentTargetOptions),
1422           // Pass in the registered parent node to the state so the onStart listener can use
1423           registeredParent: parentTarget
1424         };
1425         this.onStart(ev, pointer);
1426       },
1427       move: function (ev, pointer) {
1428         if (!this.state.isRunning) return;
1429         this.onMove(ev, pointer);
1430       },
1431       end: function (ev, pointer) {
1432         if (!this.state.isRunning) return;
1433         this.onEnd(ev, pointer);
1434         this.state.isRunning = false;
1435       },
1436       cancel: function (ev, pointer) {
1437         this.onCancel(ev, pointer);
1438         this.state = {};
1439       },
1440
1441       // Find and return the nearest parent element that has been registered to
1442       // listen for this handler via $mdGesture.register(element, 'handlerName').
1443       getNearestParent: function (node) {
1444         var current = node;
1445         while (current) {
1446           if ((current.$mdGesture || {})[this.name]) {
1447             return current;
1448           }
1449           current = current.parentNode;
1450         }
1451         return null;
1452       },
1453
1454       // Called from $mdGesture.register when an element reigsters itself with a handler.
1455       // Store the options the user gave on the DOMElement itself. These options will
1456       // be retrieved with getNearestParent when the handler starts.
1457       registerElement: function (element, options) {
1458         var self = this;
1459         element[0].$mdGesture = element[0].$mdGesture || {};
1460         element[0].$mdGesture[this.name] = options || {};
1461         element.on('$destroy', onDestroy);
1462
1463         return onDestroy;
1464
1465         function onDestroy() {
1466           delete element[0].$mdGesture[self.name];
1467           element.off('$destroy', onDestroy);
1468         }
1469       }
1470     };
1471
1472     return GestureHandler;
1473
1474     /*
1475      * Dispatch an event with jQuery
1476      * TODO: Make sure this sends bubbling events
1477      *
1478      * @param srcEvent the original DOM touch event that started this.
1479      * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
1480      * @param eventPointer the pointer object that matches this event.
1481      */
1482     function jQueryDispatchEvent(srcEvent, eventType, eventPointer) {
1483       eventPointer = eventPointer || pointer;
1484       var eventObj = new angular.element.Event(eventType);
1485
1486       eventObj.$material = true;
1487       eventObj.pointer = eventPointer;
1488       eventObj.srcEvent = srcEvent;
1489
1490       angular.extend(eventObj, {
1491         clientX: eventPointer.x,
1492         clientY: eventPointer.y,
1493         screenX: eventPointer.x,
1494         screenY: eventPointer.y,
1495         pageX: eventPointer.x,
1496         pageY: eventPointer.y,
1497         ctrlKey: srcEvent.ctrlKey,
1498         altKey: srcEvent.altKey,
1499         shiftKey: srcEvent.shiftKey,
1500         metaKey: srcEvent.metaKey
1501       });
1502       angular.element(eventPointer.target).trigger(eventObj);
1503     }
1504
1505     /*
1506      * NOTE: nativeDispatchEvent is very performance sensitive.
1507      * @param srcEvent the original DOM touch event that started this.
1508      * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
1509      * @param eventPointer the pointer object that matches this event.
1510      */
1511     function nativeDispatchEvent(srcEvent, eventType, eventPointer) {
1512       eventPointer = eventPointer || pointer;
1513       var eventObj;
1514
1515       if (eventType === 'click') {
1516         eventObj = document.createEvent('MouseEvents');
1517         eventObj.initMouseEvent(
1518           'click', true, true, window, srcEvent.detail,
1519           eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y,
1520           srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey,
1521           srcEvent.button, srcEvent.relatedTarget || null
1522         );
1523
1524       } else {
1525         eventObj = document.createEvent('CustomEvent');
1526         eventObj.initCustomEvent(eventType, true, true, {});
1527       }
1528       eventObj.$material = true;
1529       eventObj.pointer = eventPointer;
1530       eventObj.srcEvent = srcEvent;
1531       eventPointer.target.dispatchEvent(eventObj);
1532     }
1533
1534   }
1535
1536   /**
1537    * Attach Gestures: hook document and check shouldHijack clicks
1538    * @ngInject
1539    */
1540   function attachToDocument( $mdGesture, $$MdGestureHandler ) {
1541
1542     // Polyfill document.contains for IE11.
1543     // TODO: move to util
1544     document.contains || (document.contains = function (node) {
1545       return document.body.contains(node);
1546     });
1547
1548     if (!isInitialized && $mdGesture.isHijackingClicks ) {
1549       /*
1550        * If hijack clicks is true, we preventDefault any click that wasn't
1551        * sent by ngMaterial. This is because on older Android & iOS, a false, or 'ghost',
1552        * click event will be sent ~400ms after a touchend event happens.
1553        * The only way to know if this click is real is to prevent any normal
1554        * click events, and add a flag to events sent by material so we know not to prevent those.
1555        * 
1556        * Two exceptions to click events that should be prevented are:
1557        *  - click events sent by the keyboard (eg form submit)
1558        *  - events that originate from an Ionic app
1559        */
1560       document.addEventListener('click', function clickHijacker(ev) {
1561         var isKeyClick = ev.clientX === 0 && ev.clientY === 0;
1562         if (!isKeyClick && !ev.$material && !ev.isIonicTap) {
1563           ev.preventDefault();
1564           ev.stopPropagation();
1565         }
1566       }, true);
1567       
1568       isInitialized = true;
1569     }
1570
1571     // Listen to all events to cover all platforms.
1572     var START_EVENTS = 'mousedown touchstart pointerdown';
1573     var MOVE_EVENTS = 'mousemove touchmove pointermove';
1574     var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel';
1575
1576     angular.element(document)
1577       .on(START_EVENTS, gestureStart)
1578       .on(MOVE_EVENTS, gestureMove)
1579       .on(END_EVENTS, gestureEnd)
1580       // For testing
1581       .on('$$mdGestureReset', function gestureClearCache () {
1582         lastPointer = pointer = null;
1583       });
1584
1585     /*
1586      * When a DOM event happens, run all registered gesture handlers' lifecycle
1587      * methods which match the DOM event.
1588      * Eg when a 'touchstart' event happens, runHandlers('start') will call and
1589      * run `handler.cancel()` and `handler.start()` on all registered handlers.
1590      */
1591     function runHandlers(handlerEvent, event) {
1592       var handler;
1593       for (var name in HANDLERS) {
1594         handler = HANDLERS[name];
1595         if( handler instanceof $$MdGestureHandler ) {
1596
1597           if (handlerEvent === 'start') {
1598             // Run cancel to reset any handlers' state
1599             handler.cancel();
1600           }
1601           handler[handlerEvent](event, pointer);
1602
1603         }
1604       }
1605     }
1606
1607     /*
1608      * gestureStart vets if a start event is legitimate (and not part of a 'ghost click' from iOS/Android)
1609      * If it is legitimate, we initiate the pointer state and mark the current pointer's type
1610      * For example, for a touchstart event, mark the current pointer as a 'touch' pointer, so mouse events
1611      * won't effect it.
1612      */
1613     function gestureStart(ev) {
1614       // If we're already touched down, abort
1615       if (pointer) return;
1616
1617       var now = +Date.now();
1618
1619       // iOS & old android bug: after a touch event, a click event is sent 350 ms later.
1620       // If <400ms have passed, don't allow an event of a different type than the previous event
1621       if (lastPointer && !typesMatch(ev, lastPointer) && (now - lastPointer.endTime < 1500)) {
1622         return;
1623       }
1624
1625       pointer = makeStartPointer(ev);
1626
1627       runHandlers('start', ev);
1628     }
1629     /*
1630      * If a move event happens of the right type, update the pointer and run all the move handlers.
1631      * "of the right type": if a mousemove happens but our pointer started with a touch event, do nothing.
1632      */
1633     function gestureMove(ev) {
1634       if (!pointer || !typesMatch(ev, pointer)) return;
1635
1636       updatePointerState(ev, pointer);
1637       runHandlers('move', ev);
1638     }
1639     /*
1640      * If an end event happens of the right type, update the pointer, run endHandlers, and save the pointer as 'lastPointer'
1641      */
1642     function gestureEnd(ev) {
1643       if (!pointer || !typesMatch(ev, pointer)) return;
1644
1645       updatePointerState(ev, pointer);
1646       pointer.endTime = +Date.now();
1647
1648       runHandlers('end', ev);
1649
1650       lastPointer = pointer;
1651       pointer = null;
1652     }
1653
1654   }
1655   attachToDocument.$inject = ["$mdGesture", "$$MdGestureHandler"];
1656
1657   // ********************
1658   // Module Functions
1659   // ********************
1660
1661   /*
1662    * Initiate the pointer. x, y, and the pointer's type.
1663    */
1664   function makeStartPointer(ev) {
1665     var point = getEventPoint(ev);
1666     var startPointer = {
1667       startTime: +Date.now(),
1668       target: ev.target,
1669       // 'p' for pointer events, 'm' for mouse, 't' for touch
1670       type: ev.type.charAt(0)
1671     };
1672     startPointer.startX = startPointer.x = point.pageX;
1673     startPointer.startY = startPointer.y = point.pageY;
1674     return startPointer;
1675   }
1676
1677   /*
1678    * return whether the pointer's type matches the event's type.
1679    * Eg if a touch event happens but the pointer has a mouse type, return false.
1680    */
1681   function typesMatch(ev, pointer) {
1682     return ev && pointer && ev.type.charAt(0) === pointer.type;
1683   }
1684
1685   /*
1686    * Update the given pointer based upon the given DOMEvent.
1687    * Distance, velocity, direction, duration, etc
1688    */
1689   function updatePointerState(ev, pointer) {
1690     var point = getEventPoint(ev);
1691     var x = pointer.x = point.pageX;
1692     var y = pointer.y = point.pageY;
1693
1694     pointer.distanceX = x - pointer.startX;
1695     pointer.distanceY = y - pointer.startY;
1696     pointer.distance = Math.sqrt(
1697       pointer.distanceX * pointer.distanceX + pointer.distanceY * pointer.distanceY
1698     );
1699
1700     pointer.directionX = pointer.distanceX > 0 ? 'right' : pointer.distanceX < 0 ? 'left' : '';
1701     pointer.directionY = pointer.distanceY > 0 ? 'up' : pointer.distanceY < 0 ? 'down' : '';
1702
1703     pointer.duration = +Date.now() - pointer.startTime;
1704     pointer.velocityX = pointer.distanceX / pointer.duration;
1705     pointer.velocityY = pointer.distanceY / pointer.duration;
1706   }
1707
1708   /*
1709    * Normalize the point where the DOM event happened whether it's touch or mouse.
1710    * @returns point event obj with pageX and pageY on it.
1711    */
1712   function getEventPoint(ev) {
1713     ev = ev.originalEvent || ev; // support jQuery events
1714     return (ev.touches && ev.touches[0]) ||
1715       (ev.changedTouches && ev.changedTouches[0]) ||
1716       ev;
1717   }
1718
1719 })();
1720 (function(){
1721 "use strict";
1722
1723 angular.module('material.core')
1724   .provider('$$interimElement', InterimElementProvider);
1725
1726 /*
1727  * @ngdoc service
1728  * @name $$interimElement
1729  * @module material.core
1730  *
1731  * @description
1732  *
1733  * Factory that contructs `$$interimElement.$service` services.
1734  * Used internally in material design for elements that appear on screen temporarily.
1735  * The service provides a promise-like API for interacting with the temporary
1736  * elements.
1737  *
1738  * ```js
1739  * app.service('$mdToast', function($$interimElement) {
1740  *   var $mdToast = $$interimElement(toastDefaultOptions);
1741  *   return $mdToast;
1742  * });
1743  * ```
1744  * @param {object=} defaultOptions Options used by default for the `show` method on the service.
1745  *
1746  * @returns {$$interimElement.$service}
1747  *
1748  */
1749
1750 function InterimElementProvider() {
1751   createInterimElementProvider.$get = InterimElementFactory;
1752   InterimElementFactory.$inject = ["$document", "$q", "$rootScope", "$timeout", "$rootElement", "$animate", "$interpolate", "$mdCompiler", "$mdTheming"];
1753   return createInterimElementProvider;
1754
1755   /**
1756    * Returns a new provider which allows configuration of a new interimElement
1757    * service. Allows configuration of default options & methods for options,
1758    * as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method)
1759    */
1760   function createInterimElementProvider(interimFactoryName) {
1761     var EXPOSED_METHODS = ['onHide', 'onShow', 'onRemove'];
1762
1763     var customMethods = {};
1764     var providerConfig = {
1765       presets: {}
1766     };
1767
1768     var provider = {
1769       setDefaults: setDefaults,
1770       addPreset: addPreset,
1771       addMethod: addMethod,
1772       $get: factory
1773     };
1774
1775     /**
1776      * all interim elements will come with the 'build' preset
1777      */
1778     provider.addPreset('build', {
1779       methods: ['controller', 'controllerAs', 'resolve',
1780         'template', 'templateUrl', 'themable', 'transformTemplate', 'parent']
1781     });
1782
1783     factory.$inject = ["$$interimElement", "$animate", "$injector"];
1784     return provider;
1785
1786     /**
1787      * Save the configured defaults to be used when the factory is instantiated
1788      */
1789     function setDefaults(definition) {
1790       providerConfig.optionsFactory = definition.options;
1791       providerConfig.methods = (definition.methods || []).concat(EXPOSED_METHODS);
1792       return provider;
1793     }
1794
1795     /**
1796      * Add a method to the factory that isn't specific to any interim element operations
1797      */
1798
1799     function addMethod(name, fn) {
1800       customMethods[name] = fn;
1801       return provider;
1802     }
1803
1804     /**
1805      * Save the configured preset to be used when the factory is instantiated
1806      */
1807     function addPreset(name, definition) {
1808       definition = definition || {};
1809       definition.methods = definition.methods || [];
1810       definition.options = definition.options || function() { return {}; };
1811
1812       if (/^cancel|hide|show$/.test(name)) {
1813         throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!");
1814       }
1815       if (definition.methods.indexOf('_options') > -1) {
1816         throw new Error("Method '_options' in " + interimFactoryName + " is reserved!");
1817       }
1818       providerConfig.presets[name] = {
1819         methods: definition.methods.concat(EXPOSED_METHODS),
1820         optionsFactory: definition.options,
1821         argOption: definition.argOption
1822       };
1823       return provider;
1824     }
1825
1826     /**
1827      * Create a factory that has the given methods & defaults implementing interimElement
1828      */
1829     /* @ngInject */
1830     function factory($$interimElement, $animate, $injector) {
1831       var defaultMethods;
1832       var defaultOptions;
1833       var interimElementService = $$interimElement();
1834
1835       /*
1836        * publicService is what the developer will be using.
1837        * It has methods hide(), cancel(), show(), build(), and any other
1838        * presets which were set during the config phase.
1839        */
1840       var publicService = {
1841         hide: interimElementService.hide,
1842         cancel: interimElementService.cancel,
1843         show: showInterimElement
1844       };
1845
1846       defaultMethods = providerConfig.methods || [];
1847       // This must be invoked after the publicService is initialized
1848       defaultOptions = invokeFactory(providerConfig.optionsFactory, {});
1849
1850       // Copy over the simple custom methods
1851       angular.forEach(customMethods, function(fn, name) {
1852         publicService[name] = fn;
1853       });
1854
1855       angular.forEach(providerConfig.presets, function(definition, name) {
1856         var presetDefaults = invokeFactory(definition.optionsFactory, {});
1857         var presetMethods = (definition.methods || []).concat(defaultMethods);
1858
1859         // Every interimElement built with a preset has a field called `$type`,
1860         // which matches the name of the preset.
1861         // Eg in preset 'confirm', options.$type === 'confirm'
1862         angular.extend(presetDefaults, { $type: name });
1863
1864         // This creates a preset class which has setter methods for every
1865         // method given in the `.addPreset()` function, as well as every
1866         // method given in the `.setDefaults()` function.
1867         //
1868         // @example
1869         // .setDefaults({
1870         //   methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'],
1871         //   options: dialogDefaultOptions
1872         // })
1873         // .addPreset('alert', {
1874         //   methods: ['title', 'ok'],
1875         //   options: alertDialogOptions
1876         // })
1877         //
1878         // Set values will be passed to the options when interimElemnt.show() is called.
1879         function Preset(opts) {
1880           this._options = angular.extend({}, presetDefaults, opts);
1881         }
1882         angular.forEach(presetMethods, function(name) {
1883           Preset.prototype[name] = function(value) {
1884             this._options[name] = value;
1885             return this;
1886           };
1887         });
1888
1889         // Create shortcut method for one-linear methods
1890         if (definition.argOption) {
1891           var methodName = 'show' + name.charAt(0).toUpperCase() + name.slice(1);
1892           publicService[methodName] = function(arg) {
1893             var config = publicService[name](arg);
1894             return publicService.show(config);
1895           };
1896         }
1897
1898         // eg $mdDialog.alert() will return a new alert preset
1899         publicService[name] = function(arg) {
1900           // If argOption is supplied, eg `argOption: 'content'`, then we assume
1901           // if the argument is not an options object then it is the `argOption` option.
1902           //
1903           // @example `$mdToast.simple('hello')` // sets options.content to hello
1904           //                                     // because argOption === 'content'
1905           if (arguments.length && definition.argOption && !angular.isObject(arg) &&
1906               !angular.isArray(arg)) {
1907             return (new Preset())[definition.argOption](arg);
1908           } else {
1909             return new Preset(arg);
1910           }
1911
1912         };
1913       });
1914
1915       return publicService;
1916
1917       function showInterimElement(opts) {
1918         // opts is either a preset which stores its options on an _options field,
1919         // or just an object made up of options
1920         if (opts && opts._options) opts = opts._options;
1921         return interimElementService.show(
1922           angular.extend({}, defaultOptions, opts)
1923         );
1924       }
1925
1926       /**
1927        * Helper to call $injector.invoke with a local of the factory name for
1928        * this provider.
1929        * If an $mdDialog is providing options for a dialog and tries to inject
1930        * $mdDialog, a circular dependency error will happen.
1931        * We get around that by manually injecting $mdDialog as a local.
1932        */
1933       function invokeFactory(factory, defaultVal) {
1934         var locals = {};
1935         locals[interimFactoryName] = publicService;
1936         return $injector.invoke(factory || function() { return defaultVal; }, {}, locals);
1937       }
1938
1939     }
1940
1941   }
1942
1943   /* @ngInject */
1944   function InterimElementFactory($document, $q, $rootScope, $timeout, $rootElement, $animate,
1945                                  $interpolate, $mdCompiler, $mdTheming ) {
1946     var startSymbol = $interpolate.startSymbol(),
1947         endSymbol = $interpolate.endSymbol(),
1948         usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}')),
1949         processTemplate  = usesStandardSymbols ? angular.identity : replaceInterpolationSymbols;
1950
1951     return function createInterimElementService() {
1952       /*
1953        * @ngdoc service
1954        * @name $$interimElement.$service
1955        *
1956        * @description
1957        * A service used to control inserting and removing an element into the DOM.
1958        *
1959        */
1960       var stack = [];
1961       var service;
1962       return service = {
1963         show: show,
1964         hide: hide,
1965         cancel: cancel
1966       };
1967
1968       /*
1969        * @ngdoc method
1970        * @name $$interimElement.$service#show
1971        * @kind function
1972        *
1973        * @description
1974        * Adds the `$interimElement` to the DOM and returns a promise that will be resolved or rejected
1975        * with hide or cancel, respectively.
1976        *
1977        * @param {*} options is hashMap of settings
1978        * @returns a Promise
1979        *
1980        */
1981       function show(options) {
1982         if (stack.length) {
1983           return service.cancel().then(function() {
1984             return show(options);
1985           });
1986         } else {
1987           var interimElement = new InterimElement(options);
1988           stack.push(interimElement);
1989           return interimElement.show().then(function() {
1990             return interimElement.deferred.promise;
1991           });
1992         }
1993       }
1994
1995       /*
1996        * @ngdoc method
1997        * @name $$interimElement.$service#hide
1998        * @kind function
1999        *
2000        * @description
2001        * Removes the `$interimElement` from the DOM and resolves the promise returned from `show`
2002        *
2003        * @param {*} resolveParam Data to resolve the promise with
2004        * @returns a Promise that will be resolved after the element has been removed.
2005        *
2006        */
2007       function hide(response) {
2008         var interimElement = stack.shift();
2009         return interimElement && interimElement.remove().then(function() {
2010           interimElement.deferred.resolve(response);
2011         });
2012       }
2013
2014       /*
2015        * @ngdoc method
2016        * @name $$interimElement.$service#cancel
2017        * @kind function
2018        *
2019        * @description
2020        * Removes the `$interimElement` from the DOM and rejects the promise returned from `show`
2021        *
2022        * @param {*} reason Data to reject the promise with
2023        * @returns Promise that will be resolved after the element has been removed.
2024        *
2025        */
2026       function cancel(reason) {
2027         var interimElement = stack.shift();
2028         return $q.when(interimElement && interimElement.remove().then(function() {
2029           interimElement.deferred.reject(reason);
2030         }));
2031       }
2032
2033
2034       /*
2035        * Internal Interim Element Object
2036        * Used internally to manage the DOM element and related data
2037        */
2038       function InterimElement(options) {
2039         var self;
2040         var hideTimeout, element, showDone, removeDone;
2041
2042         options = options || {};
2043         options = angular.extend({
2044           preserveScope: false,
2045           scope: options.scope || $rootScope.$new(options.isolateScope),
2046           onShow: function(scope, element, options) {
2047             return $animate.enter(element, options.parent);
2048           },
2049           onRemove: function(scope, element, options) {
2050             // Element could be undefined if a new element is shown before
2051             // the old one finishes compiling.
2052             return element && $animate.leave(element) || $q.when();
2053           }
2054         }, options);
2055
2056         if (options.template) {
2057           options.template = processTemplate(options.template);
2058         }
2059
2060         return self = {
2061           options: options,
2062           deferred: $q.defer(),
2063           show: function() {
2064             var compilePromise;
2065             if (options.skipCompile) {
2066               compilePromise = $q(function(resolve) { 
2067                 resolve({
2068                   locals: {},
2069                   link: function() { return options.element; }
2070                 });
2071               });
2072             } else {
2073               compilePromise = $mdCompiler.compile(options);
2074             }
2075
2076             return showDone = compilePromise.then(function(compileData) {
2077               angular.extend(compileData.locals, self.options);
2078
2079               element = compileData.link(options.scope);
2080
2081               // Search for parent at insertion time, if not specified
2082               if (angular.isFunction(options.parent)) {
2083                 options.parent = options.parent(options.scope, element, options);
2084               } else if (angular.isString(options.parent)) {
2085                 options.parent = angular.element($document[0].querySelector(options.parent));
2086               }
2087
2088               // If parent querySelector/getter function fails, or it's just null,
2089               // find a default.
2090               if (!(options.parent || {}).length) {
2091                 var el;
2092                 if ($rootElement[0] && $rootElement[0].querySelector) {
2093                   el = $rootElement[0].querySelector(':not(svg) > body');
2094                 }
2095                 if (!el) el = $rootElement[0];
2096                 if (el.nodeName == '#comment') {
2097                   el = $document[0].body;
2098                 }
2099                 options.parent = angular.element(el);
2100               }
2101
2102               if (options.themable) $mdTheming(element);
2103               var ret = options.onShow(options.scope, element, options);
2104               return $q.when(ret)
2105                 .then(function(){
2106                   // Issue onComplete callback when the `show()` finishes
2107                   (options.onComplete || angular.noop)(options.scope, element, options);
2108                   startHideTimeout();
2109                 });
2110
2111               function startHideTimeout() {
2112                 if (options.hideDelay) {
2113                   hideTimeout = $timeout(service.cancel, options.hideDelay) ;
2114                 }
2115               }
2116             }, function(reason) { showDone = true; self.deferred.reject(reason); });
2117           },
2118           cancelTimeout: function() {
2119             if (hideTimeout) {
2120               $timeout.cancel(hideTimeout);
2121               hideTimeout = undefined;
2122             }
2123           },
2124           remove: function() {
2125             self.cancelTimeout();
2126             return removeDone = $q.when(showDone).then(function() {
2127               var ret = element ? options.onRemove(options.scope, element, options) : true;
2128               return $q.when(ret).then(function() {
2129                 if (!options.preserveScope) options.scope.$destroy();
2130                 removeDone = true;
2131               });
2132             });
2133           }
2134         };
2135       }
2136     };
2137
2138     /*
2139      * Replace `{{` and `}}` in a string (usually a template) with the actual start-/endSymbols used
2140      * for interpolation. This allows pre-defined templates (for components such as dialog, toast etc)
2141      * to continue to work in apps that use custom interpolation start-/endSymbols.
2142      *
2143      * @param {string} text The text in which to replace `{{` / `}}`
2144      * @returns {string} The modified string using the actual interpolation start-/endSymbols
2145      */
2146     function replaceInterpolationSymbols(text) {
2147       if (!text || !angular.isString(text)) return text;
2148       return text.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
2149     }
2150   }
2151
2152 }
2153
2154 })();
2155 (function(){
2156 "use strict";
2157
2158   /**
2159    * @ngdoc module
2160    * @name material.core.componentRegistry
2161    *
2162    * @description
2163    * A component instance registration service.
2164    * Note: currently this as a private service in the SideNav component.
2165    */
2166   angular.module('material.core')
2167     .factory('$mdComponentRegistry', ComponentRegistry);
2168
2169   /*
2170    * @private
2171    * @ngdoc factory
2172    * @name ComponentRegistry
2173    * @module material.core.componentRegistry
2174    *
2175    */
2176   function ComponentRegistry($log, $q) {
2177
2178     var self;
2179     var instances = [ ];
2180     var pendings = { };
2181
2182     return self = {
2183       /**
2184        * Used to print an error when an instance for a handle isn't found.
2185        */
2186       notFoundError: function(handle) {
2187         $log.error('No instance found for handle', handle);
2188       },
2189       /**
2190        * Return all registered instances as an array.
2191        */
2192       getInstances: function() {
2193         return instances;
2194       },
2195
2196       /**
2197        * Get a registered instance.
2198        * @param handle the String handle to look up for a registered instance.
2199        */
2200       get: function(handle) {
2201         if ( !isValidID(handle) ) return null;
2202
2203         var i, j, instance;
2204         for(i = 0, j = instances.length; i < j; i++) {
2205           instance = instances[i];
2206           if(instance.$$mdHandle === handle) {
2207             return instance;
2208           }
2209         }
2210         return null;
2211       },
2212
2213       /**
2214        * Register an instance.
2215        * @param instance the instance to register
2216        * @param handle the handle to identify the instance under.
2217        */
2218       register: function(instance, handle) {
2219         if ( !handle ) return angular.noop;
2220
2221         instance.$$mdHandle = handle;
2222         instances.push(instance);
2223         resolveWhen();
2224
2225         return deregister;
2226
2227         /**
2228          * Remove registration for an instance
2229          */
2230         function deregister() {
2231           var index = instances.indexOf(instance);
2232           if (index !== -1) {
2233             instances.splice(index, 1);
2234           }
2235         }
2236
2237         /**
2238          * Resolve any pending promises for this instance
2239          */
2240         function resolveWhen() {
2241           var dfd = pendings[handle];
2242           if ( dfd ) {
2243             dfd.resolve( instance );
2244             delete pendings[handle];
2245           }
2246         }
2247       },
2248
2249       /**
2250        * Async accessor to registered component instance
2251        * If not available then a promise is created to notify
2252        * all listeners when the instance is registered.
2253        */
2254       when : function(handle) {
2255         if ( isValidID(handle) ) {
2256           var deferred = $q.defer();
2257           var instance = self.get(handle);
2258
2259           if ( instance )  {
2260             deferred.resolve( instance );
2261           } else {
2262             pendings[handle] = deferred;
2263           }
2264
2265           return deferred.promise;
2266         }
2267         return $q.reject("Invalid `md-component-id` value.");
2268       }
2269
2270     };
2271
2272     function isValidID(handle){
2273       return handle && (handle !== "");
2274     }
2275
2276   }
2277   ComponentRegistry.$inject = ["$log", "$q"];
2278
2279 })();
2280 (function(){
2281 "use strict";
2282
2283 (function() {
2284   'use strict';
2285
2286   /**
2287    * @ngdoc service
2288    * @name $mdButtonInkRipple
2289    * @module material.core
2290    *
2291    * @description
2292    * Provides ripple effects for md-button.  See $mdInkRipple service for all possible configuration options.
2293    *
2294    * @param {object=} scope Scope within the current context
2295    * @param {object=} element The element the ripple effect should be applied to
2296    * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
2297    */
2298
2299   angular.module('material.core')
2300     .factory('$mdButtonInkRipple', MdButtonInkRipple);
2301
2302   function MdButtonInkRipple($mdInkRipple) {
2303     return {
2304       attach: attach
2305     };
2306
2307     function attach(scope, element, options) {
2308       var elementOptions = optionsForElement(element);
2309       return $mdInkRipple.attach(scope, element, angular.extend(elementOptions, options));
2310     };
2311
2312     function optionsForElement(element) {
2313       if (element.hasClass('md-icon-button')) {
2314         return {
2315           isMenuItem: element.hasClass('md-menu-item'),
2316           fitRipple: true,
2317           center: true
2318         };
2319       } else {
2320         return {
2321           isMenuItem: element.hasClass('md-menu-item'),
2322           dimBackground: true
2323         }
2324       }
2325     };
2326   }
2327   MdButtonInkRipple.$inject = ["$mdInkRipple"];;
2328 })();
2329
2330 })();
2331 (function(){
2332 "use strict";
2333
2334 (function() {
2335   'use strict';
2336
2337     /**
2338    * @ngdoc service
2339    * @name $mdCheckboxInkRipple
2340    * @module material.core
2341    *
2342    * @description
2343    * Provides ripple effects for md-checkbox.  See $mdInkRipple service for all possible configuration options.
2344    *
2345    * @param {object=} scope Scope within the current context
2346    * @param {object=} element The element the ripple effect should be applied to
2347    * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
2348    */
2349
2350   angular.module('material.core')
2351     .factory('$mdCheckboxInkRipple', MdCheckboxInkRipple);
2352
2353   function MdCheckboxInkRipple($mdInkRipple) {
2354     return {
2355       attach: attach
2356     };
2357
2358     function attach(scope, element, options) {
2359       return $mdInkRipple.attach(scope, element, angular.extend({
2360         center: true,
2361         dimBackground: false,
2362         fitRipple: true
2363       }, options));
2364     };
2365   }
2366   MdCheckboxInkRipple.$inject = ["$mdInkRipple"];;
2367 })();
2368
2369 })();
2370 (function(){
2371 "use strict";
2372
2373 (function() {
2374   'use strict';
2375
2376   /**
2377    * @ngdoc service
2378    * @name $mdListInkRipple
2379    * @module material.core
2380    *
2381    * @description
2382    * Provides ripple effects for md-list.  See $mdInkRipple service for all possible configuration options.
2383    *
2384    * @param {object=} scope Scope within the current context
2385    * @param {object=} element The element the ripple effect should be applied to
2386    * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
2387    */
2388
2389   angular.module('material.core')
2390     .factory('$mdListInkRipple', MdListInkRipple);
2391
2392   function MdListInkRipple($mdInkRipple) {
2393     return {
2394       attach: attach
2395     };
2396
2397     function attach(scope, element, options) {
2398       return $mdInkRipple.attach(scope, element, angular.extend({
2399         center: false,
2400         dimBackground: true,
2401         outline: false,
2402         rippleSize: 'full'
2403       }, options));
2404     };
2405   }
2406   MdListInkRipple.$inject = ["$mdInkRipple"];;
2407 })();
2408
2409 })();
2410 (function(){
2411 "use strict";
2412
2413 angular.module('material.core')
2414   .factory('$mdInkRipple', InkRippleService)
2415   .directive('mdInkRipple', InkRippleDirective)
2416   .directive('mdNoInk', attrNoDirective())
2417   .directive('mdNoBar', attrNoDirective())
2418   .directive('mdNoStretch', attrNoDirective());
2419
2420 function InkRippleDirective($mdButtonInkRipple, $mdCheckboxInkRipple) {
2421   return {
2422     controller: angular.noop,
2423     link: function (scope, element, attr) {
2424       if (attr.hasOwnProperty('mdInkRippleCheckbox')) {
2425         $mdCheckboxInkRipple.attach(scope, element);
2426       } else {
2427         $mdButtonInkRipple.attach(scope, element);
2428       }
2429     }
2430   };
2431 }
2432 InkRippleDirective.$inject = ["$mdButtonInkRipple", "$mdCheckboxInkRipple"];
2433
2434 function InkRippleService($window, $timeout) {
2435
2436   return {
2437     attach: attach
2438   };
2439
2440   function attach(scope, element, options) {
2441     if (element.controller('mdNoInk')) return angular.noop;
2442
2443     options = angular.extend({
2444       colorElement: element,
2445       mousedown: true,
2446       hover: true,
2447       focus: true,
2448       center: false,
2449       mousedownPauseTime: 150,
2450       dimBackground: false,
2451       outline: false,
2452       fullRipple: true,
2453       isMenuItem: false,
2454       fitRipple: false
2455     }, options);
2456
2457     var rippleSize,
2458         controller = element.controller('mdInkRipple') || {},
2459         counter = 0,
2460         ripples = [],
2461         states = [],
2462         isActiveExpr = element.attr('md-highlight'),
2463         isActive = false,
2464         isHeld = false,
2465         node = element[0],
2466         rippleSizeSetting = element.attr('md-ripple-size'),
2467         color = parseColor(element.attr('md-ink-ripple')) || parseColor(options.colorElement.length && $window.getComputedStyle(options.colorElement[0]).color || 'rgb(0, 0, 0)');
2468
2469     switch (rippleSizeSetting) {
2470       case 'full':
2471         options.fullRipple = true;
2472         break;
2473       case 'partial':
2474         options.fullRipple = false;
2475         break;
2476     }
2477
2478     // expose onInput for ripple testing
2479     if (options.mousedown) {
2480       element.on('$md.pressdown', onPressDown)
2481         .on('$md.pressup', onPressUp);
2482     }
2483
2484     controller.createRipple = createRipple;
2485
2486     if (isActiveExpr) {
2487       scope.$watch(isActiveExpr, function watchActive(newValue) {
2488         isActive = newValue;
2489         if (isActive && !ripples.length) {
2490           $timeout(function () { createRipple(0, 0); }, 0, false);
2491         }
2492         angular.forEach(ripples, updateElement);
2493       });
2494     }
2495
2496     // Publish self-detach method if desired...
2497     return function detach() {
2498       element.off('$md.pressdown', onPressDown)
2499         .off('$md.pressup', onPressUp);
2500       getRippleContainer().remove();
2501     };
2502
2503     /**
2504      * Gets the current ripple container
2505      * If there is no ripple container, it creates one and returns it
2506      *
2507      * @returns {angular.element} ripple container element
2508      */
2509     function getRippleContainer() {
2510       var container = element.data('$mdRippleContainer');
2511       if (container) return container;
2512       container = angular.element('<div class="md-ripple-container">');
2513       element.append(container);
2514       element.data('$mdRippleContainer', container);
2515       return container;
2516     }
2517
2518     function parseColor(color) {
2519       if (!color) return;
2520       if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, '0.1)');
2521       if (color.indexOf('rgb')  === 0) return rgbToRGBA(color);
2522       if (color.indexOf('#')    === 0) return hexToRGBA(color);
2523
2524       /**
2525        * Converts a hex value to an rgba string
2526        *
2527        * @param {string} hex value (3 or 6 digits) to be converted
2528        *
2529        * @returns {string} rgba color with 0.1 alpha
2530        */
2531       function hexToRGBA(color) {
2532         var hex = color.charAt(0) === '#' ? color.substr(1) : color,
2533           dig = hex.length / 3,
2534           red = hex.substr(0, dig),
2535           grn = hex.substr(dig, dig),
2536           blu = hex.substr(dig * 2);
2537         if (dig === 1) {
2538           red += red;
2539           grn += grn;
2540           blu += blu;
2541         }
2542         return 'rgba(' + parseInt(red, 16) + ',' + parseInt(grn, 16) + ',' + parseInt(blu, 16) + ',0.1)';
2543       }
2544
2545       /**
2546        * Converts rgb value to rgba string
2547        *
2548        * @param {string} rgb color string
2549        *
2550        * @returns {string} rgba color with 0.1 alpha
2551        */
2552       function rgbToRGBA(color) {
2553         return color.replace(')', ', 0.1)').replace('(', 'a(');
2554       }
2555
2556     }
2557
2558     function removeElement(elem, wait) {
2559       ripples.splice(ripples.indexOf(elem), 1);
2560       if (ripples.length === 0) {
2561         getRippleContainer().css({ backgroundColor: '' });
2562       }
2563       $timeout(function () { elem.remove(); }, wait, false);
2564     }
2565
2566     function updateElement(elem) {
2567       var index = ripples.indexOf(elem),
2568           state = states[index] || {},
2569           elemIsActive = ripples.length > 1 ? false : isActive,
2570           elemIsHeld   = ripples.length > 1 ? false : isHeld;
2571       if (elemIsActive || state.animating || elemIsHeld) {
2572         elem.addClass('md-ripple-visible');
2573       } else if (elem) {
2574         elem.removeClass('md-ripple-visible');
2575         if (options.outline) {
2576           elem.css({
2577             width: rippleSize + 'px',
2578             height: rippleSize + 'px',
2579             marginLeft: (rippleSize * -1) + 'px',
2580             marginTop: (rippleSize * -1) + 'px'
2581           });
2582         }
2583         removeElement(elem, options.outline ? 450 : 650);
2584       }
2585     }
2586
2587     /**
2588      * Creates a ripple at the provided coordinates
2589      *
2590      * @param {number} left cursor position
2591      * @param {number} top cursor position
2592      *
2593      * @returns {angular.element} the generated ripple element
2594      */
2595     function createRipple(left, top) {
2596
2597       color = parseColor(element.attr('md-ink-ripple')) || parseColor($window.getComputedStyle(options.colorElement[0]).color || 'rgb(0, 0, 0)');
2598
2599       var container = getRippleContainer(),
2600           size = getRippleSize(left, top),
2601           css = getRippleCss(size, left, top),
2602           elem = getRippleElement(css),
2603           index = ripples.indexOf(elem),
2604           state = states[index] || {};
2605
2606       rippleSize = size;
2607
2608       state.animating = true;
2609
2610       $timeout(function () {
2611         if (options.dimBackground) {
2612           container.css({ backgroundColor: color });
2613         }
2614         elem.addClass('md-ripple-placed md-ripple-scaled');
2615         if (options.outline) {
2616           elem.css({
2617             borderWidth: (size * 0.5) + 'px',
2618             marginLeft: (size * -0.5) + 'px',
2619             marginTop: (size * -0.5) + 'px'
2620           });
2621         } else {
2622           elem.css({ left: '50%', top: '50%' });
2623         }
2624         updateElement(elem);
2625         $timeout(function () {
2626           state.animating = false;
2627           updateElement(elem);
2628         }, (options.outline ? 450 : 225), false);
2629       }, 0, false);
2630
2631       return elem;
2632
2633       /**
2634        * Creates the ripple element with the provided css
2635        *
2636        * @param {object} css properties to be applied
2637        *
2638        * @returns {angular.element} the generated ripple element
2639        */
2640       function getRippleElement(css) {
2641         var elem = angular.element('<div class="md-ripple" data-counter="' + counter++ + '">');
2642         ripples.unshift(elem);
2643         states.unshift({ animating: true });
2644         container.append(elem);
2645         css && elem.css(css);
2646         return elem;
2647       }
2648
2649       /**
2650        * Calculate the ripple size
2651        *
2652        * @returns {number} calculated ripple diameter
2653        */
2654       function getRippleSize(left, top) {
2655         var width = container.prop('offsetWidth'),
2656             height = container.prop('offsetHeight'),
2657             multiplier, size, rect;
2658         if (options.isMenuItem) {
2659           size = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
2660         } else if (options.outline) {
2661           rect = node.getBoundingClientRect();
2662           left -= rect.left;
2663           top -= rect.top;
2664           width = Math.max(left, width - left);
2665           height = Math.max(top, height - top);
2666           size = 2 * Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
2667         } else {
2668           multiplier = options.fullRipple ? 1.1 : 0.8;
2669           size = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) * multiplier;
2670           if (options.fitRipple) {
2671             size = Math.min(height, width, size);
2672           }
2673         }
2674         return size;
2675       }
2676
2677       /**
2678        * Generates the ripple css
2679        *
2680        * @param {number} the diameter of the ripple
2681        * @param {number} the left cursor offset
2682        * @param {number} the top cursor offset
2683        *
2684        * @returns {{backgroundColor: string, borderColor: string, width: string, height: string}}
2685        */
2686       function getRippleCss(size, left, top) {
2687         var rect = node.getBoundingClientRect(),
2688             css  = {
2689               backgroundColor: rgbaToRGB(color),
2690               borderColor: rgbaToRGB(color),
2691               width: size + 'px',
2692               height: size + 'px'
2693             };
2694
2695         if (options.outline) {
2696           css.width = 0;
2697           css.height = 0;
2698         } else {
2699           css.marginLeft = css.marginTop = (size * -0.5) + 'px';
2700         }
2701
2702         if (options.center) {
2703           css.left = css.top = '50%';
2704         } else {
2705           css.left = Math.round((left - rect.left) / container.prop('offsetWidth') * 100) + '%';
2706           css.top = Math.round((top - rect.top) / container.prop('offsetHeight') * 100) + '%';
2707         }
2708
2709         return css;
2710
2711         /**
2712          * Converts rgba string to rgb, removing the alpha value
2713          *
2714          * @param {string} rgba color
2715          *
2716          * @returns {string} rgb color
2717          */
2718         function rgbaToRGB(color) {
2719           return color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')');
2720         }
2721       }
2722     }
2723
2724     /**
2725      * Handles user input start and stop events
2726      *
2727      */
2728     function onPressDown(ev) {
2729       if (!isRippleAllowed()) return;
2730
2731       createRipple(ev.pointer.x, ev.pointer.y);
2732       isHeld = true;
2733     }
2734     function onPressUp() {
2735       isHeld = false;
2736       var ripple = ripples[ ripples.length - 1 ];
2737       $timeout(function () { updateElement(ripple); }, 0, false);
2738     }
2739
2740     /**
2741      * Determines if the ripple is allowed
2742      *
2743      * @returns {boolean} true if the ripple is allowed, false if not
2744      */
2745     function isRippleAllowed() {
2746       var parent = node.parentNode;
2747       var grandparent = parent && parent.parentNode;
2748       var ancestor = grandparent && grandparent.parentNode;
2749       return !isDisabled(node) && !isDisabled(parent) && !isDisabled(grandparent) && !isDisabled(ancestor);
2750       function isDisabled (elem) {
2751         return elem && elem.hasAttribute && elem.hasAttribute('disabled');
2752       }
2753     }
2754
2755   }
2756 }
2757 InkRippleService.$inject = ["$window", "$timeout"];
2758
2759 /**
2760  * noink/nobar/nostretch directive: make any element that has one of
2761  * these attributes be given a controller, so that other directives can
2762  * `require:` these and see if there is a `no<xxx>` parent attribute.
2763  *
2764  * @usage
2765  * <hljs lang="html">
2766  * <parent md-no-ink>
2767  *   <child detect-no>
2768  *   </child>
2769  * </parent>
2770  * </hljs>
2771  *
2772  * <hljs lang="js">
2773  * myApp.directive('detectNo', function() {
2774  *   return {
2775  *     require: ['^?mdNoInk', ^?mdNoBar'],
2776  *     link: function(scope, element, attr, ctrls) {
2777  *       var noinkCtrl = ctrls[0];
2778  *       var nobarCtrl = ctrls[1];
2779  *       if (noInkCtrl) {
2780  *         alert("the md-no-ink flag has been specified on an ancestor!");
2781  *       }
2782  *       if (nobarCtrl) {
2783  *         alert("the md-no-bar flag has been specified on an ancestor!");
2784  *       }
2785  *     }
2786  *   };
2787  * });
2788  * </hljs>
2789  */
2790 function attrNoDirective() {
2791   return function() {
2792     return {
2793       controller: angular.noop
2794     };
2795   };
2796 }
2797
2798 })();
2799 (function(){
2800 "use strict";
2801
2802 (function() {
2803   'use strict';
2804
2805     /**
2806    * @ngdoc service
2807    * @name $mdTabInkRipple
2808    * @module material.core
2809    *
2810    * @description
2811    * Provides ripple effects for md-tabs.  See $mdInkRipple service for all possible configuration options.
2812    *
2813    * @param {object=} scope Scope within the current context
2814    * @param {object=} element The element the ripple effect should be applied to
2815    * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
2816    */
2817
2818   angular.module('material.core')
2819     .factory('$mdTabInkRipple', MdTabInkRipple);
2820
2821   function MdTabInkRipple($mdInkRipple) {
2822     return {
2823       attach: attach
2824     };
2825
2826     function attach(scope, element, options) {
2827       return $mdInkRipple.attach(scope, element, angular.extend({
2828         center: false,
2829         dimBackground: true,
2830         outline: false,
2831         rippleSize: 'full'
2832       }, options));
2833     };
2834   }
2835   MdTabInkRipple.$inject = ["$mdInkRipple"];;
2836 })();
2837
2838 })();
2839 (function(){
2840 "use strict";
2841
2842 angular.module('material.core.theming.palette', [])
2843 .constant('$mdColorPalette', {
2844   'red': {
2845     '50': '#ffebee',
2846     '100': '#ffcdd2',
2847     '200': '#ef9a9a',
2848     '300': '#e57373',
2849     '400': '#ef5350',
2850     '500': '#f44336',
2851     '600': '#e53935',
2852     '700': '#d32f2f',
2853     '800': '#c62828',
2854     '900': '#b71c1c',
2855     'A100': '#ff8a80',
2856     'A200': '#ff5252',
2857     'A400': '#ff1744',
2858     'A700': '#d50000',
2859     'contrastDefaultColor': 'light',
2860     'contrastDarkColors': '50 100 200 300 400 A100',
2861     'contrastStrongLightColors': '500 600 700 A200 A400 A700'
2862   },
2863   'pink': {
2864     '50': '#fce4ec',
2865     '100': '#f8bbd0',
2866     '200': '#f48fb1',
2867     '300': '#f06292',
2868     '400': '#ec407a',
2869     '500': '#e91e63',
2870     '600': '#d81b60',
2871     '700': '#c2185b',
2872     '800': '#ad1457',
2873     '900': '#880e4f',
2874     'A100': '#ff80ab',
2875     'A200': '#ff4081',
2876     'A400': '#f50057',
2877     'A700': '#c51162',
2878     'contrastDefaultColor': 'light',
2879     'contrastDarkColors': '50 100 200 300 400 A100',
2880     'contrastStrongLightColors': '500 600 A200 A400 A700'
2881   },
2882   'purple': {
2883     '50': '#f3e5f5',
2884     '100': '#e1bee7',
2885     '200': '#ce93d8',
2886     '300': '#ba68c8',
2887     '400': '#ab47bc',
2888     '500': '#9c27b0',
2889     '600': '#8e24aa',
2890     '700': '#7b1fa2',
2891     '800': '#6a1b9a',
2892     '900': '#4a148c',
2893     'A100': '#ea80fc',
2894     'A200': '#e040fb',
2895     'A400': '#d500f9',
2896     'A700': '#aa00ff',
2897     'contrastDefaultColor': 'light',
2898     'contrastDarkColors': '50 100 200 A100',
2899     'contrastStrongLightColors': '300 400 A200 A400 A700'
2900   },
2901   'deep-purple': {
2902     '50': '#ede7f6',
2903     '100': '#d1c4e9',
2904     '200': '#b39ddb',
2905     '300': '#9575cd',
2906     '400': '#7e57c2',
2907     '500': '#673ab7',
2908     '600': '#5e35b1',
2909     '700': '#512da8',
2910     '800': '#4527a0',
2911     '900': '#311b92',
2912     'A100': '#b388ff',
2913     'A200': '#7c4dff',
2914     'A400': '#651fff',
2915     'A700': '#6200ea',
2916     'contrastDefaultColor': 'light',
2917     'contrastDarkColors': '50 100 200 A100',
2918     'contrastStrongLightColors': '300 400 A200'
2919   },
2920   'indigo': {
2921     '50': '#e8eaf6',
2922     '100': '#c5cae9',
2923     '200': '#9fa8da',
2924     '300': '#7986cb',
2925     '400': '#5c6bc0',
2926     '500': '#3f51b5',
2927     '600': '#3949ab',
2928     '700': '#303f9f',
2929     '800': '#283593',
2930     '900': '#1a237e',
2931     'A100': '#8c9eff',
2932     'A200': '#536dfe',
2933     'A400': '#3d5afe',
2934     'A700': '#304ffe',
2935     'contrastDefaultColor': 'light',
2936     'contrastDarkColors': '50 100 200 A100',
2937     'contrastStrongLightColors': '300 400 A200 A400'
2938   },
2939   'blue': {
2940     '50': '#e3f2fd',
2941     '100': '#bbdefb',
2942     '200': '#90caf9',
2943     '300': '#64b5f6',
2944     '400': '#42a5f5',
2945     '500': '#2196f3',
2946     '600': '#1e88e5',
2947     '700': '#1976d2',
2948     '800': '#1565c0',
2949     '900': '#0d47a1',
2950     'A100': '#82b1ff',
2951     'A200': '#448aff',
2952     'A400': '#2979ff',
2953     'A700': '#2962ff',
2954     'contrastDefaultColor': 'light',
2955     'contrastDarkColors': '100 200 300 400 A100',
2956     'contrastStrongLightColors': '500 600 700 A200 A400 A700'
2957   },
2958   'light-blue': {
2959     '50': '#e1f5fe',
2960     '100': '#b3e5fc',
2961     '200': '#81d4fa',
2962     '300': '#4fc3f7',
2963     '400': '#29b6f6',
2964     '500': '#03a9f4',
2965     '600': '#039be5',
2966     '700': '#0288d1',
2967     '800': '#0277bd',
2968     '900': '#01579b',
2969     'A100': '#80d8ff',
2970     'A200': '#40c4ff',
2971     'A400': '#00b0ff',
2972     'A700': '#0091ea',
2973     'contrastDefaultColor': 'dark',
2974     'contrastLightColors': '500 600 700 800 900 A700',
2975     'contrastStrongLightColors': '500 600 700 800 A700'
2976   },
2977   'cyan': {
2978     '50': '#e0f7fa',
2979     '100': '#b2ebf2',
2980     '200': '#80deea',
2981     '300': '#4dd0e1',
2982     '400': '#26c6da',
2983     '500': '#00bcd4',
2984     '600': '#00acc1',
2985     '700': '#0097a7',
2986     '800': '#00838f',
2987     '900': '#006064',
2988     'A100': '#84ffff',
2989     'A200': '#18ffff',
2990     'A400': '#00e5ff',
2991     'A700': '#00b8d4',
2992     'contrastDefaultColor': 'dark',
2993     'contrastLightColors': '500 600 700 800 900',
2994     'contrastStrongLightColors': '500 600 700 800'
2995   },
2996   'teal': {
2997     '50': '#e0f2f1',
2998     '100': '#b2dfdb',
2999     '200': '#80cbc4',
3000     '300': '#4db6ac',
3001     '400': '#26a69a',
3002     '500': '#009688',
3003     '600': '#00897b',
3004     '700': '#00796b',
3005     '800': '#00695c',
3006     '900': '#004d40',
3007     'A100': '#a7ffeb',
3008     'A200': '#64ffda',
3009     'A400': '#1de9b6',
3010     'A700': '#00bfa5',
3011     'contrastDefaultColor': 'dark',
3012     'contrastLightColors': '500 600 700 800 900',
3013     'contrastStrongLightColors': '500 600 700'
3014   },
3015   'green': {
3016     '50': '#e8f5e9',
3017     '100': '#c8e6c9',
3018     '200': '#a5d6a7',
3019     '300': '#81c784',
3020     '400': '#66bb6a',
3021     '500': '#4caf50',
3022     '600': '#43a047',
3023     '700': '#388e3c',
3024     '800': '#2e7d32',
3025     '900': '#1b5e20',
3026     'A100': '#b9f6ca',
3027     'A200': '#69f0ae',
3028     'A400': '#00e676',
3029     'A700': '#00c853',
3030     'contrastDefaultColor': 'dark',
3031     'contrastLightColors': '500 600 700 800 900',
3032     'contrastStrongLightColors': '500 600 700'
3033   },
3034   'light-green': {
3035     '50': '#f1f8e9',
3036     '100': '#dcedc8',
3037     '200': '#c5e1a5',
3038     '300': '#aed581',
3039     '400': '#9ccc65',
3040     '500': '#8bc34a',
3041     '600': '#7cb342',
3042     '700': '#689f38',
3043     '800': '#558b2f',
3044     '900': '#33691e',
3045     'A100': '#ccff90',
3046     'A200': '#b2ff59',
3047     'A400': '#76ff03',
3048     'A700': '#64dd17',
3049     'contrastDefaultColor': 'dark',
3050     'contrastLightColors': '800 900',
3051     'contrastStrongLightColors': '800 900'
3052   },
3053   'lime': {
3054     '50': '#f9fbe7',
3055     '100': '#f0f4c3',
3056     '200': '#e6ee9c',
3057     '300': '#dce775',
3058     '400': '#d4e157',
3059     '500': '#cddc39',
3060     '600': '#c0ca33',
3061     '700': '#afb42b',
3062     '800': '#9e9d24',
3063     '900': '#827717',
3064     'A100': '#f4ff81',
3065     'A200': '#eeff41',
3066     'A400': '#c6ff00',
3067     'A700': '#aeea00',
3068     'contrastDefaultColor': 'dark',
3069     'contrastLightColors': '900',
3070     'contrastStrongLightColors': '900'
3071   },
3072   'yellow': {
3073     '50': '#fffde7',
3074     '100': '#fff9c4',
3075     '200': '#fff59d',
3076     '300': '#fff176',
3077     '400': '#ffee58',
3078     '500': '#ffeb3b',
3079     '600': '#fdd835',
3080     '700': '#fbc02d',
3081     '800': '#f9a825',
3082     '900': '#f57f17',
3083     'A100': '#ffff8d',
3084     'A200': '#ffff00',
3085     'A400': '#ffea00',
3086     'A700': '#ffd600',
3087     'contrastDefaultColor': 'dark'
3088   },
3089   'amber': {
3090     '50': '#fff8e1',
3091     '100': '#ffecb3',
3092     '200': '#ffe082',
3093     '300': '#ffd54f',
3094     '400': '#ffca28',
3095     '500': '#ffc107',
3096     '600': '#ffb300',
3097     '700': '#ffa000',
3098     '800': '#ff8f00',
3099     '900': '#ff6f00',
3100     'A100': '#ffe57f',
3101     'A200': '#ffd740',
3102     'A400': '#ffc400',
3103     'A700': '#ffab00',
3104     'contrastDefaultColor': 'dark'
3105   },
3106   'orange': {
3107     '50': '#fff3e0',
3108     '100': '#ffe0b2',
3109     '200': '#ffcc80',
3110     '300': '#ffb74d',
3111     '400': '#ffa726',
3112     '500': '#ff9800',
3113     '600': '#fb8c00',
3114     '700': '#f57c00',
3115     '800': '#ef6c00',
3116     '900': '#e65100',
3117     'A100': '#ffd180',
3118     'A200': '#ffab40',
3119     'A400': '#ff9100',
3120     'A700': '#ff6d00',
3121     'contrastDefaultColor': 'dark',
3122     'contrastLightColors': '800 900',
3123     'contrastStrongLightColors': '800 900'
3124   },
3125   'deep-orange': {
3126     '50': '#fbe9e7',
3127     '100': '#ffccbc',
3128     '200': '#ffab91',
3129     '300': '#ff8a65',
3130     '400': '#ff7043',
3131     '500': '#ff5722',
3132     '600': '#f4511e',
3133     '700': '#e64a19',
3134     '800': '#d84315',
3135     '900': '#bf360c',
3136     'A100': '#ff9e80',
3137     'A200': '#ff6e40',
3138     'A400': '#ff3d00',
3139     'A700': '#dd2c00',
3140     'contrastDefaultColor': 'light',
3141     'contrastDarkColors': '50 100 200 300 400 A100 A200',
3142     'contrastStrongLightColors': '500 600 700 800 900 A400 A700'
3143   },
3144   'brown': {
3145     '50': '#efebe9',
3146     '100': '#d7ccc8',
3147     '200': '#bcaaa4',
3148     '300': '#a1887f',
3149     '400': '#8d6e63',
3150     '500': '#795548',
3151     '600': '#6d4c41',
3152     '700': '#5d4037',
3153     '800': '#4e342e',
3154     '900': '#3e2723',
3155     'A100': '#d7ccc8',
3156     'A200': '#bcaaa4',
3157     'A400': '#8d6e63',
3158     'A700': '#5d4037',
3159     'contrastDefaultColor': 'light',
3160     'contrastDarkColors': '50 100 200',
3161     'contrastStrongLightColors': '300 400'
3162   },
3163   'grey': {
3164     '50': '#fafafa',
3165     '100': '#f5f5f5',
3166     '200': '#eeeeee',
3167     '300': '#e0e0e0',
3168     '400': '#bdbdbd',
3169     '500': '#9e9e9e',
3170     '600': '#757575',
3171     '700': '#616161',
3172     '800': '#424242',
3173     '900': '#212121',
3174     '1000': '#000000',
3175     'A100': '#ffffff',
3176     'A200': '#eeeeee',
3177     'A400': '#bdbdbd',
3178     'A700': '#616161',
3179     'contrastDefaultColor': 'dark',
3180     'contrastLightColors': '600 700 800 900'
3181   },
3182   'blue-grey': {
3183     '50': '#eceff1',
3184     '100': '#cfd8dc',
3185     '200': '#b0bec5',
3186     '300': '#90a4ae',
3187     '400': '#78909c',
3188     '500': '#607d8b',
3189     '600': '#546e7a',
3190     '700': '#455a64',
3191     '800': '#37474f',
3192     '900': '#263238',
3193     'A100': '#cfd8dc',
3194     'A200': '#b0bec5',
3195     'A400': '#78909c',
3196     'A700': '#455a64',
3197     'contrastDefaultColor': 'light',
3198     'contrastDarkColors': '50 100 200 300',
3199     'contrastStrongLightColors': '400 500'
3200   }
3201 });
3202
3203 })();
3204 (function(){
3205 "use strict";
3206
3207 angular.module('material.core.theming', ['material.core.theming.palette'])
3208   .directive('mdTheme', ThemingDirective)
3209   .directive('mdThemable', ThemableDirective)
3210   .provider('$mdTheming', ThemingProvider)
3211   .run(generateThemes);
3212
3213 /**
3214  * @ngdoc provider
3215  * @name $mdThemingProvider
3216  * @module material.core
3217  *
3218  * @description Provider to configure the `$mdTheming` service.
3219  */
3220
3221 /**
3222  * @ngdoc method
3223  * @name $mdThemingProvider#setDefaultTheme
3224  * @param {string} themeName Default theme name to be applied to elements. Default value is `default`.
3225  */
3226
3227 /**
3228  * @ngdoc method
3229  * @name $mdThemingProvider#alwaysWatchTheme
3230  * @param {boolean} watch Whether or not to always watch themes for changes and re-apply
3231  * classes when they change. Default is `false`. Enabling can reduce performance.
3232  */
3233
3234 /* Some Example Valid Theming Expressions
3235  * =======================================
3236  *
3237  * Intention group expansion: (valid for primary, accent, warn, background)
3238  *
3239  * {{primary-100}} - grab shade 100 from the primary palette
3240  * {{primary-100-0.7}} - grab shade 100, apply opacity of 0.7
3241  * {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette
3242  * {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-1
3243  * {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue
3244  * {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules
3245  * {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue
3246  * {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules
3247  *
3248  * Foreground expansion: Applies rgba to black/white foreground text
3249  *
3250  * {{foreground-1}} - used for primary text
3251  * {{foreground-2}} - used for secondary text/divider
3252  * {{foreground-3}} - used for disabled text
3253  * {{foreground-4}} - used for dividers
3254  *
3255  */
3256
3257 // In memory generated CSS rules; registered by theme.name
3258 var GENERATED = { };
3259
3260 // In memory storage of defined themes and color palettes (both loaded by CSS, and user specified)
3261 var PALETTES;
3262 var THEMES;
3263
3264 var DARK_FOREGROUND = {
3265   name: 'dark',
3266   '1': 'rgba(0,0,0,0.87)',
3267   '2': 'rgba(0,0,0,0.54)',
3268   '3': 'rgba(0,0,0,0.26)',
3269   '4': 'rgba(0,0,0,0.12)'
3270 };
3271 var LIGHT_FOREGROUND = {
3272   name: 'light',
3273   '1': 'rgba(255,255,255,1.0)',
3274   '2': 'rgba(255,255,255,0.7)',
3275   '3': 'rgba(255,255,255,0.3)',
3276   '4': 'rgba(255,255,255,0.12)'
3277 };
3278
3279 var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)';
3280 var LIGHT_SHADOW = '';
3281
3282 var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)');
3283 var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87');
3284 var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)');
3285
3286 var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background'];
3287 var DEFAULT_COLOR_TYPE = 'primary';
3288
3289 // A color in a theme will use these hues by default, if not specified by user.
3290 var LIGHT_DEFAULT_HUES = {
3291   'accent': {
3292     'default': 'A200',
3293     'hue-1': 'A100',
3294     'hue-2': 'A400',
3295     'hue-3': 'A700'
3296   },
3297   'background': {
3298     'default': 'A100',
3299     'hue-1': '300',
3300     'hue-2': '800',
3301     'hue-3': '900'
3302   }
3303 };
3304
3305 var DARK_DEFAULT_HUES = {
3306   'background': {
3307     'default': '800',
3308     'hue-1': '300',
3309     'hue-2': '600',
3310     'hue-3': '900'
3311   }
3312 };
3313 THEME_COLOR_TYPES.forEach(function(colorType) {
3314   // Color types with unspecified default hues will use these default hue values
3315   var defaultDefaultHues = {
3316     'default': '500',
3317     'hue-1': '300',
3318     'hue-2': '800',
3319     'hue-3': 'A100'
3320   };
3321   if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues;
3322   if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues;
3323 });
3324
3325 var VALID_HUE_VALUES = [
3326   '50', '100', '200', '300', '400', '500', '600',
3327   '700', '800', '900', 'A100', 'A200', 'A400', 'A700'
3328 ];
3329
3330 function ThemingProvider($mdColorPalette) {
3331   PALETTES = { };
3332   THEMES = { };
3333
3334   var themingProvider;
3335   var defaultTheme = 'default';
3336   var alwaysWatchTheme = false;
3337
3338   // Load JS Defined Palettes
3339   angular.extend(PALETTES, $mdColorPalette);
3340
3341   // Default theme defined in core.js
3342
3343   ThemingService.$inject = ["$rootScope", "$log"];
3344   return themingProvider = {
3345     definePalette: definePalette,
3346     extendPalette: extendPalette,
3347     theme: registerTheme,
3348
3349     setDefaultTheme: function(theme) {
3350       defaultTheme = theme;
3351     },
3352     alwaysWatchTheme: function(alwaysWatch) {
3353       alwaysWatchTheme = alwaysWatch;
3354     },
3355     $get: ThemingService,
3356     _LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,
3357     _DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,
3358     _PALETTES: PALETTES,
3359     _THEMES: THEMES,
3360     _parseRules: parseRules,
3361     _rgba: rgba
3362   };
3363
3364   // Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... });
3365   function definePalette(name, map) {
3366     map = map || {};
3367     PALETTES[name] = checkPaletteValid(name, map);
3368     return themingProvider;
3369   }
3370
3371   // Returns an new object which is a copy of a given palette `name` with variables from
3372   // `map` overwritten
3373   // Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' });
3374   function extendPalette(name, map) {
3375     return checkPaletteValid(name,  angular.extend({}, PALETTES[name] || {}, map) );
3376   }
3377
3378   // Make sure that palette has all required hues
3379   function checkPaletteValid(name, map) {
3380     var missingColors = VALID_HUE_VALUES.filter(function(field) {
3381       return !map[field];
3382     });
3383     if (missingColors.length) {
3384       throw new Error("Missing colors %1 in palette %2!"
3385                       .replace('%1', missingColors.join(', '))
3386                       .replace('%2', name));
3387     }
3388
3389     return map;
3390   }
3391
3392   // Register a theme (which is a collection of color palettes to use with various states
3393   // ie. warn, accent, primary )
3394   // Optionally inherit from an existing theme
3395   // $mdThemingProvider.theme('custom-theme').primaryPalette('red');
3396   function registerTheme(name, inheritFrom) {
3397     if (THEMES[name]) return THEMES[name];
3398
3399     inheritFrom = inheritFrom || 'default';
3400
3401     var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom;
3402     var theme = new Theme(name);
3403
3404     if (parentTheme) {
3405       angular.forEach(parentTheme.colors, function(color, colorType) {
3406         theme.colors[colorType] = {
3407           name: color.name,
3408           // Make sure a COPY of the hues is given to the child color,
3409           // not the same reference.
3410           hues: angular.extend({}, color.hues)
3411         };
3412       });
3413     }
3414     THEMES[name] = theme;
3415
3416     return theme;
3417   }
3418
3419   function Theme(name) {
3420     var self = this;
3421     self.name = name;
3422     self.colors = {};
3423
3424     self.dark = setDark;
3425     setDark(false);
3426
3427     function setDark(isDark) {
3428       isDark = arguments.length === 0 ? true : !!isDark;
3429
3430       // If no change, abort
3431       if (isDark === self.isDark) return;
3432
3433       self.isDark = isDark;
3434
3435       self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND;
3436       self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW;
3437
3438       // Light and dark themes have different default hues.
3439       // Go through each existing color type for this theme, and for every
3440       // hue value that is still the default hue value from the previous light/dark setting,
3441       // set it to the default hue value from the new light/dark setting.
3442       var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES;
3443       var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES;
3444       angular.forEach(newDefaultHues, function(newDefaults, colorType) {
3445         var color = self.colors[colorType];
3446         var oldDefaults = oldDefaultHues[colorType];
3447         if (color) {
3448           for (var hueName in color.hues) {
3449             if (color.hues[hueName] === oldDefaults[hueName]) {
3450               color.hues[hueName] = newDefaults[hueName];
3451             }
3452           }
3453         }
3454       });
3455
3456       return self;
3457     }
3458
3459     THEME_COLOR_TYPES.forEach(function(colorType) {
3460       var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType];
3461       self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) {
3462         var color = self.colors[colorType] = {
3463           name: paletteName,
3464           hues: angular.extend({}, defaultHues, hues)
3465         };
3466
3467         Object.keys(color.hues).forEach(function(name) {
3468           if (!defaultHues[name]) {
3469             throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4"
3470               .replace('%1', name)
3471               .replace('%2', self.name)
3472               .replace('%3', paletteName)
3473               .replace('%4', Object.keys(defaultHues).join(', '))
3474             );
3475           }
3476         });
3477         Object.keys(color.hues).map(function(key) {
3478           return color.hues[key];
3479         }).forEach(function(hueValue) {
3480           if (VALID_HUE_VALUES.indexOf(hueValue) == -1) {
3481             throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5"
3482               .replace('%1', hueValue)
3483               .replace('%2', self.name)
3484               .replace('%3', colorType)
3485               .replace('%4', paletteName)
3486               .replace('%5', VALID_HUE_VALUES.join(', '))
3487             );
3488           }
3489         });
3490         return self;
3491       };
3492
3493       self[colorType + 'Color'] = function() {
3494         var args = Array.prototype.slice.call(arguments);
3495         console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' +
3496                      'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.');
3497         return self[colorType + 'Palette'].apply(self, args);
3498       };
3499     });
3500   }
3501
3502   /**
3503    * @ngdoc service
3504    * @name $mdTheming
3505    *
3506    * @description
3507    *
3508    * Service that makes an element apply theming related classes to itself.
3509    *
3510    * ```js
3511    * app.directive('myFancyDirective', function($mdTheming) {
3512    *   return {
3513    *     restrict: 'e',
3514    *     link: function(scope, el, attrs) {
3515    *       $mdTheming(el);
3516    *     }
3517    *   };
3518    * });
3519    * ```
3520    * @param {el=} element to apply theming to
3521    */
3522   /* @ngInject */
3523   function ThemingService($rootScope, $log) {
3524
3525     applyTheme.inherit = function(el, parent) {
3526       var ctrl = parent.controller('mdTheme');
3527
3528       var attrThemeValue = el.attr('md-theme-watch');
3529       if ( (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false') {
3530         var deregisterWatch = $rootScope.$watch(function() {
3531           return ctrl && ctrl.$mdTheme || defaultTheme;
3532         }, changeTheme);
3533         el.on('$destroy', deregisterWatch);
3534       } else {
3535         var theme = ctrl && ctrl.$mdTheme || defaultTheme;
3536         changeTheme(theme);
3537       }
3538
3539       function changeTheme(theme) {
3540         if (!registered(theme)) {
3541           $log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' +
3542                     'Register it with $mdThemingProvider.theme().');
3543         }
3544         var oldTheme = el.data('$mdThemeName');
3545         if (oldTheme) el.removeClass('md-' + oldTheme +'-theme');
3546         el.addClass('md-' + theme + '-theme');
3547         el.data('$mdThemeName', theme);
3548       }
3549     };
3550
3551     applyTheme.THEMES = angular.extend({}, THEMES);
3552     applyTheme.defaultTheme = function() { return defaultTheme; };
3553     applyTheme.registered = registered;
3554
3555     return applyTheme;
3556
3557     function registered(themeName) {
3558       if (themeName === undefined || themeName === '') return true;
3559       return applyTheme.THEMES[themeName] !== undefined;
3560     }
3561
3562     function applyTheme(scope, el) {
3563       // Allow us to be invoked via a linking function signature.
3564       if (el === undefined) {
3565         el = scope;
3566         scope = undefined;
3567       }
3568       if (scope === undefined) {
3569         scope = $rootScope;
3570       }
3571       applyTheme.inherit(el, el);
3572     }
3573   }
3574 }
3575 ThemingProvider.$inject = ["$mdColorPalette"];
3576
3577 function ThemingDirective($mdTheming, $interpolate, $log) {
3578   return {
3579     priority: 100,
3580     link: {
3581       pre: function(scope, el, attrs) {
3582         var ctrl = {
3583           $setTheme: function(theme) {
3584             if (!$mdTheming.registered(theme)) {
3585               $log.warn('attempted to use unregistered theme \'' + theme + '\'');
3586             }
3587             ctrl.$mdTheme = theme;
3588           }
3589         };
3590         el.data('$mdThemeController', ctrl);
3591         ctrl.$setTheme($interpolate(attrs.mdTheme)(scope));
3592         attrs.$observe('mdTheme', ctrl.$setTheme);
3593       }
3594     }
3595   };
3596 }
3597 ThemingDirective.$inject = ["$mdTheming", "$interpolate", "$log"];
3598
3599 function ThemableDirective($mdTheming) {
3600   return $mdTheming;
3601 }
3602 ThemableDirective.$inject = ["$mdTheming"];
3603
3604 function parseRules(theme, colorType, rules) {
3605   checkValidPalette(theme, colorType);
3606
3607   rules = rules.replace(/THEME_NAME/g, theme.name);
3608   var generatedRules = [];
3609   var color = theme.colors[colorType];
3610
3611   var themeNameRegex = new RegExp('.md-' + theme.name + '-theme', 'g');
3612   // Matches '{{ primary-color }}', etc
3613   var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g');
3614   var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow)-?(\d\.?\d*)?\s*\}\}'?"?/g;
3615   var palette = PALETTES[color.name];
3616
3617   // find and replace simple variables where we use a specific hue, not an entire palette
3618   // eg. "{{primary-100}}"
3619   //\(' + THEME_COLOR_TYPES.join('\|') + '\)'
3620   rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity) {
3621     if (colorType === 'foreground') {
3622       if (hue == 'shadow') {
3623         return theme.foregroundShadow;
3624       } else {
3625         return theme.foregroundPalette[hue] || theme.foregroundPalette['1'];
3626       }
3627     }
3628     if (hue.indexOf('hue') === 0) {
3629       hue = theme.colors[colorType].hues[hue];
3630     }
3631     return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '').value, opacity );
3632   });
3633
3634   // For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3)
3635   angular.forEach(color.hues, function(hueValue, hueName) {
3636     var newRule = rules
3637       .replace(hueRegex, function(match, _, colorType, hueType, opacity) {
3638         return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity);
3639       });
3640     if (hueName !== 'default') {
3641       newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName);
3642     }
3643
3644     // Don't apply a selector rule to the default theme, making it easier to override
3645     // styles of the base-component
3646     if (theme.name == 'default') {
3647       newRule = newRule.replace(/\.md-default-theme/g, '');
3648     }
3649     generatedRules.push(newRule);
3650   });
3651
3652   return generatedRules;
3653 }
3654
3655 // Generate our themes at run time given the state of THEMES and PALETTES
3656 function generateThemes($injector) {
3657
3658   var head = document.getElementsByTagName('head')[0];
3659   var firstChild = head ? head.firstElementChild : null;
3660   var themeCss = $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : '';
3661
3662   if ( !firstChild ) return;
3663   if (themeCss.length === 0) return; // no rules, so no point in running this expensive task
3664
3665   // Expose contrast colors for palettes to ensure that text is always readable
3666   angular.forEach(PALETTES, sanitizePalette);
3667
3668   // MD_THEME_CSS is a string generated by the build process that includes all the themable
3669   // components as templates
3670
3671   // Break the CSS into individual rules
3672   var rulesByType = {};
3673   var rules = themeCss
3674                   .split(/\}(?!(\}|'|"|;))/)
3675                   .filter(function(rule) { return rule && rule.length; })
3676                   .map(function(rule) { return rule.trim() + '}'; });
3677
3678
3679   var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g');
3680
3681   THEME_COLOR_TYPES.forEach(function(type) {
3682     rulesByType[type] = '';
3683   });
3684
3685
3686   // Sort the rules based on type, allowing us to do color substitution on a per-type basis
3687   rules.forEach(function(rule) {
3688     var match = rule.match(ruleMatchRegex);
3689     // First: test that if the rule has '.md-accent', it goes into the accent set of rules
3690     for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) {
3691       if (rule.indexOf('.md-' + type) > -1) {
3692         return rulesByType[type] += rule;
3693       }
3694     }
3695
3696     // If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from
3697     // there
3698     for (i = 0; type = THEME_COLOR_TYPES[i]; i++) {
3699       if (rule.indexOf(type) > -1) {
3700         return rulesByType[type] += rule;
3701       }
3702     }
3703
3704     // Default to the primary array
3705     return rulesByType[DEFAULT_COLOR_TYPE] += rule;
3706   });
3707
3708     // For each theme, use the color palettes specified for
3709     // `primary`, `warn` and `accent` to generate CSS rules.
3710
3711     angular.forEach(THEMES, function(theme) {
3712       if ( !GENERATED[theme.name] ) {
3713
3714
3715         THEME_COLOR_TYPES.forEach(function(colorType) {
3716           var styleStrings = parseRules(theme, colorType, rulesByType[colorType]);
3717           while (styleStrings.length) {
3718             var style = document.createElement('style');
3719                 style.setAttribute('type', 'text/css');
3720             style.appendChild(document.createTextNode(styleStrings.shift()));
3721             head.insertBefore(style, firstChild);
3722           }
3723         });
3724
3725
3726         if (theme.colors.primary.name == theme.colors.accent.name) {
3727           console.warn("$mdThemingProvider: Using the same palette for primary and" +
3728                        " accent. This violates the material design spec.");
3729         }
3730
3731         GENERATED[theme.name] = true;
3732       }
3733     });
3734
3735
3736   // *************************
3737   // Internal functions
3738   // *************************
3739
3740   // The user specifies a 'default' contrast color as either light or dark,
3741   // then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light)
3742   function sanitizePalette(palette) {
3743     var defaultContrast = palette.contrastDefaultColor;
3744     var lightColors = palette.contrastLightColors || [];
3745     var strongLightColors = palette.contrastStrongLightColors || [];
3746     var darkColors = palette.contrastDarkColors || [];
3747
3748     // These colors are provided as space-separated lists
3749     if (typeof lightColors === 'string') lightColors = lightColors.split(' ');
3750     if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' ');
3751     if (typeof darkColors === 'string') darkColors = darkColors.split(' ');
3752
3753     // Cleanup after ourselves
3754     delete palette.contrastDefaultColor;
3755     delete palette.contrastLightColors;
3756     delete palette.contrastStrongLightColors;
3757     delete palette.contrastDarkColors;
3758
3759     // Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR }
3760     angular.forEach(palette, function(hueValue, hueName) {
3761       if (angular.isObject(hueValue)) return; // Already converted
3762       // Map everything to rgb colors
3763       var rgbValue = colorToRgbaArray(hueValue);
3764       if (!rgbValue) {
3765         throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected."
3766                         .replace('%1', hueValue)
3767                         .replace('%2', palette.name)
3768                         .replace('%3', hueName));
3769       }
3770
3771       palette[hueName] = {
3772         value: rgbValue,
3773         contrast: getContrastColor()
3774       };
3775       function getContrastColor() {
3776         if (defaultContrast === 'light') {
3777           if (darkColors.indexOf(hueName) > -1) {
3778             return DARK_CONTRAST_COLOR;
3779           } else {
3780             return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
3781               : LIGHT_CONTRAST_COLOR;
3782           }
3783         } else {
3784           if (lightColors.indexOf(hueName) > -1) {
3785             return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
3786               : LIGHT_CONTRAST_COLOR;
3787           } else {
3788             return DARK_CONTRAST_COLOR;
3789           }
3790         }
3791       }
3792     });
3793   }
3794
3795
3796 }
3797 generateThemes.$inject = ["$injector"];
3798
3799 function checkValidPalette(theme, colorType) {
3800   // If theme attempts to use a palette that doesnt exist, throw error
3801   if (!PALETTES[ (theme.colors[colorType] || {}).name ]) {
3802     throw new Error(
3803       "You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3"
3804                     .replace('%1', theme.name)
3805                     .replace('%2', colorType)
3806                     .replace('%3', Object.keys(PALETTES).join(', '))
3807     );
3808   }
3809 }
3810
3811 function colorToRgbaArray(clr) {
3812   if (angular.isArray(clr) && clr.length == 3) return clr;
3813   if (/^rgb/.test(clr)) {
3814     return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) {
3815       return i == 3 ? parseFloat(value, 10) : parseInt(value, 10);
3816     });
3817   }
3818   if (clr.charAt(0) == '#') clr = clr.substring(1);
3819   if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return;
3820
3821   var dig = clr.length / 3;
3822   var red = clr.substr(0, dig);
3823   var grn = clr.substr(dig, dig);
3824   var blu = clr.substr(dig * 2);
3825   if (dig === 1) {
3826     red += red;
3827     grn += grn;
3828     blu += blu;
3829   }
3830   return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)];
3831 }
3832
3833 function rgba(rgbArray, opacity) {
3834   if ( !rgbArray ) return "rgb('0,0,0')";
3835
3836   if (rgbArray.length == 4) {
3837     rgbArray = angular.copy(rgbArray);
3838     opacity ? rgbArray.pop() : opacity = rgbArray.pop();
3839   }
3840   return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ?
3841     'rgba(' + rgbArray.join(',') + ',' + opacity + ')' :
3842     'rgb(' + rgbArray.join(',') + ')';
3843 }
3844
3845
3846 })();
3847 (function(){
3848 "use strict";
3849
3850 /**
3851  * @ngdoc module
3852  * @name material.components.autocomplete
3853  */
3854 /*
3855  * @see js folder for autocomplete implementation
3856  */
3857 angular.module('material.components.autocomplete', [
3858   'material.core',
3859   'material.components.icon'
3860 ]);
3861
3862 })();
3863 (function(){
3864 "use strict";
3865
3866 /*
3867  * @ngdoc module
3868  * @name material.components.backdrop
3869  * @description Backdrop
3870  */
3871
3872 /**
3873  * @ngdoc directive
3874  * @name mdBackdrop
3875  * @module material.components.backdrop
3876  *
3877  * @restrict E
3878  *
3879  * @description
3880  * `<md-backdrop>` is a backdrop element used by other components, such as dialog and bottom sheet.
3881  * Apply class `opaque` to make the backdrop use the theme backdrop color.
3882  *
3883  */
3884
3885 angular.module('material.components.backdrop', [
3886   'material.core'
3887 ])
3888   .directive('mdBackdrop', BackdropDirective);
3889
3890 function BackdropDirective($mdTheming) {
3891   return $mdTheming;
3892 }
3893 BackdropDirective.$inject = ["$mdTheming"];
3894
3895 })();
3896 (function(){
3897 "use strict";
3898
3899 /**
3900  * @ngdoc module
3901  * @name material.components.bottomSheet
3902  * @description
3903  * BottomSheet
3904  */
3905 angular.module('material.components.bottomSheet', [
3906   'material.core',
3907   'material.components.backdrop'
3908 ])
3909   .directive('mdBottomSheet', MdBottomSheetDirective)
3910   .provider('$mdBottomSheet', MdBottomSheetProvider);
3911
3912 function MdBottomSheetDirective() {
3913   return {
3914     restrict: 'E'
3915   };
3916 }
3917
3918 /**
3919  * @ngdoc service
3920  * @name $mdBottomSheet
3921  * @module material.components.bottomSheet
3922  *
3923  * @description
3924  * `$mdBottomSheet` opens a bottom sheet over the app and provides a simple promise API.
3925  *
3926  * ## Restrictions
3927  *
3928  * - The bottom sheet's template must have an outer `<md-bottom-sheet>` element.
3929  * - Add the `md-grid` class to the bottom sheet for a grid layout.
3930  * - Add the `md-list` class to the bottom sheet for a list layout.
3931  *
3932  * @usage
3933  * <hljs lang="html">
3934  * <div ng-controller="MyController">
3935  *   <md-button ng-click="openBottomSheet()">
3936  *     Open a Bottom Sheet!
3937  *   </md-button>
3938  * </div>
3939  * </hljs>
3940  * <hljs lang="js">
3941  * var app = angular.module('app', ['ngMaterial']);
3942  * app.controller('MyController', function($scope, $mdBottomSheet) {
3943  *   $scope.openBottomSheet = function() {
3944  *     $mdBottomSheet.show({
3945  *       template: '<md-bottom-sheet>Hello!</md-bottom-sheet>'
3946  *     });
3947  *   };
3948  * });
3949  * </hljs>
3950  */
3951
3952  /**
3953  * @ngdoc method
3954  * @name $mdBottomSheet#show
3955  *
3956  * @description
3957  * Show a bottom sheet with the specified options.
3958  *
3959  * @param {object} options An options object, with the following properties:
3960  *
3961  *   - `templateUrl` - `{string=}`: The url of an html template file that will
3962  *   be used as the content of the bottom sheet. Restrictions: the template must
3963  *   have an outer `md-bottom-sheet` element.
3964  *   - `template` - `{string=}`: Same as templateUrl, except this is an actual
3965  *   template string.
3966  *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
3967  *     This scope will be destroyed when the bottom sheet is removed unless `preserveScope` is set to true.
3968  *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
3969  *   - `controller` - `{string=}`: The controller to associate with this bottom sheet.
3970  *   - `locals` - `{string=}`: An object containing key/value pairs. The keys will
3971  *   be used as names of values to inject into the controller. For example,
3972  *   `locals: {three: 3}` would inject `three` into the controller with the value
3973  *   of 3.
3974  *   - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option,
3975  *   the location of the click will be used as the starting point for the opening animation
3976  *   of the the dialog.
3977  *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
3978  *   and the bottom sheet will not open until the promises resolve.
3979  *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
3980  *   - `parent` - `{element=}`: The element to append the bottom sheet to. The `parent` may be a `function`, `string`,
3981  *   `object`, or null. Defaults to appending to the body of the root element (or the root element) of the application.
3982  *   e.g. angular.element(document.getElementById('content')) or "#content"
3983  *   - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the bottom sheet is open.
3984  *     Default true.
3985  *
3986  * @returns {promise} A promise that can be resolved with `$mdBottomSheet.hide()` or
3987  * rejected with `$mdBottomSheet.cancel()`.
3988  */
3989
3990 /**
3991  * @ngdoc method
3992  * @name $mdBottomSheet#hide
3993  *
3994  * @description
3995  * Hide the existing bottom sheet and resolve the promise returned from
3996  * `$mdBottomSheet.show()`. This call will close the most recently opened/current bottomsheet (if any).
3997  *
3998  * @param {*=} response An argument for the resolved promise.
3999  *
4000  */
4001
4002 /**
4003  * @ngdoc method
4004  * @name $mdBottomSheet#cancel
4005  *
4006  * @description
4007  * Hide the existing bottom sheet and reject the promise returned from
4008  * `$mdBottomSheet.show()`.
4009  *
4010  * @param {*=} response An argument for the rejected promise.
4011  *
4012  */
4013
4014 function MdBottomSheetProvider($$interimElementProvider) {
4015   // how fast we need to flick down to close the sheet, pixels/ms
4016   var CLOSING_VELOCITY = 0.5;
4017   var PADDING = 80; // same as css
4018
4019   bottomSheetDefaults.$inject = ["$animate", "$mdConstant", "$mdUtil", "$timeout", "$compile", "$mdTheming", "$mdBottomSheet", "$rootElement", "$mdGesture"];
4020   return $$interimElementProvider('$mdBottomSheet')
4021     .setDefaults({
4022       methods: ['disableParentScroll', 'escapeToClose', 'targetEvent'],
4023       options: bottomSheetDefaults
4024     });
4025
4026   /* @ngInject */
4027   function bottomSheetDefaults($animate, $mdConstant, $mdUtil, $timeout, $compile, $mdTheming, $mdBottomSheet, $rootElement, $mdGesture) {
4028     var backdrop;
4029
4030     return {
4031       themable: true,
4032       targetEvent: null,
4033       onShow: onShow,
4034       onRemove: onRemove,
4035       escapeToClose: true,
4036       disableParentScroll: true
4037     };
4038
4039
4040     function onShow(scope, element, options) {
4041
4042       element = $mdUtil.extractElementByName(element, 'md-bottom-sheet');
4043
4044       // Add a backdrop that will close on click
4045       backdrop = $compile('<md-backdrop class="md-opaque md-bottom-sheet-backdrop">')(scope);
4046       backdrop.on('click', function() {
4047         $timeout($mdBottomSheet.cancel);
4048       });
4049       $mdTheming.inherit(backdrop, options.parent);
4050
4051       $animate.enter(backdrop, options.parent, null);
4052
4053       var bottomSheet = new BottomSheet(element, options.parent);
4054       options.bottomSheet = bottomSheet;
4055
4056       // Give up focus on calling item
4057       options.targetEvent && angular.element(options.targetEvent.target).blur();
4058       $mdTheming.inherit(bottomSheet.element, options.parent);
4059
4060       if (options.disableParentScroll) {
4061         options.lastOverflow = options.parent.css('overflow');
4062         options.parent.css('overflow', 'hidden');
4063       }
4064
4065       return $animate.enter(bottomSheet.element, options.parent)
4066         .then(function() {
4067           var focusable = angular.element(
4068             element[0].querySelector('button') ||
4069             element[0].querySelector('a') ||
4070             element[0].querySelector('[ng-click]')
4071           );
4072           focusable.focus();
4073
4074           if (options.escapeToClose) {
4075             options.rootElementKeyupCallback = function(e) {
4076               if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
4077                 $timeout($mdBottomSheet.cancel);
4078               }
4079             };
4080             $rootElement.on('keyup', options.rootElementKeyupCallback);
4081           }
4082         });
4083
4084     }
4085
4086     function onRemove(scope, element, options) {
4087
4088       var bottomSheet = options.bottomSheet;
4089
4090       $animate.leave(backdrop);
4091       return $animate.leave(bottomSheet.element).then(function() {
4092         if (options.disableParentScroll) {
4093           options.parent.css('overflow', options.lastOverflow);
4094           delete options.lastOverflow;
4095         }
4096
4097         bottomSheet.cleanup();
4098
4099         // Restore focus
4100         options.targetEvent && angular.element(options.targetEvent.target).focus();
4101       });
4102     }
4103
4104     /**
4105      * BottomSheet class to apply bottom-sheet behavior to an element
4106      */
4107     function BottomSheet(element, parent) {
4108       var deregister = $mdGesture.register(parent, 'drag', { horizontal: false });
4109       parent.on('$md.dragstart', onDragStart)
4110         .on('$md.drag', onDrag)
4111         .on('$md.dragend', onDragEnd);
4112
4113       return {
4114         element: element,
4115         cleanup: function cleanup() {
4116           deregister();
4117           parent.off('$md.dragstart', onDragStart)
4118             .off('$md.drag', onDrag)
4119             .off('$md.dragend', onDragEnd);
4120         }
4121       };
4122
4123       function onDragStart(ev) {
4124         // Disable transitions on transform so that it feels fast
4125         element.css($mdConstant.CSS.TRANSITION_DURATION, '0ms');
4126       }
4127
4128       function onDrag(ev) {
4129         var transform = ev.pointer.distanceY;
4130         if (transform < 5) {
4131           // Slow down drag when trying to drag up, and stop after PADDING
4132           transform = Math.max(-PADDING, transform / 2);
4133         }
4134         element.css($mdConstant.CSS.TRANSFORM, 'translate3d(0,' + (PADDING + transform) + 'px,0)');
4135       }
4136
4137       function onDragEnd(ev) {
4138         if (ev.pointer.distanceY > 0 &&
4139             (ev.pointer.distanceY > 20 || Math.abs(ev.pointer.velocityY) > CLOSING_VELOCITY)) {
4140           var distanceRemaining = element.prop('offsetHeight') - ev.pointer.distanceY;
4141           var transitionDuration = Math.min(distanceRemaining / ev.pointer.velocityY * 0.75, 500);
4142           element.css($mdConstant.CSS.TRANSITION_DURATION, transitionDuration + 'ms');
4143           $timeout($mdBottomSheet.cancel);
4144         } else {
4145           element.css($mdConstant.CSS.TRANSITION_DURATION, '');
4146           element.css($mdConstant.CSS.TRANSFORM, '');
4147         }
4148       }
4149     }
4150
4151   }
4152
4153 }
4154 MdBottomSheetProvider.$inject = ["$$interimElementProvider"];
4155
4156 })();
4157 (function(){
4158 "use strict";
4159
4160 /**
4161  * @ngdoc module
4162  * @name material.components.button
4163  * @description
4164  *
4165  * Button
4166  */
4167 angular
4168     .module('material.components.button', [ 'material.core' ])
4169     .directive('mdButton', MdButtonDirective);
4170
4171 /**
4172  * @ngdoc directive
4173  * @name mdButton
4174  * @module material.components.button
4175  *
4176  * @restrict E
4177  *
4178  * @description
4179  * `<md-button>` is a button directive with optional ink ripples (default enabled).
4180  *
4181  * If you supply a `href` or `ng-href` attribute, it will become an `<a>` element. Otherwise, it will
4182  * become a `<button>` element. As per the [Material Design specifications](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
4183  * the FAB button background is filled with the accent color [by default]. The primary color palette may be used with
4184  * the `md-primary` class.
4185  *
4186  * @param {boolean=} md-no-ink If present, disable ripple ink effects.
4187  * @param {expression=} ng-disabled En/Disable based on the expression
4188  * @param {string=} md-ripple-size Overrides the default ripple size logic. Options: `full`, `partial`, `auto`
4189  * @param {string=} aria-label Adds alternative text to button for accessibility, useful for icon buttons.
4190  * If no default text is found, a warning will be logged.
4191  *
4192  * @usage
4193  *
4194  * Regular buttons:
4195  *
4196  * <hljs lang="html">
4197  *  <md-button> Flat Button </md-button>
4198  *  <md-button href="http://google.com"> Flat link </md-button>
4199  *  <md-button class="md-raised"> Raised Button </md-button>
4200  *  <md-button ng-disabled="true"> Disabled Button </md-button>
4201  *  <md-button>
4202  *    <md-icon md-svg-src="your/icon.svg"></md-icon>
4203  *    Register Now
4204  *  </md-button>
4205  * </hljs>
4206  *
4207  * FAB buttons:
4208  *
4209  * <hljs lang="html">
4210  *  <md-button class="md-fab" aria-label="FAB">
4211  *    <md-icon md-svg-src="your/icon.svg"></md-icon>
4212  *  </md-button>
4213  *  <!-- mini-FAB -->
4214  *  <md-button class="md-fab md-mini" aria-label="Mini FAB">
4215  *    <md-icon md-svg-src="your/icon.svg"></md-icon>
4216  *  </md-button>
4217  *  <!-- Button with SVG Icon -->
4218  *  <md-button class="md-icon-button" aria-label="Custom Icon Button">
4219  *    <md-icon md-svg-icon="path/to/your.svg"></md-icon>
4220  *  </md-button>
4221  * </hljs>
4222  */
4223 function MdButtonDirective($mdButtonInkRipple, $mdTheming, $mdAria, $timeout) {
4224
4225   return {
4226     restrict: 'EA',
4227     replace: true,
4228     transclude: true,
4229     template: getTemplate,
4230     link: postLink
4231   };
4232
4233   function isAnchor(attr) {
4234     return angular.isDefined(attr.href) || angular.isDefined(attr.ngHref) || angular.isDefined(attr.ngLink) || angular.isDefined(attr.uiSref);
4235   }
4236
4237   function getTemplate(element, attr) {
4238     return isAnchor(attr) ?
4239            '<a class="md-button" ng-transclude></a>' :
4240            '<button class="md-button" ng-transclude></button>';
4241   }
4242
4243   function postLink(scope, element, attr) {
4244     var node = element[0];
4245     $mdTheming(element);
4246     $mdButtonInkRipple.attach(scope, element);
4247
4248     var elementHasText = node.textContent.trim();
4249     if (!elementHasText) {
4250       $mdAria.expect(element, 'aria-label');
4251     }
4252
4253     // For anchor elements, we have to set tabindex manually when the
4254     // element is disabled
4255     if (isAnchor(attr) && angular.isDefined(attr.ngDisabled) ) {
4256       scope.$watch(attr.ngDisabled, function(isDisabled) {
4257         element.attr('tabindex', isDisabled ? -1 : 0);
4258       });
4259     }
4260
4261     // disabling click event when disabled is true
4262     element.on('click', function(e){
4263       if (attr.disabled === true) {
4264         e.preventDefault();
4265         e.stopImmediatePropagation();
4266       }
4267     });
4268
4269     // restrict focus styles to the keyboard
4270     scope.mouseActive = false;
4271     element.on('mousedown', function() {
4272         scope.mouseActive = true;
4273         $timeout(function(){
4274           scope.mouseActive = false;
4275         }, 100);
4276       })
4277       .on('focus', function() {
4278         if(scope.mouseActive === false) { element.addClass('md-focused'); }
4279       })
4280       .on('blur', function() { element.removeClass('md-focused'); });
4281   }
4282
4283 }
4284 MdButtonDirective.$inject = ["$mdButtonInkRipple", "$mdTheming", "$mdAria", "$timeout"];
4285
4286 })();
4287 (function(){
4288 "use strict";
4289
4290 /**
4291  * @ngdoc module
4292  * @name material.components.card
4293  *
4294  * @description
4295  * Card components.
4296  */
4297 angular.module('material.components.card', [
4298   'material.core'
4299 ])
4300   .directive('mdCard', mdCardDirective);
4301
4302
4303
4304 /**
4305  * @ngdoc directive
4306  * @name mdCard
4307  * @module material.components.card
4308  *
4309  * @restrict E
4310  *
4311  * @description
4312  * The `<md-card>` directive is a container element used within `<md-content>` containers.
4313  *
4314  * An image included as a direct descendant will fill the card's width, while the `<md-card-content>`
4315  * container will wrap text content and provide padding. An `<md-card-footer>` element can be
4316  * optionally included to put content flush against the bottom edge of the card.
4317  *
4318  * Action buttons can be included in an element with the `.md-actions` class, also used in `md-dialog`.
4319  * You can then position buttons using layout attributes.
4320  *
4321  * Cards have constant width and variable heights; where the maximum height is limited to what can
4322  * fit within a single view on a platform, but it can temporarily expand as needed.
4323  *
4324  * @usage
4325  * ###Card with optional footer
4326  * <hljs lang="html">
4327  * <md-card>
4328  *  <img src="card-image.png" class="md-card-image" alt="image caption">
4329  *  <md-card-content>
4330  *    <h2>Card headline</h2>
4331  *    <p>Card content</p>
4332  *  </md-card-content>
4333  *  <md-card-footer>
4334  *    Card footer
4335  *  </md-card-footer>
4336  * </md-card>
4337  * </hljs>
4338  *
4339  * ###Card with actions
4340  * <hljs lang="html">
4341  * <md-card>
4342  *  <img src="card-image.png" class="md-card-image" alt="image caption">
4343  *  <md-card-content>
4344  *    <h2>Card headline</h2>
4345  *    <p>Card content</p>
4346  *  </md-card-content>
4347  *  <div class="md-actions" layout="row" layout-align="end center">
4348  *    <md-button>Action 1</md-button>
4349  *    <md-button>Action 2</md-button>
4350  *  </div>
4351  * </md-card>
4352  * </hljs>
4353  *
4354  */
4355 function mdCardDirective($mdTheming) {
4356   return {
4357     restrict: 'E',
4358     link: function($scope, $element, $attr) {
4359       $mdTheming($element);
4360     }
4361   };
4362 }
4363 mdCardDirective.$inject = ["$mdTheming"];
4364
4365 })();
4366 (function(){
4367 "use strict";
4368
4369 /**
4370  * @ngdoc module
4371  * @name material.components.checkbox
4372  * @description Checkbox module!
4373  */
4374 angular
4375   .module('material.components.checkbox', ['material.core'])
4376   .directive('mdCheckbox', MdCheckboxDirective);
4377
4378 /**
4379  * @ngdoc directive
4380  * @name mdCheckbox
4381  * @module material.components.checkbox
4382  * @restrict E
4383  *
4384  * @description
4385  * The checkbox directive is used like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
4386  *
4387  * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
4388  * the checkbox is in the accent color by default. The primary color palette may be used with
4389  * the `md-primary` class.
4390  *
4391  * @param {string} ng-model Assignable angular expression to data-bind to.
4392  * @param {string=} name Property name of the form under which the control is published.
4393  * @param {expression=} ng-true-value The value to which the expression should be set when selected.
4394  * @param {expression=} ng-false-value The value to which the expression should be set when not selected.
4395  * @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element.
4396  * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects
4397  * @param {string=} aria-label Adds label to checkbox for accessibility.
4398  * Defaults to checkbox's text. If no default text is found, a warning will be logged.
4399  *
4400  * @usage
4401  * <hljs lang="html">
4402  * <md-checkbox ng-model="isChecked" aria-label="Finished?">
4403  *   Finished ?
4404  * </md-checkbox>
4405  *
4406  * <md-checkbox md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
4407  *   No Ink Effects
4408  * </md-checkbox>
4409  *
4410  * <md-checkbox ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
4411  *   Disabled
4412  * </md-checkbox>
4413  *
4414  * </hljs>
4415  *
4416  */
4417 function MdCheckboxDirective(inputDirective, $mdInkRipple, $mdAria, $mdConstant, $mdTheming, $mdUtil, $timeout) {
4418   inputDirective = inputDirective[0];
4419   var CHECKED_CSS = 'md-checked';
4420
4421   return {
4422     restrict: 'E',
4423     transclude: true,
4424     require: '?ngModel',
4425     priority:210, // Run before ngAria
4426     template: 
4427       '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
4428         '<div class="md-icon"></div>' +
4429       '</div>' +
4430       '<div ng-transclude class="md-label"></div>',
4431     compile: compile
4432   };
4433
4434   // **********************************************************
4435   // Private Methods
4436   // **********************************************************
4437
4438   function compile (tElement, tAttrs) {
4439
4440     tAttrs.type = 'checkbox';
4441     tAttrs.tabindex = tAttrs.tabindex || '0';
4442     tElement.attr('role', tAttrs.type);
4443
4444     return function postLink(scope, element, attr, ngModelCtrl) {
4445       ngModelCtrl = ngModelCtrl || $mdUtil.fakeNgModel();
4446       $mdTheming(element);
4447
4448       if (attr.ngChecked) {
4449         scope.$watch(
4450             scope.$eval.bind(scope, attr.ngChecked),
4451             ngModelCtrl.$setViewValue.bind(ngModelCtrl)
4452         );
4453       }
4454       $$watchExpr('ngDisabled', 'tabindex', {
4455         true: '-1',
4456         false: attr.tabindex
4457       });
4458       $mdAria.expectWithText(element, 'aria-label');
4459
4460       // Reuse the original input[type=checkbox] directive from Angular core.
4461       // This is a bit hacky as we need our own event listener and own render
4462       // function.
4463       inputDirective.link.pre(scope, {
4464         on: angular.noop,
4465         0: {}
4466       }, attr, [ngModelCtrl]);
4467
4468       scope.mouseActive = false;
4469       element.on('click', listener)
4470         .on('keypress', keypressHandler)
4471         .on('mousedown', function() {
4472           scope.mouseActive = true;
4473           $timeout(function(){
4474             scope.mouseActive = false;
4475           }, 100);
4476         })
4477         .on('focus', function() {
4478           if(scope.mouseActive === false) { element.addClass('md-focused'); }
4479         })
4480         .on('blur', function() { element.removeClass('md-focused'); });
4481
4482       ngModelCtrl.$render = render;
4483
4484       function $$watchExpr(expr, htmlAttr, valueOpts) {
4485         if (attr[expr]) {
4486           scope.$watch(attr[expr], function(val) {
4487             if (valueOpts[val]) {
4488               element.attr(htmlAttr, valueOpts[val]);
4489             }
4490           });
4491         }
4492       }
4493
4494       function keypressHandler(ev) {
4495         var keyCode = ev.which || ev.keyCode;
4496         if (keyCode === $mdConstant.KEY_CODE.SPACE || keyCode === $mdConstant.KEY_CODE.ENTER) {
4497           ev.preventDefault();
4498           if (!element.hasClass('md-focused')) { element.addClass('md-focused'); }
4499           listener(ev);
4500         }
4501       }
4502       function listener(ev) {
4503         if (element[0].hasAttribute('disabled')) return;
4504
4505         scope.$apply(function() {
4506           // Toggle the checkbox value...
4507           var viewValue = attr.ngChecked ? attr.checked : !ngModelCtrl.$viewValue;
4508
4509           ngModelCtrl.$setViewValue( viewValue, ev && ev.type);
4510           ngModelCtrl.$render();
4511         });
4512       }
4513
4514       function render() {
4515         if(ngModelCtrl.$viewValue) {
4516           element.addClass(CHECKED_CSS);
4517         } else {
4518           element.removeClass(CHECKED_CSS);
4519         }
4520       }
4521     };
4522   }
4523 }
4524 MdCheckboxDirective.$inject = ["inputDirective", "$mdInkRipple", "$mdAria", "$mdConstant", "$mdTheming", "$mdUtil", "$timeout"];
4525
4526 })();
4527 (function(){
4528 "use strict";
4529
4530 /**
4531  * @ngdoc module
4532  * @name material.components.chips
4533  */
4534 /*
4535  * @see js folder for chips implementation
4536  */
4537 angular.module('material.components.chips', [
4538   'material.core',
4539   'material.components.autocomplete'
4540 ]);
4541
4542 })();
4543 (function(){
4544 "use strict";
4545
4546 /**
4547  * @ngdoc module
4548  * @name material.components.content
4549  *
4550  * @description
4551  * Scrollable content
4552  */
4553 angular.module('material.components.content', [
4554   'material.core'
4555 ])
4556   .directive('mdContent', mdContentDirective);
4557
4558 /**
4559  * @ngdoc directive
4560  * @name mdContent
4561  * @module material.components.content
4562  *
4563  * @restrict E
4564  *
4565  * @description
4566  * The `<md-content>` directive is a container element useful for scrollable content
4567  *
4568  * @usage
4569  *
4570  * - Add the `[layout-padding]` attribute to make the content padded.
4571  *
4572  * <hljs lang="html">
4573  *  <md-content layout-padding>
4574  *      Lorem ipsum dolor sit amet, ne quod novum mei.
4575  *  </md-content>
4576  * </hljs>
4577  *
4578  */
4579
4580 function mdContentDirective($mdTheming) {
4581   return {
4582     restrict: 'E',
4583     controller: ['$scope', '$element', ContentController],
4584     link: function(scope, element, attr) {
4585       var node = element[0];
4586
4587       $mdTheming(element);
4588       scope.$broadcast('$mdContentLoaded', element);
4589
4590       iosScrollFix(element[0]);
4591     }
4592   };
4593
4594   function ContentController($scope, $element) {
4595     this.$scope = $scope;
4596     this.$element = $element;
4597   }
4598 }
4599 mdContentDirective.$inject = ["$mdTheming"];
4600
4601 function iosScrollFix(node) {
4602   // IOS FIX:
4603   // If we scroll where there is no more room for the webview to scroll,
4604   // by default the webview itself will scroll up and down, this looks really
4605   // bad.  So if we are scrolling to the very top or bottom, add/subtract one
4606   angular.element(node).on('$md.pressdown', function(ev) {
4607     // Only touch events
4608     if (ev.pointer.type !== 't') return;
4609     // Don't let a child content's touchstart ruin it for us.
4610     if (ev.$materialScrollFixed) return;
4611     ev.$materialScrollFixed = true;
4612
4613     if (node.scrollTop === 0) {
4614       node.scrollTop = 1;
4615     } else if (node.scrollHeight === node.scrollTop + node.offsetHeight) {
4616       node.scrollTop -= 1;
4617     }
4618   });
4619 }
4620
4621 })();
4622 (function(){
4623 "use strict";
4624
4625 /**
4626  * @ngdoc module
4627  * @name material.components.dialog
4628  */
4629 angular.module('material.components.dialog', [
4630   'material.core',
4631   'material.components.backdrop'
4632 ])
4633   .directive('mdDialog', MdDialogDirective)
4634   .provider('$mdDialog', MdDialogProvider);
4635
4636 function MdDialogDirective($$rAF, $mdTheming) {
4637   return {
4638     restrict: 'E',
4639     link: function(scope, element, attr) {
4640       $mdTheming(element);
4641       $$rAF(function() {
4642         var content = element[0].querySelector('md-dialog-content');
4643         if (content && content.scrollHeight > content.clientHeight) {
4644           element.addClass('md-content-overflow');
4645         }
4646       });
4647     }
4648   };
4649 }
4650 MdDialogDirective.$inject = ["$$rAF", "$mdTheming"];
4651
4652 /**
4653  * @ngdoc service
4654  * @name $mdDialog
4655  * @module material.components.dialog
4656  *
4657  * @description
4658  * `$mdDialog` opens a dialog over the app to inform users about critical information or require
4659  *  them to make decisions. There are two approaches for setup: a simple promise API
4660  *  and regular object syntax.
4661  *
4662  * ## Restrictions
4663  *
4664  * - The dialog is always given an isolate scope.
4665  * - The dialog's template must have an outer `<md-dialog>` element.
4666  *   Inside, use an `<md-dialog-content>` element for the dialog's content, and use
4667  *   an element with class `md-actions` for the dialog's actions.
4668  * - Dialogs must cover the entire application to keep interactions inside of them.
4669  * Use the `parent` option to change where dialogs are appended.
4670  *
4671  * ## Sizing
4672  * - Complex dialogs can be sized with `flex="percentage"`, i.e. `flex="66"`.
4673  * - Default max-width is 80% of the `rootElement` or `parent`.
4674  *
4675  * @usage
4676  * <hljs lang="html">
4677  * <div  ng-app="demoApp" ng-controller="EmployeeController">
4678  *   <div>
4679  *     <md-button ng-click="showAlert()" class="md-raised md-warn">
4680  *       Employee Alert!
4681  *       </md-button>
4682  *   </div>
4683  *   <div>
4684  *     <md-button ng-click="showDialog($event)" class="md-raised">
4685  *       Custom Dialog
4686  *       </md-button>
4687  *   </div>
4688  *   <div>
4689  *     <md-button ng-click="closeAlert()" ng-disabled="!hasAlert()" class="md-raised">
4690  *       Close Alert
4691  *     </md-button>
4692  *   </div>
4693  *   <div>
4694  *     <md-button ng-click="showGreeting($event)" class="md-raised md-primary" >
4695  *       Greet Employee
4696  *       </md-button>
4697  *   </div>
4698  * </div>
4699  * </hljs>
4700  *
4701  * ### JavaScript: object syntax
4702  * <hljs lang="js">
4703  * (function(angular, undefined){
4704  *   "use strict";
4705  *
4706  *   angular
4707  *    .module('demoApp', ['ngMaterial'])
4708  *    .controller('AppCtrl', AppController);
4709  *
4710  *   function AppController($scope, $mdDialog) {
4711  *     var alert;
4712  *     $scope.showAlert = showAlert;
4713  *     $scope.showDialog = showDialog;
4714  *     $scope.items = [1, 2, 3];
4715  *
4716  *     // Internal method
4717  *     function showAlert() {
4718  *       alert = $mdDialog.alert({
4719  *         title: 'Attention',
4720  *         content: 'This is an example of how easy dialogs can be!',
4721  *         ok: 'Close'
4722  *       });
4723  *
4724  *       $mdDialog
4725  *         .show( alert )
4726  *         .finally(function() {
4727  *           alert = undefined;
4728  *         });
4729  *     }
4730  *
4731  *     function showDialog($event) {
4732  *        var parentEl = angular.element(document.body);
4733  *        $mdDialog.show({
4734  *          parent: parentEl,
4735  *          targetEvent: $event,
4736  *          template:
4737  *            '<md-dialog aria-label="List dialog">' +
4738  *            '  <md-dialog-content>'+
4739  *            '    <md-list>'+
4740  *            '      <md-list-item ng-repeat="item in items">'+
4741  *            '       <p>Number {{item}}</p>' +
4742  *            '      </md-item>'+
4743  *            '    </md-list>'+
4744  *            '  </md-dialog-content>' +
4745  *            '  <div class="md-actions">' +
4746  *            '    <md-button ng-click="closeDialog()" class="md-primary">' +
4747  *            '      Close Dialog' +
4748  *            '    </md-button>' +
4749  *            '  </div>' +
4750  *            '</md-dialog>',
4751  *          locals: {
4752  *            items: $scope.items
4753  *          },
4754  *          controller: DialogController
4755  *       });
4756  *       function DialogController($scope, $mdDialog, items) {
4757  *         $scope.items = items;
4758  *         $scope.closeDialog = function() {
4759  *           $mdDialog.hide();
4760  *         }
4761  *       }
4762  *     }
4763  *   }
4764  * })(angular);
4765  * </hljs>
4766  *
4767  * ### JavaScript: promise API syntax, custom dialog template
4768  * <hljs lang="js">
4769  * (function(angular, undefined){
4770  *   "use strict";
4771  *
4772  *   angular
4773  *     .module('demoApp', ['ngMaterial'])
4774  *     .controller('EmployeeController', EmployeeEditor)
4775  *     .controller('GreetingController', GreetingController);
4776  *
4777  *   // Fictitious Employee Editor to show how to use simple and complex dialogs.
4778  *
4779  *   function EmployeeEditor($scope, $mdDialog) {
4780  *     var alert;
4781  *
4782  *     $scope.showAlert = showAlert;
4783  *     $scope.closeAlert = closeAlert;
4784  *     $scope.showGreeting = showCustomGreeting;
4785  *
4786  *     $scope.hasAlert = function() { return !!alert };
4787  *     $scope.userName = $scope.userName || 'Bobby';
4788  *
4789  *     // Dialog #1 - Show simple alert dialog and cache
4790  *     // reference to dialog instance
4791  *
4792  *     function showAlert() {
4793  *       alert = $mdDialog.alert()
4794  *         .title('Attention, ' + $scope.userName)
4795  *         .content('This is an example of how easy dialogs can be!')
4796  *         .ok('Close');
4797  *
4798  *       $mdDialog
4799  *           .show( alert )
4800  *           .finally(function() {
4801  *             alert = undefined;
4802  *           });
4803  *     }
4804  *
4805  *     // Close the specified dialog instance and resolve with 'finished' flag
4806  *     // Normally this is not needed, just use '$mdDialog.hide()' to close
4807  *     // the most recent dialog popup.
4808  *
4809  *     function closeAlert() {
4810  *       $mdDialog.hide( alert, "finished" );
4811  *       alert = undefined;
4812  *     }
4813  *
4814  *     // Dialog #2 - Demonstrate more complex dialogs construction and popup.
4815  *
4816  *     function showCustomGreeting($event) {
4817  *         $mdDialog.show({
4818  *           targetEvent: $event,
4819  *           template:
4820  *             '<md-dialog>' +
4821  *
4822  *             '  <md-dialog-content>Hello {{ employee }}!</md-dialog-content>' +
4823  *
4824  *             '  <div class="md-actions">' +
4825  *             '    <md-button ng-click="closeDialog()" class="md-primary">' +
4826  *             '      Close Greeting' +
4827  *             '    </md-button>' +
4828  *             '  </div>' +
4829  *             '</md-dialog>',
4830  *           controller: 'GreetingController',
4831  *           onComplete: afterShowAnimation,
4832  *           locals: { employee: $scope.userName }
4833  *         });
4834  *
4835  *         // When the 'enter' animation finishes...
4836  *
4837  *         function afterShowAnimation(scope, element, options) {
4838  *            // post-show code here: DOM element focus, etc.
4839  *         }
4840  *     }
4841  *
4842  *     // Dialog #3 - Demonstrate use of ControllerAs and passing $scope to dialog
4843  *     //             Here we used ng-controller="GreetingController as vm" and
4844  *     //             $scope.vm === <controller instance>
4845  *
4846  *     function showCustomGreeting() {
4847  *
4848  *        $mdDialog.show({
4849  *           clickOutsideToClose: true,
4850  *
4851  *           scope: $scope,        // use parent scope in template
4852  *           preserveScope: true,  // do not forget this if use parent scope
4853
4854  *           // Since GreetingController is instantiated with ControllerAs syntax
4855  *           // AND we are passing the parent '$scope' to the dialog, we MUST
4856  *           // use 'vm.<xxx>' in the template markup
4857  *
4858  *           template: '<md-dialog>' +
4859  *                     '  <md-dialog-content>' +
4860  *                     '     Hi There {{vm.employee}}' +
4861  *                     '  </md-dialog-content>' +
4862  *                     '</md-dialog>',
4863  *
4864  *           controller: function DialogController($scope, $mdDialog) {
4865  *             $scope.closeDialog = function() {
4866  *               $mdDialog.hide();
4867  *             }
4868  *           }
4869  *        });
4870  *     }
4871  *
4872  *   }
4873  *
4874  *   // Greeting controller used with the more complex 'showCustomGreeting()' custom dialog
4875  *
4876  *   function GreetingController($scope, $mdDialog, employee) {
4877  *     // Assigned from construction <code>locals</code> options...
4878  *     $scope.employee = employee;
4879  *
4880  *     $scope.closeDialog = function() {
4881  *       // Easily hides most recent dialog shown...
4882  *       // no specific instance reference is needed.
4883  *       $mdDialog.hide();
4884  *     };
4885  *   }
4886  *
4887  * })(angular);
4888  * </hljs>
4889  */
4890
4891  /**
4892  * @ngdoc method
4893  * @name $mdDialog#alert
4894  *
4895  * @description
4896  * Builds a preconfigured dialog with the specified message.
4897  *
4898  * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
4899  *
4900  * - $mdDialogPreset#title(string) - sets title to string
4901  * - $mdDialogPreset#content(string) - sets content / message to string
4902  * - $mdDialogPreset#ok(string) - sets okay button text to string
4903  * - $mdDialogPreset#theme(string) - sets the theme of the dialog
4904  *
4905  */
4906
4907  /**
4908  * @ngdoc method
4909  * @name $mdDialog#confirm
4910  *
4911  * @description
4912  * Builds a preconfigured dialog with the specified message. You can call show and the promise returned
4913  * will be resolved only if the user clicks the confirm action on the dialog.
4914  *
4915  * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
4916  *
4917  * Additionally, it supports the following methods:
4918  *
4919  * - $mdDialogPreset#title(string) - sets title to string
4920  * - $mdDialogPreset#content(string) - sets content / message to string
4921  * - $mdDialogPreset#ok(string) - sets okay button text to string
4922  * - $mdDialogPreset#cancel(string) - sets cancel button text to string
4923  * - $mdDialogPreset#theme(string) - sets the theme of the dialog
4924  *
4925  */
4926
4927 /**
4928  * @ngdoc method
4929  * @name $mdDialog#show
4930  *
4931  * @description
4932  * Show a dialog with the specified options.
4933  *
4934  * @param {object} optionsOrPreset Either provide an `$mdDialogPreset` returned from `alert()`, and
4935  * `confirm()`, or an options object with the following properties:
4936  *   - `templateUrl` - `{string=}`: The url of a template that will be used as the content
4937  *   of the dialog.
4938  *   - `template` - `{string=}`: Same as templateUrl, except this is an actual template string.
4939  *   - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option,
4940  *     the location of the click will be used as the starting point for the opening animation
4941  *     of the the dialog.
4942  *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified,
4943  *     it will create a new isolate scope.
4944  *     This scope will be destroyed when the dialog is removed unless `preserveScope` is set to true.
4945  *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
4946  *   - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the dialog is open.
4947  *     Default true.
4948  *   - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog.
4949  *     Default true.
4950  *   - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the dialog to
4951  *     close it. Default false.
4952  *   - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the dialog.
4953  *     Default true.
4954  *   - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on open. Only disable if
4955  *     focusing some other way, as focus management is required for dialogs to be accessible.
4956  *     Defaults to true.
4957  *   - `controller` - `{string=}`: The controller to associate with the dialog. The controller
4958  *     will be injected with the local `$mdDialog`, which passes along a scope for the dialog.
4959  *   - `locals` - `{object=}`: An object containing key/value pairs. The keys will be used as names
4960  *     of values to inject into the controller. For example, `locals: {three: 3}` would inject
4961  *     `three` into the controller, with the value 3. If `bindToController` is true, they will be
4962  *     copied to the controller instead.
4963  *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
4964  *     These values will not be available until after initialization.
4965  *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values, and the
4966  *     dialog will not open until all of the promises resolve.
4967  *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
4968  *   - `parent` - `{element=}`: The element to append the dialog to. Defaults to appending
4969  *     to the root element of the application.
4970  *   - `onComplete` `{function=}`: Callback function used to announce when the show() action is
4971  *     finished.
4972  *
4973  * @returns {promise} A promise that can be resolved with `$mdDialog.hide()` or
4974  * rejected with `$mdDialog.cancel()`.
4975  */
4976
4977 /**
4978  * @ngdoc method
4979  * @name $mdDialog#hide
4980  *
4981  * @description
4982  * Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`.
4983  *
4984  * @param {*=} response An argument for the resolved promise.
4985  *
4986  * @returns {promise} A promise that is resolved when the dialog has been closed.
4987  */
4988
4989 /**
4990  * @ngdoc method
4991  * @name $mdDialog#cancel
4992  *
4993  * @description
4994  * Hide an existing dialog and reject the promise returned from `$mdDialog.show()`.
4995  *
4996  * @param {*=} response An argument for the rejected promise.
4997  *
4998  * @returns {promise} A promise that is resolved when the dialog has been closed.
4999  */
5000
5001 function MdDialogProvider($$interimElementProvider) {
5002
5003   var alertDialogMethods = ['title', 'content', 'ariaLabel', 'ok'];
5004
5005   advancedDialogOptions.$inject = ["$mdDialog", "$mdTheming"];
5006   dialogDefaultOptions.$inject = ["$mdAria", "$document", "$mdUtil", "$mdConstant", "$mdTheming", "$mdDialog", "$timeout", "$rootElement", "$animate", "$$rAF", "$q"];
5007   return $$interimElementProvider('$mdDialog')
5008     .setDefaults({
5009       methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent', 'parent'],
5010       options: dialogDefaultOptions
5011     })
5012     .addPreset('alert', {
5013       methods: ['title', 'content', 'ariaLabel', 'ok', 'theme'],
5014       options: advancedDialogOptions
5015     })
5016     .addPreset('confirm', {
5017       methods: ['title', 'content', 'ariaLabel', 'ok', 'cancel', 'theme'],
5018       options: advancedDialogOptions
5019     });
5020
5021   /* @ngInject */
5022   function advancedDialogOptions($mdDialog, $mdTheming) {
5023     return {
5024       template: [
5025         '<md-dialog md-theme="{{ dialog.theme }}" aria-label="{{ dialog.ariaLabel }}">',
5026           '<md-dialog-content role="document" tabIndex="-1">',
5027             '<h2 class="md-title">{{ dialog.title }}</h2>',
5028             '<p>{{ dialog.content }}</p>',
5029           '</md-dialog-content>',
5030           '<div class="md-actions">',
5031             '<md-button ng-if="dialog.$type == \'confirm\'"' +
5032                       ' ng-click="dialog.abort()" class="md-primary">',
5033               '{{ dialog.cancel }}',
5034             '</md-button>',
5035             '<md-button ng-click="dialog.hide()" class="md-primary">',
5036               '{{ dialog.ok }}',
5037             '</md-button>',
5038           '</div>',
5039         '</md-dialog>'
5040       ].join(''),
5041       controller: function mdDialogCtrl() {
5042         this.hide = function() {
5043           $mdDialog.hide(true);
5044         };
5045         this.abort = function() {
5046           $mdDialog.cancel();
5047         };
5048       },
5049       controllerAs: 'dialog',
5050       bindToController: true,
5051       theme: $mdTheming.defaultTheme()
5052     };
5053   }
5054
5055   /* @ngInject */
5056   function dialogDefaultOptions($mdAria, $document, $mdUtil, $mdConstant, $mdTheming, $mdDialog, $timeout, $rootElement, $animate, $$rAF, $q) {
5057     return {
5058       hasBackdrop: true,
5059       isolateScope: true,
5060       onShow: onShow,
5061       onRemove: onRemove,
5062       clickOutsideToClose: false,
5063       escapeToClose: true,
5064       targetEvent: null,
5065       focusOnOpen: true,
5066       disableParentScroll: true,
5067       transformTemplate: function(template) {
5068         return '<div class="md-dialog-container">' + template + '</div>';
5069       }
5070     };
5071
5072     function trapFocus(ev) {
5073       var dialog = document.querySelector('md-dialog');
5074
5075       if (dialog && !dialog.contains(ev.target)) {
5076         ev.stopImmediatePropagation();
5077         dialog.focus();
5078       }
5079     }
5080
5081     // On show method for dialogs
5082     function onShow(scope, element, options) {
5083       angular.element($document[0].body).addClass('md-dialog-is-showing');
5084       element = $mdUtil.extractElementByName(element, 'md-dialog');
5085
5086       // Incase the user provides a raw dom element, always wrap it in jqLite
5087       options.parent = angular.element(options.parent);
5088
5089       options.popInTarget = angular.element((options.targetEvent || {}).target);
5090       var closeButton = findCloseButton();
5091
5092       if (options.hasBackdrop) {
5093         // Fix for IE 10
5094         var computeFrom = (options.parent[0] == $document[0].body && $document[0].documentElement
5095                            && $document[0].documentElement.scrollTop) ? angular.element($document[0].documentElement) : options.parent;
5096         var parentOffset = computeFrom.prop('scrollTop');
5097         options.backdrop = angular.element('<md-backdrop class="md-dialog-backdrop md-opaque">');
5098         options.backdrop.css('top', parentOffset +'px');
5099         $mdTheming.inherit(options.backdrop, options.parent);
5100         $animate.enter(options.backdrop, options.parent);
5101         element.css('top', parentOffset +'px');
5102       }
5103
5104       var role = 'dialog',
5105           elementToFocus = closeButton;
5106
5107       if (options.$type === 'alert') {
5108         role = 'alertdialog';
5109         elementToFocus = element.find('md-dialog-content');
5110       }
5111
5112       configureAria(element.find('md-dialog'), role, options);
5113
5114       document.addEventListener('focus', trapFocus, true);
5115
5116       if (options.disableParentScroll) {
5117         options.lastOverflow = options.parent.css('overflow');
5118         options.parent.css('overflow', 'hidden');
5119       }
5120
5121       return dialogPopIn(
5122         element,
5123         options.parent,
5124         options.popInTarget && options.popInTarget.length && options.popInTarget
5125       )
5126       .then(function() {
5127
5128         applyAriaToSiblings(element, true);
5129
5130         if (options.escapeToClose) {
5131           options.rootElementKeyupCallback = function(e) {
5132             if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
5133               $timeout($mdDialog.cancel);
5134             }
5135           };
5136           $rootElement.on('keyup', options.rootElementKeyupCallback);
5137         }
5138
5139         if (options.clickOutsideToClose) {
5140           options.dialogClickOutsideCallback = function(ev) {
5141             // Only close if we click the flex container outside the backdrop
5142             if (ev.target === element[0]) {
5143               $timeout($mdDialog.cancel);
5144             }
5145           };
5146           element.on('click', options.dialogClickOutsideCallback);
5147         }
5148
5149         if (options.focusOnOpen) {
5150           elementToFocus.focus();
5151         }
5152       });
5153
5154
5155       function findCloseButton() {
5156         //If no element with class dialog-close, try to find the last
5157         //button child in md-actions and assume it is a close button
5158         var closeButton = element[0].querySelector('.dialog-close');
5159         if (!closeButton) {
5160           var actionButtons = element[0].querySelectorAll('.md-actions button');
5161           closeButton = actionButtons[ actionButtons.length - 1 ];
5162         }
5163         return angular.element(closeButton);
5164       }
5165
5166     }
5167
5168     // On remove function for all dialogs
5169     function onRemove(scope, element, options) {
5170       angular.element($document[0].body).removeClass('md-dialog-is-showing');
5171
5172       if (options.backdrop) {
5173         $animate.leave(options.backdrop);
5174       }
5175       if (options.disableParentScroll) {
5176         options.parent.css('overflow', options.lastOverflow);
5177         delete options.lastOverflow;
5178       }
5179       if (options.escapeToClose) {
5180         $rootElement.off('keyup', options.rootElementKeyupCallback);
5181       }
5182       if (options.clickOutsideToClose) {
5183         element.off('click', options.dialogClickOutsideCallback);
5184       }
5185
5186       applyAriaToSiblings(element, false);
5187
5188       document.removeEventListener('focus', trapFocus, true);
5189
5190       return dialogPopOut(
5191         element,
5192         options.parent,
5193         options.popInTarget && options.popInTarget.length && options.popInTarget
5194       ).then(function() {
5195         element.remove();
5196         options.popInTarget && options.popInTarget.focus();
5197       });
5198
5199     }
5200
5201     /**
5202      * Inject ARIA-specific attributes appropriate for Dialogs
5203      */
5204     function configureAria(element, role, options) {
5205
5206       element.attr({
5207         'role': role,
5208         'tabIndex': '-1'
5209       });
5210
5211       var dialogContent = element.find('md-dialog-content');
5212       if (dialogContent.length === 0){
5213         dialogContent = element;
5214       }
5215
5216       var dialogId = element.attr('id') || ('dialog_' + $mdUtil.nextUid());
5217       dialogContent.attr('id', dialogId);
5218       element.attr('aria-describedby', dialogId);
5219
5220       if (options.ariaLabel) {
5221         $mdAria.expect(element, 'aria-label', options.ariaLabel);
5222       }
5223       else {
5224         $mdAria.expectAsync(element, 'aria-label', function() {
5225           var words = dialogContent.text().split(/\s+/);
5226           if (words.length > 3) words = words.slice(0,3).concat('...');
5227           return words.join(' ');
5228         });
5229       }
5230     }
5231     /**
5232      * Utility function to filter out raw DOM nodes
5233      */
5234     function isNodeOneOf(elem, nodeTypeArray) {
5235       if (nodeTypeArray.indexOf(elem.nodeName) !== -1) {
5236         return true;
5237       }
5238     }
5239     /**
5240      * Walk DOM to apply or remove aria-hidden on sibling nodes
5241      * and parent sibling nodes
5242      *
5243      * Prevents screen reader interaction behind modal window
5244      * on swipe interfaces
5245      */
5246     function applyAriaToSiblings(element, value) {
5247       var attribute = 'aria-hidden';
5248
5249       // get raw DOM node
5250       element = element[0];
5251
5252       function walkDOM(element) {
5253         while (element.parentNode) {
5254           if (element === document.body) {
5255             return;
5256           }
5257           var children = element.parentNode.children;
5258           for (var i = 0; i < children.length; i++) {
5259             // skip over child if it is an ascendant of the dialog
5260             // or a script or style tag
5261             if (element !== children[i] && !isNodeOneOf(children[i], ['SCRIPT', 'STYLE'])) {
5262               children[i].setAttribute(attribute, value);
5263             }
5264           }
5265
5266           walkDOM(element = element.parentNode);
5267         }
5268       }
5269       walkDOM(element);
5270     }
5271
5272     function dialogPopIn(container, parentElement, clickElement) {
5273       var dialogEl = container.find('md-dialog');
5274
5275       parentElement.append(container);
5276       transformToClickElement(dialogEl, clickElement);
5277
5278       $$rAF(function() {
5279         dialogEl.addClass('transition-in')
5280           .css($mdConstant.CSS.TRANSFORM, '');
5281       });
5282
5283       return $mdUtil.transitionEndPromise(dialogEl);
5284     }
5285
5286     function dialogPopOut(container, parentElement, clickElement) {
5287       var dialogEl = container.find('md-dialog');
5288
5289       dialogEl.addClass('transition-out').removeClass('transition-in');
5290       transformToClickElement(dialogEl, clickElement);
5291
5292       return $mdUtil.transitionEndPromise(dialogEl);
5293     }
5294
5295     function transformToClickElement(dialogEl, clickElement) {
5296       if (clickElement) {
5297         var clickRect = clickElement[0].getBoundingClientRect();
5298         var dialogRect = dialogEl[0].getBoundingClientRect();
5299
5300         var scaleX = Math.min(0.5, clickRect.width / dialogRect.width);
5301         var scaleY = Math.min(0.5, clickRect.height / dialogRect.height);
5302
5303         dialogEl.css($mdConstant.CSS.TRANSFORM, 'translate3d(' +
5304           (-dialogRect.left + clickRect.left + clickRect.width/2 - dialogRect.width/2) + 'px,' +
5305           (-dialogRect.top + clickRect.top + clickRect.height/2 - dialogRect.height/2) + 'px,' +
5306           '0) scale(' + scaleX + ',' + scaleY + ')'
5307         );
5308       }
5309     }
5310
5311     function dialogTransitionEnd(dialogEl) {
5312       var deferred = $q.defer();
5313       dialogEl.on($mdConstant.CSS.TRANSITIONEND, finished);
5314       function finished(ev) {
5315         //Make sure this transitionend didn't bubble up from a child
5316         if (ev.target === dialogEl[0]) {
5317           dialogEl.off($mdConstant.CSS.TRANSITIONEND, finished);
5318           deferred.resolve();
5319         }
5320       }
5321       return deferred.promise;
5322     }
5323
5324   }
5325 }
5326 MdDialogProvider.$inject = ["$$interimElementProvider"];
5327
5328 })();
5329 (function(){
5330 "use strict";
5331
5332 /**
5333  * @ngdoc module
5334  * @name material.components.divider
5335  * @description Divider module!
5336  */
5337 angular.module('material.components.divider', [
5338   'material.core'
5339 ])
5340   .directive('mdDivider', MdDividerDirective);
5341
5342 /**
5343  * @ngdoc directive
5344  * @name mdDivider
5345  * @module material.components.divider
5346  * @restrict E
5347  *
5348  * @description
5349  * 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.
5350  *
5351  * @param {boolean=} md-inset Add this attribute to activate the inset divider style.
5352  * @usage
5353  * <hljs lang="html">
5354  * <md-divider></md-divider>
5355  *
5356  * <md-divider md-inset></md-divider>
5357  * </hljs>
5358  *
5359  */
5360 function MdDividerDirective($mdTheming) {
5361   return {
5362     restrict: 'E',
5363     link: $mdTheming
5364   };
5365 }
5366 MdDividerDirective.$inject = ["$mdTheming"];
5367
5368 })();
5369 (function(){
5370 "use strict";
5371
5372 /**
5373  * @ngdoc module
5374  * @name material.components.gridList
5375  */
5376 angular.module('material.components.gridList', ['material.core'])
5377        .directive('mdGridList', GridListDirective)
5378        .directive('mdGridTile', GridTileDirective)
5379        .directive('mdGridTileFooter', GridTileCaptionDirective)
5380        .directive('mdGridTileHeader', GridTileCaptionDirective)
5381        .factory('$mdGridLayout', GridLayoutFactory);
5382
5383 /**
5384  * @ngdoc directive
5385  * @name mdGridList
5386  * @module material.components.gridList
5387  * @restrict E
5388  * @description
5389  * Grid lists are an alternative to standard list views. Grid lists are distinct
5390  * from grids used for layouts and other visual presentations.
5391  *
5392  * A grid list is best suited to presenting a homogenous data type, typically
5393  * images, and is optimized for visual comprehension and differentiating between
5394  * like data types.
5395  *
5396  * A grid list is a continuous element consisting of tessellated, regular
5397  * subdivisions called cells that contain tiles (`md-grid-tile`).
5398  *
5399  * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7OVlEaXZ5YmU1Xzg/components_grids_usage2.png"
5400  *    style="width: 300px; height: auto; margin-right: 16px;" alt="Concept of grid explained visually">
5401  * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7VGhsOE5idWlJWXM/components_grids_usage3.png"
5402  *    style="width: 300px; height: auto;" alt="Grid concepts legend">
5403  *
5404  * Cells are arrayed vertically and horizontally within the grid.
5405  *
5406  * Tiles hold content and can span one or more cells vertically or horizontally.
5407  *
5408  * ### Responsive Attributes
5409  *
5410  * The `md-grid-list` directive supports "responsive" attributes, which allow
5411  * different `md-cols`, `md-gutter` and `md-row-height` values depending on the
5412  * currently matching media query (as defined in `$mdConstant.MEDIA`).
5413  *
5414  * In order to set a responsive attribute, first define the fallback value with
5415  * the standard attribute name, then add additional attributes with the
5416  * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
5417  * (ie. `md-cols-lg="8"`)
5418  *
5419  * @param {number} md-cols Number of columns in the grid.
5420  * @param {string} md-row-height One of
5421  * <ul>
5422  *   <li>CSS length - Fixed height rows (eg. `8px` or `1rem`)</li>
5423  *   <li>`{width}:{height}` - Ratio of width to height (eg.
5424  *   `md-row-height="16:9"`)</li>
5425  *   <li>`"fit"` - Height will be determined by subdividing the available
5426  *   height by the number of rows</li>
5427  * </ul>
5428  * @param {string=} md-gutter The amount of space between tiles in CSS units
5429  *     (default 1px)
5430  * @param {expression=} md-on-layout Expression to evaluate after layout. Event
5431  *     object is available as `$event`, and contains performance information.
5432  *
5433  * @usage
5434  * Basic:
5435  * <hljs lang="html">
5436  * <md-grid-list md-cols="5" md-gutter="1em" md-row-height="4:3">
5437  *   <md-grid-tile></md-grid-tile>
5438  * </md-grid-list>
5439  * </hljs>
5440  *
5441  * Fixed-height rows:
5442  * <hljs lang="html">
5443  * <md-grid-list md-cols="4" md-row-height="200px" ...>
5444  *   <md-grid-tile></md-grid-tile>
5445  * </md-grid-list>
5446  * </hljs>
5447  *
5448  * Fit rows:
5449  * <hljs lang="html">
5450  * <md-grid-list md-cols="4" md-row-height="fit" style="height: 400px;" ...>
5451  *   <md-grid-tile></md-grid-tile>
5452  * </md-grid-list>
5453  * </hljs>
5454  *
5455  * Using responsive attributes:
5456  * <hljs lang="html">
5457  * <md-grid-list
5458  *     md-cols-sm="2"
5459  *     md-cols-md="4"
5460  *     md-cols-lg="8"
5461  *     md-cols-gt-lg="12"
5462  *     ...>
5463  *   <md-grid-tile></md-grid-tile>
5464  * </md-grid-list>
5465  * </hljs>
5466  */
5467 function GridListDirective($interpolate, $mdConstant, $mdGridLayout, $mdMedia) {
5468   return {
5469     restrict: 'E',
5470     controller: GridListController,
5471     scope: {
5472       mdOnLayout: '&'
5473     },
5474     link: postLink
5475   };
5476
5477   function postLink(scope, element, attrs, ctrl) {
5478     // Apply semantics
5479     element.attr('role', 'list');
5480
5481     // Provide the controller with a way to trigger layouts.
5482     ctrl.layoutDelegate = layoutDelegate;
5483
5484     var invalidateLayout = angular.bind(ctrl, ctrl.invalidateLayout),
5485         unwatchAttrs = watchMedia();
5486       scope.$on('$destroy', unwatchMedia);
5487
5488     /**
5489      * Watches for changes in media, invalidating layout as necessary.
5490      */
5491     function watchMedia() {
5492       for (var mediaName in $mdConstant.MEDIA) {
5493         $mdMedia(mediaName); // initialize
5494         $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
5495             .addListener(invalidateLayout);
5496       }
5497       return $mdMedia.watchResponsiveAttributes(
5498           ['md-cols', 'md-row-height'], attrs, layoutIfMediaMatch);
5499     }
5500
5501     function unwatchMedia() {
5502       ctrl.layoutDelegate = angular.noop;
5503
5504       unwatchAttrs();
5505       for (var mediaName in $mdConstant.MEDIA) {
5506         $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
5507             .removeListener(invalidateLayout);
5508       }
5509     }
5510
5511     /**
5512      * Performs grid layout if the provided mediaName matches the currently
5513      * active media type.
5514      */
5515     function layoutIfMediaMatch(mediaName) {
5516       if (mediaName == null) {
5517         // TODO(shyndman): It would be nice to only layout if we have
5518         // instances of attributes using this media type
5519         ctrl.invalidateLayout();
5520       } else if ($mdMedia(mediaName)) {
5521         ctrl.invalidateLayout();
5522       }
5523     }
5524
5525     var lastLayoutProps;
5526
5527     /**
5528      * Invokes the layout engine, and uses its results to lay out our
5529      * tile elements.
5530      *
5531      * @param {boolean} tilesInvalidated Whether tiles have been
5532      *    added/removed/moved since the last layout. This is to avoid situations
5533      *    where tiles are replaced with properties identical to their removed
5534      *    counterparts.
5535      */
5536     function layoutDelegate(tilesInvalidated) {
5537       var tiles = getTileElements();
5538       var props = {
5539         tileSpans: getTileSpans(tiles),
5540         colCount: getColumnCount(),
5541         rowMode: getRowMode(),
5542         rowHeight: getRowHeight(),
5543         gutter: getGutter()
5544       };
5545
5546       if (!tilesInvalidated && angular.equals(props, lastLayoutProps)) {
5547         return;
5548       }
5549
5550       var performance =
5551         $mdGridLayout(props.colCount, props.tileSpans, tiles)
5552           .map(function(tilePositions, rowCount) {
5553             return {
5554               grid: {
5555                 element: element,
5556                 style: getGridStyle(props.colCount, rowCount,
5557                     props.gutter, props.rowMode, props.rowHeight)
5558               },
5559               tiles: tilePositions.map(function(ps, i) {
5560                 return {
5561                   element: angular.element(tiles[i]),
5562                   style: getTileStyle(ps.position, ps.spans,
5563                       props.colCount, props.rowCount,
5564                       props.gutter, props.rowMode, props.rowHeight)
5565                 }
5566               })
5567             }
5568           })
5569           .reflow()
5570           .performance();
5571
5572       // Report layout
5573       scope.mdOnLayout({
5574         $event: {
5575           performance: performance
5576         }
5577       });
5578
5579       lastLayoutProps = props;
5580     }
5581
5582     // Use $interpolate to do some simple string interpolation as a convenience.
5583
5584     var startSymbol = $interpolate.startSymbol();
5585     var endSymbol = $interpolate.endSymbol();
5586
5587     // Returns an expression wrapped in the interpolator's start and end symbols.
5588     function expr(exprStr) {
5589       return startSymbol + exprStr + endSymbol;
5590     }
5591
5592     // The amount of space a single 1x1 tile would take up (either width or height), used as
5593     // a basis for other calculations. This consists of taking the base size percent (as would be
5594     // if evenly dividing the size between cells), and then subtracting the size of one gutter.
5595     // However, since there are no gutters on the edges, each tile only uses a fration
5596     // (gutterShare = numGutters / numCells) of the gutter size. (Imagine having one gutter per
5597     // tile, and then breaking up the extra gutter on the edge evenly among the cells).
5598     var UNIT = $interpolate(expr('share') + '% - (' + expr('gutter') + ' * ' + expr('gutterShare') + ')');
5599
5600     // The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value.
5601     // The position comes the size of a 1x1 tile plus gutter for each previous tile in the
5602     // row/column (offset).
5603     var POSITION  = $interpolate('calc((' + expr('unit') + ' + ' + expr('gutter') + ') * ' + expr('offset') + ')');
5604
5605     // The actual size of a tile, e.g., width or height, taking rowSpan or colSpan into account.
5606     // This is computed by multiplying the base unit by the rowSpan/colSpan, and then adding back
5607     // in the space that the gutter would normally have used (which was already accounted for in
5608     // the base unit calculation).
5609     var DIMENSION = $interpolate('calc((' + expr('unit') + ') * ' + expr('span') + ' + (' + expr('span') + ' - 1) * ' + expr('gutter') + ')');
5610
5611     /**
5612      * Gets the styles applied to a tile element described by the given parameters.
5613      * @param {{row: number, col: number}} position The row and column indices of the tile.
5614      * @param {{row: number, col: number}} spans The rowSpan and colSpan of the tile.
5615      * @param {number} colCount The number of columns.
5616      * @param {number} rowCount The number of rows.
5617      * @param {string} gutter The amount of space between tiles. This will be something like
5618      *     '5px' or '2em'.
5619      * @param {string} rowMode The row height mode. Can be one of:
5620      *     'fixed': all rows have a fixed size, given by rowHeight,
5621      *     'ratio': row height defined as a ratio to width, or
5622      *     'fit': fit to the grid-list element height, divinding evenly among rows.
5623      * @param {string|number} rowHeight The height of a row. This is only used for 'fixed' mode and
5624      *     for 'ratio' mode. For 'ratio' mode, this is the *ratio* of width-to-height (e.g., 0.75).
5625      * @returns {Object} Map of CSS properties to be applied to the style element. Will define
5626      *     values for top, left, width, height, marginTop, and paddingTop.
5627      */
5628     function getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) {
5629       // TODO(shyndman): There are style caching opportunities here.
5630
5631       // Percent of the available horizontal space that one column takes up.
5632       var hShare = (1 / colCount) * 100;
5633
5634       // Fraction of the gutter size that each column takes up.
5635       var hGutterShare = (colCount - 1) / colCount;
5636
5637       // Base horizontal size of a column.
5638       var hUnit = UNIT({share: hShare, gutterShare: hGutterShare, gutter: gutter});
5639
5640       // The width and horizontal position of each tile is always calculated the same way, but the
5641       // height and vertical position depends on the rowMode.
5642       var style = {
5643         left: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }),
5644         width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }),
5645         // resets
5646         paddingTop: '',
5647         marginTop: '',
5648         top: '',
5649         height: ''
5650       };
5651
5652       switch (rowMode) {
5653         case 'fixed':
5654           // In fixed mode, simply use the given rowHeight.
5655           style.top = POSITION({ unit: rowHeight, offset: position.row, gutter: gutter });
5656           style.height = DIMENSION({ unit: rowHeight, span: spans.row, gutter: gutter });
5657           break;
5658
5659         case 'ratio':
5660           // Percent of the available vertical space that one row takes up. Here, rowHeight holds
5661           // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333.
5662           var vShare = hShare / rowHeight;
5663
5664           // Base veritcal size of a row.
5665           var vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
5666
5667           // padidngTop and marginTop are used to maintain the given aspect ratio, as
5668           // a percentage-based value for these properties is applied to the *width* of the
5669           // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
5670           style.paddingTop = DIMENSION({ unit: vUnit, span: spans.row, gutter: gutter});
5671           style.marginTop = POSITION({ unit: vUnit, offset: position.row, gutter: gutter });
5672           break;
5673
5674         case 'fit':
5675           // Fraction of the gutter size that each column takes up.
5676           var vGutterShare = (rowCount - 1) / rowCount;
5677
5678           // Percent of the available vertical space that one row takes up.
5679           var vShare = (1 / rowCount) * 100;
5680
5681           // Base vertical size of a row.
5682           var vUnit = UNIT({share: vShare, gutterShare: vGutterShare, gutter: gutter});
5683
5684           style.top = POSITION({unit: vUnit, offset: position.row, gutter: gutter});
5685           style.height = DIMENSION({unit: vUnit, span: spans.row, gutter: gutter});
5686           break;
5687       }
5688
5689       return style;
5690     }
5691
5692     function getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) {
5693       var style = {
5694         height: '',
5695         paddingBottom: ''
5696       };
5697
5698       switch(rowMode) {
5699         case 'fixed':
5700           style.height = DIMENSION({ unit: rowHeight, span: rowCount, gutter: gutter });
5701           break;
5702
5703         case 'ratio':
5704           // rowHeight is width / height
5705           var hGutterShare = colCount === 1 ? 0 : (colCount - 1) / colCount,
5706               hShare = (1 / colCount) * 100,
5707               vShare = hShare * (1 / rowHeight),
5708               vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
5709
5710           style.paddingBottom = DIMENSION({ unit: vUnit, span: rowCount, gutter: gutter});
5711           break;
5712
5713         case 'fit':
5714           // noop, as the height is user set
5715           break;
5716       }
5717
5718       return style;
5719     }
5720
5721     function getTileElements() {
5722       return [].filter.call(element.children(), function(ele) {
5723         return ele.tagName == 'MD-GRID-TILE';
5724       });
5725     }
5726
5727     /**
5728      * Gets an array of objects containing the rowspan and colspan for each tile.
5729      * @returns {Array<{row: number, col: number}>}
5730      */
5731     function getTileSpans(tileElements) {
5732       return [].map.call(tileElements, function(ele) {
5733         var ctrl = angular.element(ele).controller('mdGridTile');
5734         return {
5735           row: parseInt(
5736               $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-rowspan'), 10) || 1,
5737           col: parseInt(
5738               $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-colspan'), 10) || 1
5739         };
5740       });
5741     }
5742
5743     function getColumnCount() {
5744       var colCount = parseInt($mdMedia.getResponsiveAttribute(attrs, 'md-cols'), 10);
5745       if (isNaN(colCount)) {
5746         throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value';
5747       }
5748       return colCount;
5749     }
5750
5751     function getGutter() {
5752       return applyDefaultUnit($mdMedia.getResponsiveAttribute(attrs, 'md-gutter') || 1);
5753     }
5754
5755     function getRowHeight() {
5756       var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
5757       switch (getRowMode()) {
5758         case 'fixed':
5759           return applyDefaultUnit(rowHeight);
5760         case 'ratio':
5761           var whRatio = rowHeight.split(':');
5762           return parseFloat(whRatio[0]) / parseFloat(whRatio[1]);
5763         case 'fit':
5764           return 0; // N/A
5765       }
5766     }
5767
5768     function getRowMode() {
5769       var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
5770       if (rowHeight == 'fit') {
5771         return 'fit';
5772       } else if (rowHeight.indexOf(':') !== -1) {
5773         return 'ratio';
5774       } else {
5775         return 'fixed';
5776       }
5777     }
5778
5779     function applyDefaultUnit(val) {
5780       return /\D$/.test(val) ? val : val + 'px';
5781     }
5782   }
5783 }
5784 GridListDirective.$inject = ["$interpolate", "$mdConstant", "$mdGridLayout", "$mdMedia"];
5785
5786 /* @ngInject */
5787 function GridListController($timeout) {
5788   this.layoutInvalidated = false;
5789   this.tilesInvalidated = false;
5790   this.$timeout_ = $timeout;
5791   this.layoutDelegate = angular.noop;
5792 }
5793 GridListController.$inject = ["$timeout"];
5794
5795 GridListController.prototype = {
5796   invalidateTiles: function() {
5797     this.tilesInvalidated = true;
5798     this.invalidateLayout();
5799   },
5800
5801   invalidateLayout: function() {
5802     if (this.layoutInvalidated) {
5803       return;
5804     }
5805     this.layoutInvalidated = true;
5806     this.$timeout_(angular.bind(this, this.layout));
5807   },
5808
5809   layout: function() {
5810     try {
5811       this.layoutDelegate(this.tilesInvalidated);
5812     } finally {
5813       this.layoutInvalidated = false;
5814       this.tilesInvalidated = false;
5815     }
5816   }
5817 };
5818
5819
5820 /* @ngInject */
5821 function GridLayoutFactory($mdUtil) {
5822   var defaultAnimator = GridTileAnimator;
5823
5824   /**
5825    * Set the reflow animator callback
5826    */
5827   GridLayout.animateWith = function(customAnimator) {
5828     defaultAnimator = !angular.isFunction(customAnimator) ? GridTileAnimator : customAnimator;
5829   };
5830
5831   return GridLayout;
5832
5833   /**
5834    * Publish layout function
5835    */
5836   function GridLayout(colCount, tileSpans) {
5837       var self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime;
5838
5839       layoutTime = $mdUtil.time(function() {
5840         layoutInfo = calculateGridFor(colCount, tileSpans);
5841       });
5842
5843       return self = {
5844
5845         /**
5846          * An array of objects describing each tile's position in the grid.
5847          */
5848         layoutInfo: function() {
5849           return layoutInfo;
5850         },
5851
5852         /**
5853          * Maps grid positioning to an element and a set of styles using the
5854          * provided updateFn.
5855          */
5856         map: function(updateFn) {
5857           mapTime = $mdUtil.time(function() {
5858             var info = self.layoutInfo();
5859             gridStyles = updateFn(info.positioning, info.rowCount);
5860           });
5861           return self;
5862         },
5863
5864         /**
5865          * Default animator simply sets the element.css( <styles> ). An alternate
5866          * animator can be provided as an argument. The function has the following
5867          * signature:
5868          *
5869          *    function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>)
5870          */
5871         reflow: function(animatorFn) {
5872           reflowTime = $mdUtil.time(function() {
5873             var animator = animatorFn || defaultAnimator;
5874             animator(gridStyles.grid, gridStyles.tiles);
5875           });
5876           return self;
5877         },
5878
5879         /**
5880          * Timing for the most recent layout run.
5881          */
5882         performance: function() {
5883           return {
5884             tileCount: tileSpans.length,
5885             layoutTime: layoutTime,
5886             mapTime: mapTime,
5887             reflowTime: reflowTime,
5888             totalTime: layoutTime + mapTime + reflowTime
5889           };
5890         }
5891       };
5892     }
5893
5894   /**
5895    * Default Gridlist animator simple sets the css for each element;
5896    * NOTE: any transitions effects must be manually set in the CSS.
5897    * e.g.
5898    *
5899    *  md-grid-tile {
5900    *    transition: all 700ms ease-out 50ms;
5901    *  }
5902    *
5903    */
5904   function GridTileAnimator(grid, tiles) {
5905     grid.element.css(grid.style);
5906     tiles.forEach(function(t) {
5907       t.element.css(t.style);
5908     })
5909   }
5910
5911   /**
5912    * Calculates the positions of tiles.
5913    *
5914    * The algorithm works as follows:
5915    *    An Array<Number> with length colCount (spaceTracker) keeps track of
5916    *    available tiling positions, where elements of value 0 represents an
5917    *    empty position. Space for a tile is reserved by finding a sequence of
5918    *    0s with length <= than the tile's colspan. When such a space has been
5919    *    found, the occupied tile positions are incremented by the tile's
5920    *    rowspan value, as these positions have become unavailable for that
5921    *    many rows.
5922    *
5923    *    If the end of a row has been reached without finding space for the
5924    *    tile, spaceTracker's elements are each decremented by 1 to a minimum
5925    *    of 0. Rows are searched in this fashion until space is found.
5926    */
5927   function calculateGridFor(colCount, tileSpans) {
5928     var curCol = 0,
5929         curRow = 0,
5930         spaceTracker = newSpaceTracker();
5931
5932     return {
5933       positioning: tileSpans.map(function(spans, i) {
5934         return {
5935           spans: spans,
5936           position: reserveSpace(spans, i)
5937         };
5938       }),
5939       rowCount: curRow + Math.max.apply(Math, spaceTracker)
5940     };
5941
5942     function reserveSpace(spans, i) {
5943       if (spans.col > colCount) {
5944         throw 'md-grid-list: Tile at position ' + i + ' has a colspan ' +
5945             '(' + spans.col + ') that exceeds the column count ' +
5946             '(' + colCount + ')';
5947       }
5948
5949       var start = 0,
5950           end = 0;
5951
5952       // TODO(shyndman): This loop isn't strictly necessary if you can
5953       // determine the minimum number of rows before a space opens up. To do
5954       // this, recognize that you've iterated across an entire row looking for
5955       // space, and if so fast-forward by the minimum rowSpan count. Repeat
5956       // until the required space opens up.
5957       while (end - start < spans.col) {
5958         if (curCol >= colCount) {
5959           nextRow();
5960           continue;
5961         }
5962
5963         start = spaceTracker.indexOf(0, curCol);
5964         if (start === -1 || (end = findEnd(start + 1)) === -1) {
5965           start = end = 0;
5966           nextRow();
5967           continue;
5968         }
5969
5970         curCol = end + 1;
5971       }
5972
5973       adjustRow(start, spans.col, spans.row);
5974       curCol = start + spans.col;
5975
5976       return {
5977         col: start,
5978         row: curRow
5979       };
5980     }
5981
5982     function nextRow() {
5983       curCol = 0;
5984       curRow++;
5985       adjustRow(0, colCount, -1); // Decrement row spans by one
5986     }
5987
5988     function adjustRow(from, cols, by) {
5989       for (var i = from; i < from + cols; i++) {
5990         spaceTracker[i] = Math.max(spaceTracker[i] + by, 0);
5991       }
5992     }
5993
5994     function findEnd(start) {
5995       var i;
5996       for (i = start; i < spaceTracker.length; i++) {
5997         if (spaceTracker[i] !== 0) {
5998           return i;
5999         }
6000       }
6001
6002       if (i === spaceTracker.length) {
6003         return i;
6004       }
6005     }
6006
6007     function newSpaceTracker() {
6008       var tracker = [];
6009       for (var i = 0; i < colCount; i++) {
6010         tracker.push(0);
6011       }
6012       return tracker;
6013     }
6014   }
6015 }
6016 GridLayoutFactory.$inject = ["$mdUtil"];
6017
6018 /**
6019  * @ngdoc directive
6020  * @name mdGridTile
6021  * @module material.components.gridList
6022  * @restrict E
6023  * @description
6024  * Tiles contain the content of an `md-grid-list`. They span one or more grid
6025  * cells vertically or horizontally, and use `md-grid-tile-{footer,header}` to
6026  * display secondary content.
6027  *
6028  * ### Responsive Attributes
6029  *
6030  * The `md-grid-tile` directive supports "responsive" attributes, which allow
6031  * different `md-rowspan` and `md-colspan` values depending on the currently
6032  * matching media query (as defined in `$mdConstant.MEDIA`).
6033  *
6034  * In order to set a responsive attribute, first define the fallback value with
6035  * the standard attribute name, then add additional attributes with the
6036  * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
6037  * (ie. `md-colspan-sm="4"`)
6038  *
6039  * @param {number=} md-colspan The number of columns to span (default 1). Cannot
6040  *    exceed the number of columns in the grid. Supports interpolation.
6041  * @param {number=} md-rowspan The number of rows to span (default 1). Supports
6042  *     interpolation.
6043  *
6044  * @usage
6045  * With header:
6046  * <hljs lang="html">
6047  * <md-grid-tile>
6048  *   <md-grid-tile-header>
6049  *     <h3>This is a header</h3>
6050  *   </md-grid-tile-header>
6051  * </md-grid-tile>
6052  * </hljs>
6053  *
6054  * With footer:
6055  * <hljs lang="html">
6056  * <md-grid-tile>
6057  *   <md-grid-tile-footer>
6058  *     <h3>This is a footer</h3>
6059  *   </md-grid-tile-footer>
6060  * </md-grid-tile>
6061  * </hljs>
6062  *
6063  * Spanning multiple rows/columns:
6064  * <hljs lang="html">
6065  * <md-grid-tile md-colspan="2" md-rowspan="3">
6066  * </md-grid-tile>
6067  * </hljs>
6068  *
6069  * Responsive attributes:
6070  * <hljs lang="html">
6071  * <md-grid-tile md-colspan="1" md-colspan-sm="3" md-colspan-md="5">
6072  * </md-grid-tile>
6073  * </hljs>
6074  */
6075 function GridTileDirective($mdMedia) {
6076   return {
6077     restrict: 'E',
6078     require: '^mdGridList',
6079     template: '<figure ng-transclude></figure>',
6080     transclude: true,
6081     scope: {},
6082     // Simple controller that exposes attributes to the grid directive
6083     controller: ["$attrs", function($attrs) {
6084       this.$attrs = $attrs;
6085     }],
6086     link: postLink
6087   };
6088
6089   function postLink(scope, element, attrs, gridCtrl) {
6090     // Apply semantics
6091     element.attr('role', 'listitem');
6092
6093     // If our colspan or rowspan changes, trigger a layout
6094     var unwatchAttrs = $mdMedia.watchResponsiveAttributes(['md-colspan', 'md-rowspan'],
6095         attrs, angular.bind(gridCtrl, gridCtrl.invalidateLayout));
6096
6097     // Tile registration/deregistration
6098     gridCtrl.invalidateTiles();
6099     scope.$on('$destroy', function() {
6100       unwatchAttrs();
6101       gridCtrl.invalidateLayout();
6102     });
6103
6104     if (angular.isDefined(scope.$parent.$index)) {
6105       scope.$watch(function() { return scope.$parent.$index; },
6106         function indexChanged(newIdx, oldIdx) {
6107           if (newIdx === oldIdx) {
6108             return;
6109           }
6110           gridCtrl.invalidateTiles();
6111         });
6112     }
6113   }
6114 }
6115 GridTileDirective.$inject = ["$mdMedia"];
6116
6117
6118 function GridTileCaptionDirective() {
6119   return {
6120     template: '<figcaption ng-transclude></figcaption>',
6121     transclude: true
6122   };
6123 }
6124
6125 })();
6126 (function(){
6127 "use strict";
6128
6129 /**
6130  * @ngdoc module
6131  * @name material.components.icon
6132  * @description
6133  * Icon
6134  */
6135 angular.module('material.components.icon', [
6136     'material.core'
6137   ])
6138   .directive('mdIcon', mdIconDirective);
6139
6140 /**
6141  * @ngdoc directive
6142  * @name mdIcon
6143  * @module material.components.icon
6144  *
6145  * @restrict E
6146  *
6147  * @description
6148  * The `<md-icon>` directive is an markup element useful for showing an icon based on a font-icon
6149  * or a SVG. Icons are view-only elements that should not be used directly as buttons; instead nest a `<md-icon>`
6150  * inside a `md-button` to add hover and click features.
6151  *
6152  * When using SVGs, both external SVGs (via URLs) or sets of SVGs [from icon sets] can be
6153  * easily loaded and used.When use font-icons, developers must following three (3) simple steps:
6154  *
6155  * <ol>
6156  * <li>Load the font library. e.g.<br/>
6157  *    &lt;link href="https://fonts.googleapis.com/icon?family=Material+Icons"
6158  *    rel="stylesheet"&gt;
6159  * </li>
6160  * <li> Use either (a) font-icon class names or (b) font ligatures to render the font glyph by using its textual name</li>
6161  * <li> Use &lt;md-icon md-font-icon="classname" /&gt; or <br/>
6162  *     use &lt;md-icon md-font-set="font library classname or alias"&gt; textual_name &lt;/md-icon&gt; or <br/>
6163  *     use &lt;md-icon md-font-set="font library classname or alias"&gt; numerical_character_reference &lt;/md-icon&gt;
6164  * </li>
6165  * </ol>
6166  *
6167  * Full details for these steps can be found:
6168  *
6169  * <ul>
6170  * <li>http://google.github.io/material-design-icons/</li>
6171  * <li>http://google.github.io/material-design-icons/#icon-font-for-the-web</li>
6172  * </ul>
6173  *
6174  * The Material Design icon style <code>.material-icons</code> and the icon font references are published in
6175  * Material Design Icons:
6176  *
6177  * <ul>
6178  * <li>http://www.google.com/design/icons/</li>
6179  * <li>https://www.google.com/design/icons/#ic_accessibility</li>
6180  * </ul>
6181  *
6182  * <h2 id="material_design_icons">Material Design Icons</h2>
6183  * Using the Material Design Icon-Selector, developers can easily and quickly search for a Material Design font-icon and
6184  * determine its textual name and character reference code. Click on any icon to see the slide-up information
6185  * panel with details regarding a SVG download or information on the font-icon usage.
6186  *
6187  * <a href="https://www.google.com/design/icons/#ic_accessibility" target="_blank" style="border-bottom:none;">
6188  * <img src="https://cloud.githubusercontent.com/assets/210413/7902490/fe8dd14c-0780-11e5-98fb-c821cc6475e6.png"
6189  *      aria-label="Material Design Icon-Selector" style="max-width:75%;padding-left:10%">
6190  * </a>
6191  *
6192  * <span class="image_caption">
6193  *  Click on the image above to link to the
6194  *  <a href="https://www.google.com/design/icons/#ic_accessibility" target="_blank">Material Design Icon-Selector</a>.
6195  * </span>
6196  *
6197  * @param {string} md-font-icon Name of CSS icon associated with the font-face will be used
6198  * to render the icon. Requires the fonts and the named CSS styles to be preloaded.
6199  * @param {string} md-font-set CSS style name associated with the font library; which will be assigned as
6200  * the class for the font-icon ligature. This value may also be an alias that is used to lookup the classname;
6201  * internally use `$mdIconProvider.fontSet(<alias>)` to determine the style name.
6202  * @param {string} md-svg-src URL [or expression ] used to load, cache, and display an external SVG.
6203  * @param {string} md-svg-icon Name used for lookup of the icon from the internal cache; interpolated strings or
6204  * expressions may also be used. Specific set names can be used with the syntax `<set name>:<icon name>`.<br/><br/>
6205  * To use icon sets, developers are required to pre-register the sets using the `$mdIconProvider` service.
6206  * @param {string=} aria-label Labels icon for accessibility. If an empty string is provided, icon
6207  * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no aria-label on the icon
6208  * nor a label on the parent element, a warning will be logged to the console.
6209  *
6210  * @usage
6211  * When using SVGs:
6212  * <hljs lang="html">
6213  *
6214  *  <!-- Icon ID; may contain optional icon set prefix; icons must registered using $mdIconProvider -->
6215  *  <md-icon md-svg-icon="social:android"    aria-label="android " ></md-icon>
6216  *
6217  *  <!-- Icon urls; may be preloaded in templateCache -->
6218  *  <md-icon md-svg-src="/android.svg"       aria-label="android " ></md-icon>
6219  *  <md-icon md-svg-src="{{ getAndroid() }}" aria-label="android " ></md-icon>
6220  *
6221  * </hljs>
6222  *
6223  * Use the <code>$mdIconProvider</code> to configure your application with
6224  * svg iconsets.
6225  *
6226  * <hljs lang="js">
6227  *  angular.module('appSvgIconSets', ['ngMaterial'])
6228  *    .controller('DemoCtrl', function($scope) {})
6229  *    .config(function($mdIconProvider) {
6230  *      $mdIconProvider
6231  *         .iconSet('social', 'img/icons/sets/social-icons.svg', 24)
6232  *         .defaultIconSet('img/icons/sets/core-icons.svg', 24);
6233  *     });
6234  * </hljs>
6235  *
6236  *
6237  * When using Font Icons with classnames:
6238  * <hljs lang="html">
6239  *
6240  *  <md-icon md-font-icon="android" aria-label="android" ></md-icon>
6241  *  <md-icon class="icon_home"      aria-label="Home"    ></md-icon>
6242  *
6243  * </hljs>
6244  *
6245  * When using Material Font Icons with ligatures:
6246  * <hljs lang="html">
6247  *  <!-- For Material Design Icons -->
6248  *  <!-- The class '.material-icons' is auto-added. -->
6249  *  <md-icon> face </md-icon>
6250  *  <md-icon class="md-light md-48"> face </md-icon>
6251  *  <md-icon md-font-set="material-icons"> face </md-icon>
6252  *  <md-icon> #xE87C; </md-icon>
6253  * </hljs>
6254  *
6255  * When using other Font-Icon libraries:
6256  *
6257  * <hljs lang="js">
6258  *  // Specify a font-icon style alias
6259  *  angular.config(function($mdIconProvider) {
6260  *    $mdIconProvider.fontSet('fa', 'fontawesome');
6261  *  });
6262  * </hljs>
6263  *
6264  * <hljs lang="html">
6265  *  <md-icon md-font-set="fa">email</md-icon>
6266  * </hljs>
6267  *
6268  */
6269 function mdIconDirective($mdIcon, $mdTheming, $mdAria, $interpolate ) {
6270
6271   return {
6272     scope: {
6273       fontSet : '@mdFontSet',
6274       fontIcon: '@mdFontIcon',
6275       svgIcon : '@mdSvgIcon',
6276       svgSrc  : '@mdSvgSrc'
6277     },
6278     restrict: 'E',
6279     transclude:true,
6280     template: getTemplate,
6281     link: postLink
6282   };
6283
6284   function getTemplate(element, attr) {
6285     var isEmptyAttr  = function(key) { return angular.isDefined(attr[key]) ? attr[key].length == 0 : false    },
6286         hasAttrValue = function(key) { return attr[key] && attr[key].length > 0;     },
6287         attrValue    = function(key) { return hasAttrValue(key) ? attr[key] : '' };
6288
6289     // If using the deprecated md-font-icon API
6290     // If using ligature-based font-icons, transclude the ligature or NRCs
6291
6292     var tmplFontIcon = '<span class="md-font {{classNames}}" ng-class="fontIcon"></span>';
6293     var tmplFontSet  = '<span class="{{classNames}}" ng-transclude></span>';
6294
6295     var tmpl = hasAttrValue('mdSvgIcon')     ? ''           :
6296                hasAttrValue('mdSvgSrc')      ? ''           :
6297                isEmptyAttr('mdFontIcon')     ? ''           :
6298                hasAttrValue('mdFontIcon')    ? tmplFontIcon : tmplFontSet;
6299
6300     // If available, lookup the fontSet style and add to the list of classnames
6301     // NOTE: Material Icons expects classnames like `.material-icons.md-48` instead of `.material-icons .md-48`
6302
6303     var names = (tmpl == tmplFontSet) ? $mdIcon.fontSet(attrValue('mdFontSet'))  + ' ' : '';
6304         names = (names + attrValue('class')).trim();
6305
6306     return $interpolate( tmpl )({ classNames: names });
6307   }
6308
6309
6310   /**
6311    * Directive postLink
6312    * Supports embedded SVGs, font-icons, & external SVGs
6313    */
6314   function postLink(scope, element, attr) {
6315     $mdTheming(element);
6316
6317     // If using a font-icon, then the textual name of the icon itself
6318     // provides the aria-label.
6319
6320     var label = attr.alt || scope.fontIcon || scope.svgIcon || element.text();
6321     var attrName = attr.$normalize(attr.$attr.mdSvgIcon || attr.$attr.mdSvgSrc || '');
6322
6323     if ( !attr['aria-label'] ) {
6324
6325       if (label != '' && !parentsHaveText() ) {
6326
6327         $mdAria.expect(element, 'aria-label', label);
6328         $mdAria.expect(element, 'role', 'img');
6329
6330       } else if ( !element.text() ) {
6331         // If not a font-icon with ligature, then
6332         // hide from the accessibility layer.
6333
6334         $mdAria.expect(element, 'aria-hidden', 'true');
6335       }
6336     }
6337
6338     if (attrName) {
6339       // Use either pre-configured SVG or URL source, respectively.
6340       attr.$observe(attrName, function(attrVal) {
6341
6342         element.empty();
6343         if (attrVal) {
6344           $mdIcon(attrVal).then(function(svg) {
6345             element.append(svg);
6346           });
6347         }
6348
6349       });
6350     }
6351     function parentsHaveText() {
6352       var parent = element.parent();
6353       if (parent.attr('aria-label') || parent.text()) {
6354         return true;
6355       }
6356       else if(parent.parent().attr('aria-label') || parent.parent().text()) {
6357         return true;
6358       }
6359       return false;
6360     }
6361   }
6362 }
6363 mdIconDirective.$inject = ["$mdIcon", "$mdTheming", "$mdAria", "$interpolate"];
6364
6365 })();
6366 (function(){
6367 "use strict";
6368
6369   angular
6370     .module('material.components.icon' )
6371     .provider('$mdIcon', MdIconProvider);
6372
6373   /**
6374     * @ngdoc service
6375     * @name $mdIconProvider
6376     * @module material.components.icon
6377     *
6378     * @description
6379     * `$mdIconProvider` is used only to register icon IDs with URLs. These configuration features allow
6380     * icons and icon sets to be pre-registered and associated with source URLs **before** the `<md-icon />`
6381     * directives are compiled.
6382     *
6383     * If using font-icons, the developer is repsonsible for loading the fonts.
6384     *
6385     * If using SVGs, loading of the actual svg files are deferred to on-demand requests and are loaded
6386     * internally by the `$mdIcon` service using the `$http` service. When an SVG is requested by name/ID,
6387     * the `$mdIcon` service searches its registry for the associated source URL;
6388     * that URL is used to on-demand load and parse the SVG dynamically.
6389     *
6390     * @usage
6391     * <hljs lang="js">
6392     *   app.config(function($mdIconProvider) {
6393     *
6394     *     // Configure URLs for icons specified by [set:]id.
6395     *
6396     *     $mdIconProvider
6397     *          .defaultFontSet( 'fontawesome' )
6398     *          .defaultIconSet('my/app/icons.svg')       // Register a default set of SVG icons
6399     *          .iconSet('social', 'my/app/social.svg')   // Register a named icon set of SVGs
6400     *          .icon('android', 'my/app/android.svg')    // Register a specific icon (by name)
6401     *          .icon('work:chair', 'my/app/chair.svg');  // Register icon in a specific set
6402     *   });
6403     * </hljs>
6404     *
6405     * SVG icons and icon sets can be easily pre-loaded and cached using either (a) a build process or (b) a runtime
6406     * **startup** process (shown below):
6407     *
6408     * <hljs lang="js">
6409     *   app.config(function($mdIconProvider) {
6410     *
6411     *     // Register a default set of SVG icon definitions
6412     *     $mdIconProvider.defaultIconSet('my/app/icons.svg')
6413     *
6414     *   })
6415     *   .run(function($http, $templateCache){
6416     *
6417     *     // Pre-fetch icons sources by URL and cache in the $templateCache...
6418     *     // subsequent $http calls will look there first.
6419     *
6420     *     var urls = [ 'imy/app/icons.svg', 'img/icons/android.svg'];
6421     *
6422     *     angular.forEach(urls, function(url) {
6423     *       $http.get(url, {cache: $templateCache});
6424     *     });
6425     *
6426     *   });
6427     *
6428     * </hljs>
6429     *
6430     * NOTE: the loaded SVG data is subsequently cached internally for future requests.
6431     *
6432     */
6433
6434    /**
6435     * @ngdoc method
6436     * @name $mdIconProvider#icon
6437     *
6438     * @description
6439     * Register a source URL for a specific icon name; the name may include optional 'icon set' name prefix.
6440     * These icons  will later be retrieved from the cache using `$mdIcon( <icon name> )`
6441     *
6442     * @param {string} id Icon name/id used to register the icon
6443     * @param {string} url specifies the external location for the data file. Used internally by `$http` to load the
6444     * data or as part of the lookup in `$templateCache` if pre-loading was configured.
6445     * @param {string=} iconSize Number indicating the width and height of the icons in the set. All icons
6446     * in the icon set must be the same size. Default size is 24.
6447     *
6448     * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
6449     *
6450     * @usage
6451     * <hljs lang="js">
6452     *   app.config(function($mdIconProvider) {
6453     *
6454     *     // Configure URLs for icons specified by [set:]id.
6455     *
6456     *     $mdIconProvider
6457     *          .icon('android', 'my/app/android.svg')    // Register a specific icon (by name)
6458     *          .icon('work:chair', 'my/app/chair.svg');  // Register icon in a specific set
6459     *   });
6460     * </hljs>
6461     *
6462     */
6463    /**
6464     * @ngdoc method
6465     * @name $mdIconProvider#iconSet
6466     *
6467     * @description
6468     * Register a source URL for a 'named' set of icons; group of SVG definitions where each definition
6469     * has an icon id. Individual icons can be subsequently retrieved from this cached set using
6470     * `$mdIcon(<icon set name>:<icon name>)`
6471     *
6472     * @param {string} id Icon name/id used to register the iconset
6473     * @param {string} url specifies the external location for the data file. Used internally by `$http` to load the
6474     * data or as part of the lookup in `$templateCache` if pre-loading was configured.
6475     * @param {string=} iconSize Number indicating the width and height of the icons in the set. All icons
6476     * in the icon set must be the same size. Default size is 24.
6477     *
6478     * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
6479     *
6480     *
6481     * @usage
6482     * <hljs lang="js">
6483     *   app.config(function($mdIconProvider) {
6484     *
6485     *     // Configure URLs for icons specified by [set:]id.
6486     *
6487     *     $mdIconProvider
6488     *          .iconSet('social', 'my/app/social.svg')   // Register a named icon set
6489     *   });
6490     * </hljs>
6491     *
6492     */
6493    /**
6494     * @ngdoc method
6495     * @name $mdIconProvider#defaultIconSet
6496     *
6497     * @description
6498     * Register a source URL for the default 'named' set of icons. Unless explicitly registered,
6499     * subsequent lookups of icons will failover to search this 'default' icon set.
6500     * Icon can be retrieved from this cached, default set using `$mdIcon(<name>)`
6501     *
6502     * @param {string} url specifies the external location for the data file. Used internally by `$http` to load the
6503     * data or as part of the lookup in `$templateCache` if pre-loading was configured.
6504     * @param {string=} iconSize Number indicating the width and height of the icons in the set. All icons
6505     * in the icon set must be the same size. Default size is 24.
6506     *
6507     * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
6508     *
6509     * @usage
6510     * <hljs lang="js">
6511     *   app.config(function($mdIconProvider) {
6512     *
6513     *     // Configure URLs for icons specified by [set:]id.
6514     *
6515     *     $mdIconProvider
6516     *          .defaultIconSet( 'my/app/social.svg' )   // Register a default icon set
6517     *   });
6518     * </hljs>
6519     *
6520     */
6521   /**
6522    * @ngdoc method
6523    * @name $mdIconProvider#defaultFontSet
6524    *
6525    * @description
6526    * When using Font-Icons, Angular Material assumes the the Material Design icons will be used and automatically
6527    * configures the default font-set == 'material-icons'. Note that the font-set references the font-icon library
6528    * class style that should be applied to the `<md-icon>`.
6529    *
6530    * Configuring the default means that the attributes
6531    * `md-font-set="material-icons"` or `class="material-icons"` do not need to be explicitly declared on the
6532    * `<md-icon>` markup. For example:
6533    *
6534    *  `<md-icon> face </md-icon>`
6535    *  will render as
6536    *  `<span class="material-icons"> face </span>`, and
6537    *
6538    *  `<md-icon md-font-set="fa"> face </md-icon>`
6539    *  will render as
6540    *  `<span class="fa"> face </span>`
6541    *
6542    * @param {string} name of the font-library style that should be applied to the md-icon DOM element
6543    *
6544    * @usage
6545    * <hljs lang="js">
6546    *   app.config(function($mdIconProvider) {
6547    *     $mdIconProvider.defaultFontSet( 'fontawesome' );
6548    *   });
6549    * </hljs>
6550    *
6551    */
6552
6553    /**
6554     * @ngdoc method
6555     * @name $mdIconProvider#defaultIconSize
6556     *
6557     * @description
6558     * While `<md-icon />` markup can also be style with sizing CSS, this method configures
6559     * the default width **and** height used for all icons; unless overridden by specific CSS.
6560     * The default sizing is (24px, 24px).
6561     *
6562     * @param {string} iconSize Number indicating the width and height of the icons in the set. All icons
6563     * in the icon set must be the same size. Default size is 24.
6564     *
6565     * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
6566     *
6567     * @usage
6568     * <hljs lang="js">
6569     *   app.config(function($mdIconProvider) {
6570     *
6571     *     // Configure URLs for icons specified by [set:]id.
6572     *
6573     *     $mdIconProvider
6574     *          .defaultIconSize(36)   // Register a default icon size (width == height)
6575     *   });
6576     * </hljs>
6577     *
6578     */
6579
6580  var config = {
6581    defaultIconSize: 24,
6582    defaultFontSet: 'material-icons',
6583    fontSets : [ ]
6584  };
6585
6586  function MdIconProvider() { }
6587
6588  MdIconProvider.prototype = {
6589
6590    icon : function icon(id, url, iconSize) {
6591      if ( id.indexOf(':') == -1 ) id = '$default:' + id;
6592
6593      config[id] = new ConfigurationItem(url, iconSize );
6594      return this;
6595    },
6596    iconSet : function iconSet(id, url, iconSize) {
6597      config[id] = new ConfigurationItem(url, iconSize );
6598      return this;
6599    },
6600    defaultIconSet : function defaultIconSet(url, iconSize) {
6601      var setName = '$default';
6602
6603      if ( !config[setName] ) {
6604        config[setName] = new ConfigurationItem(url, iconSize );
6605      }
6606
6607      config[setName].iconSize = iconSize || config.defaultIconSize;
6608      return this;
6609    },
6610
6611    /**
6612     * Register an alias name associated with a font-icon library style ;
6613     */
6614    fontSet : function fontSet(alias, className) {
6615     config.fontSets.push({
6616       alias : alias,
6617       fontSet : className || alias
6618     });
6619    },
6620
6621    /**
6622     * Specify a default style name associated with a font-icon library
6623     * fallback to Material Icons.
6624     *
6625     */
6626    defaultFontSet : function defaultFontSet(className) {
6627     config.defaultFontSet = !className ? '' : className;
6628     return this;
6629    },
6630
6631    defaultIconSize : function defaultIconSize(iconSize) {
6632      config.defaultIconSize = iconSize;
6633      return this;
6634    },
6635
6636    preloadIcons: function ($templateCache) {
6637      var iconProvider = this;
6638      var svgRegistry = [
6639        {
6640          id : 'md-tabs-arrow',
6641          url: 'md-tabs-arrow.svg',
6642          svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 24 24"><g><polygon points="15.4,7.4 14,6 8,12 14,18 15.4,16.6 10.8,12 "/></g></svg>'
6643        },
6644        {
6645          id : 'md-close',
6646          url: 'md-close.svg',
6647          svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 24 24"><g><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"/></g></svg>'
6648        },
6649        {
6650          id:  'md-cancel',
6651          url: 'md-cancel.svg',
6652          svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 24 24"><g><path d="M12 2c-5.53 0-10 4.47-10 10s4.47 10 10 10 10-4.47 10-10-4.47-10-10-10zm5 13.59l-1.41 1.41-3.59-3.59-3.59 3.59-1.41-1.41 3.59-3.59-3.59-3.59 1.41-1.41 3.59 3.59 3.59-3.59 1.41 1.41-3.59 3.59 3.59 3.59z"/></g></svg>'
6653        },
6654        {
6655          id:  'md-menu',
6656          url: 'md-menu.svg',
6657          svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 100 100"><path d="M 50 0 L 100 14 L 92 80 L 50 100 L 8 80 L 0 14 Z" fill="#b2b2b2"></path><path d="M 50 5 L 6 18 L 13.5 77 L 50 94 Z" fill="#E42939"></path><path d="M 50 5 L 94 18 L 86.5 77 L 50 94 Z" fill="#B72833"></path><path d="M 50 7 L 83 75 L 72 75 L 65 59 L 50 59 L 50 50 L 61 50 L 50 26 Z" fill="#b2b2b2"></path><path d="M 50 7 L 17 75 L 28 75 L 35 59 L 50 59 L 50 50 L 39 50 L 50 26 Z" fill="#fff"></path></svg>'
6658        },
6659        {
6660          id:  'md-toggle-arrow',
6661          url: 'md-toggle-arrow-svg',
6662          svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 48 48"><path d="M24 16l-12 12 2.83 2.83 9.17-9.17 9.17 9.17 2.83-2.83z"/><path d="M0 0h48v48h-48z" fill="none"/></svg>'
6663        }
6664      ];
6665
6666      svgRegistry.forEach(function(asset){
6667        iconProvider.icon(asset.id,  asset.url);
6668        $templateCache.put(asset.url, asset.svg);
6669      });
6670
6671    },
6672
6673    $get : ['$http', '$q', '$log', '$templateCache', function($http, $q, $log, $templateCache) {
6674      this.preloadIcons($templateCache);
6675      return MdIconService(config, $http, $q, $log, $templateCache);
6676    }]
6677  };
6678
6679    /**
6680     *  Configuration item stored in the Icon registry; used for lookups
6681     *  to load if not already cached in the `loaded` cache
6682     */
6683    function ConfigurationItem(url, iconSize) {
6684      this.url = url;
6685      this.iconSize = iconSize || config.defaultIconSize;
6686    }
6687
6688  /**
6689   * @ngdoc service
6690   * @name $mdIcon
6691   * @module material.components.icon
6692   *
6693   * @description
6694   * The `$mdIcon` service is a function used to lookup SVG icons.
6695   *
6696   * @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
6697   * 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
6698   * performed. The Id may be a unique icon ID or may include an iconSet ID prefix.
6699   *
6700   * For the **id** query to work properly, this means that all id-to-URL mappings must have been previously configured
6701   * using the `$mdIconProvider`.
6702   *
6703   * @returns {obj} Clone of the initial SVG DOM element; which was created from the SVG markup in the SVG data file.
6704   *
6705   * @usage
6706   * <hljs lang="js">
6707   * function SomeDirective($mdIcon) {
6708   *
6709   *   // See if the icon has already been loaded, if not
6710   *   // then lookup the icon from the registry cache, load and cache
6711   *   // it for future requests.
6712   *   // NOTE: ID queries require configuration with $mdIconProvider
6713   *
6714   *   $mdIcon('android').then(function(iconEl)    { element.append(iconEl); });
6715   *   $mdIcon('work:chair').then(function(iconEl) { element.append(iconEl); });
6716   *
6717   *   // Load and cache the external SVG using a URL
6718   *
6719   *   $mdIcon('img/icons/android.svg').then(function(iconEl) {
6720   *     element.append(iconEl);
6721   *   });
6722   * };
6723   * </hljs>
6724   *
6725   * NOTE: The `<md-icon />  ` directive internally uses the `$mdIcon` service to query, loaded, and instantiate
6726   * SVG DOM elements.
6727   */
6728  function MdIconService(config, $http, $q, $log, $templateCache) {
6729    var iconCache = {};
6730    var urlRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i;
6731
6732    Icon.prototype = { clone : cloneSVG, prepare: prepareAndStyle };
6733    getIcon.fontSet = findRegisteredFontSet;
6734
6735    // Publish service...
6736    return getIcon;
6737
6738    /**
6739     * Actual $mdIcon service is essentially a lookup function
6740     */
6741    function getIcon(id) {
6742      id = id || '';
6743
6744      // If already loaded and cached, use a clone of the cached icon.
6745      // Otherwise either load by URL, or lookup in the registry and then load by URL, and cache.
6746
6747      if ( iconCache[id]         ) return $q.when( iconCache[id].clone() );
6748      if ( urlRegex.test(id)     ) return loadByURL(id).then( cacheIcon(id) );
6749      if ( id.indexOf(':') == -1 ) id = '$default:' + id;
6750
6751      return loadByID(id)
6752          .catch(loadFromIconSet)
6753          .catch(announceIdNotFound)
6754          .catch(announceNotFound)
6755          .then( cacheIcon(id) );
6756    }
6757
6758    /**
6759     * Lookup registered fontSet style using its alias...
6760     * If not found,
6761     */
6762    function findRegisteredFontSet(alias) {
6763       var useDefault = angular.isUndefined(alias) || !(alias && alias.length);
6764       if ( useDefault ) return config.defaultFontSet;
6765
6766       var result = alias;
6767       angular.forEach(config.fontSets, function(it){
6768         if ( it.alias == alias ) result = it.fontSet || result;
6769       });
6770
6771       return result;
6772    }
6773
6774    /**
6775     * Prepare and cache the loaded icon for the specified `id`
6776     */
6777    function cacheIcon( id ) {
6778
6779      return function updateCache( icon ) {
6780        iconCache[id] = isIcon(icon) ? icon : new Icon(icon, config[id]);
6781
6782        return iconCache[id].clone();
6783      };
6784    }
6785
6786    /**
6787     * Lookup the configuration in the registry, if !registered throw an error
6788     * otherwise load the icon [on-demand] using the registered URL.
6789     *
6790     */
6791    function loadByID(id) {
6792      var iconConfig = config[id];
6793
6794      return !iconConfig ? $q.reject(id) : loadByURL(iconConfig.url).then(function(icon) {
6795        return new Icon(icon, iconConfig);
6796      });
6797    }
6798
6799    /**
6800     *    Loads the file as XML and uses querySelector( <id> ) to find
6801     *    the desired node...
6802     */
6803    function loadFromIconSet(id) {
6804      var setName = id.substring(0, id.lastIndexOf(':')) || '$default';
6805      var iconSetConfig = config[setName];
6806
6807      return !iconSetConfig ? $q.reject(id) : loadByURL(iconSetConfig.url).then(extractFromSet);
6808
6809      function extractFromSet(set) {
6810        var iconName = id.slice(id.lastIndexOf(':') + 1);
6811        var icon = set.querySelector('#' + iconName);
6812        return !icon ? $q.reject(id) : new Icon(icon, iconSetConfig);
6813      }
6814    }
6815
6816    /**
6817     * Load the icon by URL (may use the $templateCache).
6818     * Extract the data for later conversion to Icon
6819     */
6820    function loadByURL(url) {
6821      return $http
6822        .get(url, { cache: $templateCache })
6823        .then(function(response) {
6824          return angular.element('<div>').append(response.data).find('svg')[0];
6825        });
6826    }
6827
6828    /**
6829     * User did not specify a URL and the ID has not been registered with the $mdIcon
6830     * registry
6831     */
6832    function announceIdNotFound(id) {
6833      var msg;
6834
6835      if (angular.isString(id)) {
6836        msg = 'icon ' + id + ' not found';
6837        $log.warn(msg);
6838      }
6839
6840      return $q.reject(msg || id);
6841    }
6842
6843    /**
6844     * Catch HTTP or generic errors not related to incorrect icon IDs.
6845     */
6846    function announceNotFound(err) {
6847      var msg = angular.isString(err) ? err : (err.message || err.data || err.statusText);
6848      $log.warn(msg);
6849
6850      return $q.reject(msg);
6851    }
6852
6853    /**
6854     * Check target signature to see if it is an Icon instance.
6855     */
6856    function isIcon(target) {
6857      return angular.isDefined(target.element) && angular.isDefined(target.config);
6858    }
6859
6860    /**
6861     *  Define the Icon class
6862     */
6863    function Icon(el, config) {
6864      if (el.tagName != 'svg') {
6865        el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">').append(el)[0];
6866      }
6867
6868      // Inject the namespace if not available...
6869      if ( !el.getAttribute('xmlns') ) {
6870        el.setAttribute('xmlns', "http://www.w3.org/2000/svg");
6871      }
6872
6873      this.element = el;
6874      this.config = config;
6875      this.prepare();
6876    }
6877
6878    /**
6879     *  Prepare the DOM element that will be cached in the
6880     *  loaded iconCache store.
6881     */
6882    function prepareAndStyle() {
6883      var iconSize = this.config ? this.config.iconSize : config.defaultIconSize;
6884          angular.forEach({
6885            'fit'   : '',
6886            'height': '100%',
6887            'width' : '100%',
6888            'preserveAspectRatio': 'xMidYMid meet',
6889            'viewBox' : this.element.getAttribute('viewBox') || ('0 0 ' + iconSize + ' ' + iconSize)
6890          }, function(val, attr) {
6891            this.element.setAttribute(attr, val);
6892          }, this);
6893
6894          angular.forEach({
6895            'pointer-events' : 'none',
6896            'display' : 'block'
6897          }, function(val, style) {
6898            this.element.style[style] = val;
6899          }, this);
6900    }
6901
6902    /**
6903     * Clone the Icon DOM element.
6904     */
6905    function cloneSVG(){
6906      return this.element.cloneNode(true);
6907    }
6908
6909  }
6910
6911 })();
6912 (function(){
6913 "use strict";
6914
6915 /**
6916  * @ngdoc module
6917  * @name material.components.input
6918  */
6919
6920 angular.module('material.components.input', [
6921   'material.core'
6922 ])
6923   .directive('mdInputContainer', mdInputContainerDirective)
6924   .directive('label', labelDirective)
6925   .directive('input', inputTextareaDirective)
6926   .directive('textarea', inputTextareaDirective)
6927   .directive('mdMaxlength', mdMaxlengthDirective)
6928   .directive('placeholder', placeholderDirective);
6929
6930 /**
6931  * @ngdoc directive
6932  * @name mdInputContainer
6933  * @module material.components.input
6934  *
6935  * @restrict E
6936  *
6937  * @description
6938  * `<md-input-container>` is the parent of any input or textarea element.
6939  *
6940  * Input and textarea elements will not behave properly unless the md-input-container
6941  * parent is provided.
6942  *
6943  * @param md-is-error {expression=} When the given expression evaluates to true, the input container will go into error state. Defaults to erroring if the input has been touched and is invalid.
6944  * @param md-no-float {boolean=} When present, placeholders will not be converted to floating labels
6945  *
6946  * @usage
6947  * <hljs lang="html">
6948  *
6949  * <md-input-container>
6950  *   <label>Username</label>
6951  *   <input type="text" ng-model="user.name">
6952  * </md-input-container>
6953  *
6954  * <md-input-container>
6955  *   <label>Description</label>
6956  *   <textarea ng-model="user.description"></textarea>
6957  * </md-input-container>
6958  *
6959  * </hljs>
6960  */
6961 function mdInputContainerDirective($mdTheming, $parse) {
6962   ContainerCtrl.$inject = ["$scope", "$element", "$attrs"];
6963   return {
6964     restrict: 'E',
6965     link: postLink,
6966     controller: ContainerCtrl
6967   };
6968
6969   function postLink(scope, element, attr) {
6970     $mdTheming(element);
6971   }
6972   function ContainerCtrl($scope, $element, $attrs) {
6973     var self = this;
6974
6975     self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);
6976
6977     self.delegateClick = function() {
6978       self.input.focus();
6979     };
6980     self.element = $element;
6981     self.setFocused = function(isFocused) {
6982       $element.toggleClass('md-input-focused', !!isFocused);
6983     };
6984     self.setHasValue = function(hasValue) {
6985       $element.toggleClass('md-input-has-value', !!hasValue);
6986     };
6987     self.setInvalid = function(isInvalid) {
6988       $element.toggleClass('md-input-invalid', !!isInvalid);
6989     };
6990     $scope.$watch(function() {
6991       return self.label && self.input;
6992     }, function(hasLabelAndInput) {
6993       if (hasLabelAndInput && !self.label.attr('for')) {
6994         self.label.attr('for', self.input.attr('id'));
6995       }
6996     });
6997   }
6998 }
6999 mdInputContainerDirective.$inject = ["$mdTheming", "$parse"];
7000
7001 function labelDirective() {
7002   return {
7003     restrict: 'E',
7004     require: '^?mdInputContainer',
7005     link: function(scope, element, attr, containerCtrl) {
7006       if (!containerCtrl || attr.mdNoFloat) return;
7007
7008       containerCtrl.label = element;
7009       scope.$on('$destroy', function() {
7010         containerCtrl.label = null;
7011       });
7012     }
7013   };
7014 }
7015
7016 /**
7017  * @ngdoc directive
7018  * @name mdInput
7019  * @restrict E
7020  * @module material.components.input
7021  *
7022  * @description
7023  * Use the `<input>` or the  `<textarea>` as a child of an `<md-input-container>`.
7024  *
7025  * @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is specified, a character counter will be shown underneath the input.<br/><br/>
7026  * The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength` or maxlength attributes.
7027  * @param {string=} aria-label Aria-label is required when no label is present.  A warning message will be logged in the console if not present.
7028  * @param {string=} placeholder An alternative approach to using aria-label when the label is not present.  The placeholder text is copied to the aria-label attribute.
7029  *
7030  * @usage
7031  * <hljs lang="html">
7032  * <md-input-container>
7033  *   <label>Color</label>
7034  *   <input type="text" ng-model="color" required md-maxlength="10">
7035  * </md-input-container>
7036  * </hljs>
7037  * <h3>With Errors</h3>
7038  *
7039  * <hljs lang="html">
7040  * <form name="userForm">
7041  *   <md-input-container>
7042  *     <label>Last Name</label>
7043  *     <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
7044  *     <div ng-messages="userForm.lastName.$error" ng-show="userForm.lastName.$dirty">
7045  *       <div ng-message="required">This is required!</div>
7046  *       <div ng-message="md-maxlength">That's too long!</div>
7047  *       <div ng-message="minlength">That's too short!</div>
7048  *     </div>
7049  *   </md-input-container>
7050  *   <md-input-container>
7051  *     <label>Biography</label>
7052  *     <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
7053  *     <div ng-messages="userForm.bio.$error" ng-show="userForm.bio.$dirty">
7054  *       <div ng-message="required">This is required!</div>
7055  *       <div ng-message="md-maxlength">That's too long!</div>
7056  *     </div>
7057  *   </md-input-container>
7058  *   <md-input-container>
7059  *     <input aria-label='title' ng-model='title'>
7060  *   </md-input-container>
7061  *   <md-input-container>
7062  *     <input placeholder='title' ng-model='title'>
7063  *   </md-input-container>
7064  * </form>
7065  * </hljs>
7066  *
7067  * Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
7068  * Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
7069  *
7070  */
7071
7072 function inputTextareaDirective($mdUtil, $window, $mdAria) {
7073   return {
7074     restrict: 'E',
7075     require: ['^?mdInputContainer', '?ngModel'],
7076     link: postLink
7077   };
7078
7079   function postLink(scope, element, attr, ctrls) {
7080
7081     var containerCtrl = ctrls[0];
7082     var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
7083     var isReadonly = angular.isDefined(attr.readonly);
7084
7085     if ( !containerCtrl ) return;
7086     if (containerCtrl.input) {
7087       throw new Error("<md-input-container> can only have *one* <input> or <textarea> child element!");
7088     }
7089     containerCtrl.input = element;
7090
7091     if(!containerCtrl.label) {
7092       $mdAria.expect(element, 'aria-label', element.attr('placeholder'));
7093     }
7094
7095     element.addClass('md-input');
7096     if (!element.attr('id')) {
7097       element.attr('id', 'input_' + $mdUtil.nextUid());
7098     }
7099
7100     if (element[0].tagName.toLowerCase() === 'textarea') {
7101       setupTextarea();
7102     }
7103
7104     var isErrorGetter = containerCtrl.isErrorGetter || function() {
7105       return ngModelCtrl.$invalid && ngModelCtrl.$touched;
7106     };
7107     scope.$watch(isErrorGetter, containerCtrl.setInvalid);
7108
7109     ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
7110     ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);
7111
7112     element.on('input', inputCheckValue);
7113
7114     if (!isReadonly) {
7115       element
7116         .on('focus', function(ev) {
7117           containerCtrl.setFocused(true);
7118         })
7119         .on('blur', function(ev) {
7120           containerCtrl.setFocused(false);
7121           inputCheckValue();
7122         });
7123
7124     }
7125
7126     //ngModelCtrl.$setTouched();
7127     //if( ngModelCtrl.$invalid ) containerCtrl.setInvalid();
7128
7129     scope.$on('$destroy', function() {
7130       containerCtrl.setFocused(false);
7131       containerCtrl.setHasValue(false);
7132       containerCtrl.input = null;
7133     });
7134
7135     /**
7136      *
7137      */
7138     function ngModelPipelineCheckValue(arg) {
7139       containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
7140       return arg;
7141     }
7142     function inputCheckValue() {
7143       // An input's value counts if its length > 0,
7144       // or if the input's validity state says it has bad input (eg string in a number input)
7145       containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity||{}).badInput);
7146     }
7147
7148     function setupTextarea() {
7149       var node = element[0];
7150       var onChangeTextarea = $mdUtil.debounce(growTextarea, 1);
7151
7152       function pipelineListener(value) {
7153         onChangeTextarea();
7154         return value;
7155       }
7156
7157       if (ngModelCtrl) {
7158         ngModelCtrl.$formatters.push(pipelineListener);
7159         ngModelCtrl.$viewChangeListeners.push(pipelineListener);
7160       } else {
7161         onChangeTextarea();
7162       }
7163       element.on('keydown input', onChangeTextarea);
7164       element.on('scroll', onScroll);
7165       angular.element($window).on('resize', onChangeTextarea);
7166
7167       scope.$on('$destroy', function() {
7168         angular.element($window).off('resize', onChangeTextarea);
7169       });
7170
7171       function growTextarea() {
7172         node.style.height = "auto";
7173         node.scrollTop = 0;
7174         var height = getHeight();
7175         if (height) node.style.height = height + 'px';
7176       }
7177
7178       function getHeight () {
7179         var line = node.scrollHeight - node.offsetHeight;
7180         return node.offsetHeight + (line > 0 ? line : 0);
7181       }
7182
7183       function onScroll(e) {
7184         node.scrollTop = 0;
7185         // for smooth new line adding
7186         var line = node.scrollHeight - node.offsetHeight;
7187         var height = node.offsetHeight + line;
7188         node.style.height = height + 'px';
7189       }
7190     }
7191   }
7192 }
7193 inputTextareaDirective.$inject = ["$mdUtil", "$window", "$mdAria"];
7194
7195 function mdMaxlengthDirective($animate) {
7196   return {
7197     restrict: 'A',
7198     require: ['ngModel', '^mdInputContainer'],
7199     link: postLink
7200   };
7201
7202   function postLink(scope, element, attr, ctrls) {
7203     var maxlength;
7204     var ngModelCtrl = ctrls[0];
7205     var containerCtrl = ctrls[1];
7206     var charCountEl = angular.element('<div class="md-char-counter">');
7207
7208     // Stop model from trimming. This makes it so whitespace
7209     // over the maxlength still counts as invalid.
7210     attr.$set('ngTrim', 'false');
7211     containerCtrl.element.append(charCountEl);
7212
7213     ngModelCtrl.$formatters.push(renderCharCount);
7214     ngModelCtrl.$viewChangeListeners.push(renderCharCount);
7215     element.on('input keydown', function() {
7216       renderCharCount(); //make sure it's called with no args
7217     });
7218
7219     scope.$watch(attr.mdMaxlength, function(value) {
7220       maxlength = value;
7221       if (angular.isNumber(value) && value > 0) {
7222         if (!charCountEl.parent().length) {
7223           $animate.enter(charCountEl, containerCtrl.element,
7224                          angular.element(containerCtrl.element[0].lastElementChild));
7225         }
7226         renderCharCount();
7227       } else {
7228         $animate.leave(charCountEl);
7229       }
7230     });
7231
7232     ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
7233       if (!angular.isNumber(maxlength) || maxlength < 0) {
7234         return true;
7235       }
7236       return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
7237     };
7238
7239     function renderCharCount(value) {
7240       charCountEl.text( ( element.val() || value || '' ).length + '/' + maxlength );
7241       return value;
7242     }
7243   }
7244 }
7245 mdMaxlengthDirective.$inject = ["$animate"];
7246
7247 function placeholderDirective($log) {
7248   var blackListElements = ['MD-SELECT'];
7249   return {
7250     restrict: 'A',
7251     require: '^^?mdInputContainer',
7252     priority: 200,
7253     link: postLink
7254   };
7255
7256   function postLink(scope, element, attr, inputContainer) {
7257     if (!inputContainer) return;
7258     if (blackListElements.indexOf(element[0].nodeName) != -1) return;
7259     if (angular.isDefined(inputContainer.element.attr('md-no-float'))) return;
7260
7261     var placeholderText = attr.placeholder;
7262     element.removeAttr('placeholder');
7263
7264     if ( inputContainer.element.find('label').length == 0 ) {
7265       var placeholder = '<label ng-click="delegateClick()">' + placeholderText + '</label>';
7266
7267       inputContainer.element.addClass('md-icon-float');
7268       inputContainer.element.prepend(placeholder);
7269     } else {
7270       $log.warn("The placeholder='" + placeholderText + "' will be ignored since this md-input-container has a child label element.");
7271     }
7272
7273   }
7274 }
7275 placeholderDirective.$inject = ["$log"];
7276
7277 })();
7278 (function(){
7279 "use strict";
7280
7281 /**
7282  * @ngdoc module
7283  * @name material.components.list
7284  * @description
7285  * List module
7286  */
7287 angular.module('material.components.list', [
7288   'material.core'
7289 ])
7290   .controller('MdListController', MdListController)
7291   .directive('mdList', mdListDirective)
7292   .directive('mdListItem', mdListItemDirective);
7293
7294 /**
7295  * @ngdoc directive
7296  * @name mdList
7297  * @module material.components.list
7298  *
7299  * @restrict E
7300  *
7301  * @description
7302  * The `<md-list>` directive is a list container for 1..n `<md-list-item>` tags.
7303  *
7304  * @usage
7305  * <hljs lang="html">
7306  * <md-list>
7307  *   <md-list-item class="md-2-line" ng-repeat="item in todos">
7308  *     <md-checkbox ng-model="item.done"></md-checkbox>
7309  *     <div class="md-list-item-text">
7310  *       <h3>{{item.title}}</h3>
7311  *       <p>{{item.description}}</p>
7312  *     </div>
7313  *   </md-list-item>
7314  * </md-list>
7315  * </hljs>
7316  */
7317
7318 function mdListDirective($mdTheming) {
7319   return {
7320     restrict: 'E',
7321     compile: function(tEl) {
7322       tEl[0].setAttribute('role', 'list');
7323       return $mdTheming;
7324     }
7325   };
7326 }
7327 mdListDirective.$inject = ["$mdTheming"];
7328 /**
7329  * @ngdoc directive
7330  * @name mdListItem
7331  * @module material.components.list
7332  *
7333  * @restrict E
7334  *
7335  * @description
7336  * The `<md-list-item>` directive is a container intended for row items in a `<md-list>` container.
7337  *
7338  * @usage
7339  * <hljs lang="html">
7340  *  <md-list>
7341  *    <md-list-item>
7342  *            Item content in list
7343  *    </md-list-item>
7344  *  </md-list>
7345  * </hljs>
7346  *
7347  */
7348 function mdListItemDirective($mdAria, $mdConstant, $timeout) {
7349   var proxiedTypes = ['md-checkbox', 'md-switch'];
7350   return {
7351     restrict: 'E',
7352     controller: 'MdListController',
7353     compile: function(tEl, tAttrs) {
7354       // Check for proxy controls (no ng-click on parent, and a control inside)
7355       var secondaryItem = tEl[0].querySelector('.md-secondary');
7356       var hasProxiedElement;
7357       var proxyElement;
7358
7359       tEl[0].setAttribute('role', 'listitem');
7360
7361       if (!tAttrs.ngClick) {
7362         for (var i = 0, type; type = proxiedTypes[i]; ++i) {
7363           if (proxyElement = tEl[0].querySelector(type)) {
7364             hasProxiedElement = true;
7365             break;
7366           }
7367         }
7368         if (hasProxiedElement) {
7369           wrapIn('div');
7370         } else if (!tEl[0].querySelector('md-button')) {
7371           tEl.addClass('md-no-proxy');
7372         }
7373       } else {
7374         wrapIn('button');
7375       }
7376       setupToggleAria();
7377
7378
7379       function setupToggleAria() {
7380         var toggleTypes = ['md-switch', 'md-checkbox'];
7381         var toggle;
7382
7383         for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) {
7384           if (toggle = tEl.find(toggleType)[0]) {
7385             if (!toggle.hasAttribute('aria-label')) {
7386               var p = tEl.find('p')[0];
7387               if (!p) return;
7388               toggle.setAttribute('aria-label', 'Toggle ' + p.textContent);
7389             }
7390           }
7391         }
7392       }
7393
7394       function wrapIn(type) {
7395         var container;
7396         if (type == 'div') {
7397           container = angular.element('<div class="md-no-style md-list-item-inner">');
7398           container.append(tEl.contents());
7399           tEl.addClass('md-proxy-focus');
7400         } else {
7401           container = angular.element('<md-button class="md-no-style"><div class="md-list-item-inner"></div></md-button>');
7402           var copiedAttrs = ['ng-click', 'aria-label', 'ng-disabled'];
7403           angular.forEach(copiedAttrs, function(attr) {
7404             if (tEl[0].hasAttribute(attr)) {
7405               container[0].setAttribute(attr, tEl[0].getAttribute(attr));
7406               tEl[0].removeAttribute(attr);
7407             }
7408           });
7409           container.children().eq(0).append(tEl.contents());
7410         }
7411
7412         tEl[0].setAttribute('tabindex', '-1');
7413         tEl.append(container);
7414
7415         if (secondaryItem && secondaryItem.hasAttribute('ng-click')) {
7416           $mdAria.expect(secondaryItem, 'aria-label');
7417           var buttonWrapper = angular.element('<md-button class="md-secondary-container md-icon-button">');
7418           buttonWrapper.attr('ng-click', secondaryItem.getAttribute('ng-click'));
7419           secondaryItem.removeAttribute('ng-click');
7420           secondaryItem.setAttribute('tabindex', '-1');
7421           secondaryItem.classList.remove('md-secondary');
7422           buttonWrapper.append(secondaryItem);
7423           secondaryItem = buttonWrapper[0];
7424         }
7425
7426         // Check for a secondary item and move it outside
7427         if ( secondaryItem && (
7428           secondaryItem.hasAttribute('ng-click') ||
7429             ( tAttrs.ngClick &&
7430              isProxiedElement(secondaryItem) )
7431         )) {
7432           tEl.addClass('md-with-secondary');
7433           tEl.append(secondaryItem);
7434         }
7435       }
7436
7437       function isProxiedElement(el) {
7438         return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1;
7439       }
7440
7441       return postLink;
7442
7443       function postLink($scope, $element, $attr, ctrl) {
7444
7445         var proxies    = [],
7446             firstChild = $element[0].firstElementChild,
7447             hasClick   = firstChild && firstChild.hasAttribute('ng-click');
7448
7449         computeProxies();
7450         computeClickable();
7451
7452         if ($element.hasClass('md-proxy-focus') && proxies.length) {
7453           angular.forEach(proxies, function(proxy) {
7454             proxy = angular.element(proxy);
7455
7456             $scope.mouseActive = false;
7457             proxy.on('mousedown', function() {
7458               $scope.mouseActive = true;
7459               $timeout(function(){
7460                 $scope.mouseActive = false;
7461               }, 100);
7462             })
7463             .on('focus', function() {
7464               if ($scope.mouseActive === false) { $element.addClass('md-focused'); }
7465               proxy.on('blur', function proxyOnBlur() {
7466                 $element.removeClass('md-focused');
7467                 proxy.off('blur', proxyOnBlur);
7468               });
7469             });
7470           });
7471         }
7472
7473         function computeProxies() {
7474           var children = $element.children();
7475           if (children.length && !children[0].hasAttribute('ng-click')) {
7476             angular.forEach(proxiedTypes, function(type) {
7477               angular.forEach(firstChild.querySelectorAll(type), function(child) {
7478                 proxies.push(child);
7479               });
7480             });
7481           }
7482         }
7483         function computeClickable() {
7484           if (proxies.length || hasClick) {
7485             $element.addClass('md-clickable');
7486
7487             ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style')));
7488           }
7489         }
7490
7491         if (!hasClick && !proxies.length) {
7492           firstChild && firstChild.addEventListener('keypress', function(e) {
7493             if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA') {
7494               var keyCode = e.which || e.keyCode;
7495               if (keyCode == $mdConstant.KEY_CODE.SPACE) {
7496                 if (firstChild) {
7497                   firstChild.click();
7498                   e.preventDefault();
7499                   e.stopPropagation();
7500                 }
7501               }
7502             }
7503           });
7504         }
7505
7506         $element.off('click');
7507         $element.off('keypress');
7508
7509         if (proxies.length && firstChild) {
7510           $element.children().eq(0).on('click', function(e) {
7511             if (firstChild.contains(e.target)) {
7512               angular.forEach(proxies, function(proxy) {
7513                 if (e.target !== proxy && !proxy.contains(e.target)) {
7514                   angular.element(proxy).triggerHandler('click');
7515                 }
7516               });
7517             }
7518           });
7519         }
7520       }
7521     }
7522   };
7523 }
7524 mdListItemDirective.$inject = ["$mdAria", "$mdConstant", "$timeout"];
7525
7526 /*
7527  * @private
7528  * @ngdoc controller
7529  * @name MdListController
7530  * @module material.components.list
7531  *
7532  */
7533 function MdListController($scope, $element, $mdListInkRipple) {
7534   var ctrl = this;
7535   ctrl.attachRipple = attachRipple;
7536
7537   function attachRipple (scope, element) {
7538     var options = {};
7539     $mdListInkRipple.attach(scope, element, options);
7540   }
7541 }
7542 MdListController.$inject = ["$scope", "$element", "$mdListInkRipple"];
7543
7544
7545 })();
7546 (function(){
7547 "use strict";
7548
7549 /**
7550  * @ngdoc module
7551  * @name material.components.progressCircular
7552  * @description Circular Progress module!
7553  */
7554 angular.module('material.components.progressCircular', [
7555   'material.core'
7556 ])
7557   .directive('mdProgressCircular', MdProgressCircularDirective);
7558
7559 /**
7560  * @ngdoc directive
7561  * @name mdProgressCircular
7562  * @module material.components.progressCircular
7563  * @restrict E
7564  *
7565 * @description
7566  * The circular progress directive is used to make loading content in your app as delightful and
7567  * painless as possible by minimizing the amount of visual change a user sees before they can view
7568  * and interact with content.
7569  *
7570  * For operations where the percentage of the operation completed can be determined, use a
7571  * determinate indicator. They give users a quick sense of how long an operation will take.
7572  *
7573  * For operations where the user is asked to wait a moment while something finishes up, and it’s
7574  * not necessary to expose what's happening behind the scenes and how long it will take, use an
7575  * indeterminate indicator.
7576  *
7577  * @param {string} md-mode Select from one of two modes: determinate and indeterminate.
7578  * @param {number=} value In determinate mode, this number represents the percentage of the
7579  *     circular progress. Default: 0
7580  * @param {number=} md-diameter This specifies the diamter of the circular progress. Default: 48
7581  *
7582  * @usage
7583  * <hljs lang="html">
7584  * <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
7585  *
7586  * <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
7587  *
7588  * <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
7589  *
7590  * <md-progress-circular md-mode="indeterminate"></md-progress-circular>
7591  * </hljs>
7592  */
7593 function MdProgressCircularDirective($mdConstant, $mdTheming) {
7594   return {
7595     restrict: 'E',
7596     template:
7597         // The progress 'circle' is composed of two half-circles: the left side and the right
7598         // side. Each side has CSS applied to 'fill-in' the half-circle to the appropriate progress.
7599         '<div class="md-spinner-wrapper">' +
7600           '<div class="md-inner">' +
7601             '<div class="md-gap"></div>' +
7602             '<div class="md-left">' +
7603               '<div class="md-half-circle"></div>' +
7604             '</div>' +
7605             '<div class="md-right">' +
7606               '<div class="md-half-circle"></div>' +
7607             '</div>' +
7608           '</div>' +
7609         '</div>',
7610     compile: compile
7611   };
7612
7613   function compile(tElement) {
7614     // The javascript in this file is mainly responsible for setting the correct aria attributes.
7615     // The animation of the progress spinner is done entirely with just CSS.
7616     tElement.attr('aria-valuemin', 0);
7617     tElement.attr('aria-valuemax', 100);
7618     tElement.attr('role', 'progressbar');
7619
7620     return postLink;
7621   }
7622
7623   function postLink(scope, element, attr) {
7624     $mdTheming(element);
7625     var circle = element[0];
7626
7627     // Scale the progress circle based on the default diameter.
7628     var diameter = attr.mdDiameter || 48;
7629     var scale = diameter / 48;
7630     circle.style[$mdConstant.CSS.TRANSFORM] = 'scale(' + scale + ')';
7631
7632     attr.$observe('value', function(value) {
7633       var percentValue = clamp(value);
7634       element.attr('aria-valuenow', percentValue);
7635     });
7636   }
7637
7638   /**
7639    * Clamps the value to be between 0 and 100.
7640    * @param {number} value The value to clamp.
7641    * @returns {number}
7642    */
7643   function clamp(value) {
7644     return Math.max(0, Math.min(value || 0, 100));
7645   }
7646 }
7647 MdProgressCircularDirective.$inject = ["$mdConstant", "$mdTheming"];
7648
7649 })();
7650 (function(){
7651 "use strict";
7652
7653 /**
7654  * @ngdoc module
7655  * @name material.components.progressLinear
7656  * @description Linear Progress module!
7657  */
7658 angular.module('material.components.progressLinear', [
7659   'material.core'
7660 ])
7661   .directive('mdProgressLinear', MdProgressLinearDirective);
7662
7663 /**
7664  * @ngdoc directive
7665  * @name mdProgressLinear
7666  * @module material.components.progressLinear
7667  * @restrict E
7668  *
7669  * @description
7670  * The linear progress directive is used to make loading content in your app as delightful and painless as possible by minimizing the amount of visual change a user sees before they can view and interact with content. Each operation should only be represented by one activity indicator—for example, one refresh operation should not display both a refresh bar and an activity circle.
7671  *
7672  * For operations where the percentage of the operation completed can be determined, use a determinate indicator. They give users a quick sense of how long an operation will take.
7673  *
7674  * For operations where the user is asked to wait a moment while something finishes up, and it’s not necessary to expose what's happening behind the scenes and how long it will take, use an indeterminate indicator.
7675  *
7676  * @param {string} md-mode Select from one of four modes: determinate, indeterminate, buffer or query.
7677  * @param {number=} value In determinate and buffer modes, this number represents the percentage of the primary progress bar. Default: 0
7678  * @param {number=} md-buffer-value In the buffer mode, this number represents the precentage of the secondary progress bar. Default: 0
7679  *
7680  * @usage
7681  * <hljs lang="html">
7682  * <md-progress-linear md-mode="determinate" value="..."></md-progress-linear>
7683  *
7684  * <md-progress-linear md-mode="determinate" ng-value="..."></md-progress-linear>
7685  *
7686  * <md-progress-linear md-mode="indeterminate"></md-progress-linear>
7687  *
7688  * <md-progress-linear md-mode="buffer" value="..." md-buffer-value="..."></md-progress-linear>
7689  *
7690  * <md-progress-linear md-mode="query"></md-progress-linear>
7691  * </hljs>
7692  */
7693 function MdProgressLinearDirective($$rAF, $mdConstant, $mdTheming) {
7694
7695   return {
7696     restrict: 'E',
7697     template: '<div class="md-container">' +
7698       '<div class="md-dashed"></div>' +
7699       '<div class="md-bar md-bar1"></div>' +
7700       '<div class="md-bar md-bar2"></div>' +
7701       '</div>',
7702     compile: compile
7703   };
7704   
7705   function compile(tElement, tAttrs, transclude) {
7706     tElement.attr('aria-valuemin', 0);
7707     tElement.attr('aria-valuemax', 100);
7708     tElement.attr('role', 'progressbar');
7709
7710     return postLink;
7711   }
7712   function postLink(scope, element, attr) {
7713     $mdTheming(element);
7714     var bar1Style = element[0].querySelector('.md-bar1').style,
7715       bar2Style = element[0].querySelector('.md-bar2').style,
7716       container = angular.element(element[0].querySelector('.md-container'));
7717
7718     attr.$observe('value', function(value) {
7719       if (attr.mdMode == 'query') {
7720         return;
7721       }
7722
7723       var clamped = clamp(value);
7724       element.attr('aria-valuenow', clamped);
7725       bar2Style[$mdConstant.CSS.TRANSFORM] = transforms[clamped];
7726     });
7727
7728     attr.$observe('mdBufferValue', function(value) {
7729       bar1Style[$mdConstant.CSS.TRANSFORM] = transforms[clamp(value)];
7730     });
7731
7732     $$rAF(function() {
7733       container.addClass('md-ready');
7734     });
7735   }
7736
7737   function clamp(value) {
7738     if (value > 100) {
7739       return 100;
7740     }
7741
7742     if (value < 0) {
7743       return 0;
7744     }
7745
7746     return Math.ceil(value || 0);
7747   }
7748 }
7749 MdProgressLinearDirective.$inject = ["$$rAF", "$mdConstant", "$mdTheming"];
7750
7751
7752 // **********************************************************
7753 // Private Methods
7754 // **********************************************************
7755 var transforms = (function() {
7756   var values = new Array(101);
7757   for(var i = 0; i < 101; i++){
7758     values[i] = makeTransform(i);
7759   }
7760
7761   return values;
7762
7763   function makeTransform(value){
7764     var scale = value/100;
7765     var translateX = (value-100)/2;
7766     return 'translateX(' + translateX.toString() + '%) scale(' + scale.toString() + ', 1)';
7767   }
7768 })();
7769
7770 })();
7771 (function(){
7772 "use strict";
7773
7774 /**
7775  * @ngdoc module
7776  * @name material.components.radioButton
7777  * @description radioButton module!
7778  */
7779 angular.module('material.components.radioButton', [
7780   'material.core'
7781 ])
7782   .directive('mdRadioGroup', mdRadioGroupDirective)
7783   .directive('mdRadioButton', mdRadioButtonDirective);
7784
7785 /**
7786  * @ngdoc directive
7787  * @module material.components.radioButton
7788  * @name mdRadioGroup
7789  *
7790  * @restrict E
7791  *
7792  * @description
7793  * The `<md-radio-group>` directive identifies a grouping
7794  * container for the 1..n grouped radio buttons; specified using nested
7795  * `<md-radio-button>` tags.
7796  *
7797  * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
7798  * the radio button is in the accent color by default. The primary color palette may be used with
7799  * the `md-primary` class.
7800  *
7801  * Note: `<md-radio-group>` and `<md-radio-button>` handle tabindex differently
7802  * than the native `<input type='radio'>` controls. Whereas the native controls
7803  * force the user to tab through all the radio buttons, `<md-radio-group>`
7804  * is focusable, and by default the `<md-radio-button>`s are not.
7805  *
7806  * @param {string} ng-model Assignable angular expression to data-bind to.
7807  * @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects.
7808  *
7809  * @usage
7810  * <hljs lang="html">
7811  * <md-radio-group ng-model="selected">
7812  *
7813  *   <md-radio-button
7814  *        ng-repeat="d in colorOptions"
7815  *        ng-value="d.value" aria-label="{{ d.label }}">
7816  *
7817  *          {{ d.label }}
7818  *
7819  *   </md-radio-button>
7820  *
7821  * </md-radio-group>
7822  * </hljs>
7823  *
7824  */
7825 function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) {
7826   RadioGroupController.prototype = createRadioGroupControllerProto();
7827
7828   return {
7829     restrict: 'E',
7830     controller: ['$element', RadioGroupController],
7831     require: ['mdRadioGroup', '?ngModel'],
7832     link: { pre: linkRadioGroup }
7833   };
7834
7835   function linkRadioGroup(scope, element, attr, ctrls) {
7836     $mdTheming(element);
7837     var rgCtrl = ctrls[0];
7838     var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
7839
7840     function setFocus() {
7841       if (!element.hasClass('md-focused')) { element.addClass('md-focused'); }
7842     }
7843
7844     function keydownListener(ev) {
7845       var keyCode = ev.which || ev.keyCode;
7846       switch(keyCode) {
7847         case $mdConstant.KEY_CODE.LEFT_ARROW:
7848         case $mdConstant.KEY_CODE.UP_ARROW:
7849           ev.preventDefault();
7850           rgCtrl.selectPrevious();
7851           setFocus();
7852           break;
7853
7854         case $mdConstant.KEY_CODE.RIGHT_ARROW:
7855         case $mdConstant.KEY_CODE.DOWN_ARROW:
7856           ev.preventDefault();
7857           rgCtrl.selectNext();
7858           setFocus();
7859           break;
7860
7861         case $mdConstant.KEY_CODE.ENTER:
7862           var form = angular.element($mdUtil.getClosest(element[0], 'form'));
7863           if (form.length > 0) {
7864             form.triggerHandler('submit');
7865           }
7866           break;
7867       }
7868     }
7869
7870     rgCtrl.init(ngModelCtrl);
7871
7872     scope.mouseActive = false;
7873     element.attr({
7874               'role': 'radiogroup',
7875               'tabIndex': element.attr('tabindex') || '0'
7876             })
7877             .on('keydown', keydownListener)
7878             .on('mousedown', function(event) {
7879               scope.mouseActive = true;
7880               $timeout(function() {
7881                 scope.mouseActive = false;
7882               }, 100);
7883             })
7884             .on('focus', function() {
7885               if(scope.mouseActive === false) { rgCtrl.$element.addClass('md-focused'); }
7886             })
7887             .on('blur', function() { rgCtrl.$element.removeClass('md-focused'); });
7888   }
7889
7890   function RadioGroupController($element) {
7891     this._radioButtonRenderFns = [];
7892     this.$element = $element;
7893   }
7894
7895   function createRadioGroupControllerProto() {
7896     return {
7897       init: function(ngModelCtrl) {
7898         this._ngModelCtrl = ngModelCtrl;
7899         this._ngModelCtrl.$render = angular.bind(this, this.render);
7900       },
7901       add: function(rbRender) {
7902         this._radioButtonRenderFns.push(rbRender);
7903       },
7904       remove: function(rbRender) {
7905         var index = this._radioButtonRenderFns.indexOf(rbRender);
7906         if (index !== -1) {
7907           this._radioButtonRenderFns.splice(index, 1);
7908         }
7909       },
7910       render: function() {
7911         this._radioButtonRenderFns.forEach(function(rbRender) {
7912           rbRender();
7913         });
7914       },
7915       setViewValue: function(value, eventType) {
7916         this._ngModelCtrl.$setViewValue(value, eventType);
7917         // update the other radio buttons as well
7918         this.render();
7919       },
7920       getViewValue: function() {
7921         return this._ngModelCtrl.$viewValue;
7922       },
7923       selectNext: function() {
7924         return changeSelectedButton(this.$element, 1);
7925       },
7926       selectPrevious: function() {
7927         return changeSelectedButton(this.$element, -1);
7928       },
7929       setActiveDescendant: function (radioId) {
7930         this.$element.attr('aria-activedescendant', radioId);
7931       }
7932     };
7933   }
7934   /**
7935    * Change the radio group's selected button by a given increment.
7936    * If no button is selected, select the first button.
7937    */
7938   function changeSelectedButton(parent, increment) {
7939     // Coerce all child radio buttons into an array, then wrap then in an iterator
7940     var buttons = $mdUtil.iterator(parent[0].querySelectorAll('md-radio-button'), true);
7941
7942     if (buttons.count()) {
7943       var validate = function (button) {
7944         // If disabled, then NOT valid
7945         return !angular.element(button).attr("disabled");
7946       };
7947       var selected = parent[0].querySelector('md-radio-button.md-checked');
7948       var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
7949       // Activate radioButton's click listener (triggerHandler won't create a real click event)
7950       angular.element(target).triggerHandler('click');
7951
7952
7953     }
7954   }
7955
7956 }
7957 mdRadioGroupDirective.$inject = ["$mdUtil", "$mdConstant", "$mdTheming", "$timeout"];
7958
7959 /**
7960  * @ngdoc directive
7961  * @module material.components.radioButton
7962  * @name mdRadioButton
7963  *
7964  * @restrict E
7965  *
7966  * @description
7967  * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
7968  *
7969  * While similar to the `<input type="radio" ng-model="" value="">` directive,
7970  * the `<md-radio-button>` directive provides ink effects, ARIA support, and
7971  * supports use within named radio groups.
7972  *
7973  * @param {string} ngModel Assignable angular expression to data-bind to.
7974  * @param {string=} ngChange Angular expression to be executed when input changes due to user
7975  *    interaction with the input element.
7976  * @param {string} ngValue Angular expression which sets the value to which the expression should
7977  *    be set when selected.*
7978  * @param {string} value The value to which the expression should be set when selected.
7979  * @param {string=} name Property name of the form under which the control is published.
7980  * @param {string=} aria-label Adds label to radio button for accessibility.
7981  * Defaults to radio button's text. If no text content is available, a warning will be logged.
7982  *
7983  * @usage
7984  * <hljs lang="html">
7985  *
7986  * <md-radio-button value="1" aria-label="Label 1">
7987  *   Label 1
7988  * </md-radio-button>
7989  *
7990  * <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
7991  *   Green
7992  * </md-radio-button>
7993  *
7994  * </hljs>
7995  *
7996  */
7997 function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
7998
7999   var CHECKED_CSS = 'md-checked';
8000
8001   return {
8002     restrict: 'E',
8003     require: '^mdRadioGroup',
8004     transclude: true,
8005     template: '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
8006                 '<div class="md-off"></div>' +
8007                 '<div class="md-on"></div>' +
8008               '</div>' +
8009               '<div ng-transclude class="md-label"></div>',
8010     link: link
8011   };
8012
8013   function link(scope, element, attr, rgCtrl) {
8014     var lastChecked;
8015
8016     $mdTheming(element);
8017     configureAria(element, scope);
8018
8019     rgCtrl.add(render);
8020     attr.$observe('value', render);
8021
8022     element
8023       .on('click', listener)
8024       .on('$destroy', function() {
8025         rgCtrl.remove(render);
8026       });
8027
8028     function listener(ev) {
8029       if (element[0].hasAttribute('disabled')) return;
8030
8031       scope.$apply(function() {
8032         rgCtrl.setViewValue(attr.value, ev && ev.type);
8033       });
8034     }
8035
8036     function render() {
8037       var checked = (rgCtrl.getViewValue() == attr.value);
8038       if (checked === lastChecked) {
8039         return;
8040       }
8041       lastChecked = checked;
8042       element.attr('aria-checked', checked);
8043       if (checked) {
8044         element.addClass(CHECKED_CSS);
8045         rgCtrl.setActiveDescendant(element.attr('id'));
8046       } else {
8047         element.removeClass(CHECKED_CSS);
8048       }
8049     }
8050     /**
8051      * Inject ARIA-specific attributes appropriate for each radio button
8052      */
8053     function configureAria( element, scope ){
8054       scope.ariaId = buildAriaID();
8055
8056       element.attr({
8057         'id' :  scope.ariaId,
8058         'role' : 'radio',
8059         'aria-checked' : 'false'
8060       });
8061
8062       $mdAria.expectWithText(element, 'aria-label');
8063
8064       /**
8065        * Build a unique ID for each radio button that will be used with aria-activedescendant.
8066        * Preserve existing ID if already specified.
8067        * @returns {*|string}
8068        */
8069       function buildAriaID() {
8070         return attr.id || ( 'radio' + "_" + $mdUtil.nextUid() );
8071       }
8072     }
8073   }
8074 }
8075 mdRadioButtonDirective.$inject = ["$mdAria", "$mdUtil", "$mdTheming"];
8076
8077 })();
8078 (function(){
8079 "use strict";
8080
8081 /**
8082  * @ngdoc module
8083  * @name material.components.select
8084  */
8085
8086 /***************************************************
8087
8088 ### TODO ###
8089 **DOCUMENTATION AND DEMOS**
8090
8091 - [ ] ng-model with child mdOptions (basic)
8092 - [ ] ng-model="foo" ng-model-options="{ trackBy: '$value.id' }" for objects
8093 - [ ] mdOption with value
8094 - [ ] Usage with input inside
8095
8096 ### TODO - POST RC1 ###
8097 - [ ] Abstract placement logic in $mdSelect service to $mdMenu service
8098
8099 ***************************************************/
8100
8101 var SELECT_EDGE_MARGIN = 8;
8102 var selectNextId = 0;
8103
8104 angular.module('material.components.select', [
8105   'material.core',
8106   'material.components.backdrop'
8107 ])
8108 .directive('mdSelect', SelectDirective)
8109 .directive('mdSelectMenu', SelectMenuDirective)
8110 .directive('mdOption', OptionDirective)
8111 .directive('mdOptgroup', OptgroupDirective)
8112 .provider('$mdSelect', SelectProvider);
8113
8114
8115 /**
8116  * @ngdoc directive
8117  * @name mdSelect
8118  * @restrict E
8119  * @module material.components.select
8120  *
8121  * @description Displays a select box, bound to an ng-model.
8122  *
8123  * @param {expression} ng-model The model!
8124  * @param {boolean=} multiple Whether it's multiple.
8125  * @param {string=} placeholder Placeholder hint text.
8126  * @param {string=} aria-label Optional label for accessibility. Only necessary if no placeholder or
8127  * explicit label is present.
8128  *
8129  * @usage
8130  * With a placeholder (label and aria-label are added dynamically)
8131  * <hljs lang="html">
8132  *   <md-select
8133  *     ng-model="someModel"
8134  *     placeholder="Select a state">
8135  *     <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
8136  *   </md-select>
8137  * </hljs>
8138  *
8139  * With an explicit label
8140  * <hljs lang="html">
8141  *   <md-select
8142  *     ng-model="someModel">
8143  *     <md-select-label>Select a state</md-select-label>
8144  *     <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
8145  *   </md-select>
8146  * </hljs>
8147  */
8148 function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $interpolate, $compile, $parse) {
8149   return {
8150     restrict: 'E',
8151     require: ['mdSelect', 'ngModel', '?^form'],
8152     compile: compile,
8153     controller: function() { } // empty placeholder controller to be initialized in link
8154   };
8155
8156   function compile(element, attr) {
8157     // The user is allowed to provide a label for the select as md-select-label child
8158     var labelEl = element.find('md-select-label').remove();
8159
8160     // If not provided, we automatically make one
8161     if (!labelEl.length) {
8162       labelEl = angular.element('<md-select-label><span></span></md-select-label>');
8163     } else {
8164       if (!labelEl[0].firstElementChild) {
8165         var spanWrapper = angular.element('<span>');
8166         spanWrapper.append(labelEl.contents());
8167         labelEl.append(spanWrapper);
8168       }
8169     }
8170     labelEl.append('<span class="md-select-icon" aria-hidden="true"></span>');
8171     labelEl.addClass('md-select-label');
8172     if (!labelEl[0].hasAttribute('id')) {
8173       labelEl.attr('id', 'select_label_' + $mdUtil.nextUid());
8174     }
8175
8176     // There's got to be an md-content inside. If there's not one, let's add it.
8177     if (!element.find('md-content').length) {
8178       element.append( angular.element('<md-content>').append(element.contents()) );
8179     }
8180
8181     // Add progress spinner for md-options-loading
8182     if (attr.mdOnOpen) {
8183       element.find('md-content').prepend(
8184         angular.element('<md-progress-circular>')
8185                .attr('md-mode', 'indeterminate')
8186                .attr('ng-hide', '$$loadingAsyncDone')
8187                .wrap('<div>')
8188                .parent()
8189       );
8190     }
8191
8192     if (attr.name) {
8193       var autofillClone = angular.element('<select class="md-visually-hidden">');
8194       autofillClone.attr({
8195         'name': '.' + attr.name,
8196         'ng-model': attr.ngModel,
8197         'aria-hidden': 'true',
8198         'tabindex': '-1'
8199       });
8200       var opts = element.find('md-option');
8201       angular.forEach(opts, function(el) {
8202         var newEl = angular.element('<option>' + el.innerHTML + '</option>');
8203         if (el.hasAttribute('ng-value')) newEl.attr('ng-value', el.getAttribute('ng-value'));
8204         else if (el.hasAttribute('value')) newEl.attr('value', el.getAttribute('value'));
8205         autofillClone.append(newEl);
8206       });
8207
8208       element.parent().append(autofillClone);
8209     }
8210
8211     // Use everything that's left inside element.contents() as the contents of the menu
8212     var selectTemplate = '<div class="md-select-menu-container">' +
8213         '<md-select-menu ' +
8214         (angular.isDefined(attr.multiple) ? 'multiple' : '') + '>' +
8215           element.html() +
8216         '</md-select-menu></div>';
8217
8218     element.empty().append(labelEl);
8219
8220     attr.tabindex = attr.tabindex || '0';
8221
8222     return function postLink(scope, element, attr, ctrls) {
8223       var isOpen;
8224       var isDisabled;
8225
8226       var mdSelectCtrl = ctrls[0];
8227       var ngModel = ctrls[1];
8228       var formCtrl = ctrls[2];
8229
8230       var labelEl = element.find('md-select-label');
8231       var customLabel = labelEl.text().length !== 0;
8232       var selectContainer, selectScope, selectMenuCtrl;
8233       createSelect();
8234
8235       $mdTheming(element);
8236
8237       if (attr.name && formCtrl) {
8238         var selectEl = element.parent()[0].querySelector('select[name=".' + attr.name + '"]')
8239         formCtrl.$removeControl(angular.element(selectEl).controller());
8240       }
8241
8242       var originalRender = ngModel.$render;
8243       ngModel.$render = function() {
8244         originalRender();
8245         syncLabelText();
8246       };
8247
8248       mdSelectCtrl.setLabelText = function(text) {
8249         if (customLabel) return; // Assume that user is handling it on their own
8250         mdSelectCtrl.setIsPlaceholder(!text);
8251         text = text || attr.placeholder || '';
8252         var target = customLabel ? labelEl : labelEl.children().eq(0);
8253         target.text(text);
8254       };
8255
8256       mdSelectCtrl.setIsPlaceholder = function(val) {
8257         val ? labelEl.addClass('md-placeholder') : labelEl.removeClass('md-placeholder');
8258       };
8259
8260       scope.$$postDigest(function() {
8261         setAriaLabel();
8262         syncLabelText();
8263       });
8264
8265       function setAriaLabel() {
8266         var labelText = element.attr('placeholder');
8267         if (!labelText) {
8268           labelText = element.find('md-select-label').text();
8269         }
8270         $mdAria.expect(element, 'aria-label', labelText);
8271       }
8272
8273       function syncLabelText() {
8274         if (selectContainer) {
8275           selectMenuCtrl = selectMenuCtrl || selectContainer.find('md-select-menu').controller('mdSelectMenu');
8276           mdSelectCtrl.setLabelText(selectMenuCtrl.selectedLabels());
8277         }
8278       }
8279
8280       var deregisterWatcher;
8281       attr.$observe('ngMultiple', function(val) {
8282         if (deregisterWatcher) deregisterWatcher();
8283         var parser = $parse(val);
8284         deregisterWatcher = scope.$watch(function() { return parser(scope); }, function(multiple, prevVal) {
8285           if (multiple === undefined && prevVal === undefined) return; // assume compiler did a good job
8286           if (multiple) {
8287             element.attr('multiple', 'multiple');
8288           } else {
8289             element.removeAttr('multiple');
8290           }
8291           if (selectContainer) {
8292             selectMenuCtrl.setMultiple(multiple);
8293             originalRender = ngModel.$render;
8294             ngModel.$render = function() {
8295               originalRender();
8296               syncLabelText();
8297             };
8298             selectMenuCtrl.refreshViewValue();
8299             ngModel.$render();
8300           }
8301         });
8302       });
8303
8304       attr.$observe('disabled', function(disabled) {
8305         if (typeof disabled == "string") {
8306           disabled = true;
8307         }
8308         // Prevent click event being registered twice
8309         if (isDisabled !== undefined && isDisabled === disabled) {
8310           return;
8311         }
8312         isDisabled = disabled;
8313         if (disabled) {
8314           element.attr({'tabindex': -1, 'aria-disabled': 'true'});
8315           element.off('click', openSelect);
8316           element.off('keydown', handleKeypress);
8317         } else {
8318           element.attr({'tabindex': attr.tabindex, 'aria-disabled': 'false'});
8319           element.on('click', openSelect);
8320           element.on('keydown', handleKeypress);
8321         }
8322       });
8323       if (!attr.disabled && !attr.ngDisabled) {
8324         element.attr({'tabindex': attr.tabindex, 'aria-disabled': 'false'});
8325         element.on('click', openSelect);
8326         element.on('keydown', handleKeypress);
8327       }
8328
8329       var ariaAttrs = {
8330         role: 'combobox',
8331         'aria-expanded': 'false'
8332       };
8333       if (!element[0].hasAttribute('id')) {
8334         ariaAttrs.id = 'select_' + $mdUtil.nextUid();
8335       }
8336       element.attr(ariaAttrs);
8337
8338       scope.$on('$destroy', function() {
8339         if (isOpen) {
8340           $mdSelect.cancel().then(function() {
8341             selectContainer.remove();
8342           });
8343         } else {
8344           selectContainer.remove();
8345         }
8346       });
8347
8348
8349       // Create a fake select to find out the label value
8350       function createSelect() {
8351         selectContainer = angular.element(selectTemplate);
8352         var selectEl = selectContainer.find('md-select-menu');
8353         selectEl.data('$ngModelController', ngModel);
8354         selectEl.data('$mdSelectController', mdSelectCtrl);
8355         selectScope = scope.$new();
8356         selectContainer = $compile(selectContainer)(selectScope);
8357         selectMenuCtrl = selectContainer.find('md-select-menu').controller('mdSelectMenu');
8358       }
8359
8360       function handleKeypress(e) {
8361         var allowedCodes = [32, 13, 38, 40];
8362         if (allowedCodes.indexOf(e.keyCode) != -1 ) {
8363           // prevent page scrolling on interaction
8364           e.preventDefault();
8365           openSelect(e);
8366         } else {
8367           if (e.keyCode <= 90 && e.keyCode >= 31) {
8368             e.preventDefault();
8369             var node = selectMenuCtrl.optNodeForKeyboardSearch(e);
8370             if (!node) return;
8371             var optionCtrl = angular.element(node).controller('mdOption');
8372             if (!selectMenuCtrl.isMultiple) {
8373               selectMenuCtrl.deselect( Object.keys(selectMenuCtrl.selected)[0] );
8374             }
8375             selectMenuCtrl.select(optionCtrl.hashKey, optionCtrl.value);
8376             selectMenuCtrl.refreshViewValue();
8377             ngModel.$render();
8378           }
8379         }
8380       }
8381
8382       function openSelect() {
8383         scope.$evalAsync(function() {
8384           isOpen = true;
8385           $mdSelect.show({
8386             scope: selectScope,
8387             preserveScope: true,
8388             skipCompile: true,
8389             element: selectContainer,
8390             target: element[0],
8391             hasBackdrop: true,
8392             loadingAsync: attr.mdOnOpen ? scope.$eval(attr.mdOnOpen) || true : false,
8393           }).then(function(selectedText) {
8394             isOpen = false;
8395           });
8396         });
8397       }
8398     };
8399   }
8400 }
8401 SelectDirective.$inject = ["$mdSelect", "$mdUtil", "$mdTheming", "$mdAria", "$interpolate", "$compile", "$parse"];
8402
8403 function SelectMenuDirective($parse, $mdUtil, $mdTheming) {
8404
8405   SelectMenuController.$inject = ["$scope", "$attrs", "$element"];
8406   return {
8407     restrict: 'E',
8408     require: ['mdSelectMenu', '?ngModel'],
8409     controller: SelectMenuController,
8410     link: { pre: preLink }
8411   };
8412
8413   // We use preLink instead of postLink to ensure that the select is initialized before
8414   // its child options run postLink.
8415   function preLink(scope, element, attr, ctrls) {
8416     var selectCtrl = ctrls[0];
8417     var ngModel = ctrls[1];
8418
8419     $mdTheming(element);
8420     element.on('click', clickListener);
8421     element.on('keypress', keyListener);
8422     if (ngModel) selectCtrl.init(ngModel);
8423     configureAria();
8424
8425     function configureAria() {
8426       element.attr({
8427         'id': 'select_menu_' + $mdUtil.nextUid(),
8428         'role': 'listbox',
8429         'aria-multiselectable': (selectCtrl.isMultiple ? 'true' : 'false')
8430       });
8431     }
8432
8433     function keyListener(e) {
8434       if (e.keyCode == 13 || e.keyCode == 32) {
8435         clickListener(e);
8436       }
8437     }
8438
8439     function clickListener(ev) {
8440       var option = $mdUtil.getClosest(ev.target, 'md-option');
8441       var optionCtrl = option && angular.element(option).data('$mdOptionController');
8442       if (!option || !optionCtrl) return;
8443
8444       var optionHashKey = selectCtrl.hashGetter(optionCtrl.value);
8445       var isSelected = angular.isDefined(selectCtrl.selected[optionHashKey]);
8446
8447       scope.$apply(function() {
8448         if (selectCtrl.isMultiple) {
8449           if (isSelected) {
8450             selectCtrl.deselect(optionHashKey);
8451           } else {
8452             selectCtrl.select(optionHashKey, optionCtrl.value);
8453           }
8454         } else {
8455           if (!isSelected) {
8456             selectCtrl.deselect( Object.keys(selectCtrl.selected)[0] );
8457             selectCtrl.select( optionHashKey, optionCtrl.value );
8458           }
8459         }
8460         selectCtrl.refreshViewValue();
8461       });
8462     }
8463   }
8464
8465
8466
8467   function SelectMenuController($scope, $attrs, $element) {
8468     var self = this;
8469     self.isMultiple = angular.isDefined($attrs.multiple);
8470     // selected is an object with keys matching all of the selected options' hashed values
8471     self.selected = {};
8472     // options is an object with keys matching every option's hash value,
8473     // and values matching every option's controller.
8474     self.options = {};
8475
8476     $scope.$watch(function() { return self.options; }, function() {
8477       self.ngModel.$render();
8478     }, true);
8479
8480     var deregisterCollectionWatch;
8481     self.setMultiple = function(isMultiple) {
8482       var ngModel = self.ngModel;
8483       self.isMultiple = isMultiple;
8484       if (deregisterCollectionWatch) deregisterCollectionWatch();
8485
8486       if (self.isMultiple) {
8487         ngModel.$validators['md-multiple'] = validateArray;
8488         ngModel.$render = renderMultiple;
8489
8490         // watchCollection on the model because by default ngModel only watches the model's
8491         // reference. This allowed the developer to also push and pop from their array.
8492         $scope.$watchCollection($attrs.ngModel, function(value) {
8493           if (validateArray(value)) renderMultiple(value);
8494         });
8495       } else {
8496         delete ngModel.$validators['md-multiple'];
8497         ngModel.$render = renderSingular;
8498       }
8499
8500       function validateArray(modelValue, viewValue) {
8501         // If a value is truthy but not an array, reject it.
8502         // If value is undefined/falsy, accept that it's an empty array.
8503         return angular.isArray(modelValue || viewValue || []);
8504       }
8505     };
8506
8507     var searchStr = '';
8508     var clearSearchTimeout, optNodes, optText;
8509     var CLEAR_SEARCH_AFTER = 300;
8510     self.optNodeForKeyboardSearch = function(e) {
8511       clearSearchTimeout && clearTimeout(clearSearchTimeout);
8512       clearSearchTimeout = setTimeout(function() {
8513         clearSearchTimeout = undefined;
8514         searchStr = '';
8515         optText = undefined;
8516         optNodes = undefined;
8517       }, CLEAR_SEARCH_AFTER);
8518       searchStr += String.fromCharCode(e.keyCode);
8519       var search = new RegExp('^' + searchStr, 'i');
8520       if (!optNodes) {
8521         optNodes = $element.find('md-option');
8522         optText = new Array(optNodes.length);
8523         angular.forEach(optNodes, function(el, i) {
8524           optText[i] = el.textContent.trim();
8525         });
8526       }
8527       for (var i = 0; i < optText.length; ++i) {
8528         if (search.test(optText[i])) {
8529           return optNodes[i];
8530         }
8531       }
8532     };
8533
8534
8535     self.init = function(ngModel) {
8536       self.ngModel = ngModel;
8537
8538       // Allow users to provide `ng-model="foo" ng-model-options="{trackBy: 'foo.id'}"` so
8539       // that we can properly compare objects set on the model to the available options
8540       if (ngModel.$options && ngModel.$options.trackBy) {
8541         var trackByLocals = {};
8542         var trackByParsed = $parse(ngModel.$options.trackBy);
8543         self.hashGetter = function(value, valueScope) {
8544           trackByLocals.$value = value;
8545           return trackByParsed(valueScope || $scope, trackByLocals);
8546         };
8547       // If the user doesn't provide a trackBy, we automatically generate an id for every
8548       // value passed in
8549       } else {
8550         self.hashGetter = function getHashValue(value) {
8551           if (angular.isObject(value)) {
8552             return 'object_' + (value.$$mdSelectId || (value.$$mdSelectId = ++selectNextId));
8553           }
8554           return value;
8555         };
8556       }
8557       self.setMultiple(self.isMultiple);
8558     };
8559
8560     self.selectedLabels = function() {
8561       var selectedOptionEls = nodesToArray($element[0].querySelectorAll('md-option[selected]'));
8562       if (selectedOptionEls.length) {
8563         return selectedOptionEls.map(function(el) { return el.textContent; }).join(', ');
8564       } else {
8565         return '';
8566       }
8567     };
8568
8569     self.select = function(hashKey, hashedValue) {
8570       var option = self.options[hashKey];
8571       option && option.setSelected(true);
8572       self.selected[hashKey] = hashedValue;
8573     };
8574     self.deselect = function(hashKey) {
8575       var option = self.options[hashKey];
8576       option && option.setSelected(false);
8577       delete self.selected[hashKey];
8578     };
8579
8580     self.addOption = function(hashKey, optionCtrl) {
8581       if (angular.isDefined(self.options[hashKey])) {
8582         throw new Error('Duplicate md-option values are not allowed in a select. ' +
8583                         'Duplicate value "' + optionCtrl.value + '" found.');
8584       }
8585       self.options[hashKey] = optionCtrl;
8586
8587       // If this option's value was already in our ngModel, go ahead and select it.
8588       if (angular.isDefined(self.selected[hashKey])) {
8589         self.select(hashKey, optionCtrl.value);
8590         self.refreshViewValue();
8591       }
8592     };
8593     self.removeOption = function(hashKey) {
8594       delete self.options[hashKey];
8595       // Don't deselect an option when it's removed - the user's ngModel should be allowed
8596       // to have values that do not match a currently available option.
8597     };
8598
8599     self.refreshViewValue = function() {
8600       var values = [];
8601       var option;
8602       for (var hashKey in self.selected) {
8603          // If this hashKey has an associated option, push that option's value to the model.
8604          if ((option = self.options[hashKey])) {
8605            values.push(option.value);
8606          } else {
8607            // Otherwise, the given hashKey has no associated option, and we got it
8608            // from an ngModel value at an earlier time. Push the unhashed value of
8609            // this hashKey to the model.
8610            // This allows the developer to put a value in the model that doesn't yet have
8611            // an associated option.
8612            values.push(self.selected[hashKey]);
8613          }
8614       }
8615       self.ngModel.$setViewValue(self.isMultiple ? values : values[0]);
8616     };
8617
8618     function renderMultiple() {
8619       var newSelectedValues = self.ngModel.$modelValue || self.ngModel.$viewValue;
8620       if (!angular.isArray(newSelectedValues)) return;
8621
8622       var oldSelected = Object.keys(self.selected);
8623
8624       var newSelectedHashes = newSelectedValues.map(self.hashGetter);
8625       var deselected = oldSelected.filter(function(hash) {
8626         return newSelectedHashes.indexOf(hash) === -1;
8627       });
8628
8629       deselected.forEach(self.deselect);
8630       newSelectedHashes.forEach(function(hashKey, i) {
8631         self.select(hashKey, newSelectedValues[i]);
8632       });
8633     }
8634     function renderSingular() {
8635       var value = self.ngModel.$viewValue || self.ngModel.$modelValue;
8636       Object.keys(self.selected).forEach(self.deselect);
8637       self.select( self.hashGetter(value), value );
8638     }
8639   }
8640
8641 }
8642 SelectMenuDirective.$inject = ["$parse", "$mdUtil", "$mdTheming"];
8643
8644 function OptionDirective($mdButtonInkRipple, $mdUtil) {
8645
8646   OptionController.$inject = ["$element"];
8647   return {
8648     restrict: 'E',
8649     require: ['mdOption', '^^mdSelectMenu'],
8650     controller: OptionController,
8651     compile: compile
8652   };
8653
8654   function compile(element, attr) {
8655     // Manual transclusion to avoid the extra inner <span> that ng-transclude generates
8656     element.append( angular.element('<div class="md-text">').append(element.contents()) );
8657
8658     element.attr('tabindex', attr.tabindex || '0');
8659     return postLink;
8660   }
8661
8662   function postLink(scope, element, attr, ctrls) {
8663     var optionCtrl = ctrls[0];
8664     var selectCtrl = ctrls[1];
8665
8666     if (angular.isDefined(attr.ngValue)) {
8667       scope.$watch(attr.ngValue, setOptionValue);
8668     } else if (angular.isDefined(attr.value)) {
8669       setOptionValue(attr.value);
8670     } else {
8671       scope.$watch(function() { return element.text(); }, setOptionValue);
8672     }
8673
8674     scope.$$postDigest(function() {
8675       attr.$observe('selected', function(selected) {
8676         if (!angular.isDefined(selected)) return;
8677         if (selected) {
8678           if (!selectCtrl.isMultiple) {
8679             selectCtrl.deselect( Object.keys(selectCtrl.selected)[0] );
8680           }
8681           selectCtrl.select(optionCtrl.hashKey, optionCtrl.value);
8682         } else {
8683           selectCtrl.deselect(optionCtrl.hashKey);
8684         }
8685         selectCtrl.refreshViewValue();
8686         selectCtrl.ngModel.$render();
8687       });
8688     });
8689
8690     $mdButtonInkRipple.attach(scope, element);
8691     configureAria();
8692
8693     function setOptionValue(newValue, oldValue) {
8694       var oldHashKey = selectCtrl.hashGetter(oldValue, scope);
8695       var newHashKey = selectCtrl.hashGetter(newValue, scope);
8696
8697       optionCtrl.hashKey = newHashKey;
8698       optionCtrl.value = newValue;
8699
8700       selectCtrl.removeOption(oldHashKey, optionCtrl);
8701       selectCtrl.addOption(newHashKey, optionCtrl);
8702     }
8703
8704     scope.$on('$destroy', function() {
8705       selectCtrl.removeOption(optionCtrl.hashKey, optionCtrl);
8706     });
8707
8708     function configureAria() {
8709       var ariaAttrs = {
8710         'role': 'option',
8711         'aria-selected': 'false'
8712       };
8713
8714       if (!element[0].hasAttribute('id')) {
8715         ariaAttrs.id = 'select_option_' + $mdUtil.nextUid();
8716       }
8717       element.attr(ariaAttrs);
8718     }
8719   }
8720
8721   function OptionController($element) {
8722     this.selected = false;
8723     this.setSelected = function(isSelected) {
8724       if (isSelected && !this.selected) {
8725         $element.attr({
8726           'selected': 'selected',
8727           'aria-selected': 'true'
8728         });
8729       } else if (!isSelected && this.selected) {
8730         $element.removeAttr('selected');
8731         $element.attr('aria-selected', 'false');
8732       }
8733       this.selected = isSelected;
8734     };
8735   }
8736
8737 }
8738 OptionDirective.$inject = ["$mdButtonInkRipple", "$mdUtil"];
8739
8740 function OptgroupDirective() {
8741   return {
8742     restrict: 'E',
8743     compile: compile
8744   };
8745   function compile(el, attrs) {
8746     var labelElement = el.find('label');
8747     if (!labelElement.length) {
8748       labelElement = angular.element('<label>');
8749       el.prepend(labelElement);
8750     }
8751     if (attrs.label) labelElement.text(attrs.label);
8752   }
8753 }
8754
8755 function SelectProvider($$interimElementProvider) {
8756   selectDefaultOptions.$inject = ["$mdSelect", "$mdConstant", "$$rAF", "$mdUtil", "$mdTheming", "$timeout", "$window"];
8757   return $$interimElementProvider('$mdSelect')
8758     .setDefaults({
8759       methods: ['target'],
8760       options: selectDefaultOptions
8761     });
8762
8763   /* @ngInject */
8764   function selectDefaultOptions($mdSelect, $mdConstant, $$rAF, $mdUtil, $mdTheming, $timeout, $window ) {
8765     return {
8766       parent: 'body',
8767       onShow: onShow,
8768       onRemove: onRemove,
8769       hasBackdrop: true,
8770       disableParentScroll: true,
8771       themable: true
8772     };
8773
8774     function onShow(scope, element, opts) {
8775       if (!opts.target) {
8776         throw new Error('$mdSelect.show() expected a target element in options.target but got ' +
8777                         '"' + opts.target + '"!');
8778       }
8779
8780       angular.extend(opts, {
8781         isRemoved: false,
8782         target: angular.element(opts.target), //make sure it's not a naked dom node
8783         parent: angular.element(opts.parent),
8784         selectEl: element.find('md-select-menu'),
8785         contentEl: element.find('md-content'),
8786         backdrop: opts.hasBackdrop && angular.element('<md-backdrop class="md-select-backdrop md-click-catcher">')
8787       });
8788
8789       opts.resizeFn = function() {
8790         $$rAF(function() {
8791           $$rAF(function() {
8792             animateSelect(scope, element, opts);
8793           });
8794         });
8795       };
8796
8797       angular.element($window).on('resize', opts.resizeFn);
8798       angular.element($window).on('orientationchange', opts.resizeFn);
8799
8800
8801       configureAria();
8802
8803       element.removeClass('md-leave');
8804
8805       var optionNodes = opts.selectEl[0].getElementsByTagName('md-option');
8806
8807       if (opts.loadingAsync && opts.loadingAsync.then) {
8808         opts.loadingAsync.then(function() {
8809           scope.$$loadingAsyncDone = true;
8810           // Give ourselves two frames for the progress loader to clear out.
8811           $$rAF(function() {
8812             $$rAF(function() {
8813               // Don't go forward if the select has been removed in this time...
8814               if (opts.isRemoved) return;
8815               animateSelect(scope, element, opts);
8816             });
8817           });
8818         });
8819       } else if (opts.loadingAsync) {
8820         scope.$$loadingAsyncDone = true;
8821       }
8822
8823       if (opts.disableParentScroll && !$mdUtil.getClosest(opts.target, 'MD-DIALOG')) {
8824         opts.restoreScroll = $mdUtil.disableScrollAround(opts.target);
8825       } else {
8826         opts.disableParentScroll = false;
8827       }
8828       // Only activate click listeners after a short time to stop accidental double taps/clicks
8829       // from clicking the wrong item
8830       $timeout(activateInteraction, 75, false);
8831
8832       if (opts.backdrop) {
8833         $mdTheming.inherit(opts.backdrop, opts.parent);
8834         opts.parent.append(opts.backdrop);
8835       }
8836       opts.parent.append(element);
8837
8838       // Give the select a frame to 'initialize' in the DOM,
8839       // so we can read its height/width/position
8840       $$rAF(function() {
8841         $$rAF(function() {
8842           if (opts.isRemoved) return;
8843           animateSelect(scope, element, opts);
8844         });
8845       });
8846
8847       return $mdUtil.transitionEndPromise(opts.selectEl, {timeout: 350});
8848
8849       function configureAria() {
8850         opts.target.attr('aria-expanded', 'true');
8851       }
8852
8853       function activateInteraction() {
8854         if (opts.isRemoved) return;
8855         var selectCtrl = opts.selectEl.controller('mdSelectMenu') || {};
8856         element.addClass('md-clickable');
8857
8858         opts.backdrop && opts.backdrop.on('click', function(e) {
8859           e.preventDefault();
8860           e.stopPropagation();
8861           opts.restoreFocus = false;
8862           scope.$apply($mdSelect.cancel);
8863         });
8864
8865         // Escape to close
8866         opts.selectEl.on('keydown', function(ev) {
8867           switch (ev.keyCode) {
8868             case $mdConstant.KEY_CODE.SPACE:
8869             case $mdConstant.KEY_CODE.ENTER:
8870               var option = $mdUtil.getClosest(ev.target, 'md-option');
8871               if (option) {
8872                 opts.selectEl.triggerHandler({
8873                   type: 'click',
8874                   target: option
8875                 });
8876                 ev.preventDefault();
8877               }
8878               break;
8879             case $mdConstant.KEY_CODE.TAB:
8880             case $mdConstant.KEY_CODE.ESCAPE:
8881               ev.preventDefault();
8882               opts.restoreFocus = true;
8883               scope.$apply($mdSelect.cancel);
8884           }
8885         });
8886
8887         // Cycling of options, and closing on enter
8888         opts.selectEl.on('keydown', function(ev) {
8889           switch (ev.keyCode) {
8890             case $mdConstant.KEY_CODE.UP_ARROW: return focusPrevOption();
8891             case $mdConstant.KEY_CODE.DOWN_ARROW: return focusNextOption();
8892             default:
8893               if (ev.keyCode >= 31 && ev.keyCode <= 90) {
8894                 var optNode = opts.selectEl.controller('mdSelectMenu').optNodeForKeyboardSearch(ev);
8895                 optNode && optNode.focus();
8896               }
8897           }
8898         });
8899
8900
8901         function focusOption(direction) {
8902           var optionsArray = nodesToArray(optionNodes);
8903           var index = optionsArray.indexOf(opts.focusedNode);
8904           if (index === -1) {
8905             // We lost the previously focused element, reset to first option
8906             index = 0;
8907           } else if (direction === 'next' && index < optionsArray.length - 1) {
8908             index++;
8909           } else if (direction === 'prev' && index > 0) {
8910             index--;
8911           }
8912           var newOption = opts.focusedNode = optionsArray[index];
8913           newOption && newOption.focus();
8914         }
8915         function focusNextOption() {
8916           focusOption('next');
8917         }
8918         function focusPrevOption() {
8919           focusOption('prev');
8920         }
8921
8922         opts.selectEl.on('click', checkCloseMenu);
8923         opts.selectEl.on('keydown', function(e) {
8924           if (e.keyCode == 32 || e.keyCode == 13) {
8925             checkCloseMenu();
8926           }
8927         });
8928
8929         function checkCloseMenu() {
8930           if (!selectCtrl.isMultiple) {
8931             opts.restoreFocus = true;
8932             scope.$evalAsync(function() {
8933               $mdSelect.hide(selectCtrl.ngModel.$viewValue);
8934             });
8935           }
8936         }
8937       }
8938
8939     }
8940
8941     function onRemove(scope, element, opts) {
8942       opts.isRemoved = true;
8943       element.addClass('md-leave')
8944         .removeClass('md-clickable');
8945       opts.target.attr('aria-expanded', 'false');
8946
8947
8948       angular.element($window).off('resize', opts.resizeFn);
8949       angular.element($window).off('orientationchange', opts.resizefn);
8950       opts.resizeFn = undefined;
8951
8952       var mdSelect = opts.selectEl.controller('mdSelect');
8953       if (mdSelect) {
8954         mdSelect.setLabelText(opts.selectEl.controller('mdSelectMenu').selectedLabels());
8955       }
8956
8957       return $mdUtil.transitionEndPromise(element, { timeout: 350 }).then(function() {
8958         element.removeClass('md-active');
8959         opts.backdrop && opts.backdrop.remove();
8960         if (element[0].parentNode === opts.parent[0]) {
8961           opts.parent[0].removeChild(element[0]); // use browser to avoid $destroy event
8962         }
8963         if (opts.disableParentScroll) {
8964           opts.restoreScroll();
8965         }
8966         if (opts.restoreFocus) opts.target.focus();
8967       });
8968     }
8969
8970     function animateSelect(scope, element, opts) {
8971       var containerNode = element[0],
8972           targetNode = opts.target[0].firstElementChild.firstElementChild, // target the first span, functioning as the label
8973           parentNode = opts.parent[0],
8974           selectNode = opts.selectEl[0],
8975           contentNode = opts.contentEl[0],
8976           parentRect = parentNode.getBoundingClientRect(),
8977           targetRect = targetNode.getBoundingClientRect(),
8978           shouldOpenAroundTarget = false,
8979           bounds = {
8980             left: parentRect.left + SELECT_EDGE_MARGIN,
8981             top: SELECT_EDGE_MARGIN,
8982             bottom: parentRect.height - SELECT_EDGE_MARGIN,
8983             right: parentRect.width - SELECT_EDGE_MARGIN - ($mdUtil.floatingScrollbars() ? 16 : 0)
8984           },
8985           spaceAvailable = {
8986             top: targetRect.top - bounds.top,
8987             left: targetRect.left - bounds.left,
8988             right: bounds.right - (targetRect.left + targetRect.width),
8989             bottom: bounds.bottom - (targetRect.top + targetRect.height)
8990           },
8991           maxWidth = parentRect.width - SELECT_EDGE_MARGIN * 2,
8992           isScrollable = contentNode.scrollHeight > contentNode.offsetHeight,
8993           selectedNode = selectNode.querySelector('md-option[selected]'),
8994           optionNodes = selectNode.getElementsByTagName('md-option'),
8995           optgroupNodes = selectNode.getElementsByTagName('md-optgroup');
8996
8997
8998       var centeredNode;
8999       // If a selected node, center around that
9000       if (selectedNode) {
9001         centeredNode = selectedNode;
9002       // If there are option groups, center around the first option group
9003       } else if (optgroupNodes.length) {
9004         centeredNode = optgroupNodes[0];
9005       // Otherwise, center around the first optionNode
9006       } else if (optionNodes.length){
9007         centeredNode = optionNodes[0];
9008       // In case there are no options, center on whatever's in there... (eg progress indicator)
9009       } else {
9010         centeredNode = contentNode.firstElementChild || contentNode;
9011       }
9012
9013       if (contentNode.offsetWidth > maxWidth) {
9014         contentNode.style['max-width'] = maxWidth + 'px';
9015       }
9016       if (shouldOpenAroundTarget) {
9017         contentNode.style['min-width'] = targetRect.width + 'px';
9018       }
9019
9020       // Remove padding before we compute the position of the menu
9021       if (isScrollable) {
9022         selectNode.classList.add('md-overflow');
9023       }
9024
9025       // Get the selectMenuRect *after* max-width is possibly set above
9026       var selectMenuRect = selectNode.getBoundingClientRect();
9027       var centeredRect = getOffsetRect(centeredNode);
9028
9029       if (centeredNode) {
9030         var centeredStyle = $window.getComputedStyle(centeredNode);
9031         centeredRect.paddingLeft = parseInt(centeredStyle.paddingLeft, 10) || 0;
9032         centeredRect.paddingRight = parseInt(centeredStyle.paddingRight, 10) || 0;
9033       }
9034
9035       var focusedNode = centeredNode;
9036       if ((focusedNode.tagName || '').toUpperCase() === 'MD-OPTGROUP') {
9037         focusedNode = optionNodes[0] || contentNode.firstElementChild || contentNode;
9038       }
9039
9040       if (isScrollable) {
9041         var scrollBuffer = contentNode.offsetHeight / 2;
9042         contentNode.scrollTop = centeredRect.top + centeredRect.height / 2 - scrollBuffer;
9043
9044         if (spaceAvailable.top < scrollBuffer) {
9045           contentNode.scrollTop = Math.min(
9046             centeredRect.top,
9047             contentNode.scrollTop + scrollBuffer - spaceAvailable.top
9048           );
9049         } else if (spaceAvailable.bottom < scrollBuffer) {
9050           contentNode.scrollTop = Math.max(
9051             centeredRect.top + centeredRect.height - selectMenuRect.height,
9052             contentNode.scrollTop - scrollBuffer + spaceAvailable.bottom
9053           );
9054         }
9055       }
9056
9057       var left, top, transformOrigin;
9058       if (shouldOpenAroundTarget) {
9059         left = targetRect.left;
9060         top = targetRect.top + targetRect.height;
9061         transformOrigin = '50% 0';
9062         if (top + selectMenuRect.height > bounds.bottom) {
9063           top = targetRect.top - selectMenuRect.height;
9064           transformOrigin = '50% 100%';
9065         }
9066       } else {
9067         left = targetRect.left + centeredRect.left - centeredRect.paddingLeft;
9068         top = Math.floor(targetRect.top + targetRect.height / 2 - centeredRect.height / 2 -
9069           centeredRect.top + contentNode.scrollTop);
9070
9071
9072         transformOrigin = (centeredRect.left + targetRect.width / 2) + 'px ' +
9073         (centeredRect.top + centeredRect.height / 2 - contentNode.scrollTop) + 'px 0px';
9074
9075         containerNode.style.minWidth = targetRect.width + centeredRect.paddingLeft +
9076           centeredRect.paddingRight + 'px';
9077       }
9078
9079       // Keep left and top within the window
9080       var containerRect = containerNode.getBoundingClientRect();
9081       containerNode.style.left = clamp(bounds.left, left, bounds.right - containerRect.width) + 'px';
9082       containerNode.style.top = clamp(bounds.top, top, bounds.bottom - containerRect.height) + 'px';
9083       selectNode.style[$mdConstant.CSS.TRANSFORM_ORIGIN] = transformOrigin;
9084
9085       selectNode.style[$mdConstant.CSS.TRANSFORM] = 'scale(' +
9086         Math.min(targetRect.width / selectMenuRect.width, 1.0) + ',' +
9087         Math.min(targetRect.height / selectMenuRect.height, 1.0) +
9088       ')';
9089
9090
9091       $$rAF(function() {
9092         element.addClass('md-active');
9093         selectNode.style[$mdConstant.CSS.TRANSFORM] = '';
9094         if (focusedNode) {
9095           opts.focusedNode = focusedNode;
9096           focusedNode.focus();
9097         }
9098       });
9099     }
9100
9101   }
9102
9103   function clamp(min, n, max) {
9104     return Math.max(min, Math.min(n, max));
9105   }
9106
9107   function getOffsetRect(node) {
9108     return node ? {
9109       left: node.offsetLeft,
9110       top: node.offsetTop,
9111       width: node.offsetWidth,
9112       height: node.offsetHeight
9113     } : { left: 0, top: 0, width: 0, height: 0 };
9114   }
9115 }
9116 SelectProvider.$inject = ["$$interimElementProvider"];
9117
9118 // Annoying method to copy nodes to an array, thanks to IE
9119 function nodesToArray(nodes) {
9120   var results = [];
9121   for (var i = 0; i < nodes.length; ++i) {
9122     results.push(nodes.item(i));
9123   }
9124   return results;
9125 }
9126
9127 })();
9128 (function(){
9129 "use strict";
9130
9131 /**
9132  * @ngdoc module
9133  * @name material.components.sidenav
9134  *
9135  * @description
9136  * A Sidenav QP component.
9137  */
9138 angular.module('material.components.sidenav', [
9139     'material.core',
9140     'material.components.backdrop'
9141   ])
9142   .factory('$mdSidenav', SidenavService )
9143   .directive('mdSidenav', SidenavDirective)
9144   .directive('mdSidenavFocus', SidenavFocusDirective)
9145   .controller('$mdSidenavController', SidenavController);
9146
9147
9148 /**
9149  * @private
9150  * @ngdoc service
9151  * @name $mdSidenav
9152  * @module material.components.sidenav
9153  *
9154  * @description
9155  * `$mdSidenav` makes it easy to interact with multiple sidenavs
9156  * in an app.
9157  *
9158  * @usage
9159  * <hljs lang="js">
9160  * // Async lookup for sidenav instance; will resolve when the instance is available
9161  * $mdSidenav(componentId).then(function(instance) {
9162  *   $log.debug( componentId + "is now ready" );
9163  * });
9164  * // Async toggle the given sidenav;
9165  * // when instance is known ready and lazy lookup is not needed.
9166  * $mdSidenav(componentId)
9167  *    .toggle()
9168  *    .then(function(){
9169  *      $log.debug('toggled');
9170  *    });
9171  * // Async open the given sidenav
9172  * $mdSidenav(componentId)
9173  *    .open()
9174  *    .then(function(){
9175  *      $log.debug('opened');
9176  *    });
9177  * // Async close the given sidenav
9178  * $mdSidenav(componentId)
9179  *    .close()
9180  *    .then(function(){
9181  *      $log.debug('closed');
9182  *    });
9183  * // Sync check to see if the specified sidenav is set to be open
9184  * $mdSidenav(componentId).isOpen();
9185  * // Sync check to whether given sidenav is locked open
9186  * // If this is true, the sidenav will be open regardless of close()
9187  * $mdSidenav(componentId).isLockedOpen();
9188  * </hljs>
9189  */
9190 function SidenavService($mdComponentRegistry, $q) {
9191   return function(handle) {
9192
9193     // Lookup the controller instance for the specified sidNav instance
9194     var self;
9195     var errorMsg = "SideNav '" + handle + "' is not available!";
9196     var instance = $mdComponentRegistry.get(handle);
9197
9198     if(!instance) {
9199       $mdComponentRegistry.notFoundError(handle);
9200     }
9201
9202     return self = {
9203       // -----------------
9204       // Sync methods
9205       // -----------------
9206       isOpen: function() {
9207         return instance && instance.isOpen();
9208       },
9209       isLockedOpen: function() {
9210         return instance && instance.isLockedOpen();
9211       },
9212       // -----------------
9213       // Async methods
9214       // -----------------
9215       toggle: function() {
9216         return instance ? instance.toggle() : $q.reject(errorMsg);
9217       },
9218       open: function() {
9219         return instance ? instance.open() : $q.reject(errorMsg);
9220       },
9221       close: function() {
9222         return instance ? instance.close() : $q.reject(errorMsg);
9223       },
9224       then : function( callbackFn ) {
9225         var promise = instance ? $q.when(instance) : waitForInstance();
9226         return promise.then( callbackFn || angular.noop );
9227       }
9228     };
9229
9230     /**
9231      * Deferred lookup of component instance using $component registry
9232      */
9233     function waitForInstance() {
9234       return $mdComponentRegistry
9235                 .when(handle)
9236                 .then(function( it ){
9237                   instance = it;
9238                   return it;
9239                 });
9240     }
9241   };
9242 }
9243 SidenavService.$inject = ["$mdComponentRegistry", "$q"];
9244 /**
9245  * @ngdoc directive
9246  * @name mdSidenavFocus
9247  * @module material.components.sidenav
9248  *
9249  * @restrict A
9250  *
9251  * @description
9252  * `$mdSidenavFocus` provides a way to specify the focused element when a sidenav opens.
9253  * This is completely optional, as the sidenav itself is focused by default.
9254  *
9255  * @usage
9256  * <hljs lang="html">
9257  * <md-sidenav>
9258  *   <form>
9259  *     <md-input-container>
9260  *       <label for="testInput">Label</label>
9261  *       <input id="testInput" type="text" md-sidenav-focus>
9262  *     </md-input-container>
9263  *   </form>
9264  * </md-sidenav>
9265  * </hljs>
9266  **/
9267 function SidenavFocusDirective() {
9268   return {
9269     restrict: 'A',
9270     require: '^mdSidenav',
9271     link: function(scope, element, attr, sidenavCtrl) {
9272       sidenavCtrl.focusElement(element);
9273     }
9274   };
9275 }
9276 /**
9277  * @ngdoc directive
9278  * @name mdSidenav
9279  * @module material.components.sidenav
9280  * @restrict E
9281  *
9282  * @description
9283  *
9284  * A Sidenav component that can be opened and closed programatically.
9285  *
9286  * By default, upon opening it will slide out on top of the main content area.
9287  *
9288  * For keyboard and screen reader accessibility, focus is sent to the sidenav wrapper by default.
9289  * It can be overridden with the `md-sidenav-focus` directive on the child element you want focused.
9290  *
9291  * @usage
9292  * <hljs lang="html">
9293  * <div layout="row" ng-controller="MyController">
9294  *   <md-sidenav md-component-id="left" class="md-sidenav-left">
9295  *     Left Nav!
9296  *   </md-sidenav>
9297  *
9298  *   <md-content>
9299  *     Center Content
9300  *     <md-button ng-click="openLeftMenu()">
9301  *       Open Left Menu
9302  *     </md-button>
9303  *   </md-content>
9304  *
9305  *   <md-sidenav md-component-id="right"
9306  *     md-is-locked-open="$mdMedia('min-width: 333px')"
9307  *     class="md-sidenav-right">
9308  *     <form>
9309  *       <md-input-container>
9310  *         <label for="testInput">Test input</label>
9311  *         <input id="testInput" type="text"
9312  *                ng-model="data" md-sidenav-focus>
9313  *       </md-input-container>
9314  *     </form>
9315  *   </md-sidenav>
9316  * </div>
9317  * </hljs>
9318  *
9319  * <hljs lang="js">
9320  * var app = angular.module('myApp', ['ngMaterial']);
9321  * app.controller('MyController', function($scope, $mdSidenav) {
9322  *   $scope.openLeftMenu = function() {
9323  *     $mdSidenav('left').toggle();
9324  *   };
9325  * });
9326  * </hljs>
9327  *
9328  * @param {expression=} md-is-open A model bound to whether the sidenav is opened.
9329  * @param {string=} md-component-id componentId to use with $mdSidenav service.
9330  * @param {expression=} md-is-locked-open When this expression evalutes to true,
9331  * the sidenav 'locks open': it falls into the content's flow instead
9332  * of appearing over it. This overrides the `is-open` attribute.
9333  *
9334  * The $mdMedia() service is exposed to the is-locked-open attribute, which
9335  * can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
9336  * Examples:
9337  *
9338  *   - `<md-sidenav md-is-locked-open="shouldLockOpen"></md-sidenav>`
9339  *   - `<md-sidenav md-is-locked-open="$mdMedia('min-width: 1000px')"></md-sidenav>`
9340  *   - `<md-sidenav md-is-locked-open="$mdMedia('sm')"></md-sidenav>` (locks open on small screens)
9341  */
9342 function SidenavDirective($timeout, $animate, $parse, $log, $mdMedia, $mdConstant, $compile, $mdTheming, $q, $document) {
9343   return {
9344     restrict: 'E',
9345     scope: {
9346       isOpen: '=?mdIsOpen'
9347     },
9348     controller: '$mdSidenavController',
9349     compile: function(element) {
9350       element.addClass('md-closed');
9351       element.attr('tabIndex', '-1');
9352       return postLink;
9353     }
9354   };
9355
9356   /**
9357    * Directive Post Link function...
9358    */
9359   function postLink(scope, element, attr, sidenavCtrl) {
9360     var lastParentOverFlow;
9361     var triggeringElement = null;
9362     var promise = $q.when(true);
9363
9364     var isLockedOpenParsed = $parse(attr.mdIsLockedOpen);
9365     var isLocked = function() {
9366       return isLockedOpenParsed(scope.$parent, {
9367         $media: function(arg) {
9368           $log.warn("$media is deprecated for is-locked-open. Use $mdMedia instead.");
9369           return $mdMedia(arg);
9370         },
9371         $mdMedia: $mdMedia
9372       });
9373     };
9374     var backdrop = $compile(
9375       '<md-backdrop class="md-sidenav-backdrop md-opaque ng-enter">'
9376     )(scope);
9377
9378     element.on('$destroy', sidenavCtrl.destroy);
9379     $mdTheming.inherit(backdrop, element);
9380
9381     scope.$watch(isLocked, updateIsLocked);
9382     scope.$watch('isOpen', updateIsOpen);
9383
9384
9385     // Publish special accessor for the Controller instance
9386     sidenavCtrl.$toggleOpen = toggleOpen;
9387     sidenavCtrl.focusElement( sidenavCtrl.focusElement() || element );
9388
9389     /**
9390      * Toggle the DOM classes to indicate `locked`
9391      * @param isLocked
9392      */
9393     function updateIsLocked(isLocked, oldValue) {
9394       scope.isLockedOpen = isLocked;
9395       if (isLocked === oldValue) {
9396         element.toggleClass('md-locked-open', !!isLocked);
9397       } else {
9398         $animate[isLocked ? 'addClass' : 'removeClass'](element, 'md-locked-open');
9399       }
9400       backdrop.toggleClass('md-locked-open', !!isLocked);
9401     }
9402
9403     /**
9404      * Toggle the SideNav view and attach/detach listeners
9405      * @param isOpen
9406      */
9407     function updateIsOpen(isOpen) {
9408       var parent = element.parent();
9409
9410       parent[isOpen ? 'on' : 'off']('keydown', onKeyDown);
9411       backdrop[isOpen ? 'on' : 'off']('click', close);
9412
9413       if ( isOpen ) {
9414         // Capture upon opening..
9415         triggeringElement = $document[0].activeElement;
9416       }
9417       var focusEl = sidenavCtrl.focusElement();
9418
9419       disableParentScroll(isOpen);
9420
9421       return promise = $q.all([
9422                 isOpen ? $animate.enter(backdrop, parent) : $animate.leave(backdrop),
9423                 $animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed')
9424               ])
9425               .then(function() {
9426                 // Perform focus when animations are ALL done...
9427                 if (scope.isOpen) {
9428                   focusEl && focusEl.focus();
9429                 }
9430               });
9431     }
9432
9433     /**
9434      * Prevent parent scrolling (when the SideNav is open)
9435      */
9436     function disableParentScroll(disabled) {
9437       var parent = element.parent();
9438       if ( disabled ) {
9439         lastParentOverFlow = parent.css('overflow');
9440         parent.css('overflow', 'hidden');
9441       } else if (angular.isDefined(lastParentOverFlow)) {
9442         parent.css('overflow', lastParentOverFlow);
9443         lastParentOverFlow = undefined;
9444       }
9445     }
9446
9447     /**
9448      * Toggle the sideNav view and publish a promise to be resolved when
9449      * the view animation finishes.
9450      *
9451      * @param isOpen
9452      * @returns {*}
9453      */
9454     function toggleOpen( isOpen ) {
9455       if (scope.isOpen == isOpen ) {
9456
9457         return $q.when(true);
9458
9459       } else {
9460         var deferred = $q.defer();
9461
9462         // Toggle value to force an async `updateIsOpen()` to run
9463         scope.isOpen = isOpen;
9464
9465         $timeout(function() {
9466
9467           // When the current `updateIsOpen()` animation finishes
9468           promise.then(function(result) {
9469
9470             if ( !scope.isOpen ) {
9471               // reset focus to originating element (if available) upon close
9472               triggeringElement && triggeringElement.focus();
9473               triggeringElement = null;
9474             }
9475
9476             deferred.resolve(result);
9477           });
9478
9479         },0,false);
9480
9481         return deferred.promise;
9482       }
9483     }
9484
9485     /**
9486      * Auto-close sideNav when the `escape` key is pressed.
9487      * @param evt
9488      */
9489     function onKeyDown(ev) {
9490       var isEscape = (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE);
9491       return isEscape ? close(ev) : $q.when(true);
9492     }
9493
9494     /**
9495      * With backdrop `clicks` or `escape` key-press, immediately
9496      * apply the CSS close transition... Then notify the controller
9497      * to close() and perform its own actions.
9498      */
9499     function close(ev) {
9500       ev.preventDefault();
9501       ev.stopPropagation();
9502
9503       return sidenavCtrl.close();
9504     }
9505
9506   }
9507 }
9508 SidenavDirective.$inject = ["$timeout", "$animate", "$parse", "$log", "$mdMedia", "$mdConstant", "$compile", "$mdTheming", "$q", "$document"];
9509
9510 /*
9511  * @private
9512  * @ngdoc controller
9513  * @name SidenavController
9514  * @module material.components.sidenav
9515  *
9516  */
9517 function SidenavController($scope, $element, $attrs, $mdComponentRegistry, $q) {
9518
9519   var self = this,
9520       focusElement;
9521
9522   // Use Default internal method until overridden by directive postLink
9523
9524   // Synchronous getters
9525   self.isOpen = function() { return !!$scope.isOpen; };
9526   self.isLockedOpen = function() { return !!$scope.isLockedOpen; };
9527
9528   // Async actions
9529   self.open   = function() { return self.$toggleOpen( true );  };
9530   self.close  = function() { return self.$toggleOpen( false ); };
9531   self.toggle = function() { return self.$toggleOpen( !$scope.isOpen );  };
9532   self.focusElement = function(el) {
9533     if ( angular.isDefined(el) ) {
9534       focusElement = el;
9535     }
9536     return focusElement;
9537   };
9538
9539   self.$toggleOpen = function() { return $q.when($scope.isOpen); };
9540
9541   self.destroy = $mdComponentRegistry.register(self, $attrs.mdComponentId);
9542 }
9543 SidenavController.$inject = ["$scope", "$element", "$attrs", "$mdComponentRegistry", "$q"];
9544
9545 })();
9546 (function(){
9547 "use strict";
9548
9549   /**
9550    * @ngdoc module
9551    * @name material.components.slider
9552    */
9553   angular.module('material.components.slider', [
9554     'material.core'
9555   ])
9556   .directive('mdSlider', SliderDirective);
9557
9558 /**
9559  * @ngdoc directive
9560  * @name mdSlider
9561  * @module material.components.slider
9562  * @restrict E
9563  * @description
9564  * The `<md-slider>` component allows the user to choose from a range of
9565  * values.
9566  *
9567  * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
9568  * the slider is in the accent color by default. The primary color palette may be used with
9569  * the `md-primary` class.
9570  *
9571  * It has two modes: 'normal' mode, where the user slides between a wide range
9572  * of values, and 'discrete' mode, where the user slides between only a few
9573  * select values.
9574  *
9575  * To enable discrete mode, add the `md-discrete` attribute to a slider,
9576  * and use the `step` attribute to change the distance between
9577  * values the user is allowed to pick.
9578  *
9579  * @usage
9580  * <h4>Normal Mode</h4>
9581  * <hljs lang="html">
9582  * <md-slider ng-model="myValue" min="5" max="500">
9583  * </md-slider>
9584  * </hljs>
9585  * <h4>Discrete Mode</h4>
9586  * <hljs lang="html">
9587  * <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
9588  * </md-slider>
9589  * </hljs>
9590  *
9591  * @param {boolean=} md-discrete Whether to enable discrete mode.
9592  * @param {number=} step The distance between values the user is allowed to pick. Default 1.
9593  * @param {number=} min The minimum value the user is allowed to pick. Default 0.
9594  * @param {number=} max The maximum value the user is allowed to pick. Default 100.
9595  */
9596 function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdTheming, $mdGesture, $parse) {
9597   return {
9598     scope: {},
9599     require: '?ngModel',
9600     template:
9601       '<div class="md-slider-wrapper">\
9602         <div class="md-track-container">\
9603           <div class="md-track"></div>\
9604           <div class="md-track md-track-fill"></div>\
9605           <div class="md-track-ticks"></div>\
9606         </div>\
9607         <div class="md-thumb-container">\
9608           <div class="md-thumb"></div>\
9609           <div class="md-focus-thumb"></div>\
9610           <div class="md-focus-ring"></div>\
9611           <div class="md-sign">\
9612             <span class="md-thumb-text"></span>\
9613           </div>\
9614           <div class="md-disabled-thumb"></div>\
9615         </div>\
9616       </div>',
9617     compile: compile
9618   };
9619
9620   // **********************************************************
9621   // Private Methods
9622   // **********************************************************
9623
9624   function compile (tElement, tAttrs) {
9625     tElement.attr({
9626       tabIndex: 0,
9627       role: 'slider'
9628     });
9629
9630     $mdAria.expect(tElement, 'aria-label');
9631
9632     return postLink;
9633   }
9634
9635   function postLink(scope, element, attr, ngModelCtrl) {
9636     $mdTheming(element);
9637     ngModelCtrl = ngModelCtrl || {
9638       // Mock ngModelController if it doesn't exist to give us
9639       // the minimum functionality needed
9640       $setViewValue: function(val) {
9641         this.$viewValue = val;
9642         this.$viewChangeListeners.forEach(function(cb) { cb(); });
9643       },
9644       $parsers: [],
9645       $formatters: [],
9646       $viewChangeListeners: []
9647     };
9648
9649     var isDisabledParsed = attr.ngDisabled && $parse(attr.ngDisabled);
9650     var isDisabledGetter = isDisabledParsed ?
9651       function() { return isDisabledParsed(scope.$parent); } :
9652       angular.noop;
9653     var thumb = angular.element(element[0].querySelector('.md-thumb'));
9654     var thumbText = angular.element(element[0].querySelector('.md-thumb-text'));
9655     var thumbContainer = thumb.parent();
9656     var trackContainer = angular.element(element[0].querySelector('.md-track-container'));
9657     var activeTrack = angular.element(element[0].querySelector('.md-track-fill'));
9658     var tickContainer = angular.element(element[0].querySelector('.md-track-ticks'));
9659     var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
9660
9661     // Default values, overridable by attrs
9662     angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(0);
9663     angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(100);
9664     angular.isDefined(attr.step)? attr.$observe('step', updateStep) : updateStep(1);
9665
9666     // We have to manually stop the $watch on ngDisabled because it exists
9667     // on the parent scope, and won't be automatically destroyed when
9668     // the component is destroyed.
9669     var stopDisabledWatch = angular.noop;
9670     if (attr.ngDisabled) {
9671       stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
9672     }
9673
9674     $mdGesture.register(element, 'drag');
9675
9676     element
9677       .on('keydown', keydownListener)
9678       .on('$md.pressdown', onPressDown)
9679       .on('$md.pressup', onPressUp)
9680       .on('$md.dragstart', onDragStart)
9681       .on('$md.drag', onDrag)
9682       .on('$md.dragend', onDragEnd);
9683
9684     // On resize, recalculate the slider's dimensions and re-render
9685     function updateAll() {
9686       refreshSliderDimensions();
9687       ngModelRender();
9688       redrawTicks();
9689     }
9690     setTimeout(updateAll);
9691
9692     var debouncedUpdateAll = $$rAF.throttle(updateAll);
9693     angular.element($window).on('resize', debouncedUpdateAll);
9694
9695     scope.$on('$destroy', function() {
9696       angular.element($window).off('resize', debouncedUpdateAll);
9697       stopDisabledWatch();
9698     });
9699
9700     ngModelCtrl.$render = ngModelRender;
9701     ngModelCtrl.$viewChangeListeners.push(ngModelRender);
9702     ngModelCtrl.$formatters.push(minMaxValidator);
9703     ngModelCtrl.$formatters.push(stepValidator);
9704
9705     /**
9706      * Attributes
9707      */
9708     var min;
9709     var max;
9710     var step;
9711     function updateMin(value) {
9712       min = parseFloat(value);
9713       element.attr('aria-valuemin', value);
9714       updateAll();
9715     }
9716     function updateMax(value) {
9717       max = parseFloat(value);
9718       element.attr('aria-valuemax', value);
9719       updateAll();
9720     }
9721     function updateStep(value) {
9722       step = parseFloat(value);
9723       redrawTicks();
9724     }
9725     function updateAriaDisabled(isDisabled) {
9726       element.attr('aria-disabled', !!isDisabled);
9727     }
9728
9729     // Draw the ticks with canvas.
9730     // The alternative to drawing ticks with canvas is to draw one element for each tick,
9731     // which could quickly become a performance bottleneck.
9732     var tickCanvas, tickCtx;
9733     function redrawTicks() {
9734       if (!angular.isDefined(attr.mdDiscrete)) return;
9735
9736       var numSteps = Math.floor( (max - min) / step );
9737       if (!tickCanvas) {
9738         var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
9739         tickCanvas = angular.element('<canvas style="position:absolute;">');
9740         tickCtx = tickCanvas[0].getContext('2d');
9741         tickCtx.fillStyle = trackTicksStyle.backgroundColor || 'black';
9742         tickContainer.append(tickCanvas);
9743       }
9744       var dimensions = getSliderDimensions();
9745       tickCanvas[0].width = dimensions.width;
9746       tickCanvas[0].height = dimensions.height;
9747
9748       var distance;
9749       for (var i = 0; i <= numSteps; i++) {
9750         distance = Math.floor(dimensions.width * (i / numSteps));
9751         tickCtx.fillRect(distance - 1, 0, 2, dimensions.height);
9752       }
9753     }
9754
9755
9756     /**
9757      * Refreshing Dimensions
9758      */
9759     var sliderDimensions = {};
9760     refreshSliderDimensions();
9761     function refreshSliderDimensions() {
9762       sliderDimensions = trackContainer[0].getBoundingClientRect();
9763     }
9764     function getSliderDimensions() {
9765       throttledRefreshDimensions();
9766       return sliderDimensions;
9767     }
9768
9769     /**
9770      * left/right arrow listener
9771      */
9772     function keydownListener(ev) {
9773       if(element[0].hasAttribute('disabled')) {
9774         return;
9775       }
9776
9777       var changeAmount;
9778       if (ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
9779         changeAmount = -step;
9780       } else if (ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
9781         changeAmount = step;
9782       }
9783       if (changeAmount) {
9784         if (ev.metaKey || ev.ctrlKey || ev.altKey) {
9785           changeAmount *= 4;
9786         }
9787         ev.preventDefault();
9788         ev.stopPropagation();
9789         scope.$evalAsync(function() {
9790           setModelValue(ngModelCtrl.$viewValue + changeAmount);
9791         });
9792       }
9793     }
9794
9795     /**
9796      * ngModel setters and validators
9797      */
9798     function setModelValue(value) {
9799       ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
9800     }
9801     function ngModelRender() {
9802       if (isNaN(ngModelCtrl.$viewValue)) {
9803         ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
9804       }
9805
9806       var percent = (ngModelCtrl.$viewValue - min) / (max - min);
9807       scope.modelValue = ngModelCtrl.$viewValue;
9808       element.attr('aria-valuenow', ngModelCtrl.$viewValue);
9809       setSliderPercent(percent);
9810       thumbText.text( ngModelCtrl.$viewValue );
9811     }
9812
9813     function minMaxValidator(value) {
9814       if (angular.isNumber(value)) {
9815         return Math.max(min, Math.min(max, value));
9816       }
9817     }
9818     function stepValidator(value) {
9819       if (angular.isNumber(value)) {
9820         var formattedValue = (Math.round(value / step) * step);
9821         // Format to 3 digits after the decimal point - fixes #2015.
9822         return (Math.round(formattedValue * 1000) / 1000);
9823       }
9824     }
9825
9826     /**
9827      * @param percent 0-1
9828      */
9829     function setSliderPercent(percent) {
9830       activeTrack.css('width', (percent * 100) + '%');
9831       thumbContainer.css(
9832         'left',
9833         (percent * 100) + '%'
9834       );
9835       element.toggleClass('md-min', percent === 0);
9836     }
9837
9838
9839     /**
9840      * Slide listeners
9841      */
9842     var isDragging = false;
9843     var isDiscrete = angular.isDefined(attr.mdDiscrete);
9844
9845     function onPressDown(ev) {
9846       if (isDisabledGetter()) return;
9847
9848       element.addClass('active');
9849       element[0].focus();
9850       refreshSliderDimensions();
9851
9852       var exactVal = percentToValue( positionToPercent( ev.pointer.x ));
9853       var closestVal = minMaxValidator( stepValidator(exactVal) );
9854       scope.$apply(function() {
9855         setModelValue( closestVal );
9856         setSliderPercent( valueToPercent(closestVal));
9857       });
9858     }
9859     function onPressUp(ev) {
9860       if (isDisabledGetter()) return;
9861
9862       element.removeClass('dragging active');
9863
9864       var exactVal = percentToValue( positionToPercent( ev.pointer.x ));
9865       var closestVal = minMaxValidator( stepValidator(exactVal) );
9866       scope.$apply(function() {
9867         setModelValue(closestVal);
9868         ngModelRender();
9869       });
9870     }
9871     function onDragStart(ev) {
9872       if (isDisabledGetter()) return;
9873       isDragging = true;
9874       ev.stopPropagation();
9875
9876       element.addClass('dragging');
9877       setSliderFromEvent(ev);
9878     }
9879     function onDrag(ev) {
9880       if (!isDragging) return;
9881       ev.stopPropagation();
9882       setSliderFromEvent(ev);
9883     }
9884     function onDragEnd(ev) {
9885       if (!isDragging) return;
9886       ev.stopPropagation();
9887       isDragging = false;
9888     }
9889
9890     function setSliderFromEvent(ev) {
9891       // While panning discrete, update only the
9892       // visual positioning but not the model value.
9893       if ( isDiscrete ) adjustThumbPosition( ev.pointer.x );
9894       else              doSlide( ev.pointer.x );
9895     }
9896
9897     /**
9898      * Slide the UI by changing the model value
9899      * @param x
9900      */
9901     function doSlide( x ) {
9902       scope.$evalAsync( function() {
9903         setModelValue( percentToValue( positionToPercent(x) ));
9904       });
9905     }
9906
9907     /**
9908      * Slide the UI without changing the model (while dragging/panning)
9909      * @param x
9910      */
9911     function adjustThumbPosition( x ) {
9912       var exactVal = percentToValue( positionToPercent( x ));
9913       var closestVal = minMaxValidator( stepValidator(exactVal) );
9914       setSliderPercent( positionToPercent(x) );
9915       thumbText.text( closestVal );
9916     }
9917
9918     /**
9919      * Convert horizontal position on slider to percentage value of offset from beginning...
9920      * @param x
9921      * @returns {number}
9922      */
9923     function positionToPercent( x ) {
9924       return Math.max(0, Math.min(1, (x - sliderDimensions.left) / (sliderDimensions.width)));
9925     }
9926
9927     /**
9928      * Convert percentage offset on slide to equivalent model value
9929      * @param percent
9930      * @returns {*}
9931      */
9932     function percentToValue( percent ) {
9933       return (min + percent * (max - min));
9934     }
9935
9936     function valueToPercent( val ) {
9937       return (val - min)/(max - min);
9938     }
9939   }
9940 }
9941 SliderDirective.$inject = ["$$rAF", "$window", "$mdAria", "$mdUtil", "$mdConstant", "$mdTheming", "$mdGesture", "$parse"];
9942
9943 })();
9944 (function(){
9945 "use strict";
9946
9947 /*
9948  * @ngdoc module
9949  * @name material.components.sticky
9950  * @description
9951  *
9952  * Sticky effects for md
9953  */
9954
9955 angular.module('material.components.sticky', [
9956   'material.core',
9957   'material.components.content'
9958 ])
9959   .factory('$mdSticky', MdSticky);
9960
9961 /*
9962  * @ngdoc service
9963  * @name $mdSticky
9964  * @module material.components.sticky
9965  *
9966  * @description
9967  * The `$mdSticky`service provides a mixin to make elements sticky.
9968  *
9969  * @returns A `$mdSticky` function that takes three arguments:
9970  *   - `scope`
9971  *   - `element`: The element that will be 'sticky'
9972  *   - `elementClone`: A clone of the element, that will be shown
9973  *     when the user starts scrolling past the original element.
9974  *     If not provided, it will use the result of `element.clone()`.
9975  */
9976
9977 function MdSticky($document, $mdConstant, $compile, $$rAF, $mdUtil) {
9978
9979   var browserStickySupport = checkStickySupport();
9980
9981   /**
9982    * Registers an element as sticky, used internally by directives to register themselves
9983    */
9984   return function registerStickyElement(scope, element, stickyClone) {
9985     var contentCtrl = element.controller('mdContent');
9986     if (!contentCtrl) return;
9987
9988     if (browserStickySupport) {
9989       element.css({
9990         position: browserStickySupport,
9991         top: 0,
9992         'z-index': 2
9993       });
9994     } else {
9995       var $$sticky = contentCtrl.$element.data('$$sticky');
9996       if (!$$sticky) {
9997         $$sticky = setupSticky(contentCtrl);
9998         contentCtrl.$element.data('$$sticky', $$sticky);
9999       }
10000
10001       var deregister = $$sticky.add(element, stickyClone || element.clone());
10002       scope.$on('$destroy', deregister);
10003     }
10004   };
10005
10006   function setupSticky(contentCtrl) {
10007     var contentEl = contentCtrl.$element;
10008
10009     // Refresh elements is very expensive, so we use the debounced
10010     // version when possible.
10011     var debouncedRefreshElements = $$rAF.throttle(refreshElements);
10012
10013     // setupAugmentedScrollEvents gives us `$scrollstart` and `$scroll`,
10014     // more reliable than `scroll` on android.
10015     setupAugmentedScrollEvents(contentEl);
10016     contentEl.on('$scrollstart', debouncedRefreshElements);
10017     contentEl.on('$scroll', onScroll);
10018
10019     var self;
10020     var stickyBaseoffset = contentEl.prop('offsetTop');
10021     return self = {
10022       prev: null,
10023       current: null, //the currently stickied item
10024       next: null,
10025       items: [],
10026       add: add,
10027       refreshElements: refreshElements
10028     };
10029
10030     /***************
10031      * Public
10032      ***************/
10033     // Add an element and its sticky clone to this content's sticky collection
10034     function add(element, stickyClone) {
10035       stickyClone.addClass('md-sticky-clone');
10036       stickyClone.css('top', stickyBaseoffset + 'px');
10037
10038       var item = {
10039         element: element,
10040         clone: stickyClone
10041       };
10042       self.items.push(item);
10043
10044       contentEl.parent().prepend(item.clone);
10045
10046       debouncedRefreshElements();
10047
10048       return function remove() {
10049         self.items.forEach(function(item, index) {
10050           if (item.element[0] === element[0]) {
10051             self.items.splice(index, 1);
10052             item.clone.remove();
10053           }
10054         });
10055         debouncedRefreshElements();
10056       };
10057     }
10058
10059     function refreshElements() {
10060       // Sort our collection of elements by their current position in the DOM.
10061       // We need to do this because our elements' order of being added may not
10062       // be the same as their order of display.
10063       self.items.forEach(refreshPosition);
10064       self.items = self.items.sort(function(a, b) {
10065         return a.top < b.top ? -1 : 1;
10066       });
10067
10068       // Find which item in the list should be active, 
10069       // based upon the content's current scroll position
10070       var item;
10071       var currentScrollTop = contentEl.prop('scrollTop');
10072       for (var i = self.items.length - 1; i >= 0; i--) {
10073         if (currentScrollTop > self.items[i].top) {
10074           item = self.items[i];
10075           break;
10076         }
10077       }
10078       setCurrentItem(item);
10079     }
10080
10081
10082     /***************
10083      * Private
10084      ***************/
10085
10086     // Find the `top` of an item relative to the content element,
10087     // and also the height.
10088     function refreshPosition(item) {
10089       // Find the top of an item by adding to the offsetHeight until we reach the 
10090       // content element.
10091       var current = item.element[0];
10092       item.top = 0;
10093       item.left = 0;
10094       while (current && current !== contentEl[0]) {
10095         item.top += current.offsetTop;
10096         item.left += current.offsetLeft;
10097         current = current.offsetParent;
10098       }
10099       item.height = item.element.prop('offsetHeight');
10100       item.clone.css('margin-left', item.left + 'px');
10101       if ($mdUtil.floatingScrollbars()) {
10102         item.clone.css('margin-right', '0');
10103       }
10104     }
10105
10106
10107     // As we scroll, push in and select the correct sticky element.
10108     function onScroll() {
10109       var scrollTop = contentEl.prop('scrollTop');
10110       var isScrollingDown = scrollTop > (onScroll.prevScrollTop || 0);
10111       onScroll.prevScrollTop = scrollTop;
10112
10113       // At the top?
10114       if (scrollTop === 0) {
10115         setCurrentItem(null);
10116
10117       // Going to next item?
10118       } else if (isScrollingDown && self.next) {
10119         if (self.next.top - scrollTop <= 0) {
10120           // Sticky the next item if we've scrolled past its position.
10121           setCurrentItem(self.next);
10122         } else if (self.current) {
10123           // Push the current item up when we're almost at the next item.
10124           if (self.next.top - scrollTop <= self.next.height) {
10125             translate(self.current, self.next.top - self.next.height - scrollTop);
10126           } else {
10127             translate(self.current, null);
10128           }
10129         }
10130         
10131       // Scrolling up with a current sticky item?
10132       } else if (!isScrollingDown && self.current) {
10133         if (scrollTop < self.current.top) {
10134           // Sticky the previous item if we've scrolled up past
10135           // the original position of the currently stickied item.
10136           setCurrentItem(self.prev);
10137         }
10138         // Scrolling up, and just bumping into the item above (just set to current)?
10139         // If we have a next item bumping into the current item, translate
10140         // the current item up from the top as it scrolls into view.
10141         if (self.current && self.next) {
10142           if (scrollTop >= self.next.top - self.current.height) {
10143             translate(self.current, self.next.top - scrollTop - self.current.height);
10144           } else {
10145             translate(self.current, null);
10146           }
10147         }
10148       }
10149     }
10150      
10151    function setCurrentItem(item) {
10152      if (self.current === item) return;
10153      // Deactivate currently active item
10154      if (self.current) {
10155        translate(self.current, null);
10156        setStickyState(self.current, null);
10157      }
10158
10159      // Activate new item if given
10160      if (item) {
10161        setStickyState(item, 'active');
10162      }
10163
10164      self.current = item;
10165      var index = self.items.indexOf(item);
10166      // If index === -1, index + 1 = 0. It works out.
10167      self.next = self.items[index + 1];
10168      self.prev = self.items[index - 1];
10169      setStickyState(self.next, 'next');
10170      setStickyState(self.prev, 'prev');
10171    }
10172
10173    function setStickyState(item, state) {
10174      if (!item || item.state === state) return;
10175      if (item.state) {
10176        item.clone.attr('sticky-prev-state', item.state);
10177        item.element.attr('sticky-prev-state', item.state);
10178      }
10179      item.clone.attr('sticky-state', state);
10180      item.element.attr('sticky-state', state);
10181      item.state = state;
10182    }
10183
10184    function translate(item, amount) {
10185      if (!item) return;
10186      if (amount === null || amount === undefined) {
10187        if (item.translateY) {
10188          item.translateY = null;
10189          item.clone.css($mdConstant.CSS.TRANSFORM, '');
10190        }
10191      } else {
10192        item.translateY = amount;
10193        item.clone.css(
10194          $mdConstant.CSS.TRANSFORM, 
10195          'translate3d(' + item.left + 'px,' + amount + 'px,0)'
10196        );
10197      }
10198    }
10199   }
10200
10201   // Function to check for browser sticky support
10202   function checkStickySupport($el) {
10203     var stickyProp;
10204     var testEl = angular.element('<div>');
10205     $document[0].body.appendChild(testEl[0]);
10206
10207     var stickyProps = ['sticky', '-webkit-sticky'];
10208     for (var i = 0; i < stickyProps.length; ++i) {
10209       testEl.css({position: stickyProps[i], top: 0, 'z-index': 2});
10210       if (testEl.css('position') == stickyProps[i]) {
10211         stickyProp = stickyProps[i];
10212         break;
10213       }
10214     }
10215     testEl.remove();
10216     return stickyProp;
10217   }
10218
10219   // Android 4.4 don't accurately give scroll events.
10220   // To fix this problem, we setup a fake scroll event. We say:
10221   // > If a scroll or touchmove event has happened in the last DELAY milliseconds, 
10222   //   then send a `$scroll` event every animationFrame.
10223   // Additionally, we add $scrollstart and $scrollend events.
10224   function setupAugmentedScrollEvents(element) {
10225     var SCROLL_END_DELAY = 200;
10226     var isScrolling;
10227     var lastScrollTime;
10228     element.on('scroll touchmove', function() {
10229       if (!isScrolling) {
10230         isScrolling = true;
10231         $$rAF(loopScrollEvent);
10232         element.triggerHandler('$scrollstart');
10233       }
10234       element.triggerHandler('$scroll');
10235       lastScrollTime = +$mdUtil.now();
10236     });
10237
10238     function loopScrollEvent() {
10239       if (+$mdUtil.now() - lastScrollTime > SCROLL_END_DELAY) {
10240         isScrolling = false;
10241         element.triggerHandler('$scrollend');
10242       } else {
10243         element.triggerHandler('$scroll');
10244         $$rAF(loopScrollEvent);
10245       }
10246     }
10247   }
10248
10249 }
10250 MdSticky.$inject = ["$document", "$mdConstant", "$compile", "$$rAF", "$mdUtil"];
10251
10252 })();
10253 (function(){
10254 "use strict";
10255
10256 /**
10257  * @ngdoc module
10258  * @name material.components.subheader
10259  * @description
10260  * SubHeader module
10261  *
10262  *  Subheaders are special list tiles that delineate distinct sections of a
10263  *  list or grid list and are typically related to the current filtering or
10264  *  sorting criteria. Subheader tiles are either displayed inline with tiles or
10265  *  can be associated with content, for example, in an adjacent column.
10266  *
10267  *  Upon scrolling, subheaders remain pinned to the top of the screen and remain
10268  *  pinned until pushed on or off screen by the next subheader. @see [Material
10269  *  Design Specifications](https://www.google.com/design/spec/components/subheaders.html)
10270  *
10271  *  > To improve the visual grouping of content, use the system color for your subheaders.
10272  *
10273  */
10274 angular.module('material.components.subheader', [
10275   'material.core',
10276   'material.components.sticky'
10277 ])
10278   .directive('mdSubheader', MdSubheaderDirective);
10279
10280 /**
10281  * @ngdoc directive
10282  * @name mdSubheader
10283  * @module material.components.subheader
10284  *
10285  * @restrict E
10286  *
10287  * @description
10288  * The `<md-subheader>` directive is a subheader for a section. By default it is sticky.
10289  * You can make it not sticky by applying the `md-no-sticky` class to the subheader.
10290  *
10291  *
10292  * @usage
10293  * <hljs lang="html">
10294  * <md-subheader>Online Friends</md-subheader>
10295  * </hljs>
10296  */
10297
10298 function MdSubheaderDirective($mdSticky, $compile, $mdTheming) {
10299   return {
10300     restrict: 'E',
10301     replace: true,
10302     transclude: true,
10303     template: 
10304       '<h2 class="md-subheader">' +
10305         '<div class="md-subheader-inner">' +
10306           '<span class="md-subheader-content"></span>' +
10307         '</div>' +
10308       '</h2>',
10309     compile: function(element, attr, transclude) {
10310       return function postLink(scope, element, attr) {
10311         $mdTheming(element);
10312         var outerHTML = element[0].outerHTML;
10313
10314         function getContent(el) {
10315           return angular.element(el[0].querySelector('.md-subheader-content'));
10316         }
10317
10318         // Transclude the user-given contents of the subheader
10319         // the conventional way.
10320         transclude(scope, function(clone) {
10321           getContent(element).append(clone);
10322         });
10323
10324         // Create another clone, that uses the outer and inner contents
10325         // of the element, that will be 'stickied' as the user scrolls.
10326         if (!element.hasClass('md-no-sticky')) {
10327           transclude(scope, function(clone) {
10328             var stickyClone = $compile(angular.element(outerHTML))(scope);
10329             getContent(stickyClone).append(clone);
10330             $mdSticky(scope, element, stickyClone);
10331           });
10332         }
10333       };
10334     }
10335   };
10336 }
10337 MdSubheaderDirective.$inject = ["$mdSticky", "$compile", "$mdTheming"];
10338
10339 })();
10340 (function(){
10341 "use strict";
10342
10343 /**
10344  * @ngdoc module
10345  * @name material.components.swipe
10346  * @description Swipe module!
10347  */
10348 /**
10349  * @ngdoc directive
10350  * @module material.components.swipe
10351  * @name mdSwipeLeft
10352  *
10353  * @restrict A
10354  *
10355  * @description
10356  * The md-swipe-left directives allows you to specify custom behavior when an element is swiped
10357  * left.
10358  *
10359  * @usage
10360  * <hljs lang="html">
10361  * <div md-swipe-left="onSwipeLeft()">Swipe me left!</div>
10362  * </hljs>
10363  */
10364 /**
10365  * @ngdoc directive
10366  * @module material.components.swipe
10367  * @name mdSwipeRight
10368  *
10369  * @restrict A
10370  *
10371  * @description
10372  * The md-swipe-right directives allows you to specify custom behavior when an element is swiped
10373  * right.
10374  *
10375  * @usage
10376  * <hljs lang="html">
10377  * <div md-swipe-right="onSwipeRight()">Swipe me right!</div>
10378  * </hljs>
10379  */
10380
10381 angular.module('material.components.swipe', ['material.core'])
10382     .directive('mdSwipeLeft', getDirective('SwipeLeft'))
10383     .directive('mdSwipeRight', getDirective('SwipeRight'));
10384
10385 function getDirective(name) {
10386   var directiveName = 'md' + name;
10387   var eventName = '$md.' + name.toLowerCase();
10388
10389     DirectiveFactory.$inject = ["$parse"];
10390   return DirectiveFactory;
10391
10392   /* @ngInject */
10393   function DirectiveFactory($parse) {
10394       return { restrict: 'A', link: postLink };
10395       function postLink(scope, element, attr) {
10396         var fn = $parse(attr[directiveName]);
10397         element.on(eventName, function(ev) {
10398           scope.$apply(function() { fn(scope, { $event: ev }); });
10399         });
10400       }
10401     }
10402 }
10403
10404
10405
10406 })();
10407 (function(){
10408 "use strict";
10409
10410 /**
10411  * @private
10412  * @ngdoc module
10413  * @name material.components.switch
10414  */
10415
10416 angular.module('material.components.switch', [
10417   'material.core',
10418   'material.components.checkbox'
10419 ])
10420   .directive('mdSwitch', MdSwitch);
10421
10422 /**
10423  * @private
10424  * @ngdoc directive
10425  * @module material.components.switch
10426  * @name mdSwitch
10427  * @restrict E
10428  *
10429  * The switch directive is used very much like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
10430  *
10431  * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
10432  * the switch is in the accent color by default. The primary color palette may be used with
10433  * the `md-primary` class.
10434  *
10435  * @param {string} ng-model Assignable angular expression to data-bind to.
10436  * @param {string=} name Property name of the form under which the control is published.
10437  * @param {expression=} ng-true-value The value to which the expression should be set when selected.
10438  * @param {expression=} ng-false-value The value to which the expression should be set when not selected.
10439  * @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element.
10440  * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects.
10441  * @param {string=} aria-label Publish the button label used by screen-readers for accessibility. Defaults to the switch's text.
10442  *
10443  * @usage
10444  * <hljs lang="html">
10445  * <md-switch ng-model="isActive" aria-label="Finished?">
10446  *   Finished ?
10447  * </md-switch>
10448  *
10449  * <md-switch md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
10450  *   No Ink Effects
10451  * </md-switch>
10452  *
10453  * <md-switch ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
10454  *   Disabled
10455  * </md-switch>
10456  *
10457  * </hljs>
10458  */
10459 function MdSwitch(mdCheckboxDirective, $mdTheming, $mdUtil, $document, $mdConstant, $parse, $$rAF, $mdGesture) {
10460   var checkboxDirective = mdCheckboxDirective[0];
10461
10462   return {
10463     restrict: 'E',
10464     priority:210, // Run before ngAria
10465     transclude: true,
10466     template:
10467       '<div class="md-container">' +
10468         '<div class="md-bar"></div>' +
10469         '<div class="md-thumb-container">' +
10470           '<div class="md-thumb" md-ink-ripple md-ink-ripple-checkbox></div>' +
10471         '</div>'+
10472       '</div>' +
10473       '<div ng-transclude class="md-label">' +
10474       '</div>',
10475     require: '?ngModel',
10476     compile: compile
10477   };
10478
10479   function compile(element, attr) {
10480     var checkboxLink = checkboxDirective.compile(element, attr);
10481     // no transition on initial load
10482     element.addClass('md-dragging');
10483
10484     return function (scope, element, attr, ngModel) {
10485       ngModel = ngModel || $mdUtil.fakeNgModel();
10486       var disabledGetter = $parse(attr.ngDisabled);
10487       var thumbContainer = angular.element(element[0].querySelector('.md-thumb-container'));
10488       var switchContainer = angular.element(element[0].querySelector('.md-container'));
10489
10490       // no transition on initial load
10491       $$rAF(function() {
10492         element.removeClass('md-dragging');
10493       });
10494
10495       checkboxLink(scope, element, attr, ngModel);
10496
10497       if (angular.isDefined(attr.ngDisabled)) {
10498         scope.$watch(disabledGetter, function(isDisabled) {
10499           element.attr('tabindex', isDisabled ? -1 : 0);
10500         });
10501       }
10502
10503       // These events are triggered by setup drag
10504       $mdGesture.register(switchContainer, 'drag');
10505       switchContainer
10506         .on('$md.dragstart', onDragStart)
10507         .on('$md.drag', onDrag)
10508         .on('$md.dragend', onDragEnd);
10509
10510       var drag;
10511       function onDragStart(ev) {
10512         // Don't go if ng-disabled===true
10513         if (disabledGetter(scope)) return;
10514         ev.stopPropagation();
10515
10516         element.addClass('md-dragging');
10517         drag = {
10518           width: thumbContainer.prop('offsetWidth')
10519         };
10520         element.removeClass('transition');
10521       }
10522
10523       function onDrag(ev) {
10524         if (!drag) return;
10525         ev.stopPropagation();
10526         ev.srcEvent && ev.srcEvent.preventDefault();
10527
10528         var percent = ev.pointer.distanceX / drag.width;
10529
10530         //if checked, start from right. else, start from left
10531         var translate = ngModel.$viewValue ?  1 + percent : percent;
10532         // Make sure the switch stays inside its bounds, 0-1%
10533         translate = Math.max(0, Math.min(1, translate));
10534
10535         thumbContainer.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + (100*translate) + '%,0,0)');
10536         drag.translate = translate;
10537       }
10538
10539       function onDragEnd(ev) {
10540         if (!drag) return;
10541         ev.stopPropagation();
10542
10543         element.removeClass('md-dragging');
10544         thumbContainer.css($mdConstant.CSS.TRANSFORM, '');
10545
10546         // We changed if there is no distance (this is a click a click),
10547         // or if the drag distance is >50% of the total.
10548         var isChanged = ngModel.$viewValue ? drag.translate < 0.5 : drag.translate > 0.5;
10549         if (isChanged) {
10550           applyModelValue(!ngModel.$viewValue);
10551         }
10552         drag = null;
10553       }
10554
10555       function applyModelValue(newValue) {
10556         scope.$apply(function() {
10557           ngModel.$setViewValue(newValue);
10558           ngModel.$render();
10559         });
10560       }
10561
10562     };
10563   }
10564
10565
10566 }
10567 MdSwitch.$inject = ["mdCheckboxDirective", "$mdTheming", "$mdUtil", "$document", "$mdConstant", "$parse", "$$rAF", "$mdGesture"];
10568
10569 })();
10570 (function(){
10571 "use strict";
10572
10573 /**
10574  * @ngdoc module
10575  * @name material.components.tabs
10576  * @description
10577  *
10578  *  Tabs, created with the `<md-tabs>` directive provide *tabbed* navigation with different styles.
10579  *  The Tabs component consists of clickable tabs that are aligned horizontally side-by-side.
10580  *
10581  *  Features include support for:
10582  *
10583  *  - static or dynamic tabs,
10584  *  - responsive designs,
10585  *  - accessibility support (ARIA),
10586  *  - tab pagination,
10587  *  - external or internal tab content,
10588  *  - focus indicators and arrow-key navigations,
10589  *  - programmatic lookup and access to tab controllers, and
10590  *  - dynamic transitions through different tab contents.
10591  *
10592  */
10593 /*
10594  * @see js folder for tabs implementation
10595  */
10596 angular.module('material.components.tabs', [
10597   'material.core',
10598   'material.components.icon'
10599 ]);
10600
10601 })();
10602 (function(){
10603 "use strict";
10604
10605 /**
10606  * @ngdoc module
10607  * @name material.components.toast
10608  * @description
10609  * Toast
10610  */
10611 angular.module('material.components.toast', [
10612   'material.core',
10613   'material.components.button'
10614 ])
10615   .directive('mdToast', MdToastDirective)
10616   .provider('$mdToast', MdToastProvider);
10617
10618 function MdToastDirective() {
10619   return {
10620     restrict: 'E'
10621   };
10622 }
10623
10624 /**
10625  * @ngdoc service
10626  * @name $mdToast
10627  * @module material.components.toast
10628  *
10629  * @description
10630  * `$mdToast` is a service to build a toast notification on any position
10631  * on the screen with an optional duration, and provides a simple promise API.
10632  *
10633  *
10634  * ## Restrictions on custom toasts
10635  * - The toast's template must have an outer `<md-toast>` element.
10636  * - For a toast action, use element with class `md-action`.
10637  * - Add the class `md-capsule` for curved corners.
10638  *
10639  * @usage
10640  * <hljs lang="html">
10641  * <div ng-controller="MyController">
10642  *   <md-button ng-click="openToast()">
10643  *     Open a Toast!
10644  *   </md-button>
10645  * </div>
10646  * </hljs>
10647  *
10648  * <hljs lang="js">
10649  * var app = angular.module('app', ['ngMaterial']);
10650  * app.controller('MyController', function($scope, $mdToast) {
10651  *   $scope.openToast = function($event) {
10652  *     $mdToast.show($mdToast.simple().content('Hello!'));
10653  *     // Could also do $mdToast.showSimple('Hello');
10654  *   };
10655  * });
10656  * </hljs>
10657  */
10658
10659 /**
10660  * @ngdoc method
10661  * @name $mdToast#showSimple
10662  * 
10663  * @description
10664  * Convenience method which builds and shows a simple toast.
10665  *
10666  * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
10667  * rejected with `$mdToast.cancel()`.
10668  *
10669  */
10670
10671  /**
10672  * @ngdoc method
10673  * @name $mdToast#simple
10674  *
10675  * @description
10676  * Builds a preconfigured toast.
10677  *
10678  * @returns {obj} a `$mdToastPreset` with the chainable configuration methods:
10679  *
10680  * - $mdToastPreset#content(string) - sets toast content to string
10681  * - $mdToastPreset#action(string) - adds an action button, which resolves the promise returned from `show()` if clicked.
10682  * - $mdToastPreset#highlightAction(boolean) - sets action button to be highlighted
10683  * - $mdToastPreset#capsule(boolean) - adds 'md-capsule' class to the toast (curved corners)
10684  * - $mdToastPreset#theme(boolean) - sets the theme on the toast to theme (default is `$mdThemingProvider`'s default theme)
10685  */
10686
10687 /**
10688  * @ngdoc method
10689  * @name $mdToast#updateContent
10690  * 
10691  * @description
10692  * Updates the content of an existing toast. Useful for updating things like counts, etc.
10693  *
10694  */
10695
10696  /**
10697  * @ngdoc method
10698  * @name $mdToast#build
10699  *
10700  * @description
10701  * Creates a custom `$mdToastPreset` that you can configure.
10702  *
10703  * @returns {obj} a `$mdToastPreset` with the chainable configuration methods for shows' options (see below).
10704  */
10705
10706  /**
10707  * @ngdoc method
10708  * @name $mdToast#show
10709  *
10710  * @description Shows the toast.
10711  *
10712  * @param {object} optionsOrPreset Either provide an `$mdToastPreset` returned from `simple()`
10713  * and `build()`, or an options object with the following properties:
10714  *
10715  *   - `templateUrl` - `{string=}`: The url of an html template file that will
10716  *     be used as the content of the toast. Restrictions: the template must
10717  *     have an outer `md-toast` element.
10718  *   - `template` - `{string=}`: Same as templateUrl, except this is an actual
10719  *     template string.
10720  *   - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
10721  *     This scope will be destroyed when the toast is removed unless `preserveScope` is set to true.
10722  *   - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
10723  *   - `hideDelay` - `{number=}`: How many milliseconds the toast should stay
10724  *     active before automatically closing.  Set to 0 or false to have the toast stay open until
10725  *     closed manually. Default: 3000.
10726  *   - `position` - `{string=}`: Where to place the toast. Available: any combination
10727  *     of 'bottom', 'left', 'top', 'right', 'fit'. Default: 'bottom left'.
10728  *   - `controller` - `{string=}`: The controller to associate with this toast.
10729  *     The controller will be injected the local `$hideToast`, which is a function
10730  *     used to hide the toast.
10731  *   - `locals` - `{string=}`: An object containing key/value pairs. The keys will
10732  *     be used as names of values to inject into the controller. For example,
10733  *     `locals: {three: 3}` would inject `three` into the controller with the value
10734  *     of 3.
10735  *   - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in. These values will not be available until after initialization.
10736  *   - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
10737  *     and the toast will not open until the promises resolve.
10738  *   - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
10739  *   - `parent` - `{element=}`: The element to append the toast to. Defaults to appending
10740  *     to the root element of the application.
10741  *
10742  * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
10743  * rejected with `$mdToast.cancel()`.
10744  */
10745
10746 /**
10747  * @ngdoc method
10748  * @name $mdToast#hide
10749  *
10750  * @description
10751  * Hide an existing toast and resolve the promise returned from `$mdToast.show()`.
10752  *
10753  * @param {*=} response An argument for the resolved promise.
10754  *
10755  * @returns {promise} a promise that is called when the existing element is removed from the DOM
10756  *
10757  */
10758
10759 /**
10760  * @ngdoc method
10761  * @name $mdToast#cancel
10762  *
10763  * @description
10764  * Hide the existing toast and reject the promise returned from
10765  * `$mdToast.show()`.
10766  *
10767  * @param {*=} response An argument for the rejected promise.
10768  *
10769  * @returns {promise} a promise that is called when the existing element is removed from the DOM
10770  *
10771  */
10772
10773 function MdToastProvider($$interimElementProvider) {
10774   var activeToastContent;
10775   var $mdToast = $$interimElementProvider('$mdToast')
10776     .setDefaults({
10777       methods: ['position', 'hideDelay', 'capsule' ],
10778       options: toastDefaultOptions
10779     })
10780     .addPreset('simple', {
10781       argOption: 'content',
10782       methods: ['content', 'action', 'highlightAction', 'theme', 'parent'],
10783       options: /* @ngInject */ ["$mdToast", "$mdTheming", function($mdToast, $mdTheming) {
10784         var opts = {
10785           template: [
10786             '<md-toast md-theme="{{ toast.theme }}" ng-class="{\'md-capsule\': toast.capsule}">',
10787               '<span flex>{{ toast.content }}</span>',
10788               '<md-button class="md-action" ng-if="toast.action" ng-click="toast.resolve()" ng-class="{\'md-highlight\': toast.highlightAction}">',
10789                 '{{ toast.action }}',
10790               '</md-button>',
10791             '</md-toast>'
10792           ].join(''),
10793           controller: /* @ngInject */ ["$scope", function mdToastCtrl($scope) {
10794             var self = this;
10795             $scope.$watch(function() { return activeToastContent; }, function() {
10796               self.content = activeToastContent;
10797             });
10798             this.resolve = function() {
10799               $mdToast.hide();
10800             };
10801           }],
10802           theme: $mdTheming.defaultTheme(),
10803           controllerAs: 'toast',
10804           bindToController: true
10805         };
10806         return opts;
10807       }]
10808     })
10809     .addMethod('updateContent', function(newContent) {
10810       activeToastContent = newContent;
10811     });
10812
10813   toastDefaultOptions.$inject = ["$timeout", "$animate", "$mdToast", "$mdUtil"];
10814     return $mdToast;
10815
10816   /* @ngInject */
10817   function toastDefaultOptions($timeout, $animate, $mdToast, $mdUtil) {
10818     return {
10819       onShow: onShow,
10820       onRemove: onRemove,
10821       position: 'bottom left',
10822       themable: true,
10823       hideDelay: 3000
10824     };
10825
10826     function onShow(scope, element, options) {
10827       element = $mdUtil.extractElementByName(element, 'md-toast');
10828
10829       // 'top left' -> 'md-top md-left'
10830       activeToastContent = options.content;
10831       element.addClass(options.position.split(' ').map(function(pos) {
10832         return 'md-' + pos;
10833       }).join(' '));
10834       options.parent.addClass(toastOpenClass(options.position));
10835
10836       options.onSwipe = function(ev, gesture) {
10837         //Add swipeleft/swiperight class to element so it can animate correctly
10838         element.addClass('md-' + ev.type.replace('$md.',''));
10839         $timeout($mdToast.cancel);
10840       };
10841       element.on('$md.swipeleft $md.swiperight', options.onSwipe);
10842       return $animate.enter(element, options.parent);
10843     }
10844
10845     function onRemove(scope, element, options) {
10846       element.off('$md.swipeleft $md.swiperight', options.onSwipe);
10847       options.parent.removeClass(toastOpenClass(options.position));
10848       return $animate.leave(element);
10849     }
10850
10851     function toastOpenClass(position) {
10852       return 'md-toast-open-' +
10853         (position.indexOf('top') > -1 ? 'top' : 'bottom');
10854     }
10855   }
10856
10857 }
10858 MdToastProvider.$inject = ["$$interimElementProvider"];
10859
10860 })();
10861 (function(){
10862 "use strict";
10863
10864 /**
10865  * @ngdoc module
10866  * @name material.components.toolbar
10867  */
10868 angular.module('material.components.toolbar', [
10869   'material.core',
10870   'material.components.content'
10871 ])
10872   .directive('mdToolbar', mdToolbarDirective);
10873
10874 /**
10875  * @ngdoc directive
10876  * @name mdToolbar
10877  * @module material.components.toolbar
10878  * @restrict E
10879  * @description
10880  * `md-toolbar` is used to place a toolbar in your app.
10881  *
10882  * Toolbars are usually used above a content area to display the title of the
10883  * current page, and show relevant action buttons for that page.
10884  *
10885  * You can change the height of the toolbar by adding either the
10886  * `md-medium-tall` or `md-tall` class to the toolbar.
10887  *
10888  * @usage
10889  * <hljs lang="html">
10890  * <div layout="column" layout-fill>
10891  *   <md-toolbar>
10892  *
10893  *     <div class="md-toolbar-tools">
10894  *       <span>My App's Title</span>
10895  *
10896  *       <!-- fill up the space between left and right area -->
10897  *       <span flex></span>
10898  *
10899  *       <md-button>
10900  *         Right Bar Button
10901  *       </md-button>
10902  *     </div>
10903  *
10904  *   </md-toolbar>
10905  *   <md-content>
10906  *     Hello!
10907  *   </md-content>
10908  * </div>
10909  * </hljs>
10910  *
10911  * @param {boolean=} md-scroll-shrink Whether the header should shrink away as
10912  * the user scrolls down, and reveal itself as the user scrolls up.
10913  * Note: for scrollShrink to work, the toolbar must be a sibling of a
10914  * `md-content` element, placed before it. See the scroll shrink demo.
10915  *
10916  *
10917  * @param {number=} md-shrink-speed-factor How much to change the speed of the toolbar's
10918  * shrinking by. For example, if 0.25 is given then the toolbar will shrink
10919  * at one fourth the rate at which the user scrolls down. Default 0.5.
10920  */
10921 function mdToolbarDirective($$rAF, $mdConstant, $mdUtil, $mdTheming, $animate, $timeout) {
10922
10923   return {
10924     restrict: 'E',
10925     controller: angular.noop,
10926     link: function(scope, element, attr) {
10927       $mdTheming(element);
10928
10929       if (angular.isDefined(attr.mdScrollShrink)) {
10930         setupScrollShrink();
10931       }
10932
10933       function setupScrollShrink() {
10934         // Current "y" position of scroll
10935         var y = 0;
10936         // Store the last scroll top position
10937         var prevScrollTop = 0;
10938
10939         var shrinkSpeedFactor = attr.mdShrinkSpeedFactor || 0.5;
10940
10941         var toolbarHeight;
10942         var contentElement;
10943
10944         var debouncedContentScroll = $$rAF.throttle(onContentScroll);
10945         var debouncedUpdateHeight = $mdUtil.debounce(updateToolbarHeight, 5 * 1000);
10946
10947         // Wait for $mdContentLoaded event from mdContent directive.
10948         // If the mdContent element is a sibling of our toolbar, hook it up
10949         // to scroll events.
10950         scope.$on('$mdContentLoaded', onMdContentLoad);
10951
10952         function onMdContentLoad($event, newContentEl) {
10953           // Toolbar and content must be siblings
10954           if (element.parent()[0] === newContentEl.parent()[0]) {
10955             // unhook old content event listener if exists
10956             if (contentElement) {
10957               contentElement.off('scroll', debouncedContentScroll);
10958             }
10959
10960             newContentEl.on('scroll', debouncedContentScroll);
10961             newContentEl.attr('scroll-shrink', 'true');
10962
10963             contentElement = newContentEl;
10964             $$rAF(updateToolbarHeight);
10965           }
10966         }
10967
10968         function updateToolbarHeight() {
10969           toolbarHeight = element.prop('offsetHeight');
10970           // Add a negative margin-top the size of the toolbar to the content el.
10971           // The content will start transformed down the toolbarHeight amount,
10972           // so everything looks normal.
10973           //
10974           // As the user scrolls down, the content will be transformed up slowly
10975           // to put the content underneath where the toolbar was.
10976           var margin =  (-toolbarHeight * shrinkSpeedFactor) + 'px';
10977           contentElement.css('margin-top', margin);
10978           contentElement.css('margin-bottom', margin);
10979
10980           onContentScroll();
10981         }
10982
10983         function onContentScroll(e) {
10984           var scrollTop = e ? e.target.scrollTop : prevScrollTop;
10985
10986           debouncedUpdateHeight();
10987
10988           y = Math.min(
10989             toolbarHeight / shrinkSpeedFactor,
10990             Math.max(0, y + scrollTop - prevScrollTop)
10991           );
10992
10993           element.css(
10994             $mdConstant.CSS.TRANSFORM,
10995             'translate3d(0,' + (-y * shrinkSpeedFactor) + 'px,0)'
10996           );
10997           contentElement.css(
10998             $mdConstant.CSS.TRANSFORM,
10999             'translate3d(0,' + ((toolbarHeight - y) * shrinkSpeedFactor) + 'px,0)'
11000           );
11001
11002           prevScrollTop = scrollTop;
11003
11004             if (element.hasClass('md-whiteframe-z1')) {
11005               if (!y) {
11006                 $timeout(function () { $animate.removeClass(element, 'md-whiteframe-z1'); });
11007               }
11008             } else {
11009               if (y) {
11010                 $timeout(function () { $animate.addClass(element, 'md-whiteframe-z1'); });
11011               }
11012             }
11013         }
11014
11015       }
11016
11017     }
11018   };
11019
11020 }
11021 mdToolbarDirective.$inject = ["$$rAF", "$mdConstant", "$mdUtil", "$mdTheming", "$animate", "$timeout"];
11022
11023 })();
11024 (function(){
11025 "use strict";
11026
11027 /**
11028  * @ngdoc module
11029  * @name material.components.tooltip
11030  */
11031 angular
11032     .module('material.components.tooltip', [ 'material.core' ])
11033     .directive('mdTooltip', MdTooltipDirective);
11034
11035 /**
11036  * @ngdoc directive
11037  * @name mdTooltip
11038  * @module material.components.tooltip
11039  * @description
11040  * Tooltips are used to describe elements that are interactive and primarily graphical (not textual).
11041  *
11042  * Place a `<md-tooltip>` as a child of the element it describes.
11043  *
11044  * A tooltip will activate when the user focuses, hovers over, or touches the parent.
11045  *
11046  * @usage
11047  * <hljs lang="html">
11048  * <md-button class="md-fab md-accent" aria-label="Play">
11049  *   <md-tooltip>
11050  *     Play Music
11051  *   </md-tooltip>
11052  *   <md-icon icon="img/icons/ic_play_arrow_24px.svg"></md-icon>
11053  * </md-button>
11054  * </hljs>
11055  *
11056  * @param {expression=} md-visible Boolean bound to whether the tooltip is
11057  * currently visible.
11058  * @param {number=} md-delay How many milliseconds to wait to show the tooltip after the user focuses, hovers, or touches the parent. Defaults to 400ms.
11059  * @param {string=} md-direction Which direction would you like the tooltip to go?  Supports left, right, top, and bottom.  Defaults to bottom.
11060  * @param {boolean=} md-autohide If present or provided with a boolean value, the tooltip will hide on mouse leave, regardless of focus
11061  */
11062 function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdTheming, $rootElement,
11063                             $animate, $q) {
11064
11065   var TOOLTIP_SHOW_DELAY = 300;
11066   var TOOLTIP_WINDOW_EDGE_SPACE = 8;
11067
11068   return {
11069     restrict: 'E',
11070     transclude: true,
11071     priority:210, // Before ngAria
11072     template: '\
11073         <div class="md-background"></div>\
11074         <div class="md-content" ng-transclude></div>',
11075     scope: {
11076       visible: '=?mdVisible',
11077       delay: '=?mdDelay',
11078       autohide: '=?mdAutohide'
11079     },
11080     link: postLink
11081   };
11082
11083   function postLink(scope, element, attr) {
11084
11085     $mdTheming(element);
11086
11087     var parent        = getParentWithPointerEvents(),
11088         background    = angular.element(element[0].getElementsByClassName('md-background')[0]),
11089         content       = angular.element(element[0].getElementsByClassName('md-content')[0]),
11090         direction     = attr.mdDirection,
11091         current       = getNearestContentElement(),
11092         tooltipParent = angular.element(current || document.body),
11093         debouncedOnResize = $$rAF.throttle(function () { if (scope.visible) positionTooltip(); });
11094
11095     return init();
11096
11097     function init () {
11098       setDefaults();
11099       manipulateElement();
11100       bindEvents();
11101       configureWatchers();
11102       addAriaLabel();
11103     }
11104
11105     function setDefaults () {
11106       if (!angular.isDefined(attr.mdDelay)) scope.delay = TOOLTIP_SHOW_DELAY;
11107     }
11108
11109     function configureWatchers () {
11110       scope.$on('$destroy', function() {
11111         scope.visible = false;
11112         element.remove();
11113         angular.element($window).off('resize', debouncedOnResize);
11114       });
11115       scope.$watch('visible', function (isVisible) {
11116         if (isVisible) showTooltip();
11117         else hideTooltip();
11118       });
11119     }
11120
11121     function addAriaLabel () {
11122       if (!parent.attr('aria-label') && !parent.text().trim()) {
11123         parent.attr('aria-label', element.text().trim());
11124       }
11125     }
11126
11127     function manipulateElement () {
11128       element.detach();
11129       element.attr('role', 'tooltip');
11130     }
11131
11132     function getParentWithPointerEvents () {
11133       var parent = element.parent();
11134       while (parent && $window.getComputedStyle(parent[0])['pointer-events'] == 'none') {
11135         parent = parent.parent();
11136       }
11137       return parent;
11138     }
11139
11140      function getNearestContentElement () {
11141        var current = element.parent()[0];
11142        // Look for the nearest parent md-content, stopping at the rootElement.
11143        while (current && current !== $rootElement[0] && current !== document.body) {
11144          current = current.parentNode;
11145        }
11146        return current;
11147      }
11148
11149     function hasComputedStyleValue(key, value) {
11150         // Check if we should show it or not...
11151         var computedStyles = $window.getComputedStyle(element[0]);
11152         return angular.isDefined(computedStyles[key]) && (computedStyles[key] == value);
11153     }
11154
11155     function bindEvents () {
11156       var mouseActive = false;
11157       var enterHandler = function() {
11158         if (!hasComputedStyleValue('pointer-events','none')) {
11159           setVisible(true);
11160         }
11161       };
11162       var leaveHandler = function () {
11163         var autohide = scope.hasOwnProperty('autohide') ? scope.autohide : attr.hasOwnProperty('mdAutohide');
11164         if (autohide || mouseActive || ($document[0].activeElement !== parent[0]) ) {
11165           setVisible(false);
11166         }
11167         mouseActive = false;
11168       };
11169
11170       // to avoid `synthetic clicks` we listen to mousedown instead of `click`
11171       parent.on('mousedown', function() { mouseActive = true; });
11172       parent.on('focus mouseenter touchstart', enterHandler );
11173       parent.on('blur mouseleave touchend touchcancel', leaveHandler );
11174
11175
11176       angular.element($window).on('resize', debouncedOnResize);
11177     }
11178
11179     function setVisible (value) {
11180       setVisible.value = !!value;
11181       if (!setVisible.queued) {
11182         if (value) {
11183           setVisible.queued = true;
11184           $timeout(function() {
11185             scope.visible = setVisible.value;
11186             setVisible.queued = false;
11187           }, scope.delay);
11188         } else {
11189           $timeout(function() { scope.visible = false; });
11190         }
11191       }
11192     }
11193
11194     function showTooltip() {
11195       // Insert the element before positioning it, so we can get the position
11196       // and check if we should display it
11197       tooltipParent.append(element);
11198
11199       // Check if we should display it or not.
11200       // This handles hide-* and show-* along with any user defined css
11201       if ( hasComputedStyleValue('display','none') ) {
11202         scope.visible = false;
11203         element.detach();
11204         return;
11205       }
11206
11207       positionTooltip();
11208       angular.forEach([element, background, content], function (element) {
11209         $animate.addClass(element, 'md-show');
11210       });
11211     }
11212
11213     function hideTooltip() {
11214       $q.all([
11215         $animate.removeClass(content, 'md-show'),
11216         $animate.removeClass(background, 'md-show'),
11217         $animate.removeClass(element, 'md-show')
11218       ]).then(function () {
11219         if (!scope.visible) element.detach();
11220       });
11221     }
11222
11223     function positionTooltip() {
11224       var tipRect = $mdUtil.offsetRect(element, tooltipParent);
11225       var parentRect = $mdUtil.offsetRect(parent, tooltipParent);
11226       var newPosition = getPosition(direction);
11227
11228       // If the user provided a direction, just nudge the tooltip onto the screen
11229       // Otherwise, recalculate based on 'top' since default is 'bottom'
11230       if (direction) {
11231         newPosition = fitInParent(newPosition);
11232       } else if (newPosition.top > element.prop('offsetParent').scrollHeight - tipRect.height - TOOLTIP_WINDOW_EDGE_SPACE) {
11233         newPosition = fitInParent(getPosition('top'));
11234       }
11235
11236       element.css({top: newPosition.top + 'px', left: newPosition.left + 'px'});
11237
11238       positionBackground();
11239
11240       function positionBackground () {
11241         var size = direction === 'left' || direction === 'right'
11242               ? Math.sqrt(Math.pow(tipRect.width, 2) + Math.pow(tipRect.height / 2, 2)) * 2
11243               : Math.sqrt(Math.pow(tipRect.width / 2, 2) + Math.pow(tipRect.height, 2)) * 2,
11244             position = direction === 'left' ? { left: 100, top: 50 }
11245               : direction === 'right' ? { left: 0, top: 50 }
11246               : direction === 'top' ? { left: 50, top: 100 }
11247               : { left: 50, top: 0 };
11248         background.css({
11249           width: size + 'px',
11250           height: size + 'px',
11251           left: position.left + '%',
11252           top: position.top + '%'
11253         });
11254       }
11255
11256       function fitInParent (pos) {
11257         var newPosition = { left: pos.left, top: pos.top };
11258         newPosition.left = Math.min( newPosition.left, tooltipParent.prop('scrollWidth') - tipRect.width - TOOLTIP_WINDOW_EDGE_SPACE );
11259         newPosition.left = Math.max( newPosition.left, TOOLTIP_WINDOW_EDGE_SPACE );
11260         newPosition.top  = Math.min( newPosition.top,  tooltipParent.prop('scrollHeight') - tipRect.height - TOOLTIP_WINDOW_EDGE_SPACE );
11261         newPosition.top  = Math.max( newPosition.top,  TOOLTIP_WINDOW_EDGE_SPACE );
11262         return newPosition;
11263       }
11264
11265       function getPosition (dir) {
11266         return dir === 'left'
11267           ? { left: parentRect.left - tipRect.width - TOOLTIP_WINDOW_EDGE_SPACE,
11268               top: parentRect.top + parentRect.height / 2 - tipRect.height / 2 }
11269           : dir === 'right'
11270           ? { left: parentRect.left + parentRect.width + TOOLTIP_WINDOW_EDGE_SPACE,
11271               top: parentRect.top + parentRect.height / 2 - tipRect.height / 2 }
11272           : dir === 'top'
11273           ? { left: parentRect.left + parentRect.width / 2 - tipRect.width / 2,
11274               top: parentRect.top - tipRect.height - TOOLTIP_WINDOW_EDGE_SPACE }
11275           : { left: parentRect.left + parentRect.width / 2 - tipRect.width / 2,
11276               top: parentRect.top + parentRect.height + TOOLTIP_WINDOW_EDGE_SPACE };
11277       }
11278     }
11279
11280   }
11281
11282 }
11283 MdTooltipDirective.$inject = ["$timeout", "$window", "$$rAF", "$document", "$mdUtil", "$mdTheming", "$rootElement", "$animate", "$q"];
11284
11285 })();
11286 (function(){
11287 "use strict";
11288
11289 /**
11290  * @ngdoc module
11291  * @name material.components.whiteframe
11292  */
11293 angular.module('material.components.whiteframe', []);
11294
11295 })();
11296 (function(){
11297 "use strict";
11298
11299 angular
11300     .module('material.components.autocomplete')
11301     .controller('MdAutocompleteCtrl', MdAutocompleteCtrl);
11302
11303 var ITEM_HEIGHT = 41,
11304     MAX_HEIGHT = 5.5 * ITEM_HEIGHT,
11305     MENU_PADDING = 8;
11306
11307 function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $mdTheming, $window, $animate, $rootElement) {
11308
11309   //-- private variables
11310
11311   var self      = this,
11312       itemParts = $scope.itemsExpr.split(/ in /i),
11313       itemExpr  = itemParts[1],
11314       elements  = null,
11315       promise   = null,
11316       cache     = {},
11317       noBlur    = false,
11318       selectedItemWatchers = [],
11319       hasFocus  = false,
11320       lastCount = 0;
11321
11322   //-- public variables
11323
11324   self.scope    = $scope;
11325   self.parent   = $scope.$parent;
11326   self.itemName = itemParts[0];
11327   self.matches  = [];
11328   self.loading  = false;
11329   self.hidden   = true;
11330   self.index    = null;
11331   self.messages = [];
11332   self.id       = $mdUtil.nextUid();
11333
11334   //-- public methods
11335
11336   self.keydown  = keydown;
11337   self.blur     = blur;
11338   self.focus    = focus;
11339   self.clear    = clearValue;
11340   self.select   = select;
11341   self.getCurrentDisplayValue         = getCurrentDisplayValue;
11342   self.registerSelectedItemWatcher    = registerSelectedItemWatcher;
11343   self.unregisterSelectedItemWatcher  = unregisterSelectedItemWatcher;
11344
11345   self.listEnter = function () { noBlur = true; };
11346   self.listLeave = function () {
11347     noBlur = false;
11348     if (!hasFocus) self.hidden = true;
11349   };
11350   self.mouseUp   = function () { elements.input.focus(); };
11351
11352   return init();
11353
11354   //-- initialization methods
11355
11356   function init () {
11357     configureWatchers();
11358     $timeout(function () {
11359       gatherElements();
11360       focusElement();
11361       moveDropdown();
11362     });
11363   }
11364
11365   function positionDropdown () {
11366     if (!elements) return $timeout(positionDropdown, 0, false);
11367     var hrect  = elements.wrap.getBoundingClientRect(),
11368         vrect  = elements.snap.getBoundingClientRect(),
11369         root   = elements.root.getBoundingClientRect(),
11370         top    = vrect.bottom - root.top,
11371         bot    = root.bottom - vrect.top,
11372         left   = hrect.left - root.left,
11373         width  = hrect.width,
11374         styles = {
11375           left:     left + 'px',
11376           minWidth: width + 'px',
11377           maxWidth: Math.max(hrect.right - root.left, root.right - hrect.left) - MENU_PADDING + 'px'
11378         };
11379     if (top > bot && root.height - hrect.bottom - MENU_PADDING < MAX_HEIGHT) {
11380       styles.top = 'auto';
11381       styles.bottom = bot + 'px';
11382       styles.maxHeight = Math.min(MAX_HEIGHT, hrect.top - root.top - MENU_PADDING) + 'px';
11383     } else {
11384       styles.top = top + 'px';
11385       styles.bottom = 'auto';
11386       styles.maxHeight = Math.min(MAX_HEIGHT, root.bottom - hrect.bottom - MENU_PADDING) + 'px';
11387     }
11388     elements.$.ul.css(styles);
11389     $timeout(correctHorizontalAlignment, 0, false);
11390
11391     function correctHorizontalAlignment () {
11392       var dropdown = elements.ul.getBoundingClientRect(),
11393           styles   = {};
11394       if (dropdown.right > root.right - MENU_PADDING) {
11395         styles.left = (hrect.right - dropdown.width) + 'px';
11396       }
11397       elements.$.ul.css(styles);
11398     }
11399   }
11400
11401   function moveDropdown () {
11402     if (!elements.$.root.length) return;
11403     $mdTheming(elements.$.ul);
11404     elements.$.ul.detach();
11405     elements.$.root.append(elements.$.ul);
11406     if ($animate.pin) $animate.pin(elements.$.ul, $rootElement);
11407   }
11408
11409   function focusElement () {
11410     if ($scope.autofocus) elements.input.focus();
11411   }
11412
11413   function configureWatchers () {
11414     var wait = parseInt($scope.delay, 10) || 0;
11415     $scope.$watch('searchText', wait
11416         ? $mdUtil.debounce(handleSearchText, wait)
11417         : handleSearchText);
11418     registerSelectedItemWatcher(selectedItemChange);
11419     $scope.$watch('selectedItem', handleSelectedItemChange);
11420     $scope.$watch('$mdAutocompleteCtrl.hidden', function (hidden, oldHidden) {
11421       if (!hidden && oldHidden) positionDropdown();
11422     });
11423     angular.element($window).on('resize', positionDropdown);
11424     $scope.$on('$destroy', cleanup);
11425   }
11426
11427   function cleanup () {
11428     elements.$.ul.remove();
11429   }
11430
11431   function gatherElements () {
11432     elements = {
11433       main:  $element[0],
11434       ul:    $element.find('ul')[0],
11435       input: $element.find('input')[0],
11436       wrap:  $element.find('md-autocomplete-wrap')[0],
11437       root:  document.body
11438     };
11439     elements.li = elements.ul.getElementsByTagName('li');
11440     elements.snap = getSnapTarget();
11441     elements.$ = getAngularElements(elements);
11442   }
11443
11444   function getSnapTarget () {
11445     for (var element = $element; element.length; element = element.parent()) {
11446       if (angular.isDefined(element.attr('md-autocomplete-snap'))) return element[0];
11447     }
11448     return elements.wrap;
11449   }
11450
11451   function getAngularElements (elements) {
11452     var obj = {};
11453     for (var key in elements) {
11454       obj[key] = angular.element(elements[key]);
11455     }
11456     return obj;
11457   }
11458
11459   //-- event/change handlers
11460
11461   function selectedItemChange (selectedItem, previousSelectedItem) {
11462     if (selectedItem) {
11463       $scope.searchText = getDisplayValue(selectedItem);
11464     }
11465     if ($scope.itemChange && selectedItem !== previousSelectedItem)
11466       $scope.itemChange(getItemScope(selectedItem));
11467   }
11468
11469   function handleSelectedItemChange(selectedItem, previousSelectedItem) {
11470     for (var i = 0; i < selectedItemWatchers.length; ++i) {
11471       selectedItemWatchers[i](selectedItem, previousSelectedItem);
11472     }
11473   }
11474
11475   /**
11476    * Register a function to be called when the selected item changes.
11477    * @param cb
11478    */
11479   function registerSelectedItemWatcher(cb) {
11480     if (selectedItemWatchers.indexOf(cb) == -1) {
11481       selectedItemWatchers.push(cb);
11482     }
11483   }
11484
11485   /**
11486    * Unregister a function previously registered for selected item changes.
11487    * @param cb
11488    */
11489   function unregisterSelectedItemWatcher(cb) {
11490     var i = selectedItemWatchers.indexOf(cb);
11491     if (i != -1) {
11492       selectedItemWatchers.splice(i, 1);
11493     }
11494   }
11495
11496   function handleSearchText (searchText, previousSearchText) {
11497     self.index = getDefaultIndex();
11498     //-- do nothing on init
11499     if (searchText === previousSearchText) return;
11500     //-- clear selected item if search text no longer matches it
11501     if (searchText !== getDisplayValue($scope.selectedItem)) $scope.selectedItem = null;
11502     else return;
11503     //-- trigger change event if available
11504     if ($scope.textChange && searchText !== previousSearchText)
11505       $scope.textChange(getItemScope($scope.selectedItem));
11506     //-- cancel results if search text is not long enough
11507     if (!isMinLengthMet()) {
11508       self.loading = false;
11509       self.matches = [];
11510       self.hidden = shouldHide();
11511       updateMessages();
11512     } else {
11513       handleQuery();
11514     }
11515   }
11516
11517   function blur () {
11518     hasFocus = false;
11519     if (!noBlur) self.hidden = true;
11520   }
11521
11522   function focus () {
11523     hasFocus = true;
11524     //-- if searchText is null, let's force it to be a string
11525     if (!angular.isString($scope.searchText)) $scope.searchText = '';
11526     if ($scope.minLength > 0) return;
11527     self.hidden = shouldHide();
11528     if (!self.hidden) handleQuery();
11529   }
11530
11531   function keydown (event) {
11532     switch (event.keyCode) {
11533       case $mdConstant.KEY_CODE.DOWN_ARROW:
11534         if (self.loading) return;
11535         event.preventDefault();
11536         self.index = Math.min(self.index + 1, self.matches.length - 1);
11537         updateScroll();
11538         updateMessages();
11539         break;
11540       case $mdConstant.KEY_CODE.UP_ARROW:
11541         if (self.loading) return;
11542         event.preventDefault();
11543         self.index = self.index < 0 ? self.matches.length - 1 : Math.max(0, self.index - 1);
11544         updateScroll();
11545         updateMessages();
11546         break;
11547       case $mdConstant.KEY_CODE.TAB:
11548       case $mdConstant.KEY_CODE.ENTER:
11549         if (self.hidden || self.loading || self.index < 0 || self.matches.length < 1) return;
11550         event.preventDefault();
11551         select(self.index);
11552         break;
11553       case $mdConstant.KEY_CODE.ESCAPE:
11554         self.matches = [];
11555         self.hidden = true;
11556         self.index = getDefaultIndex();
11557         break;
11558       default:
11559     }
11560   }
11561
11562   //-- getters
11563
11564   function getMinLength () {
11565     return angular.isNumber($scope.minLength) ? $scope.minLength : 1;
11566   }
11567
11568   function getDisplayValue (item) {
11569     return (item && $scope.itemText) ? $scope.itemText(getItemScope(item)) : item;
11570   }
11571
11572   function getItemScope (item) {
11573     if (!item) return;
11574     var locals = {};
11575     if (self.itemName) locals[self.itemName] = item;
11576     return locals;
11577   }
11578
11579   function getDefaultIndex () {
11580     return $scope.autoselect ? 0 : -1;
11581   }
11582
11583   function shouldHide () {
11584     if (!isMinLengthMet()) return true;
11585   }
11586
11587   function getCurrentDisplayValue () {
11588     return getDisplayValue(self.matches[self.index]);
11589   }
11590
11591   function isMinLengthMet () {
11592     return $scope.searchText && $scope.searchText.length >= getMinLength();
11593   }
11594
11595   //-- actions
11596
11597   function select (index) {
11598     $scope.selectedItem = self.matches[index];
11599     self.hidden = true;
11600     self.index = 0;
11601     self.matches = [];
11602     //-- force form to update state for validation
11603     $timeout(function () {
11604       elements.$.input.controller('ngModel').$setViewValue(getDisplayValue($scope.selectedItem) || $scope.searchText);
11605       self.hidden = true;
11606     });
11607   }
11608
11609   function clearValue () {
11610     $scope.searchText = '';
11611     select(-1);
11612
11613     // Per http://www.w3schools.com/jsref/event_oninput.asp
11614     var eventObj = document.createEvent('CustomEvent');
11615     eventObj.initCustomEvent('input', true, true, {value: $scope.searchText});
11616     elements.input.dispatchEvent(eventObj);
11617
11618     elements.input.focus();
11619   }
11620
11621   function fetchResults (searchText) {
11622     var items = $scope.$parent.$eval(itemExpr),
11623         term = searchText.toLowerCase();
11624     if (angular.isArray(items)) {
11625       handleResults(items);
11626     } else {
11627       self.loading = true;
11628       if (items.success) items.success(handleResults);
11629       if (items.then)    items.then(handleResults);
11630       if (items.error)   items.error(function () { self.loading = false; });
11631     }
11632     function handleResults (matches) {
11633       cache[term] = matches;
11634       if (searchText !== $scope.searchText) return; //-- just cache the results if old request
11635       self.loading = false;
11636       promise = null;
11637       self.matches = matches;
11638       self.hidden = shouldHide();
11639       updateMessages();
11640       positionDropdown();
11641     }
11642   }
11643
11644   function updateMessages () {
11645     self.messages = [ getCountMessage(), getCurrentDisplayValue() ];
11646   }
11647
11648   function getCountMessage () {
11649     if (lastCount === self.matches.length) return '';
11650     lastCount = self.matches.length;
11651     switch (self.matches.length) {
11652       case 0:  return 'There are no matches available.';
11653       case 1:  return 'There is 1 match available.';
11654       default: return 'There are ' + self.matches.length + ' matches available.';
11655     }
11656   }
11657
11658   function updateScroll () {
11659     if (!elements.li[self.index]) return;
11660     var li  = elements.li[self.index],
11661         top = li.offsetTop,
11662         bot = top + li.offsetHeight,
11663         hgt = elements.ul.clientHeight;
11664     if (top < elements.ul.scrollTop) {
11665       elements.ul.scrollTop = top;
11666     } else if (bot > elements.ul.scrollTop + hgt) {
11667       elements.ul.scrollTop = bot - hgt;
11668     }
11669   }
11670
11671   function handleQuery () {
11672     var searchText = $scope.searchText,
11673         term = searchText.toLowerCase();
11674     //-- cancel promise if a promise is in progress
11675     if (promise && promise.cancel) {
11676       promise.cancel();
11677       promise = null;
11678     }
11679     //-- if results are cached, pull in cached results
11680     if (!$scope.noCache && cache[term]) {
11681       self.matches = cache[term];
11682       updateMessages();
11683     } else {
11684       fetchResults(searchText);
11685     }
11686     if (hasFocus) self.hidden = shouldHide();
11687   }
11688
11689 }
11690 MdAutocompleteCtrl.$inject = ["$scope", "$element", "$mdUtil", "$mdConstant", "$timeout", "$mdTheming", "$window", "$animate", "$rootElement"];
11691
11692 })();
11693 (function(){
11694 "use strict";
11695
11696 angular
11697     .module('material.components.autocomplete')
11698     .directive('mdAutocomplete', MdAutocomplete);
11699
11700 /**
11701  * @ngdoc directive
11702  * @name mdAutocomplete
11703  * @module material.components.autocomplete
11704  *
11705  * @description
11706  * `<md-autocomplete>` is a special input component with a drop-down of all possible matches to a custom query.
11707  * This component allows you to provide real-time suggestions as the user types in the input area.
11708  *
11709  * To start, you will need to specify the required parameters and provide a template for your results.
11710  * The content inside `md-autocomplete` will be treated as a template.
11711  *
11712  * In more complex cases, you may want to include other content such as a message to display when
11713  * no matches were found.  You can do this by wrapping your template in `md-item-template` and adding
11714  * a tag for `md-not-found`.  An example of this is shown below.
11715  * ### Validation
11716  *
11717  * You can use `ng-messages` to include validation the same way that you would normally validate;
11718  * however, if you want to replicate a standard input with a floating label, you will have to do the
11719  * following:
11720  *
11721  * - Make sure that your template is wrapped in `md-item-template`
11722  * - Add your `ng-messages` code inside of `md-autocomplete`
11723  * - Add your validation properties to `md-autocomplete` (ie. `required`)
11724  * - Add a `name` to `md-autocomplete` (to be used on the generated `input`)
11725  *
11726  * There is an example below of how this should look.
11727  *
11728  *
11729  * @param {expression} md-items An expression in the format of `item in items` to iterate over matches for your search.
11730  * @param {expression=} md-selected-item-change An expression to be run each time a new item is selected
11731  * @param {expression=} md-search-text-change An expression to be run each time the search text updates
11732  * @param {string=} md-search-text A model to bind the search query text to
11733  * @param {object=} md-selected-item A model to bind the selected item to
11734  * @param {string=} md-item-text An expression that will convert your object to a single string.
11735  * @param {string=} placeholder Placeholder text that will be forwarded to the input.
11736  * @param {boolean=} md-no-cache Disables the internal caching that happens in autocomplete
11737  * @param {boolean=} ng-disabled Determines whether or not to disable the input field
11738  * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will make suggestions
11739  * @param {number=} md-delay Specifies the amount of time (in milliseconds) to wait before looking for results
11740  * @param {boolean=} md-autofocus If true, will immediately focus the input element
11741  * @param {boolean=} md-autoselect If true, the first item will be selected by default
11742  * @param {string=} md-menu-class This will be applied to the dropdown menu for styling
11743  * @param {string=} md-floating-label This will add a floating label to autocomplete and wrap it in `md-input-container`
11744  *
11745  * @usage
11746  * ###Basic Example
11747  * <hljs lang="html">
11748  *   <md-autocomplete
11749  *       md-selected-item="selectedItem"
11750  *       md-search-text="searchText"
11751  *       md-items="item in getMatches(searchText)"
11752  *       md-item-text="item.display">
11753  *     <span md-highlight-text="searchText">{{item.display}}</span>
11754  *   </md-autocomplete>
11755  * </hljs>
11756  *
11757  * ###Example with "not found" message
11758  * <hljs lang="html">
11759  * <md-autocomplete
11760  *     md-selected-item="selectedItem"
11761  *     md-search-text="searchText"
11762  *     md-items="item in getMatches(searchText)"
11763  *     md-item-text="item.display">
11764  *   <md-item-template>
11765  *     <span md-highlight-text="searchText">{{item.display}}</span>
11766  *   </md-item-template>
11767  *   <md-not-found>
11768  *     No matches found.
11769  *   </md-not-found>
11770  * </md-autocomplete>
11771  * </hljs>
11772  *
11773  * In this example, our code utilizes `md-item-template` and `md-not-found` to specify the different
11774  * parts that make up our component.
11775  *
11776  * ### Example with validation
11777  * <hljs lang="html">
11778  * <form name="autocompleteForm">
11779  *   <md-autocomplete
11780  *       required
11781  *       input-name="autocomplete"
11782  *       md-selected-item="selectedItem"
11783  *       md-search-text="searchText"
11784  *       md-items="item in getMatches(searchText)"
11785  *       md-item-text="item.display">
11786  *     <md-item-template>
11787  *       <span md-highlight-text="searchText">{{item.display}}</span>
11788  *     </md-item-template>
11789  *     <div ng-messages="autocompleteForm.autocomplete.$error">
11790  *       <div ng-message="required">This field is required</div>
11791  *     </div>
11792  *   </md-autocomplete>
11793  * </form>
11794  * </hljs>
11795  *
11796  * In this example, our code utilizes `md-item-template` and `md-not-found` to specify the different
11797  * parts that make up our component.
11798  */
11799
11800 function MdAutocomplete ($mdTheming, $mdUtil) {
11801   return {
11802     controller:   'MdAutocompleteCtrl',
11803     controllerAs: '$mdAutocompleteCtrl',
11804     link:         link,
11805     scope:        {
11806       inputName:      '@mdInputName',
11807       inputMinlength: '@mdInputMinlength',
11808       inputMaxlength: '@mdInputMaxlength',
11809       searchText:     '=?mdSearchText',
11810       selectedItem:   '=?mdSelectedItem',
11811       itemsExpr:      '@mdItems',
11812       itemText:       '&mdItemText',
11813       placeholder:    '@placeholder',
11814       noCache:        '=?mdNoCache',
11815       itemChange:     '&?mdSelectedItemChange',
11816       textChange:     '&?mdSearchTextChange',
11817       minLength:      '=?mdMinLength',
11818       delay:          '=?mdDelay',
11819       autofocus:      '=?mdAutofocus',
11820       floatingLabel:  '@?mdFloatingLabel',
11821       autoselect:     '=?mdAutoselect',
11822       menuClass:      '@?mdMenuClass'
11823     },
11824     template: function (element, attr) {
11825       var noItemsTemplate = getNoItemsTemplate(),
11826           itemTemplate = getItemTemplate(),
11827           leftover = element.html();
11828       return '\
11829         <md-autocomplete-wrap\
11830             layout="row"\
11831             ng-class="{ \'md-whiteframe-z1\': !floatingLabel }"\
11832             role="listbox">\
11833           ' + getInputElement() + '\
11834           <md-progress-linear\
11835               ng-if="$mdAutocompleteCtrl.loading"\
11836               md-mode="indeterminate"></md-progress-linear>\
11837           <ul role="presentation"\
11838               class="md-autocomplete-suggestions md-whiteframe-z1 {{menuClass || \'\'}}"\
11839               id="ul-{{$mdAutocompleteCtrl.id}}"\
11840               ng-mouseenter="$mdAutocompleteCtrl.listEnter()"\
11841               ng-mouseleave="$mdAutocompleteCtrl.listLeave()"\
11842               ng-mouseup="$mdAutocompleteCtrl.mouseUp()">\
11843             <li ng-repeat="(index, item) in $mdAutocompleteCtrl.matches"\
11844                 ng-class="{ selected: index === $mdAutocompleteCtrl.index }"\
11845                 ng-hide="$mdAutocompleteCtrl.hidden"\
11846                 ng-click="$mdAutocompleteCtrl.select(index)"\
11847                 md-autocomplete-list-item="$mdAutocompleteCtrl.itemName">\
11848                 ' + itemTemplate + '\
11849             </li>\
11850             ' + noItemsTemplate + '\
11851           </ul>\
11852         </md-autocomplete-wrap>\
11853         <aria-status\
11854             class="md-visually-hidden"\
11855             role="status"\
11856             aria-live="assertive">\
11857           <p ng-repeat="message in $mdAutocompleteCtrl.messages" ng-if="message">{{message}}</p>\
11858         </aria-status>';
11859
11860       function getItemTemplate() {
11861         var templateTag = element.find('md-item-template').remove(),
11862             html = templateTag.length ? templateTag.html() : element.html();
11863         if (!templateTag.length) element.empty();
11864         return html;
11865       }
11866
11867       function getNoItemsTemplate() {
11868         var templateTag = element.find('md-not-found').remove(),
11869             template = templateTag.length ? templateTag.html() : '';
11870         return template
11871             ? '<li ng-if="!$mdAutocompleteCtrl.matches.length && !$mdAutocompleteCtrl.loading\
11872                          && !$mdAutocompleteCtrl.hidden"\
11873                          ng-hide="$mdAutocompleteCtrl.hidden"\
11874                          md-autocomplete-parent-scope>' + template + '</li>'
11875             : '';
11876
11877       }
11878
11879       function getInputElement() {
11880         if (attr.mdFloatingLabel) {
11881           return '\
11882             <md-input-container flex ng-if="floatingLabel">\
11883               <label>{{floatingLabel}}</label>\
11884               <input type="search"\
11885                   id="fl-input-{{$mdAutocompleteCtrl.id}}"\
11886                   name="{{inputName}}"\
11887                   autocomplete="off"\
11888                   ng-required="isRequired"\
11889                   ng-minlength="inputMinlength"\
11890                   ng-maxlength="inputMaxlength"\
11891                   ng-disabled="isDisabled"\
11892                   ng-model="$mdAutocompleteCtrl.scope.searchText"\
11893                   ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
11894                   ng-blur="$mdAutocompleteCtrl.blur()"\
11895                   ng-focus="$mdAutocompleteCtrl.focus()"\
11896                   aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
11897                   aria-label="{{floatingLabel}}"\
11898                   aria-autocomplete="list"\
11899                   aria-haspopup="true"\
11900                   aria-activedescendant=""\
11901                   aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
11902               <div md-autocomplete-parent-scope md-autocomplete-replace>' + leftover + '</div>\
11903             </md-input-container>';
11904         } else {
11905           return '\
11906             <input flex type="search"\
11907                 id="input-{{$mdAutocompleteCtrl.id}}"\
11908                 name="{{inputName}}"\
11909                 ng-if="!floatingLabel"\
11910                 autocomplete="off"\
11911                 ng-required="isRequired"\
11912                 ng-disabled="isDisabled"\
11913                 ng-model="$mdAutocompleteCtrl.scope.searchText"\
11914                 ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
11915                 ng-blur="$mdAutocompleteCtrl.blur()"\
11916                 ng-focus="$mdAutocompleteCtrl.focus()"\
11917                 placeholder="{{placeholder}}"\
11918                 aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
11919                 aria-label="{{placeholder}}"\
11920                 aria-autocomplete="list"\
11921                 aria-haspopup="true"\
11922                 aria-activedescendant=""\
11923                 aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
11924             <button\
11925                 type="button"\
11926                 tabindex="-1"\
11927                 ng-if="$mdAutocompleteCtrl.scope.searchText && !isDisabled"\
11928                 ng-click="$mdAutocompleteCtrl.clear()">\
11929               <md-icon md-svg-icon="md-close"></md-icon>\
11930               <span class="md-visually-hidden">Clear</span>\
11931             </button>\
11932                 ';
11933         }
11934       }
11935     }
11936   };
11937
11938   function link (scope, element, attr) {
11939     attr.$observe('disabled', function (value) { scope.isDisabled = value; });
11940     attr.$observe('required', function (value) { scope.isRequired = value !== null; });
11941
11942     $mdUtil.initOptionalProperties(scope, attr, {searchText:null, selectedItem:null} );
11943
11944     $mdTheming(element);
11945   }
11946 }
11947 MdAutocomplete.$inject = ["$mdTheming", "$mdUtil"];
11948
11949 })();
11950 (function(){
11951 "use strict";
11952
11953 angular
11954     .module('material.components.autocomplete')
11955     .controller('MdHighlightCtrl', MdHighlightCtrl);
11956
11957 function MdHighlightCtrl ($scope, $element, $interpolate) {
11958   this.init = init;
11959
11960   return init();
11961
11962   function init (term) {
11963     var unsafeText = $interpolate($element.html())($scope),
11964         text = angular.element('<div>').text(unsafeText).html(),
11965         flags = $element.attr('md-highlight-flags') || '',
11966         watcher = $scope.$watch(term, function (term) {
11967           var regex = getRegExp(term, flags),
11968               html = text.replace(regex, '<span class="highlight">$&</span>');
11969           $element.html(html);
11970         });
11971     $element.on('$destroy', function () { watcher(); });
11972   }
11973
11974   function sanitize (term) {
11975     if (!term) return term;
11976     return term.replace(/[\\\^\$\*\+\?\.\(\)\|\{\}\[\]]/g, '\\$&');
11977   }
11978
11979   function getRegExp (text, flags) {
11980     var str = '';
11981     if (flags.indexOf('^') >= 1) str += '^';
11982     str += text;
11983     if (flags.indexOf('$') >= 1) str += '$';
11984     return new RegExp(sanitize(str), flags.replace(/[\$\^]/g, ''));
11985   }
11986 }
11987 MdHighlightCtrl.$inject = ["$scope", "$element", "$interpolate"];
11988
11989 })();
11990 (function(){
11991 "use strict";
11992
11993 angular
11994     .module('material.components.autocomplete')
11995     .directive('mdHighlightText', MdHighlight);
11996
11997 /**
11998  * @ngdoc directive
11999  * @name mdHighlightText
12000  * @module material.components.autocomplete
12001  *
12002  * @description
12003  * The `md-highlight-text` directive allows you to specify text that should be highlighted within
12004  * an element.  Highlighted text will be wrapped in `<span class="highlight"></span>` which can
12005  * be styled through CSS.  Please note that child elements may not be used with this directive.
12006  *
12007  * @param {string} md-highlight-text A model to be searched for
12008  * @param {string=} md-highlight-flags A list of flags (loosely based on JavaScript RexExp flags).
12009  *    #### **Supported flags**:
12010  *    - `g`: Find all matches within the provided text
12011  *    - `i`: Ignore case when searching for matches
12012  *    - `$`: Only match if the text ends with the search term
12013  *    - `^`: Only match if the text begins with the search term
12014  *
12015  * @usage
12016  * <hljs lang="html">
12017  * <input placeholder="Enter a search term..." ng-model="searchTerm" type="text" />
12018  * <ul>
12019  *   <li ng-repeat="result in results" md-highlight-text="searchTerm">
12020  *     {{result.text}}
12021  *   </li>
12022  * </ul>
12023  * </hljs>
12024  */
12025
12026 function MdHighlight () {
12027   return {
12028     terminal: true,
12029     scope: false,
12030     controller: 'MdHighlightCtrl',
12031     link: function (scope, element, attr, ctrl) {
12032       ctrl.init(attr.mdHighlightText);
12033     }
12034   };
12035 }
12036
12037 })();
12038 (function(){
12039 "use strict";
12040
12041 angular
12042     .module('material.components.autocomplete')
12043     .directive('mdAutocompleteListItem', MdAutocompleteListItem);
12044
12045 function MdAutocompleteListItem ($compile, $mdUtil) {
12046   return {
12047     terminal: true,
12048     link: postLink,
12049     scope: false
12050   };
12051   function postLink (scope, element, attr) {
12052     var ctrl     = scope.$parent.$mdAutocompleteCtrl,
12053         newScope = ctrl.parent.$new(false, ctrl.parent),
12054         itemName = ctrl.scope.$eval(attr.mdAutocompleteListItem);
12055     newScope[itemName] = scope.item;
12056     $compile(element.contents())(newScope);
12057     element.attr({
12058       role: 'option',
12059       id: 'item_' + $mdUtil.nextUid()
12060     });
12061   }
12062 }
12063 MdAutocompleteListItem.$inject = ["$compile", "$mdUtil"];
12064
12065 })();
12066 (function(){
12067 "use strict";
12068
12069 angular
12070     .module('material.components.autocomplete')
12071     .directive('mdAutocompleteParentScope', MdAutocompleteParentScope);
12072
12073 function MdAutocompleteParentScope ($compile, $mdUtil) {
12074   return {
12075     restrict: 'A',
12076     terminal: true,
12077     link: postLink,
12078     scope: false
12079   };
12080   function postLink (scope, element, attr) {
12081     var ctrl     = scope.$parent.$mdAutocompleteCtrl;
12082     $compile(element.contents())(ctrl.parent);
12083     if (attr.hasOwnProperty('mdAutocompleteReplace')) {
12084       element.after(element.contents());
12085       element.remove();
12086     }
12087   }
12088 }
12089 MdAutocompleteParentScope.$inject = ["$compile", "$mdUtil"];
12090
12091 })();
12092 (function(){
12093 "use strict";
12094
12095 angular
12096     .module('material.components.chips')
12097     .directive('mdChip', MdChip);
12098
12099 /**
12100  * @ngdoc directive
12101  * @name mdChip
12102  * @module material.components.chips
12103  *
12104  * @description
12105  * `<md-chip>` is a component used within `<md-chips>` and is responsible for rendering individual
12106  * chips.
12107  *
12108  *
12109  * @usage
12110  * <hljs lang="html">
12111  *   <md-chip>{{$chip}}</md-chip>
12112  * </hljs>
12113  *
12114  */
12115
12116 // This hint text is hidden within a chip but used by screen readers to
12117 // inform the user how they can interact with a chip.
12118 var DELETE_HINT_TEMPLATE = '\
12119     <span ng-if="!$mdChipsCtrl.readonly" class="md-visually-hidden">\
12120       {{$mdChipsCtrl.deleteHint}}\
12121     </span>';
12122
12123 /**
12124  * MDChip Directive Definition
12125  *
12126  * @param $mdTheming
12127  * @param $mdInkRipple
12128  * @ngInject
12129  */
12130 function MdChip($mdTheming) {
12131   return {
12132     restrict: 'E',
12133     require: '^?mdChips',
12134     compile:  compile
12135   };
12136
12137   function compile(element, attr) {
12138     element.append(DELETE_HINT_TEMPLATE);
12139     return function postLink(scope, element, attr, ctrl) {
12140       element.addClass('md-chip');
12141       $mdTheming(element);
12142
12143       if (ctrl) angular.element(element[0].querySelector('.md-chip-content'))
12144           .on('blur', function () {
12145             ctrl.selectedChip = -1;
12146           });
12147     };
12148   }
12149 }
12150 MdChip.$inject = ["$mdTheming"];
12151
12152 })();
12153 (function(){
12154 "use strict";
12155
12156 angular
12157     .module('material.components.chips')
12158     .directive('mdChipRemove', MdChipRemove);
12159
12160 /**
12161  * @ngdoc directive
12162  * @name mdChipRemove
12163  * @module material.components.chips
12164  *
12165  * @description
12166  * `<md-chip-remove>`
12167  * Designates an element to be used as the delete button for a chip. This
12168  * element is passed as a child of the `md-chips` element.
12169  *
12170  * @usage
12171  * <hljs lang="html">
12172  *   <md-chips><button md-chip-remove>DEL</button></md-chips>
12173  * </hljs>
12174  */
12175
12176
12177 /**
12178  * MdChipRemove Directive Definition.
12179  * 
12180  * @param $compile
12181  * @param $timeout
12182  * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}
12183  * @constructor
12184  */
12185 function MdChipRemove ($timeout) {
12186   return {
12187     restrict: 'A',
12188     require: '^mdChips',
12189     scope: false,
12190     link: postLink
12191   };
12192
12193   function postLink(scope, element, attr, ctrl) {
12194     element.on('click', function(event) {
12195       scope.$apply(function() {
12196         ctrl.removeChip(scope.$$replacedScope.$index);
12197       });
12198     });
12199
12200     // Child elements aren't available until after a $timeout tick as they are hidden by an
12201     // `ng-if`. see http://goo.gl/zIWfuw
12202     $timeout(function() {
12203       element.attr({ tabindex: -1, ariaHidden: true });
12204       element.find('button').attr('tabindex', '-1');
12205     });
12206   }
12207 }
12208 MdChipRemove.$inject = ["$timeout"];
12209
12210 })();
12211 (function(){
12212 "use strict";
12213
12214 angular
12215     .module('material.components.chips')
12216     .directive('mdChipTransclude', MdChipTransclude);
12217
12218 function MdChipTransclude ($compile, $mdUtil) {
12219   return {
12220     restrict: 'EA',
12221     terminal: true,
12222     link: link,
12223     scope: false
12224   };
12225   function link (scope, element, attr) {
12226     var ctrl = scope.$parent.$mdChipsCtrl,
12227         newScope = ctrl.parent.$new(false, ctrl.parent);
12228     newScope.$$replacedScope = scope;
12229     newScope.$chip = scope.$chip;
12230     newScope.$mdChipsCtrl = ctrl;
12231     element.html(ctrl.$scope.$eval(attr.mdChipTransclude));
12232     $compile(element.contents())(newScope);
12233   }
12234 }
12235 MdChipTransclude.$inject = ["$compile", "$mdUtil"];
12236
12237 })();
12238 (function(){
12239 "use strict";
12240
12241 angular
12242     .module('material.components.chips')
12243     .controller('MdChipsCtrl', MdChipsCtrl);
12244
12245 /**
12246  * Controller for the MdChips component. Responsible for adding to and
12247  * removing from the list of chips, marking chips as selected, and binding to
12248  * the models of various input components.
12249  *
12250  * @param $scope
12251  * @param $mdConstant
12252  * @param $log
12253  * @param $element
12254  * @constructor
12255  */
12256 function MdChipsCtrl ($scope, $mdConstant, $log, $element, $timeout) {
12257   /** @type {$timeout} **/
12258   this.$timeout = $timeout;
12259
12260   /** @type {Object} */
12261   this.$mdConstant = $mdConstant;
12262
12263   /** @type {angular.$scope} */
12264   this.$scope = $scope;
12265
12266   /** @type {angular.$scope} */
12267   this.parent = $scope.$parent;
12268
12269   /** @type {$log} */
12270   this.$log = $log;
12271
12272   /** @type {$element} */
12273   this.$element = $element;
12274
12275   /** @type {angular.NgModelController} */
12276   this.ngModelCtrl = null;
12277
12278   /** @type {angular.NgModelController} */
12279   this.userInputNgModelCtrl = null;
12280
12281   /** @type {Element} */
12282   this.userInputElement = null;
12283
12284   /** @type {Array.<Object>} */
12285   this.items = [];
12286
12287   /** @type {number} */
12288   this.selectedChip = -1;
12289
12290
12291   /**
12292    * Hidden hint text for how to delete a chip. Used to give context to screen readers.
12293    * @type {string}
12294    */
12295   this.deleteHint = 'Press delete to remove this chip.';
12296
12297   /**
12298    * Hidden label for the delete button. Used to give context to screen readers.
12299    * @type {string}
12300    */
12301   this.deleteButtonLabel = 'Remove';
12302
12303   /**
12304    * Model used by the input element.
12305    * @type {string}
12306    */
12307   this.chipBuffer = '';
12308
12309   /**
12310    * Whether to use the mdOnAppend expression to transform the chip buffer
12311    * before appending it to the list.
12312    * @type {boolean}
12313    */
12314   this.useMdOnAppend = false;
12315 }
12316 MdChipsCtrl.$inject = ["$scope", "$mdConstant", "$log", "$element", "$timeout"];
12317
12318 /**
12319  * Handles the keydown event on the input element: <enter> appends the
12320  * buffer to the chip list, while backspace removes the last chip in the list
12321  * if the current buffer is empty.
12322  * @param event
12323  */
12324 MdChipsCtrl.prototype.inputKeydown = function(event) {
12325   var chipBuffer = this.getChipBuffer();
12326   switch (event.keyCode) {
12327     case this.$mdConstant.KEY_CODE.ENTER:
12328       if (this.$scope.requireMatch || !chipBuffer) break;
12329       event.preventDefault();
12330       this.appendChip(chipBuffer);
12331       this.resetChipBuffer();
12332       break;
12333     case this.$mdConstant.KEY_CODE.BACKSPACE:
12334       if (chipBuffer) break;
12335       event.stopPropagation();
12336       if (this.items.length) this.selectAndFocusChipSafe(this.items.length - 1);
12337       break;
12338   }
12339 };
12340
12341 /**
12342  * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
12343  * keys switch which chips is active
12344  * @param event
12345  */
12346 MdChipsCtrl.prototype.chipKeydown = function (event) {
12347   if (this.getChipBuffer()) return;
12348   switch (event.keyCode) {
12349     case this.$mdConstant.KEY_CODE.BACKSPACE:
12350     case this.$mdConstant.KEY_CODE.DELETE:
12351       if (this.selectedChip < 0) return;
12352       event.preventDefault();
12353       this.removeAndSelectAdjacentChip(this.selectedChip);
12354       break;
12355     case this.$mdConstant.KEY_CODE.LEFT_ARROW:
12356       event.preventDefault();
12357       if (this.selectedChip < 0) this.selectedChip = this.items.length;
12358       if (this.items.length) this.selectAndFocusChipSafe(this.selectedChip - 1);
12359       break;
12360     case this.$mdConstant.KEY_CODE.RIGHT_ARROW:
12361       event.preventDefault();
12362       this.selectAndFocusChipSafe(this.selectedChip + 1);
12363       break;
12364     case this.$mdConstant.KEY_CODE.ESCAPE:
12365     case this.$mdConstant.KEY_CODE.TAB:
12366       if (this.selectedChip < 0) return;
12367       event.preventDefault();
12368       this.onFocus();
12369       break;
12370   }
12371 };
12372
12373 /**
12374  * Get the input's placeholder - uses `placeholder` when list is empty and `secondary-placeholder`
12375  * when the list is non-empty. If `secondary-placeholder` is not provided, `placeholder` is used
12376  * always.
12377  */
12378 MdChipsCtrl.prototype.getPlaceholder = function() {
12379   // Allow `secondary-placeholder` to be blank.
12380   var useSecondary = (this.items.length &&
12381       (this.secondaryPlaceholder == '' || this.secondaryPlaceholder));
12382   return useSecondary ? this.placeholder : this.secondaryPlaceholder;
12383 };
12384
12385 /**
12386  * Removes chip at {@code index} and selects the adjacent chip.
12387  * @param index
12388  */
12389 MdChipsCtrl.prototype.removeAndSelectAdjacentChip = function(index) {
12390   var selIndex = this.getAdjacentChipIndex(index);
12391   this.removeChip(index);
12392   this.$timeout(angular.bind(this, function () {
12393       this.selectAndFocusChipSafe(selIndex);
12394   }));
12395 };
12396
12397 /**
12398  * Sets the selected chip index to -1.
12399  */
12400 MdChipsCtrl.prototype.resetSelectedChip = function() {
12401   this.selectedChip = -1;
12402 };
12403
12404 /**
12405  * Gets the index of an adjacent chip to select after deletion. Adjacency is
12406  * determined as the next chip in the list, unless the target chip is the
12407  * last in the list, then it is the chip immediately preceding the target. If
12408  * there is only one item in the list, -1 is returned (select none).
12409  * The number returned is the index to select AFTER the target has been
12410  * removed.
12411  * If the current chip is not selected, then -1 is returned to select none.
12412  */
12413 MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {
12414   var len = this.items.length - 1;
12415   return (len == 0) ? -1 :
12416       (index == len) ? index -1 : index;
12417 };
12418
12419 /**
12420  * Append the contents of the buffer to the chip list. This method will first
12421  * call out to the md-on-append method, if provided
12422  * @param newChip
12423  */
12424 MdChipsCtrl.prototype.appendChip = function(newChip) {
12425   if (this.items.indexOf(newChip) + 1) return;
12426   if (this.useMdOnAppend && this.mdOnAppend) {
12427     newChip = this.mdOnAppend({'$chip': newChip});
12428   }
12429   this.items.push(newChip);
12430 };
12431
12432 /**
12433  * Sets whether to use the md-on-append expression. This expression is
12434  * bound to scope and controller in {@code MdChipsDirective} as
12435  * {@code mdOnAppend}. Due to the nature of directive scope bindings, the
12436  * controller cannot know on its own/from the scope whether an expression was
12437  * actually provided.
12438  */
12439 MdChipsCtrl.prototype.useMdOnAppendExpression = function() {
12440   this.useMdOnAppend = true;
12441 };
12442
12443 /**
12444  * Gets the input buffer. The input buffer can be the model bound to the
12445  * default input item {@code this.chipBuffer}, the {@code selectedItem}
12446  * model of an {@code md-autocomplete}, or, through some magic, the model
12447  * bound to any inpput or text area element found within a
12448  * {@code md-input-container} element.
12449  * @return {Object|string}
12450  */
12451 MdChipsCtrl.prototype.getChipBuffer = function() {
12452   return !this.userInputElement ? this.chipBuffer :
12453       this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :
12454           this.userInputElement[0].value;
12455 };
12456
12457 /**
12458  * Resets the input buffer for either the internal input or user provided input element.
12459  */
12460 MdChipsCtrl.prototype.resetChipBuffer = function() {
12461   if (this.userInputElement) {
12462     if (this.userInputNgModelCtrl) {
12463       this.userInputNgModelCtrl.$setViewValue('');
12464       this.userInputNgModelCtrl.$render();
12465     } else {
12466       this.userInputElement[0].value = '';
12467     }
12468   } else {
12469     this.chipBuffer = '';
12470   }
12471 };
12472
12473 /**
12474  * Removes the chip at the given index.
12475  * @param index
12476  */
12477 MdChipsCtrl.prototype.removeChip = function(index) {
12478   this.items.splice(index, 1);
12479 };
12480
12481 MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {
12482   this.removeChip(index);
12483   this.onFocus();
12484 };
12485 /**
12486  * Selects the chip at `index`,
12487  * @param index
12488  */
12489 MdChipsCtrl.prototype.selectAndFocusChipSafe = function(index) {
12490   if (!this.items.length) {
12491     this.selectChip(-1);
12492     this.onFocus();
12493     return;
12494   }
12495   if (index === this.items.length) return this.onFocus();
12496   index = Math.max(index, 0);
12497   index = Math.min(index, this.items.length - 1);
12498   this.selectChip(index);
12499   this.focusChip(index);
12500 };
12501
12502 /**
12503  * Marks the chip at the given index as selected.
12504  * @param index
12505  */
12506 MdChipsCtrl.prototype.selectChip = function(index) {
12507   if (index >= -1 && index <= this.items.length) {
12508     this.selectedChip = index;
12509   } else {
12510     this.$log.warn('Selected Chip index out of bounds; ignoring.');
12511   }
12512 };
12513
12514 /**
12515  * Selects the chip at `index` and gives it focus.
12516  * @param index
12517  */
12518 MdChipsCtrl.prototype.selectAndFocusChip = function(index) {
12519   this.selectChip(index);
12520   if (index != -1) {
12521     this.focusChip(index);
12522   }
12523 };
12524
12525 /**
12526  * Call `focus()` on the chip at `index`
12527  */
12528 MdChipsCtrl.prototype.focusChip = function(index) {
12529   this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content').focus();
12530 };
12531
12532 /**
12533  * Configures the required interactions with the ngModel Controller.
12534  * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.
12535  * @param ngModelCtrl
12536  */
12537 MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {
12538   this.ngModelCtrl = ngModelCtrl;
12539
12540   var self = this;
12541   ngModelCtrl.$render = function() {
12542     // model is updated. do something.
12543     self.items = self.ngModelCtrl.$viewValue;
12544   };
12545 };
12546
12547 MdChipsCtrl.prototype.onFocus = function () {
12548   var input = this.$element[0].querySelector('input');
12549   input && input.focus();
12550   this.resetSelectedChip();
12551 };
12552
12553 MdChipsCtrl.prototype.onInputFocus = function () {
12554   this.inputHasFocus = true;
12555   this.resetSelectedChip();
12556 };
12557
12558 MdChipsCtrl.prototype.onInputBlur = function () {
12559   this.inputHasFocus = false;
12560 };
12561
12562 /**
12563  * Configure event bindings on a user-provided input element.
12564  * @param inputElement
12565  */
12566 MdChipsCtrl.prototype.configureUserInput = function(inputElement) {
12567   this.userInputElement = inputElement;
12568
12569   // Find the NgModelCtrl for the input element
12570   var ngModelCtrl = inputElement.controller('ngModel');
12571   // `.controller` will look in the parent as well.
12572   if (ngModelCtrl != this.ngModelCtrl) {
12573     this.userInputNgModelCtrl = ngModelCtrl;
12574   }
12575
12576   // Bind to keydown and focus events of input
12577   var scope = this.$scope;
12578   var ctrl = this;
12579   inputElement
12580       .attr({ tabindex: 0 })
12581       .on('keydown', function(event) { scope.$apply( angular.bind(ctrl, function() { ctrl.inputKeydown(event); })) })
12582       .on('focus', angular.bind(ctrl, ctrl.onInputFocus))
12583       .on('blur', angular.bind(ctrl, ctrl.onInputBlur));
12584 };
12585
12586 MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {
12587
12588   ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {
12589     if (item) {
12590       this.appendChip(item);
12591       this.resetChipBuffer();
12592     }
12593   }));
12594
12595   this.$element.find('input')
12596       .on('focus',angular.bind(this, this.onInputFocus) )
12597       .on('blur', angular.bind(this, this.onInputBlur) );
12598 };
12599
12600 MdChipsCtrl.prototype.hasFocus = function () {
12601   return this.inputHasFocus || this.selectedChip >= 0;
12602 };
12603
12604 })();
12605 (function(){
12606 "use strict";
12607
12608   angular
12609       .module('material.components.chips')
12610       .directive('mdChips', MdChips);
12611
12612   /**
12613    * @ngdoc directive
12614    * @name mdChips
12615    * @module material.components.chips
12616    *
12617    * @description
12618    * `<md-chips>` is an input component for building lists of strings or objects. The list items are
12619    * displayed as 'chips'. This component can make use of an `<input>` element or an
12620    * `<md-autocomplete>` element.
12621    *
12622    * <strong>Custom `<md-chip-template>` template</strong>
12623    * A custom template may be provided to render the content of each chip. This is achieved by
12624    * specifying an `<md-chip-template>` element as a child of `<md-chips>`. Note: Any attributes on
12625    * `<md-chip-template>` will be dropped as only the innerHTML is used for the chip template. The
12626    * variables `$chip` and `$index` are available in the scope of `<md-chip-template>`, representing
12627    * the chip object and its index in the list of chips, respectively.
12628    * To override the chip delete control, include an element (ideally a button) with the attribute
12629    * `md-chip-remove`. A click listener to remove the chip will be added automatically. The element
12630    * is also placed as a sibling to the chip content (on which there are also click listeners) to
12631    * avoid a nested ng-click situation.
12632    *
12633    * <h3> Pending Features </h3>
12634    * <ul style="padding-left:20px;">
12635    *
12636    *   <ul>Style
12637    *     <li>Colours for hover, press states (ripple?).</li>
12638    *   </ul>
12639    *
12640    *   <ul>List Manipulation
12641    *     <li>delete item via DEL or backspace keys when selected</li>
12642    *   </ul>
12643    *
12644    *   <ul>Validation
12645    *     <li>de-dupe values (or support duplicates, but fix the ng-repeat duplicate key issue)</li>
12646    *     <li>allow a validation callback</li>
12647    *     <li>hilighting style for invalid chips</li>
12648    *   </ul>
12649    *
12650    *   <ul>Item mutation
12651    *     <li>Support `
12652    *       <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double
12653    *       click?
12654    *     </li>
12655    *   </ul>
12656    *
12657    *   <ul>Truncation and Disambiguation (?)
12658    *     <li>Truncate chip text where possible, but do not truncate entries such that two are
12659    *     indistinguishable.</li>
12660    *   </ul>
12661    *
12662    *   <ul>Drag and Drop
12663    *     <li>Drag and drop chips between related `<md-chips>` elements.
12664    *     </li>
12665    *   </ul>
12666    * </ul>
12667    *
12668    *  <span style="font-size:.8em;text-align:center">
12669    *    Warning: This component is a WORK IN PROGRESS. If you use it now,
12670    *    it will probably break on you in the future.
12671    *  </span>
12672    *
12673    * @param {string=|object=} ng-model A model to bind the list of items to
12674    * @param {string=} placeholder Placeholder text that will be forwarded to the input.
12675    * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
12676    *    displayed when there is at least on item in the list
12677    * @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
12678    *    the input and delete buttons
12679    * @param {expression} md-on-append An expression expected to convert the input string into an
12680    *    object when adding a chip.
12681    * @param {string=} delete-hint A string read by screen readers instructing users that pressing
12682    *    the delete key will remove the chip.
12683    * @param {string=} delete-button-label A label for the delete button. Also hidden and read by
12684    *    screen readers.
12685    *
12686    * @usage
12687    * <hljs lang="html">
12688    *   <md-chips
12689    *       ng-model="myItems"
12690    *       placeholder="Add an item"
12691    *       readonly="isReadOnly">
12692    *   </md-chips>
12693    * </hljs>
12694    *
12695    */
12696
12697
12698   var MD_CHIPS_TEMPLATE = '\
12699       <md-chips-wrap\
12700           ng-if="!$mdChipsCtrl.readonly || $mdChipsCtrl.items.length > 0"\
12701           ng-keydown="$mdChipsCtrl.chipKeydown($event)"\
12702           ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus() }"\
12703           class="md-chips">\
12704         <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
12705             index="{{$index}}"\
12706             ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index}">\
12707           <div class="md-chip-content"\
12708               tabindex="-1"\
12709               aria-hidden="true"\
12710               ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)"\
12711               md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div>\
12712           <div class="md-chip-remove-container"\
12713               md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\
12714         </md-chip>\
12715         <div ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl"\
12716             class="md-chip-input-container"\
12717             md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\
12718         </div>\
12719       </md-chips-wrap>';
12720
12721   var CHIP_INPUT_TEMPLATE = '\
12722         <input\
12723             tabindex="0"\
12724             placeholder="{{$mdChipsCtrl.getPlaceholder()}}"\
12725             aria-label="{{$mdChipsCtrl.getPlaceholder()}}"\
12726             ng-model="$mdChipsCtrl.chipBuffer"\
12727             ng-focus="$mdChipsCtrl.onInputFocus()"\
12728             ng-blur="$mdChipsCtrl.onInputBlur()"\
12729             ng-keydown="$mdChipsCtrl.inputKeydown($event)">';
12730
12731   var CHIP_DEFAULT_TEMPLATE = '\
12732       <span>{{$chip}}</span>';
12733
12734   var CHIP_REMOVE_TEMPLATE = '\
12735       <button\
12736           class="md-chip-remove"\
12737           ng-if="!$mdChipsCtrl.readonly"\
12738           ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
12739           type="button"\
12740           aria-hidden="true"\
12741           tabindex="-1">\
12742         <md-icon md-svg-icon="md-close"></md-icon>\
12743         <span class="md-visually-hidden">\
12744           {{$mdChipsCtrl.deleteButtonLabel}}\
12745         </span>\
12746       </button>';
12747
12748   /**
12749    * MDChips Directive Definition
12750    */
12751   function MdChips ($mdTheming, $mdUtil, $compile, $log, $timeout) {
12752     return {
12753       template: function(element, attrs) {
12754         // Clone the element into an attribute. By prepending the attribute
12755         // name with '$', Angular won't write it into the DOM. The cloned
12756         // element propagates to the link function via the attrs argument,
12757         // where various contained-elements can be consumed.
12758         var content = attrs['$mdUserTemplate'] = element.clone();
12759         return MD_CHIPS_TEMPLATE;
12760       },
12761       require: ['mdChips'],
12762       restrict: 'E',
12763       controller: 'MdChipsCtrl',
12764       controllerAs: '$mdChipsCtrl',
12765       bindToController: true,
12766       compile: compile,
12767       scope: {
12768         readonly: '=readonly',
12769         placeholder: '@',
12770         secondaryPlaceholder: '@',
12771         mdOnAppend: '&',
12772         deleteHint: '@',
12773         deleteButtonLabel: '@',
12774         requireMatch: '=?mdRequireMatch'
12775       }
12776     };
12777
12778     /**
12779      * Builds the final template for `md-chips` and returns the postLink function.
12780      *
12781      * Building the template involves 3 key components:
12782      * static chips
12783      * chip template
12784      * input control
12785      *
12786      * If no `ng-model` is provided, only the static chip work needs to be done.
12787      *
12788      * If no user-passed `md-chip-template` exists, the default template is used. This resulting
12789      * template is appended to the chip content element.
12790      *
12791      * The remove button may be overridden by passing an element with an md-chip-remove attribute.
12792      *
12793      * If an `input` or `md-autocomplete` element is provided by the caller, it is set aside for
12794      * transclusion later. The transclusion happens in `postLink` as the parent scope is required.
12795      * If no user input is provided, a default one is appended to the input container node in the
12796      * template.
12797      *
12798      * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for
12799      * transclusion in the `postLink` function.
12800      *
12801      *
12802      * @param element
12803      * @param attr
12804      * @returns {Function}
12805      */
12806     function compile(element, attr) {
12807       // Grab the user template from attr and reset the attribute to null.
12808       var userTemplate = attr['$mdUserTemplate'];
12809       attr['$mdUserTemplate'] = null;
12810
12811       // Set the chip remove, chip contents and chip input templates. The link function will put
12812       // them on the scope for transclusion later.
12813       var chipRemoveTemplate   = getTemplateByQuery('md-chips>*[md-chip-remove]') || CHIP_REMOVE_TEMPLATE,
12814           chipContentsTemplate = getTemplateByQuery('md-chips>md-chip-template') || CHIP_DEFAULT_TEMPLATE,
12815           chipInputTemplate    = getTemplateByQuery('md-chips>md-autocomplete')
12816               || getTemplateByQuery('md-chips>input')
12817               || CHIP_INPUT_TEMPLATE,
12818           staticChips = userTemplate.find('md-chip');
12819
12820       // Warn of malformed template. See #2545
12821       if (userTemplate[0].querySelector('md-chip-template>*[md-chip-remove]')) {
12822         $log.warn('invalid placement of md-chip-remove within md-chip-template.');
12823       }
12824
12825       function getTemplateByQuery (query) {
12826         if (!attr.ngModel) return;
12827         var element = userTemplate[0].querySelector(query);
12828         return element && element.outerHTML;
12829       }
12830
12831       /**
12832        * Configures controller and transcludes.
12833        */
12834       return function postLink(scope, element, attrs, controllers) {
12835
12836         $mdUtil.initOptionalProperties(scope, attr);
12837
12838         $mdTheming(element);
12839         var mdChipsCtrl = controllers[0];
12840         mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;
12841         mdChipsCtrl.chipRemoveTemplate   = chipRemoveTemplate;
12842         mdChipsCtrl.chipInputTemplate    = chipInputTemplate;
12843
12844         element
12845             .attr({ ariaHidden: true, tabindex: -1 })
12846             .on('focus', function () { mdChipsCtrl.onFocus(); });
12847
12848         if (attr.ngModel) {
12849           mdChipsCtrl.configureNgModel(element.controller('ngModel'));
12850
12851           // If an `md-on-append` attribute was set, tell the controller to use the expression
12852           // when appending chips.
12853           if (attrs.mdOnAppend) mdChipsCtrl.useMdOnAppendExpression();
12854
12855           // The md-autocomplete and input elements won't be compiled until after this directive
12856           // is complete (due to their nested nature). Wait a tick before looking for them to
12857           // configure the controller.
12858           if (chipInputTemplate != CHIP_INPUT_TEMPLATE) {
12859             $timeout(function() {
12860               if (chipInputTemplate.indexOf('<md-autocomplete') === 0)
12861                 mdChipsCtrl
12862                     .configureAutocomplete(element.find('md-autocomplete')
12863                         .controller('mdAutocomplete'));
12864               mdChipsCtrl.configureUserInput(element.find('input'));
12865             });
12866           }
12867         }
12868
12869         // Compile with the parent's scope and prepend any static chips to the wrapper.
12870         if (staticChips.length > 0) {
12871           var compiledStaticChips = $compile(staticChips)(scope.$parent);
12872           $timeout(function() { element.find('md-chips-wrap').prepend(compiledStaticChips); });
12873         }
12874       };
12875     }
12876   }
12877   MdChips.$inject = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout"];
12878
12879 })();
12880 (function(){
12881 "use strict";
12882
12883 angular
12884     .module('material.components.chips')
12885     .controller('MdContactChipsCtrl', MdContactChipsCtrl);
12886
12887
12888
12889 /**
12890  * Controller for the MdContactChips component
12891  * @constructor
12892  */
12893 function MdContactChipsCtrl () {
12894   /** @type {Object} */
12895   this.selectedItem = null;
12896
12897   /** @type {string} */
12898   this.searchText = '';
12899 }
12900
12901
12902 MdContactChipsCtrl.prototype.queryContact = function(searchText) {
12903   var results = this.contactQuery({'$query': searchText});
12904   return this.filterSelected ?
12905       results.filter(angular.bind(this, this.filterSelectedContacts)) : results;
12906 };
12907
12908
12909 MdContactChipsCtrl.prototype.filterSelectedContacts = function(contact) {
12910   return this.contacts.indexOf(contact) == -1;
12911 };
12912
12913 })();
12914 (function(){
12915 "use strict";
12916
12917   angular
12918       .module('material.components.chips')
12919       .directive('mdContactChips', MdContactChips);
12920
12921   /**
12922    * @ngdoc directive
12923    * @name mdContactChips
12924    * @module material.components.chips
12925    *
12926    * @description
12927    * `<md-contact-chips>` is an input component based on `md-chips` and makes use of an
12928    *    `md-autocomplete` element. The component allows the caller to supply a query expression
12929    *    which returns  a list of possible contacts. The user can select one of these and add it to
12930    *    the list of chips.
12931    *
12932    * @param {string=|object=} ng-model A model to bind the list of items to
12933    * @param {string=} placeholder Placeholder text that will be forwarded to the input.
12934    * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
12935    *    displayed when there is at least on item in the list
12936    * @param {expression} md-contacts An expression expected to return contacts matching the search
12937    *    test, `$query`.
12938    * @param {string} md-contact-name The field name of the contact object representing the
12939    *    contact's name.
12940    * @param {string} md-contact-email The field name of the contact object representing the
12941    *    contact's email address.
12942    * @param {string} md-contact-image The field name of the contact object representing the
12943    *    contact's image.
12944    *
12945    *
12946    * // The following attribute has been removed but may come back.
12947    * @param {expression=} filter-selected Whether to filter selected contacts from the list of
12948    *    suggestions shown in the autocomplete.
12949    *
12950    *
12951    *
12952    * @usage
12953    * <hljs lang="html">
12954    *   <md-contact-chips
12955    *       ng-model="ctrl.contacts"
12956    *       md-contacts="ctrl.querySearch($query)"
12957    *       md-contact-name="name"
12958    *       md-contact-image="image"
12959    *       md-contact-email="email"
12960    *       placeholder="To">
12961    *   </md-contact-chips>
12962    * </hljs>
12963    *
12964    */
12965
12966
12967   var MD_CONTACT_CHIPS_TEMPLATE = '\
12968       <md-chips class="md-contact-chips"\
12969           ng-model="$mdContactChipsCtrl.contacts"\
12970           md-require-match="$mdContactChipsCtrl.requireMatch"\
12971           md-autocomplete-snap>\
12972           <md-autocomplete\
12973               md-menu-class="md-contact-chips-suggestions"\
12974               md-selected-item="$mdContactChipsCtrl.selectedItem"\
12975               md-search-text="$mdContactChipsCtrl.searchText"\
12976               md-items="item in $mdContactChipsCtrl.queryContact($mdContactChipsCtrl.searchText)"\
12977               md-item-text="$mdContactChipsCtrl.mdContactName"\
12978               md-no-cache="true"\
12979               md-autoselect\
12980               placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\
12981                   $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\
12982             <div class="md-contact-suggestion">\
12983               <img \
12984                   ng-src="{{item[$mdContactChipsCtrl.contactImage]}}"\
12985                   alt="{{item[$mdContactChipsCtrl.contactName]}}" />\
12986               <span class="md-contact-name" md-highlight-text="$mdContactChipsCtrl.searchText">\
12987                 {{item[$mdContactChipsCtrl.contactName]}}\
12988               </span>\
12989               <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\
12990             </div>\
12991           </md-autocomplete>\
12992           <md-chip-template>\
12993             <div class="md-contact-avatar">\
12994               <img \
12995                   ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\
12996                   alt="{{$chip[$mdContactChipsCtrl.contactName]}}" />\
12997             </div>\
12998             <div class="md-contact-name">\
12999               {{$chip[$mdContactChipsCtrl.contactName]}}\
13000             </div>\
13001           </md-chip-template>\
13002       </md-chips>';
13003
13004
13005   /**
13006    * MDContactChips Directive Definition
13007    *
13008    * @param $mdTheming
13009    * @returns {*}
13010    * @ngInject
13011    */
13012   function MdContactChips ($mdTheming, $mdUtil) {
13013     return {
13014       template: function(element, attrs) {
13015         return MD_CONTACT_CHIPS_TEMPLATE;
13016       },
13017       restrict: 'E',
13018       controller: 'MdContactChipsCtrl',
13019       controllerAs: '$mdContactChipsCtrl',
13020       bindToController: true,
13021       compile: compile,
13022       scope: {
13023         contactQuery: '&mdContacts',
13024         placeholder: '@',
13025         secondaryPlaceholder: '@',
13026         contactName: '@mdContactName',
13027         contactImage: '@mdContactImage',
13028         contactEmail: '@mdContactEmail',
13029         contacts: '=ngModel',
13030         requireMatch: '=?mdRequireMatch'
13031       }
13032     };
13033
13034     function compile(element, attr) {
13035       return function postLink(scope, element, attrs, controllers) {
13036
13037         $mdUtil.initOptionalProperties(scope, attr);
13038         $mdTheming(element);
13039
13040         element.attr('tabindex', '-1');
13041       };
13042     }
13043   }
13044   MdContactChips.$inject = ["$mdTheming", "$mdUtil"];
13045
13046 })();
13047 (function(){
13048 "use strict";
13049
13050 /**
13051  * @ngdoc directive
13052  * @name mdTab
13053  * @module material.components.tabs
13054  *
13055  * @restrict E
13056  *
13057  * @description
13058  * Use the `<md-tab>` a nested directive used within `<md-tabs>` to specify a tab with a **label** and optional *view content*.
13059  *
13060  * If the `label` attribute is not specified, then an optional `<md-tab-label>` tag can be used to specify more
13061  * complex tab header markup. If neither the **label** nor the **md-tab-label** are specified, then the nested
13062  * markup of the `<md-tab>` is used as the tab header markup.
13063  *
13064  * Please note that if you use `<md-tab-label>`, your content **MUST** be wrapped in the `<md-tab-body>` tag.  This
13065  * is to define a clear separation between the tab content and the tab label.
13066  *
13067  * If a tab **label** has been identified, then any **non-**`<md-tab-label>` markup
13068  * will be considered tab content and will be transcluded to the internal `<div class="md-tabs-content">` container.
13069  *
13070  * This container is used by the TabsController to show/hide the active tab's content view. This synchronization is
13071  * automatically managed by the internal TabsController whenever the tab selection changes. Selection changes can
13072  * be initiated via data binding changes, programmatic invocation, or user gestures.
13073  *
13074  * @param {string=} label Optional attribute to specify a simple string as the tab label
13075  * @param {boolean=} disabled If present, disabled tab selection.
13076  * @param {expression=} md-on-deselect Expression to be evaluated after the tab has been de-selected.
13077  * @param {expression=} md-on-select Expression to be evaluated after the tab has been selected.
13078  *
13079  *
13080  * @usage
13081  *
13082  * <hljs lang="html">
13083  * <md-tab label="" disabled="" md-on-select="" md-on-deselect="" >
13084  *   <h3>My Tab content</h3>
13085  * </md-tab>
13086  *
13087  * <md-tab >
13088  *   <md-tab-label>
13089  *     <h3>My Tab content</h3>
13090  *   </md-tab-label>
13091  *   <md-tab-body>
13092  *     <p>
13093  *       Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
13094  *       totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
13095  *       dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
13096  *       sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
13097  *     </p>
13098  *   </md-tab-body>
13099  * </md-tab>
13100  * </hljs>
13101  *
13102  */
13103 angular
13104     .module('material.components.tabs')
13105     .directive('mdTab', MdTab);
13106
13107 function MdTab () {
13108   return {
13109     require: '^?mdTabs',
13110     terminal: true,
13111     template: function (element, attr) {
13112       var label = getLabel(),
13113           body  = getTemplate();
13114       return '' +
13115           '<md-tab-label>' + label + '</md-tab-label>' +
13116           '<md-tab-body>' + body + '</md-tab-body>';
13117       function getLabel () {
13118         return getLabelElement() || getLabelAttribute() || getElementContents();
13119         function getLabelAttribute () { return attr.label; }
13120         function getLabelElement () {
13121           var label = element.find('md-tab-label');
13122           if (label.length) return label.remove().html();
13123         }
13124         function getElementContents () {
13125           var html = element.html();
13126           element.empty();
13127           return html;
13128         }
13129       }
13130       function getTemplate () {
13131         var content = element.find('md-tab-body'),
13132             template = content.length ? content.html() : attr.label ? element.html() : '';
13133         if (content.length) content.remove();
13134         else if (attr.label) element.empty();
13135         return template;
13136       }
13137     },
13138     scope: {
13139       active:   '=?mdActive',
13140       disabled: '=?ngDisabled',
13141       select:   '&?mdOnSelect',
13142       deselect: '&?mdOnDeselect'
13143     },
13144     link: postLink
13145   };
13146
13147   function postLink (scope, element, attr, ctrl) {
13148     if (!ctrl) return;
13149     var tabs = element.parent()[0].getElementsByTagName('md-tab'),
13150         index = Array.prototype.indexOf.call(tabs, element[0]),
13151         body = element.find('md-tab-body').remove(),
13152         label = element.find('md-tab-label').remove(),
13153         data = ctrl.insertTab({
13154           scope:    scope,
13155           parent:   scope.$parent,
13156           index:    index,
13157           element:  element,
13158           template: body.html(),
13159           label:    label.html()
13160         }, index);
13161
13162     scope.select   = scope.select   || angular.noop;
13163     scope.deselect = scope.deselect || angular.noop;
13164
13165     scope.$watch('active', function (active) { if (active) ctrl.select(data.getIndex()); });
13166     scope.$watch('disabled', function () { ctrl.refreshIndex(); });
13167     scope.$watch(
13168         function () {
13169           return Array.prototype.indexOf.call(tabs, element[0]);
13170         },
13171         function (newIndex) {
13172           data.index = newIndex;
13173           ctrl.updateTabOrder();
13174         }
13175     );
13176     scope.$on('$destroy', function () { ctrl.removeTab(data); });
13177
13178   }
13179 }
13180
13181 })();
13182 (function(){
13183 "use strict";
13184
13185 angular
13186     .module('material.components.tabs')
13187     .directive('mdTabItem', MdTabItem);
13188
13189 function MdTabItem () {
13190   return {
13191     require: '^?mdTabs',
13192     link: function link (scope, element, attr, ctrl) {
13193       if (!ctrl) return;
13194       ctrl.attachRipple(scope, element);
13195     }
13196   };
13197 }
13198
13199 })();
13200 (function(){
13201 "use strict";
13202
13203 angular
13204     .module('material.components.tabs')
13205     .directive('mdTabLabel', MdTabLabel);
13206
13207 function MdTabLabel () {
13208   return { terminal: true };
13209 }
13210
13211
13212 })();
13213 (function(){
13214 "use strict";
13215
13216 angular.module('material.components.tabs')
13217     .directive('mdTabScroll', MdTabScroll);
13218
13219 function MdTabScroll ($parse) {
13220   return {
13221     restrict: 'A',
13222     compile: function ($element, attr) {
13223       var fn = $parse(attr.mdTabScroll, null, true);
13224       return function ngEventHandler (scope, element) {
13225         element.on('mousewheel', function (event) {
13226           scope.$apply(function () { fn(scope, { $event: event }); });
13227         });
13228       };
13229     }
13230   }
13231 }
13232 MdTabScroll.$inject = ["$parse"];
13233
13234 })();
13235 (function(){
13236 "use strict";
13237
13238 angular
13239     .module('material.components.tabs')
13240     .controller('MdTabsController', MdTabsController);
13241
13242 /**
13243  * @ngInject
13244  */
13245 function MdTabsController ($scope, $element, $window, $timeout, $mdConstant, $mdTabInkRipple,
13246                            $mdUtil, $animate) {
13247   var ctrl      = this,
13248       locked    = false,
13249       elements  = getElements(),
13250       queue     = [],
13251       destroyed = false;
13252
13253   ctrl.scope = $scope;
13254   ctrl.parent = $scope.$parent;
13255   ctrl.tabs = [];
13256   ctrl.lastSelectedIndex = null;
13257   ctrl.focusIndex = $scope.selectedIndex || 0;
13258   ctrl.offsetLeft = 0;
13259   ctrl.hasContent = false;
13260   ctrl.hasFocus = false;
13261   ctrl.lastClick = true;
13262
13263   ctrl.redirectFocus = redirectFocus;
13264   ctrl.attachRipple = attachRipple;
13265   ctrl.shouldStretchTabs = shouldStretchTabs;
13266   ctrl.shouldPaginate = shouldPaginate;
13267   ctrl.shouldCenterTabs = shouldCenterTabs;
13268   ctrl.insertTab = insertTab;
13269   ctrl.removeTab = removeTab;
13270   ctrl.select = select;
13271   ctrl.scroll = scroll;
13272   ctrl.nextPage = nextPage;
13273   ctrl.previousPage = previousPage;
13274   ctrl.keydown = keydown;
13275   ctrl.canPageForward = canPageForward;
13276   ctrl.canPageBack = canPageBack;
13277   ctrl.refreshIndex = refreshIndex;
13278   ctrl.incrementSelectedIndex = incrementSelectedIndex;
13279   ctrl.updateInkBarStyles = $mdUtil.debounce(updateInkBarStyles, 100);
13280   ctrl.updateTabOrder = $mdUtil.debounce(updateTabOrder, 100);
13281
13282   init();
13283
13284   function init () {
13285     $scope.$watch('selectedIndex', handleSelectedIndexChange);
13286     $scope.$watch('$mdTabsCtrl.focusIndex', handleFocusIndexChange);
13287     $scope.$watch('$mdTabsCtrl.offsetLeft', handleOffsetChange);
13288     $scope.$watch('$mdTabsCtrl.hasContent', handleHasContent);
13289     angular.element($window).on('resize', handleWindowResize);
13290     angular.element(elements.paging).on('DOMSubtreeModified', ctrl.updateInkBarStyles);
13291     $timeout(updateHeightFromContent, 0, false);
13292     $timeout(adjustOffset);
13293     $scope.$on('$destroy', cleanup);
13294   }
13295
13296   function cleanup () {
13297     destroyed = true;
13298     angular.element($window).off('resize', handleWindowResize);
13299     angular.element(elements.paging).off('DOMSubtreeModified', ctrl.updateInkBarStyles);
13300   }
13301
13302   //-- Change handlers
13303
13304   function handleHasContent (hasContent) {
13305     $element[hasContent ? 'removeClass' : 'addClass']('md-no-tab-content');
13306   }
13307
13308   function handleOffsetChange (left) {
13309     var newValue = shouldCenterTabs() ? '' : '-' + left + 'px';
13310     angular.element(elements.paging).css('transform', 'translate3d(' + newValue + ', 0, 0)');
13311     $scope.$broadcast('$mdTabsPaginationChanged');
13312   }
13313
13314   function handleFocusIndexChange (newIndex, oldIndex) {
13315     if (newIndex === oldIndex) return;
13316     if (!elements.tabs[newIndex]) return;
13317     adjustOffset();
13318     redirectFocus();
13319   }
13320
13321   function handleSelectedIndexChange (newValue, oldValue) {
13322     if (newValue === oldValue) return;
13323
13324     $scope.selectedIndex = getNearestSafeIndex(newValue);
13325     ctrl.lastSelectedIndex = oldValue;
13326     ctrl.updateInkBarStyles();
13327     updateHeightFromContent();
13328     $scope.$broadcast('$mdTabsChanged');
13329     ctrl.tabs[oldValue] && ctrl.tabs[oldValue].scope.deselect();
13330     ctrl.tabs[newValue] && ctrl.tabs[newValue].scope.select();
13331   }
13332
13333   function handleResizeWhenVisible () {
13334     //-- if there is already a watcher waiting for resize, do nothing
13335     if (handleResizeWhenVisible.watcher) return;
13336     //-- otherwise, we will abuse the $watch function to check for visible
13337     handleResizeWhenVisible.watcher = $scope.$watch(function () {
13338       //-- since we are checking for DOM size, we use $timeout to wait for after the DOM updates
13339       $timeout(function () {
13340         //-- if the watcher has already run (ie. multiple digests in one cycle), do nothing
13341         if (!handleResizeWhenVisible.watcher) return;
13342
13343         if ($element.prop('offsetParent')) {
13344           handleResizeWhenVisible.watcher();
13345           handleResizeWhenVisible.watcher = null;
13346
13347           //-- we have to trigger our own $apply so that the DOM bindings will update
13348           handleWindowResize();
13349         }
13350       }, 0, false);
13351     });
13352   }
13353
13354   //-- Event handlers / actions
13355
13356   function keydown (event) {
13357     switch (event.keyCode) {
13358       case $mdConstant.KEY_CODE.LEFT_ARROW:
13359         event.preventDefault();
13360         incrementSelectedIndex(-1, true);
13361         break;
13362       case $mdConstant.KEY_CODE.RIGHT_ARROW:
13363         event.preventDefault();
13364         incrementSelectedIndex(1, true);
13365         break;
13366       case $mdConstant.KEY_CODE.SPACE:
13367       case $mdConstant.KEY_CODE.ENTER:
13368         event.preventDefault();
13369         if (!locked) $scope.selectedIndex = ctrl.focusIndex;
13370         break;
13371     }
13372     ctrl.lastClick = false;
13373   }
13374
13375   function select (index) {
13376     if (!locked) ctrl.focusIndex = $scope.selectedIndex = index;
13377     ctrl.lastClick = true;
13378     ctrl.tabs[index].element.triggerHandler('click');
13379   }
13380
13381   function scroll (event) {
13382     if (!shouldPaginate()) return;
13383     event.preventDefault();
13384     ctrl.offsetLeft = fixOffset(ctrl.offsetLeft - event.wheelDelta);
13385   }
13386
13387   function nextPage () {
13388     var viewportWidth = elements.canvas.clientWidth,
13389         totalWidth = viewportWidth + ctrl.offsetLeft,
13390         i, tab;
13391     for (i = 0; i < elements.tabs.length; i++) {
13392       tab = elements.tabs[i];
13393       if (tab.offsetLeft + tab.offsetWidth > totalWidth) break;
13394     }
13395     ctrl.offsetLeft = fixOffset(tab.offsetLeft);
13396   }
13397
13398   function previousPage () {
13399     var i, tab;
13400     for (i = 0; i < elements.tabs.length; i++) {
13401       tab = elements.tabs[i];
13402       if (tab.offsetLeft + tab.offsetWidth >= ctrl.offsetLeft) break;
13403     }
13404     ctrl.offsetLeft = fixOffset(tab.offsetLeft + tab.offsetWidth - elements.canvas.clientWidth);
13405   }
13406
13407   function handleWindowResize () {
13408     $scope.$apply(function () {
13409       ctrl.lastSelectedIndex = $scope.selectedIndex;
13410       ctrl.offsetLeft = fixOffset(ctrl.offsetLeft);
13411       $timeout(ctrl.updateInkBarStyles, 0, false);
13412     });
13413   }
13414
13415   function removeTab (tabData) {
13416     var selectedIndex = $scope.selectedIndex,
13417         tab = ctrl.tabs.splice(tabData.getIndex(), 1)[0];
13418     refreshIndex();
13419     //-- when removing a tab, if the selected index did not change, we have to manually trigger the
13420     //   tab select/deselect events
13421     if ($scope.selectedIndex === selectedIndex && !destroyed) {
13422       tab.scope.deselect();
13423       ctrl.tabs[$scope.selectedIndex] && ctrl.tabs[$scope.selectedIndex].scope.select();
13424     }
13425     $timeout(function () {
13426       ctrl.offsetLeft = fixOffset(ctrl.offsetLeft);
13427     });
13428   }
13429
13430   function insertTab (tabData, index) {
13431     var proto = {
13432           getIndex: function () { return ctrl.tabs.indexOf(tab); },
13433           isActive: function () { return this.getIndex() === $scope.selectedIndex; },
13434           isLeft:   function () { return this.getIndex() < $scope.selectedIndex; },
13435           isRight:  function () { return this.getIndex() > $scope.selectedIndex; },
13436           shouldRender: function () { return !$scope.noDisconnect || this.isActive(); },
13437           hasFocus: function () { return !ctrl.lastClick && ctrl.hasFocus && this.getIndex() === ctrl.focusIndex; },
13438           id:       $mdUtil.nextUid()
13439         },
13440         tab = angular.extend(proto, tabData);
13441     if (angular.isDefined(index)) {
13442       ctrl.tabs.splice(index, 0, tab);
13443     } else {
13444       ctrl.tabs.push(tab);
13445     }
13446     processQueue();
13447     updateHasContent();
13448     return tab;
13449   }
13450
13451   //-- Getter methods
13452
13453   function getElements () {
13454     var elements      = {};
13455
13456     //-- gather tab bar elements
13457     elements.wrapper  = $element[0].getElementsByTagName('md-tabs-wrapper')[0];
13458     elements.canvas   = elements.wrapper.getElementsByTagName('md-tabs-canvas')[0];
13459     elements.paging   = elements.canvas.getElementsByTagName('md-pagination-wrapper')[0];
13460     elements.tabs     = elements.paging.getElementsByTagName('md-tab-item');
13461     elements.dummies  = elements.canvas.getElementsByTagName('md-dummy-tab');
13462     elements.inkBar   = elements.paging.getElementsByTagName('md-ink-bar')[0];
13463
13464     //-- gather tab content elements
13465     elements.contentsWrapper = $element[0].getElementsByTagName('md-tabs-content-wrapper')[0];
13466     elements.contents = elements.contentsWrapper.getElementsByTagName('md-tab-content');
13467
13468     return elements;
13469   }
13470
13471   function canPageBack () {
13472     return ctrl.offsetLeft > 0;
13473   }
13474
13475   function canPageForward () {
13476     var lastTab = elements.tabs[elements.tabs.length - 1];
13477     return lastTab && lastTab.offsetLeft + lastTab.offsetWidth > elements.canvas.clientWidth + ctrl.offsetLeft;
13478   }
13479
13480   function shouldStretchTabs () {
13481     switch ($scope.stretchTabs) {
13482       case 'always': return true;
13483       case 'never':  return false;
13484       default:       return !shouldPaginate() && $window.matchMedia('(max-width: 600px)').matches;
13485     }
13486   }
13487
13488   function shouldCenterTabs () {
13489     return $scope.centerTabs && !shouldPaginate();
13490   }
13491
13492   function shouldPaginate () {
13493     if ($scope.noPagination) return false;
13494     var canvasWidth = $element.prop('clientWidth');
13495     angular.forEach(elements.tabs, function (tab) { canvasWidth -= tab.offsetWidth; });
13496     return canvasWidth < 0;
13497   }
13498
13499   function getNearestSafeIndex(newIndex) {
13500     var maxOffset = Math.max(ctrl.tabs.length - newIndex, newIndex),
13501         i, tab;
13502     for (i = 0; i <= maxOffset; i++) {
13503       tab = ctrl.tabs[newIndex + i];
13504       if (tab && (tab.scope.disabled !== true)) return tab.getIndex();
13505       tab = ctrl.tabs[newIndex - i];
13506       if (tab && (tab.scope.disabled !== true)) return tab.getIndex();
13507     }
13508     return newIndex;
13509   }
13510
13511   //-- Utility methods
13512
13513   function updateTabOrder () {
13514     var selectedItem = ctrl.tabs[$scope.selectedIndex],
13515         focusItem = ctrl.tabs[ctrl.focusIndex];
13516     ctrl.tabs = ctrl.tabs.sort(function (a, b) {
13517       return a.index - b.index;
13518     });
13519     $scope.selectedIndex = ctrl.tabs.indexOf(selectedItem);
13520     ctrl.focusIndex = ctrl.tabs.indexOf(focusItem);
13521   }
13522
13523   function incrementSelectedIndex (inc, focus) {
13524     var newIndex,
13525         index = focus ? ctrl.focusIndex : $scope.selectedIndex;
13526     for (newIndex = index + inc;
13527          ctrl.tabs[newIndex] && ctrl.tabs[newIndex].scope.disabled;
13528          newIndex += inc) {}
13529     if (ctrl.tabs[newIndex]) {
13530       if (focus) ctrl.focusIndex = newIndex;
13531       else $scope.selectedIndex = newIndex;
13532     }
13533   }
13534
13535   function redirectFocus () {
13536     elements.dummies[ctrl.focusIndex].focus();
13537   }
13538
13539   function adjustOffset () {
13540     if (shouldCenterTabs()) return;
13541     var tab = elements.tabs[ctrl.focusIndex],
13542         left = tab.offsetLeft,
13543         right = tab.offsetWidth + left;
13544     ctrl.offsetLeft = Math.max(ctrl.offsetLeft, fixOffset(right - elements.canvas.clientWidth));
13545     ctrl.offsetLeft = Math.min(ctrl.offsetLeft, fixOffset(left));
13546   }
13547
13548   function processQueue () {
13549     queue.forEach(function (func) { $timeout(func); });
13550     queue = [];
13551   }
13552
13553   function updateHasContent () {
13554     var hasContent = false;
13555     angular.forEach(ctrl.tabs, function (tab) {
13556       if (tab.template) hasContent = true;
13557     });
13558     ctrl.hasContent = hasContent;
13559   }
13560
13561   function refreshIndex () {
13562     $scope.selectedIndex = getNearestSafeIndex($scope.selectedIndex);
13563     ctrl.focusIndex = getNearestSafeIndex(ctrl.focusIndex);
13564   }
13565
13566   function updateHeightFromContent () {
13567     if (!$scope.dynamicHeight) return $element.css('height', '');
13568     if (!ctrl.tabs.length) return queue.push(updateHeightFromContent);
13569     var tabContent    = elements.contents[$scope.selectedIndex],
13570         contentHeight = tabContent ? tabContent.offsetHeight : 0,
13571         tabsHeight    = elements.wrapper.offsetHeight,
13572         newHeight     = contentHeight + tabsHeight,
13573         currentHeight = $element.prop('clientHeight');
13574     if (currentHeight === newHeight) return;
13575     locked = true;
13576     $animate
13577         .animate(
13578           $element,
13579           { height: currentHeight + 'px' },
13580           { height: newHeight + 'px'}
13581         )
13582         .then(function () {
13583           $element.css('height', '');
13584           locked = false;
13585         });
13586   }
13587
13588   function updateInkBarStyles () {
13589     if (!elements.tabs[$scope.selectedIndex]) return;
13590     if (!ctrl.tabs.length) return queue.push(ctrl.updateInkBarStyles);
13591     //-- if the element is not visible, we will not be able to calculate sizes until it is
13592     //-- we should treat that as a resize event rather than just updating the ink bar
13593     if (!$element.prop('offsetParent')) return handleResizeWhenVisible();
13594     var index = $scope.selectedIndex,
13595         totalWidth = elements.paging.offsetWidth,
13596         tab = elements.tabs[index],
13597         left = tab.offsetLeft,
13598         right = totalWidth - left - tab.offsetWidth;
13599     updateInkBarClassName();
13600     angular.element(elements.inkBar).css({ left: left + 'px', right: right + 'px' });
13601   }
13602
13603   function updateInkBarClassName () {
13604     var newIndex = $scope.selectedIndex,
13605         oldIndex = ctrl.lastSelectedIndex,
13606         ink = angular.element(elements.inkBar);
13607     ink.removeClass('md-left md-right');
13608     if (!angular.isNumber(oldIndex)) return;
13609     if (newIndex < oldIndex) {
13610       ink.addClass('md-left');
13611     } else if (newIndex > oldIndex) {
13612       ink.addClass('md-right');
13613     }
13614   }
13615
13616   function fixOffset (value) {
13617     if (!elements.tabs.length || !shouldPaginate()) return 0;
13618     var lastTab = elements.tabs[elements.tabs.length - 1],
13619         totalWidth = lastTab.offsetLeft + lastTab.offsetWidth;
13620     value = Math.max(0, value);
13621     value = Math.min(totalWidth - elements.canvas.clientWidth, value);
13622     return value;
13623   }
13624
13625   function attachRipple (scope, element) {
13626     var options = { colorElement: angular.element(elements.inkBar) };
13627     $mdTabInkRipple.attach(scope, element, options);
13628   }
13629 }
13630 MdTabsController.$inject = ["$scope", "$element", "$window", "$timeout", "$mdConstant", "$mdTabInkRipple", "$mdUtil", "$animate"];
13631
13632 })();
13633 (function(){
13634 "use strict";
13635
13636 /**
13637  * @ngdoc directive
13638  * @name mdTabs
13639  * @module material.components.tabs
13640  *
13641  * @restrict E
13642  *
13643  * @description
13644  * The `<md-tabs>` directive serves as the container for 1..n `<md-tab>` child directives to produces a Tabs components.
13645  * In turn, the nested `<md-tab>` directive is used to specify a tab label for the **header button** and a [optional] tab view
13646  * content that will be associated with each tab button.
13647  *
13648  * Below is the markup for its simplest usage:
13649  *
13650  *  <hljs lang="html">
13651  *  <md-tabs>
13652  *    <md-tab label="Tab #1"></md-tab>
13653  *    <md-tab label="Tab #2"></md-tab>
13654  *    <md-tab label="Tab #3"></md-tab>
13655  *  </md-tabs>
13656  *  </hljs>
13657  *
13658  * Tabs supports three (3) usage scenarios:
13659  *
13660  *  1. Tabs (buttons only)
13661  *  2. Tabs with internal view content
13662  *  3. Tabs with external view content
13663  *
13664  * **Tab-only** support is useful when tab buttons are used for custom navigation regardless of any other components, content, or views.
13665  * **Tabs with internal views** are the traditional usages where each tab has associated view content and the view switching is managed internally by the Tabs component.
13666  * **Tabs with external view content** is often useful when content associated with each tab is independently managed and data-binding notifications announce tab selection changes.
13667  *
13668  * Additional features also include:
13669  *
13670  * *  Content can include any markup.
13671  * *  If a tab is disabled while active/selected, then the next tab will be auto-selected.
13672  *
13673  * ### Explanation of tab stretching
13674  *
13675  * 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.
13676  *
13677  * On mobile devices, tabs will be expanded to fill the available horizontal space.  When this happens, all tabs will become the same size.
13678  *
13679  * On desktops, by default, stretching will never occur.
13680  *
13681  * This default behavior can be overridden through the `md-stretch-tabs` attribute.  Here is a table showing when stretching will occur:
13682  *
13683  * `md-stretch-tabs` | mobile    | desktop
13684  * ------------------|-----------|--------
13685  * `auto`            | stretched | ---
13686  * `always`          | stretched | stretched
13687  * `never`           | ---       | ---
13688  *
13689  * @param {integer=} md-selected Index of the active/selected tab
13690  * @param {boolean=} md-no-ink If present, disables ink ripple effects.
13691  * @param {boolean=} md-no-bar If present, disables the selection ink bar.
13692  * @param {string=}  md-align-tabs Attribute to indicate position of tab buttons: `bottom` or `top`; default is `top`
13693  * @param {string=} md-stretch-tabs Attribute to indicate whether or not to stretch tabs: `auto`, `always`, or `never`; default is `auto`
13694  * @param {boolean=} md-dynamic-height When enabled, the tab wrapper will resize based on the contents of the selected tab
13695  * @param {boolean=} md-center-tabs When enabled, tabs will be centered provided there is no need for pagination
13696  * @param {boolean=} md-no-pagination When enabled, pagination will remain off
13697  * @param {boolean=} md-swipe-content When enabled, swipe gestures will be enabled for the content area to jump between tabs
13698  * @param {boolean=} md-no-disconnect If your tab content has background tasks (ie. event listeners), you will want to include this to prevent the scope from being disconnected
13699  *
13700  * @usage
13701  * <hljs lang="html">
13702  * <md-tabs md-selected="selectedIndex" >
13703  *   <img ng-src="img/angular.png" class="centered">
13704  *   <md-tab
13705  *       ng-repeat="tab in tabs | orderBy:predicate:reversed"
13706  *       md-on-select="onTabSelected(tab)"
13707  *       md-on-deselect="announceDeselected(tab)"
13708  *       ng-disabled="tab.disabled">
13709  *     <md-tab-label>
13710  *       {{tab.title}}
13711  *       <img src="img/removeTab.png" ng-click="removeTab(tab)" class="delete">
13712  *     </md-tab-label>
13713  *     <md-tab-body>
13714  *       {{tab.content}}
13715  *     </md-tab-body>
13716  *   </md-tab>
13717  * </md-tabs>
13718  * </hljs>
13719  *
13720  */
13721 angular
13722     .module('material.components.tabs')
13723     .directive('mdTabs', MdTabs);
13724
13725 function MdTabs ($mdTheming, $mdUtil, $compile) {
13726   return {
13727     scope: {
13728       noPagination:  '=?mdNoPagination',
13729       dynamicHeight: '=?mdDynamicHeight',
13730       centerTabs:    '=?mdCenterTabs',
13731       selectedIndex: '=?mdSelected',
13732       stretchTabs:   '@?mdStretchTabs',
13733       swipeContent:  '=?mdSwipeContent',
13734       noDisconnect:  '=?mdNoDisconnect'
13735     },
13736     template: function (element, attr) {
13737       attr["$mdTabsTemplate"] = element.html();
13738       return '\
13739         <md-tabs-wrapper ng-class="{ \'md-stretch-tabs\': $mdTabsCtrl.shouldStretchTabs() }">\
13740           <md-tab-data></md-tab-data>\
13741           <md-prev-button\
13742               tabindex="-1"\
13743               role="button"\
13744               aria-label="Previous Page"\
13745               aria-disabled="{{!$mdTabsCtrl.canPageBack()}}"\
13746               ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageBack() }"\
13747               ng-if="$mdTabsCtrl.shouldPaginate()"\
13748               ng-click="$mdTabsCtrl.previousPage()">\
13749             <md-icon md-svg-icon="md-tabs-arrow"></md-icon>\
13750           </md-prev-button>\
13751           <md-next-button\
13752               tabindex="-1"\
13753               role="button"\
13754               aria-label="Next Page"\
13755               aria-disabled="{{!$mdTabsCtrl.canPageForward()}}"\
13756               ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageForward() }"\
13757               ng-if="$mdTabsCtrl.shouldPaginate()"\
13758               ng-click="$mdTabsCtrl.nextPage()">\
13759             <md-icon md-svg-icon="md-tabs-arrow"></md-icon>\
13760           </md-next-button>\
13761           <md-tabs-canvas\
13762               tabindex="0"\
13763               aria-activedescendant="tab-item-{{$mdTabsCtrl.tabs[$mdTabsCtrl.focusIndex].id}}"\
13764               ng-focus="$mdTabsCtrl.redirectFocus()"\
13765               ng-class="{\
13766                   \'md-paginated\': $mdTabsCtrl.shouldPaginate(),\
13767                   \'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs()\
13768               }"\
13769               ng-keydown="$mdTabsCtrl.keydown($event)"\
13770               role="tablist">\
13771             <md-pagination-wrapper\
13772                 ng-class="{ \'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs() }"\
13773                 md-tab-scroll="$mdTabsCtrl.scroll($event)">\
13774               <md-tab-item\
13775                   tabindex="-1"\
13776                   class="md-tab"\
13777                   style="max-width: {{ tabWidth ? tabWidth + \'px\' : \'none\' }}"\
13778                   ng-repeat="tab in $mdTabsCtrl.tabs"\
13779                   role="tab"\
13780                   aria-controls="tab-content-{{tab.id}}"\
13781                   aria-selected="{{tab.isActive()}}"\
13782                   aria-disabled="{{tab.scope.disabled || \'false\'}}"\
13783                   ng-click="$mdTabsCtrl.select(tab.getIndex())"\
13784                   ng-class="{\
13785                       \'md-active\':    tab.isActive(),\
13786                       \'md-focused\':   tab.hasFocus(),\
13787                       \'md-disabled\':  tab.scope.disabled\
13788                   }"\
13789                   ng-disabled="tab.scope.disabled"\
13790                   md-swipe-left="$mdTabsCtrl.nextPage()"\
13791                   md-swipe-right="$mdTabsCtrl.previousPage()"\
13792                   md-template="tab.label"\
13793                   md-scope="tab.parent"></md-tab-item>\
13794               <md-ink-bar ng-hide="noInkBar"></md-ink-bar>\
13795             </md-pagination-wrapper>\
13796             <div class="md-visually-hidden md-dummy-wrapper">\
13797               <md-dummy-tab\
13798                   tabindex="-1"\
13799                   id="tab-item-{{tab.id}}"\
13800                   role="tab"\
13801                   aria-controls="tab-content-{{tab.id}}"\
13802                   aria-selected="{{tab.isActive()}}"\
13803                   aria-disabled="{{tab.scope.disabled || \'false\'}}"\
13804                   ng-focus="$mdTabsCtrl.hasFocus = true"\
13805                   ng-blur="$mdTabsCtrl.hasFocus = false"\
13806                   ng-repeat="tab in $mdTabsCtrl.tabs"\
13807                   md-template="tab.label"\
13808                   md-scope="tab.parent"></md-dummy-tab>\
13809             </div>\
13810           </md-tabs-canvas>\
13811         </md-tabs-wrapper>\
13812         <md-tabs-content-wrapper ng-show="$mdTabsCtrl.hasContent">\
13813           <md-tab-content\
13814               id="tab-content-{{tab.id}}"\
13815               role="tabpanel"\
13816               aria-labelledby="tab-item-{{tab.id}}"\
13817               md-swipe-left="swipeContent && $mdTabsCtrl.incrementSelectedIndex(1)"\
13818               md-swipe-right="swipeContent && $mdTabsCtrl.incrementSelectedIndex(-1)"\
13819               ng-if="$mdTabsCtrl.hasContent"\
13820               ng-repeat="(index, tab) in $mdTabsCtrl.tabs"\
13821               md-connected-if="tab.isActive()"\
13822               ng-class="{\
13823                 \'md-no-transition\': $mdTabsCtrl.lastSelectedIndex == null,\
13824                 \'md-active\':        tab.isActive(),\
13825                 \'md-left\':          tab.isLeft(),\
13826                 \'md-right\':         tab.isRight(),\
13827                 \'md-no-scroll\':     dynamicHeight\
13828               }">\
13829             <div\
13830                 md-template="tab.template"\
13831                 md-scope="tab.parent"\
13832                 ng-if="tab.shouldRender()"></div>\
13833           </md-tab-content>\
13834         </md-tabs-content-wrapper>\
13835       ';
13836     },
13837     controller: 'MdTabsController',
13838     controllerAs: '$mdTabsCtrl',
13839     link: function (scope, element, attr) {
13840       compileTabData(attr.$mdTabsTemplate);
13841       delete attr.$mdTabsTemplate;
13842
13843       $mdUtil.initOptionalProperties(scope, attr);
13844
13845       //-- watch attributes
13846       attr.$observe('mdNoBar', function (value) { scope.noInkBar = angular.isDefined(value); });
13847       //-- set default value for selectedIndex
13848       scope.selectedIndex = angular.isNumber(scope.selectedIndex) ? scope.selectedIndex : 0;
13849       //-- apply themes
13850       $mdTheming(element);
13851
13852       function compileTabData (template) {
13853         var dataElement = element.find('md-tab-data');
13854         dataElement.html(template);
13855         $compile(dataElement.contents())(scope.$parent);
13856       }
13857     }
13858   };
13859 }
13860 MdTabs.$inject = ["$mdTheming", "$mdUtil", "$compile"];
13861
13862 })();
13863 (function(){
13864 "use strict";
13865
13866 angular
13867     .module('material.components.tabs')
13868     .directive('mdTemplate', MdTemplate);
13869
13870 function MdTemplate ($compile, $mdUtil, $timeout) {
13871   return {
13872     restrict: 'A',
13873     link: link,
13874     scope: {
13875       template: '=mdTemplate',
13876       compileScope: '=mdScope',
13877       connected: '=?mdConnectedIf'
13878     },
13879     require: '^?mdTabs'
13880   };
13881   function link (scope, element, attr, ctrl) {
13882     if (!ctrl) return;
13883     var compileScope = scope.compileScope.$new();
13884     element.html(scope.template);
13885     $compile(element.contents())(compileScope);
13886     return $timeout(handleScope);
13887     function handleScope () {
13888       scope.$watch('connected', function (value) { value === false ? disconnect() : reconnect(); });
13889       scope.$on('$destroy', reconnect);
13890     }
13891     function disconnect () {
13892       if (ctrl.scope.noDisconnect) return;
13893       $mdUtil.disconnectScope(compileScope);
13894     }
13895     function reconnect () {
13896       if (ctrl.scope.noDisconnect) return;
13897       $mdUtil.reconnectScope(compileScope);
13898     }
13899   }
13900 }
13901 MdTemplate.$inject = ["$compile", "$mdUtil", "$timeout"];
13902
13903 })();
13904 (function(){ 
13905 angular.module("material.core").constant("$MD_THEME_CSS", "/* mixin definition ; sets LTR and RTL within the same style call */md-autocomplete.md-THEME_NAME-theme {  background: '{{background-50}}'; }  md-autocomplete.md-THEME_NAME-theme[disabled] {    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.md-THEME_NAME-theme {  background: '{{background-50}}'; }  .md-autocomplete-suggestions.md-THEME_NAME-theme li {    color: '{{background-900}}'; }    .md-autocomplete-suggestions.md-THEME_NAME-theme li .highlight {      color: '{{background-600}}'; }    .md-autocomplete-suggestions.md-THEME_NAME-theme li:hover, .md-autocomplete-suggestions.md-THEME_NAME-theme li.selected {      background: '{{background-200}}'; }md-backdrop.md-opaque.md-THEME_NAME-theme {  background-color: '{{foreground-4-0.5}}'; }md-bottom-sheet.md-THEME_NAME-theme {  background-color: '{{background-50}}';  border-top-color: '{{background-300}}'; }  md-bottom-sheet.md-THEME_NAME-theme.md-list md-list-item {    color: '{{foreground-1}}'; }  md-bottom-sheet.md-THEME_NAME-theme .md-subheader {    background-color: '{{background-50}}'; }  md-bottom-sheet.md-THEME_NAME-theme .md-subheader {    color: '{{foreground-1}}'; }a.md-button.md-THEME_NAME-theme, .md-button.md-THEME_NAME-theme {  border-radius: 3px; }  a.md-button.md-THEME_NAME-theme:not([disabled]):hover, .md-button.md-THEME_NAME-theme:not([disabled]):hover {    background-color: '{{background-500-0.2}}'; }  a.md-button.md-THEME_NAME-theme:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme:not([disabled]).md-focused {    background-color: '{{background-500-0.2}}'; }  a.md-button.md-THEME_NAME-theme:not([disabled]).md-icon-button:hover, .md-button.md-THEME_NAME-theme:not([disabled]).md-icon-button:hover {    background-color: transparent; }  a.md-button.md-THEME_NAME-theme.md-fab, .md-button.md-THEME_NAME-theme.md-fab {    border-radius: 50%;    background-color: '{{accent-color}}';    color: '{{accent-contrast}}'; }    a.md-button.md-THEME_NAME-theme.md-fab md-icon, .md-button.md-THEME_NAME-theme.md-fab md-icon {      color: '{{accent-contrast}}'; }    a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover {      background-color: '{{accent-color}}'; }    a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused {      background-color: '{{accent-A700}}'; }  a.md-button.md-THEME_NAME-theme.md-icon-button, .md-button.md-THEME_NAME-theme.md-icon-button {    border-radius: 50%; }  a.md-button.md-THEME_NAME-theme.md-primary, .md-button.md-THEME_NAME-theme.md-primary {    color: '{{primary-color}}'; }    a.md-button.md-THEME_NAME-theme.md-primary.md-raised, a.md-button.md-THEME_NAME-theme.md-primary.md-fab, .md-button.md-THEME_NAME-theme.md-primary.md-raised, .md-button.md-THEME_NAME-theme.md-primary.md-fab {      color: '{{primary-contrast}}';      background-color: '{{primary-color}}'; }      a.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]) md-icon, a.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, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]) md-icon {        color: '{{primary-contrast}}'; }      a.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]):hover, a.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]):hover, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]):hover {        background-color: '{{primary-color}}'; }      a.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]).md-focused, a.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]).md-focused {        background-color: '{{primary-600}}'; }    a.md-button.md-THEME_NAME-theme.md-primary:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-primary:not([disabled]) md-icon {      color: '{{primary-color}}'; }  a.md-button.md-THEME_NAME-theme.md-fab, .md-button.md-THEME_NAME-theme.md-fab {    border-radius: 50%;    background-color: '{{accent-color}}';    color: '{{accent-contrast}}'; }    a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]) .md-icon, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]) .md-icon {      color: '{{accent-contrast}}'; }    a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover {      background-color: '{{accent-color}}'; }    a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused {      background-color: '{{accent-A700}}'; }  a.md-button.md-THEME_NAME-theme.md-raised, .md-button.md-THEME_NAME-theme.md-raised {    color: '{{background-contrast}}';    background-color: '{{background-50}}'; }    a.md-button.md-THEME_NAME-theme.md-raised:not([disabled]) .md-icon, .md-button.md-THEME_NAME-theme.md-raised:not([disabled]) .md-icon {      color: '{{background-contrast}}'; }    a.md-button.md-THEME_NAME-theme.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-raised:not([disabled]):hover {      background-color: '{{background-50}}'; }    a.md-button.md-THEME_NAME-theme.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-raised:not([disabled]).md-focused {      background-color: '{{background-200}}'; }  a.md-button.md-THEME_NAME-theme.md-warn, .md-button.md-THEME_NAME-theme.md-warn {    color: '{{warn-color}}'; }    a.md-button.md-THEME_NAME-theme.md-warn.md-raised, a.md-button.md-THEME_NAME-theme.md-warn.md-fab, .md-button.md-THEME_NAME-theme.md-warn.md-raised, .md-button.md-THEME_NAME-theme.md-warn.md-fab {      color: '{{warn-contrast}}';      background-color: '{{warn-color}}'; }      a.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]) md-icon, a.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, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]) md-icon {        color: '{{warn-contrast}}'; }      a.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]):hover, a.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]):hover, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]):hover {        background-color: '{{warn-color}}'; }      a.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]).md-focused, a.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]).md-focused {        background-color: '{{warn-700}}'; }    a.md-button.md-THEME_NAME-theme.md-warn:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-warn:not([disabled]) md-icon {      color: '{{warn-color}}'; }  a.md-button.md-THEME_NAME-theme.md-accent, .md-button.md-THEME_NAME-theme.md-accent {    color: '{{accent-color}}'; }    a.md-button.md-THEME_NAME-theme.md-accent.md-raised, a.md-button.md-THEME_NAME-theme.md-accent.md-fab, .md-button.md-THEME_NAME-theme.md-accent.md-raised, .md-button.md-THEME_NAME-theme.md-accent.md-fab {      color: '{{accent-contrast}}';      background-color: '{{accent-color}}'; }      a.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]) md-icon, a.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, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]) md-icon {        color: '{{accent-contrast}}'; }      a.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]):hover, a.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]):hover, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]):hover {        background-color: '{{accent-color}}'; }      a.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]).md-focused, a.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]).md-focused {        background-color: '{{accent-700}}'; }    a.md-button.md-THEME_NAME-theme.md-accent:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-accent:not([disabled]) md-icon {      color: '{{accent-color}}'; }  a.md-button.md-THEME_NAME-theme[disabled], a.md-button.md-THEME_NAME-theme.md-raised[disabled], a.md-button.md-THEME_NAME-theme.md-fab[disabled], a.md-button.md-THEME_NAME-theme.md-accent[disabled], a.md-button.md-THEME_NAME-theme.md-warn[disabled], .md-button.md-THEME_NAME-theme[disabled], .md-button.md-THEME_NAME-theme.md-raised[disabled], .md-button.md-THEME_NAME-theme.md-fab[disabled], .md-button.md-THEME_NAME-theme.md-accent[disabled], .md-button.md-THEME_NAME-theme.md-warn[disabled] {    color: '{{foreground-3}}';    cursor: not-allowed; }    a.md-button.md-THEME_NAME-theme[disabled] md-icon, a.md-button.md-THEME_NAME-theme.md-raised[disabled] md-icon, a.md-button.md-THEME_NAME-theme.md-fab[disabled] md-icon, a.md-button.md-THEME_NAME-theme.md-accent[disabled] md-icon, a.md-button.md-THEME_NAME-theme.md-warn[disabled] md-icon, .md-button.md-THEME_NAME-theme[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-raised[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-fab[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-accent[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-warn[disabled] md-icon {      color: '{{foreground-3}}'; }  a.md-button.md-THEME_NAME-theme.md-raised[disabled], a.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-fab[disabled] {    background-color: '{{foreground-4}}'; }  a.md-button.md-THEME_NAME-theme[disabled], .md-button.md-THEME_NAME-theme[disabled] {    background-color: transparent; }md-card.md-THEME_NAME-theme {  background-color: '{{background-color}}';  border-radius: 2px; }  md-card.md-THEME_NAME-theme .md-card-image {    border-radius: 2px 2px 0 0; }md-checkbox.md-THEME_NAME-theme .md-ripple {  color: '{{accent-600}}'; }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-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: '{{background-200}}'; }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-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: '{{background-200}}'; }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-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] .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-label {  color: '{{foreground-3}}'; }md-chips.md-THEME_NAME-theme .md-chips {  box-shadow: 0 1px '{{background-300}}'; }  md-chips.md-THEME_NAME-theme .md-chips.md-focused {    box-shadow: 0 2px '{{primary-color}}'; }md-chips.md-THEME_NAME-theme .md-chip {  background: '{{background-300}}';  color: '{{background-800}}'; }  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-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 {  background-color: '{{background-color}}'; }md-dialog.md-THEME_NAME-theme {  border-radius: 4px;  background-color: '{{background-color}}'; }  md-dialog.md-THEME_NAME-theme.md-content-overflow .md-actions {    border-top-color: '{{foreground-4}}'; }md-divider.md-THEME_NAME-theme {  border-top-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}}';  text-shadow: '{{foreground-shadow}}'; }  md-input-container.md-THEME_NAME-theme .md-input::-webkit-input-placeholder, md-input-container.md-THEME_NAME-theme .md-input::-moz-placeholder, md-input-container.md-THEME_NAME-theme .md-input:-moz-placeholder, md-input-container.md-THEME_NAME-theme .md-input:-ms-input-placeholder {    color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme > md-icon {  color: '{{foreground-1}}'; }md-input-container.md-THEME_NAME-theme label, md-input-container.md-THEME_NAME-theme .md-placeholder {  text-shadow: '{{foreground-shadow}}';  color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme ng-messages, md-input-container.md-THEME_NAME-theme [ng-message], md-input-container.md-THEME_NAME-theme [data-ng-message], md-input-container.md-THEME_NAME-theme [x-ng-message] {  color: '{{warn-500}}'; }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 {  border-color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused label {  color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused md-icon {  color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input {  border-color: '{{accent-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent label {  color: '{{accent-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input {  border-color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn label {  color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input {  border-color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid.md-input-focused label {  color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid data-ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid x-ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid [ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid [data-ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid [x-ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid .md-char-counter {  color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme .md-input[disabled], [disabled] md-input-container.md-THEME_NAME-theme .md-input {  border-bottom-color: transparent;  color: '{{foreground-3}}';  background-image: linear-gradient(to right, '{{foreground-3}}' 0%, '{{foreground-3}}' 33%, transparent 0%);  background-image: -ms-linear-gradient(left, transparent 0%, '{{foreground-3}}' 100%); }md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h3, md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h4, md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h3, md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h4 {  color: '{{foreground-1}}'; }md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text p, md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text p {  color: '{{foreground-2}}'; }md-list.md-THEME_NAME-theme .md-proxy-focus.md-focused div.md-no-style {  background-color: '{{background-100}}'; }md-list.md-THEME_NAME-theme md-list-item > md-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-list.md-THEME_NAME-theme md-list-item button {  background-color: '{{background-color}}'; }  md-list.md-THEME_NAME-theme md-list-item button.md-button:not([disabled]):hover {    background-color: '{{background-color}}'; }md-progress-circular.md-THEME_NAME-theme {  background-color: transparent; }  md-progress-circular.md-THEME_NAME-theme .md-inner .md-gap {    border-top-color: '{{primary-color}}';    border-bottom-color: '{{primary-color}}'; }  md-progress-circular.md-THEME_NAME-theme .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme .md-inner .md-right .md-half-circle {    border-top-color: '{{primary-color}}'; }  md-progress-circular.md-THEME_NAME-theme .md-inner .md-right .md-half-circle {    border-right-color: '{{primary-color}}'; }  md-progress-circular.md-THEME_NAME-theme .md-inner .md-left .md-half-circle {    border-left-color: '{{primary-color}}'; }  md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-gap {    border-top-color: '{{warn-color}}';    border-bottom-color: '{{warn-color}}'; }  md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-right .md-half-circle {    border-top-color: '{{warn-color}}'; }  md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-right .md-half-circle {    border-right-color: '{{warn-color}}'; }  md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-left .md-half-circle {    border-left-color: '{{warn-color}}'; }  md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-gap {    border-top-color: '{{accent-color}}';    border-bottom-color: '{{accent-color}}'; }  md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-right .md-half-circle {    border-top-color: '{{accent-color}}'; }  md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-right .md-half-circle {    border-right-color: '{{accent-color}}'; }  md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-left .md-half-circle {    border-left-color: '{{accent-color}}'; }md-progress-linear.md-THEME_NAME-theme .md-container {  background-color: '{{primary-100}}'; }md-progress-linear.md-THEME_NAME-theme .md-bar {  background-color: '{{primary-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn .md-container {  background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn .md-bar {  background-color: '{{warn-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent .md-container {  background-color: '{{accent-100}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent .md-bar {  background-color: '{{accent-color}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-bar1 {  background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-dashed:before {  background: radial-gradient('{{warn-100}}' 0%, '{{warn-100}}' 16%, transparent 42%); }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-bar1 {  background-color: '{{accent-100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-dashed:before {  background: radial-gradient('{{accent-100}}' 0%, '{{accent-100}}' 16%, transparent 42%); }md-radio-button.md-THEME_NAME-theme .md-off {  border-color: '{{foreground-2}}'; }md-radio-button.md-THEME_NAME-theme .md-on {  background-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked .md-off {  border-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked .md-ink-ripple {  color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme .md-container .md-ripple {  color: '{{accent-600}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-on {  background-color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off {  border-color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple {  color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple {  color: '{{primary-600}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-on {  background-color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off {  border-color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple {  color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple {  color: '{{warn-600}}'; }md-radio-group.md-THEME_NAME-theme[disabled], md-radio-button.md-THEME_NAME-theme[disabled] {  color: '{{foreground-3}}'; }  md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-off, md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-off {    border-color: '{{foreground-3}}'; }  md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-on, md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-on {    border-color: '{{foreground-3}}'; }md-radio-group.md-THEME_NAME-theme.md-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:not([disabled]).md-primary .md-container:before {  background-color: '{{primary-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-primary .md-container:before {  background-color: '{{warn-color-0.26}}'; }md-select.md-THEME_NAME-theme.ng-invalid.ng-dirty .md-select-label {  color: '{{warn-500}}' !important;  border-bottom-color: '{{warn-500}}' !important; }md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-label {  border-bottom-color: '{{primary-color}}';  color: '{{ foreground-1 }}'; }  md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-label.md-placeholder {    color: '{{ foreground-1 }}'; }md-select.md-THEME_NAME-theme:not([disabled]):focus.md-accent .md-select-label {  border-bottom-color: '{{accent-color}}'; }md-select.md-THEME_NAME-theme:not([disabled]):focus.md-warn .md-select-label {  border-bottom-color: '{{warn-color}}'; }md-select.md-THEME_NAME-theme[disabled] .md-select-label {  color: '{{foreground-3}}'; }  md-select.md-THEME_NAME-theme[disabled] .md-select-label.md-placeholder {    color: '{{foreground-3}}'; }md-select.md-THEME_NAME-theme .md-select-label {  border-bottom-color: '{{foreground-4}}'; }  md-select.md-THEME_NAME-theme .md-select-label.md-placeholder {    color: '{{foreground-2}}'; }md-select-menu.md-THEME_NAME-theme md-optgroup {  color: '{{foreground-2}}'; }  md-select-menu.md-THEME_NAME-theme md-optgroup md-option {    color: '{{foreground-1}}'; }md-select-menu.md-THEME_NAME-theme md-option[selected] {  color: '{{primary-500}}'; }  md-select-menu.md-THEME_NAME-theme md-option[selected]:focus {    color: '{{primary-600}}'; }  md-select-menu.md-THEME_NAME-theme md-option[selected].md-accent {    color: '{{accent-500}}'; }    md-select-menu.md-THEME_NAME-theme md-option[selected].md-accent:focus {      color: '{{accent-600}}'; }md-select-menu.md-THEME_NAME-theme md-option:focus:not([selected]) {  background: '{{background-200}}'; }md-sidenav.md-THEME_NAME-theme {  background-color: '{{background-color}}'; }md-slider.md-THEME_NAME-theme .md-track {  background-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme .md-track-ticks {  background-color: '{{foreground-4}}'; }md-slider.md-THEME_NAME-theme .md-focus-thumb {  background-color: '{{foreground-2}}'; }md-slider.md-THEME_NAME-theme .md-focus-ring {  border-color: '{{foreground-4}}'; }md-slider.md-THEME_NAME-theme .md-disabled-thumb {  border-color: '{{background-color}}'; }md-slider.md-THEME_NAME-theme.md-min .md-thumb:after {  background-color: '{{background-color}}'; }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-thumb-text {  color: '{{accent-contrast}}'; }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-thumb-text {  color: '{{warn-contrast}}'; }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-thumb-text {  color: '{{primary-contrast}}'; }md-slider.md-THEME_NAME-theme[disabled] .md-thumb:after {  border-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme[disabled]:not(.md-min) .md-thumb:after {  background-color: '{{foreground-3}}'; }.md-subheader.md-THEME_NAME-theme {  color: '{{ foreground-2-0.23 }}';  background-color: '{{background-color}}'; }  .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-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-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-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-thumb {  background-color: '{{warn-color}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-warn .md-bar {  background-color: '{{warn-color-0.5}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-warn.md-focused .md-thumb:before {  background-color: '{{warn-color-0.26}}'; }md-switch.md-THEME_NAME-theme[disabled] .md-thumb {  background-color: '{{background-400}}'; }md-switch.md-THEME_NAME-theme[disabled] .md-bar {  background-color: '{{foreground-4}}'; }md-tabs.md-THEME_NAME-theme md-tabs-wrapper {  background-color: transparent;  border-color: '{{foreground-4}}'; }md-tabs.md-THEME_NAME-theme .md-paginator md-icon {  color: '{{primary-color}}'; }md-tabs.md-THEME_NAME-theme md-ink-bar {  color: '{{accent-color}}';  background: '{{accent-color}}'; }md-tabs.md-THEME_NAME-theme .md-tab {  color: '{{foreground-2}}'; }  md-tabs.md-THEME_NAME-theme .md-tab[disabled] {    color: '{{foreground-3}}'; }  md-tabs.md-THEME_NAME-theme .md-tab.md-active, md-tabs.md-THEME_NAME-theme .md-tab.md-focused {    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-100}}'; }md-tabs.md-THEME_NAME-theme.md-accent md-tabs-wrapper {  background-color: '{{accent-color}}'; }md-tabs.md-THEME_NAME-theme.md-accent md-tab-item:not([disabled]) {  color: '{{accent-100}}'; }  md-tabs.md-THEME_NAME-theme.md-accent md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-accent md-tab-item:not([disabled]).md-focused {    color: '{{accent-contrast}}'; }  md-tabs.md-THEME_NAME-theme.md-accent md-tab-item:not([disabled]).md-focused {    background: '{{accent-contrast-0.1}}'; }md-tabs.md-THEME_NAME-theme.md-accent 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-tab-item:not([disabled]) {  color: '{{primary-100}}'; }  md-tabs.md-THEME_NAME-theme.md-primary md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-primary md-tab-item:not([disabled]).md-focused {    color: '{{primary-contrast}}'; }  md-tabs.md-THEME_NAME-theme.md-primary 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-tab-item:not([disabled]) {  color: '{{warn-100}}'; }  md-tabs.md-THEME_NAME-theme.md-warn md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-warn md-tab-item:not([disabled]).md-focused {    color: '{{warn-contrast}}'; }  md-tabs.md-THEME_NAME-theme.md-warn 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-tab-item:not([disabled]) {  color: '{{primary-100}}'; }  md-toolbar > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-active, md-toolbar > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused {    color: '{{primary-contrast}}'; }  md-toolbar > md-tabs.md-THEME_NAME-theme 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-tab-item:not([disabled]) {  color: '{{accent-100}}'; }  md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-active, md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused {    color: '{{accent-contrast}}'; }  md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused {    background: '{{accent-contrast-0.1}}'; }md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme 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-tab-item:not([disabled]) {  color: '{{warn-100}}'; }  md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-active, md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused {    color: '{{warn-contrast}}'; }  md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused {    background: '{{warn-contrast-0.1}}'; }md-toast.md-THEME_NAME-theme {  background-color: #323232;  color: '{{background-50}}'; }  md-toast.md-THEME_NAME-theme .md-button {    color: '{{background-50}}'; }    md-toast.md-THEME_NAME-theme .md-button.md-highlight {      color: '{{primary-A200}}'; }      md-toast.md-THEME_NAME-theme .md-button.md-highlight.md-accent {        color: '{{accent-A200}}'; }      md-toast.md-THEME_NAME-theme .md-button.md-highlight.md-warn {        color: '{{warn-A200}}'; }md-toolbar.md-THEME_NAME-theme {  background-color: '{{primary-color}}';  color: '{{primary-contrast}}'; }  md-toolbar.md-THEME_NAME-theme md-icon {    color: '{{primary-contrast}}'; }  md-toolbar.md-THEME_NAME-theme .md-button {    color: '{{primary-contrast}}'; }  md-toolbar.md-THEME_NAME-theme.md-accent {    background-color: '{{accent-color}}';    color: '{{accent-contrast}}'; }  md-toolbar.md-THEME_NAME-theme.md-warn {    background-color: '{{warn-color}}';    color: '{{warn-contrast}}'; }md-tooltip.md-THEME_NAME-theme {  color: '{{background-A100}}'; }  md-tooltip.md-THEME_NAME-theme .md-background {    background-color: '{{foreground-2}}'; }"); 
13906 })();
13907
13908
13909 })(window, window.angular);