wrapper inside (preferred) or outside\n\n usage = \"<\" + nodeName + \" \" + className + \">\" + nodeName + \">\";\n url = \"https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers\";\n message = \"Markup '{0}' may not work as expected in IE Browsers. Consult '{1}' for details.\";\n\n $log.warn($mdUtil.supplant(message, [usage, url]));\n }\n }\n\n }\n\n\n /**\n * For the Layout attribute value, validate or replace with default\n * fallback value\n */\n function validateAttributeValue(className, value, updateFn) {\n var origValue = value;\n\n if (!needsInterpolation(value)) {\n switch (className.replace(SUFFIXES,\"\")) {\n case 'layout' :\n if (!findIn(value, LAYOUT_OPTIONS)) {\n value = LAYOUT_OPTIONS[0]; // 'row';\n }\n break;\n\n case 'flex' :\n if (!findIn(value, FLEX_OPTIONS)) {\n if (isNaN(value)) {\n value = '';\n }\n }\n break;\n\n case 'flex-offset' :\n case 'flex-order' :\n if (!value || isNaN(+value)) {\n value = '0';\n }\n break;\n\n case 'layout-align' :\n var axis = extractAlignAxis(value);\n value = $mdUtil.supplant(\"{main}-{cross}\",axis);\n break;\n\n case 'layout-padding' :\n case 'layout-margin' :\n case 'layout-fill' :\n case 'layout-wrap' :\n case 'layout-nowrap' :\n value = '';\n break;\n }\n\n if (value != origValue) {\n (updateFn || angular.noop)(value);\n }\n }\n\n return value ? value.trim() : \"\";\n }\n\n /**\n * Replace current attribute value with fallback value\n */\n function buildUpdateFn(element, className, attrs) {\n return function updateAttrValue(fallback) {\n if (!needsInterpolation(fallback)) {\n // Do not modify the element's attribute value; so\n // uses '
' will not\n // be affected. Just update the attrs value.\n attrs[attrs.$normalize(className)] = fallback;\n }\n };\n }\n\n /**\n * See if the original value has interpolation symbols:\n * e.g. flex-gt-md=\"{{triggerPoint}}\"\n */\n function needsInterpolation(value) {\n return (value || \"\").indexOf($interpolate.startSymbol()) > -1;\n }\n\n function getNormalizedAttrValue(className, attrs, defaultVal) {\n var normalizedAttr = attrs.$normalize(className);\n return attrs[normalizedAttr] ? attrs[normalizedAttr].trim().replace(WHITESPACE, \"-\") : defaultVal || null;\n }\n\n function findIn(item, list, replaceWith) {\n item = replaceWith && item ? item.replace(WHITESPACE, replaceWith) : item;\n\n var found = false;\n if (item) {\n list.forEach(function(it) {\n it = replaceWith ? it.replace(WHITESPACE, replaceWith) : it;\n found = found || (it === item);\n });\n }\n return found;\n }\n\n function extractAlignAxis(attrValue) {\n var axis = {\n main : \"start\",\n cross: \"stretch\"\n }, values;\n\n attrValue = (attrValue || \"\");\n\n if (attrValue.indexOf(\"-\") === 0 || attrValue.indexOf(\" \") === 0) {\n // For missing main-axis values\n attrValue = \"none\" + attrValue;\n }\n\n values = attrValue.toLowerCase().trim().replace(WHITESPACE, \"-\").split(\"-\");\n if (values.length && (values[0] === \"space\")) {\n // for main-axis values of \"space-around\" or \"space-between\"\n values = [values[0]+\"-\"+values[1],values[2]];\n }\n\n if (values.length > 0) axis.main = values[0] || axis.main;\n if (values.length > 1) axis.cross = values[1] || axis.cross;\n\n if (ALIGNMENT_MAIN_AXIS.indexOf(axis.main) < 0) axis.main = \"start\";\n if (ALIGNMENT_CROSS_AXIS.indexOf(axis.cross) < 0) axis.cross = \"stretch\";\n\n return axis;\n }\n\n\n})();\n\n})();\n(function(){\n\"use strict\";\n\n/**\r\n * @ngdoc module\r\n * @name material.core.liveannouncer\r\n * @description\r\n * AngularJS Material Live Announcer to provide accessibility for Voice Readers.\r\n */\r\nMdLiveAnnouncer.$inject = [\"$timeout\"];\r\nangular\r\n .module('material.core')\r\n .service('$mdLiveAnnouncer', MdLiveAnnouncer);\r\n\r\n/**\r\n * @ngdoc service\r\n * @name $mdLiveAnnouncer\r\n * @module material.core.liveannouncer\r\n *\r\n * @description\r\n *\r\n * Service to announce messages to supported screenreaders.\r\n *\r\n * > The `$mdLiveAnnouncer` service is internally used for components to provide proper accessibility.\r\n *\r\n *
\r\n * module.controller('AppCtrl', function($mdLiveAnnouncer) {\r\n * // Basic announcement (Polite Mode)\r\n * $mdLiveAnnouncer.announce('Hey Google');\r\n *\r\n * // Custom announcement (Assertive Mode)\r\n * $mdLiveAnnouncer.announce('Hey Google', 'assertive');\r\n * });\r\n * \r\n *\r\n */\r\nfunction MdLiveAnnouncer($timeout) {\r\n /** @private @const @type {!angular.$timeout} */\r\n this._$timeout = $timeout;\r\n\r\n /** @private @const @type {!HTMLElement} */\r\n this._liveElement = this._createLiveElement();\r\n\r\n /** @private @const @type {!number} */\r\n this._announceTimeout = 100;\r\n}\r\n\r\n/**\r\n * @ngdoc method\r\n * @name $mdLiveAnnouncer#announce\r\n * @description Announces messages to supported screenreaders.\r\n * @param {string} message Message to be announced to the screenreader\r\n * @param {'off'|'polite'|'assertive'} politeness The politeness of the announcer element.\r\n */\r\nMdLiveAnnouncer.prototype.announce = function(message, politeness) {\r\n if (!politeness) {\r\n politeness = 'polite';\r\n }\r\n\r\n var self = this;\r\n\r\n self._liveElement.textContent = '';\r\n self._liveElement.setAttribute('aria-live', politeness);\r\n\r\n // This 100ms timeout is necessary for some browser + screen-reader combinations:\r\n // - Both JAWS and NVDA over IE11 will not announce anything without a non-zero timeout.\r\n // - With Chrome and IE11 with NVDA or JAWS, a repeated (identical) message won't be read a\r\n // second time without clearing and then using a non-zero delay.\r\n // (using JAWS 17 at time of this writing).\r\n self._$timeout(function() {\r\n self._liveElement.textContent = message;\r\n }, self._announceTimeout, false);\r\n};\r\n\r\n/**\r\n * Creates a live announcer element, which listens for DOM changes and announces them\r\n * to the screenreaders.\r\n * @returns {!HTMLElement}\r\n * @private\r\n */\r\nMdLiveAnnouncer.prototype._createLiveElement = function() {\r\n var liveEl = document.createElement('div');\r\n\r\n liveEl.classList.add('md-visually-hidden');\r\n liveEl.setAttribute('role', 'status');\r\n liveEl.setAttribute('aria-atomic', 'true');\r\n liveEl.setAttribute('aria-live', 'polite');\r\n\r\n document.body.appendChild(liveEl);\r\n\r\n return liveEl;\r\n};\r\n\n})();\n(function(){\n\"use strict\";\n\n/**\n * @ngdoc service\n * @name $$mdMeta\n * @module material.core.meta\n *\n * @description\n *\n * A provider and a service that simplifies meta tags access\n *\n * Note: This is intended only for use with dynamic meta tags such as browser color and title.\n * Tags that are only processed when the page is rendered (such as `charset`, and `http-equiv`)\n * will not work since `$$mdMeta` adds the tags after the page has already been loaded.\n *\n * ```js\n * app.config(function($$mdMetaProvider) {\n * var removeMeta = $$mdMetaProvider.setMeta('meta-name', 'content');\n * var metaValue = $$mdMetaProvider.getMeta('meta-name'); // -> 'content'\n *\n * removeMeta();\n * });\n *\n * app.controller('myController', function($$mdMeta) {\n * var removeMeta = $$mdMeta.setMeta('meta-name', 'content');\n * var metaValue = $$mdMeta.getMeta('meta-name'); // -> 'content'\n *\n * removeMeta();\n * });\n * ```\n *\n * @returns {$$mdMeta.$service}\n *\n */\nangular.module('material.core.meta', [])\n .provider('$$mdMeta', function () {\n var head = angular.element(document.head);\n var metaElements = {};\n\n /**\n * Checks if the requested element was written manually and maps it\n *\n * @param {string} name meta tag 'name' attribute value\n * @returns {boolean} returns true if there is an element with the requested name\n */\n function mapExistingElement(name) {\n if (metaElements[name]) {\n return true;\n }\n\n var element = document.getElementsByName(name)[0];\n\n if (!element) {\n return false;\n }\n\n metaElements[name] = angular.element(element);\n\n return true;\n }\n\n /**\n * @ngdoc method\n * @name $$mdMeta#setMeta\n *\n * @description\n * Creates meta element with the 'name' and 'content' attributes,\n * if the meta tag is already created than we replace the 'content' value\n *\n * @param {string} name meta tag 'name' attribute value\n * @param {string} content meta tag 'content' attribute value\n * @returns {function} remove function\n *\n */\n function setMeta(name, content) {\n mapExistingElement(name);\n\n if (!metaElements[name]) {\n var newMeta = angular.element('
');\n head.append(newMeta);\n metaElements[name] = newMeta;\n }\n else {\n metaElements[name].attr('content', content);\n }\n\n return function () {\n metaElements[name].attr('content', '');\n metaElements[name].remove();\n delete metaElements[name];\n };\n }\n\n /**\n * @ngdoc method\n * @name $$mdMeta#getMeta\n *\n * @description\n * Gets the 'content' attribute value of the wanted meta element\n *\n * @param {string} name meta tag 'name' attribute value\n * @returns {string} content attribute value\n */\n function getMeta(name) {\n if (!mapExistingElement(name)) {\n throw Error('$$mdMeta: could not find a meta tag with the name \\'' + name + '\\'');\n }\n\n return metaElements[name].attr('content');\n }\n\n var module = {\n setMeta: setMeta,\n getMeta: getMeta\n };\n\n return angular.extend({}, module, {\n $get: function () {\n return module;\n }\n });\n });\n})();\n(function(){\n\"use strict\";\n\n /**\n * @ngdoc module\n * @name material.core.componentRegistry\n *\n * @description\n * A component instance registration service.\n * Note: currently this as a private service in the SideNav component.\n */\n ComponentRegistry.$inject = [\"$log\", \"$q\"];\n angular.module('material.core')\n .factory('$mdComponentRegistry', ComponentRegistry);\n\n /*\n * @private\n * @ngdoc factory\n * @name ComponentRegistry\n * @module material.core.componentRegistry\n *\n */\n function ComponentRegistry($log, $q) {\n\n var self;\n var instances = [];\n var pendings = { };\n\n return self = {\n /**\n * Used to print an error when an instance for a handle isn't found.\n */\n notFoundError: function(handle, msgContext) {\n $log.error((msgContext || \"\") + 'No instance found for handle', handle);\n },\n /**\n * Return all registered instances as an array.\n */\n getInstances: function() {\n return instances;\n },\n\n /**\n * Get a registered instance.\n * @param handle the String handle to look up for a registered instance.\n */\n get: function(handle) {\n if (!isValidID(handle)) return null;\n\n var i, j, instance;\n for (i = 0, j = instances.length; i < j; i++) {\n instance = instances[i];\n if (instance.$$mdHandle === handle) {\n return instance;\n }\n }\n return null;\n },\n\n /**\n * Register an instance.\n * @param instance the instance to register\n * @param handle the handle to identify the instance under.\n */\n register: function(instance, handle) {\n if (!handle) return angular.noop;\n\n instance.$$mdHandle = handle;\n instances.push(instance);\n resolveWhen();\n\n return deregister;\n\n /**\n * Remove registration for an instance\n */\n function deregister() {\n var index = instances.indexOf(instance);\n if (index !== -1) {\n instances.splice(index, 1);\n }\n }\n\n /**\n * Resolve any pending promises for this instance\n */\n function resolveWhen() {\n var dfd = pendings[handle];\n if (dfd) {\n dfd.forEach(function (promise) {\n promise.resolve(instance);\n });\n delete pendings[handle];\n }\n }\n },\n\n /**\n * Async accessor to registered component instance\n * If not available then a promise is created to notify\n * all listeners when the instance is registered.\n */\n when : function(handle) {\n if (isValidID(handle)) {\n var deferred = $q.defer();\n var instance = self.get(handle);\n\n if (instance) {\n deferred.resolve(instance);\n } else {\n if (pendings[handle] === undefined) {\n pendings[handle] = [];\n }\n pendings[handle].push(deferred);\n }\n\n return deferred.promise;\n }\n return $q.reject(\"Invalid `md-component-id` value.\");\n }\n\n };\n\n function isValidID(handle){\n return handle && (handle !== \"\");\n }\n\n }\n\n})();\n(function(){\n\"use strict\";\n\n(function() {\n 'use strict';\n\n /**\n * @ngdoc service\n * @name $mdButtonInkRipple\n * @module material.core\n *\n * @description\n * Provides ripple effects for md-button. See $mdInkRipple service for all possible configuration options.\n *\n * @param {object=} scope Scope within the current context\n * @param {object=} element The element the ripple effect should be applied to\n * @param {object=} options (Optional) Configuration options to override the default ripple configuration\n */\n\n MdButtonInkRipple.$inject = [\"$mdInkRipple\"];\n angular.module('material.core')\n .factory('$mdButtonInkRipple', MdButtonInkRipple);\n\n function MdButtonInkRipple($mdInkRipple) {\n return {\n attach: function attachRipple(scope, element, options) {\n options = angular.extend(optionsForElement(element), options);\n\n return $mdInkRipple.attach(scope, element, options);\n }\n };\n\n function optionsForElement(element) {\n if (element.hasClass('md-icon-button')) {\n return {\n isMenuItem: element.hasClass('md-menu-item'),\n fitRipple: true,\n center: true\n };\n } else {\n return {\n isMenuItem: element.hasClass('md-menu-item'),\n dimBackground: true\n };\n }\n }\n }\n})();\n\n})();\n(function(){\n\"use strict\";\n\n(function() {\n 'use strict';\n\n /**\n * @ngdoc service\n * @name $mdCheckboxInkRipple\n * @module material.core\n *\n * @description\n * Provides ripple effects for md-checkbox. See $mdInkRipple service for all possible configuration options.\n *\n * @param {object=} scope Scope within the current context\n * @param {object=} element The element the ripple effect should be applied to\n * @param {object=} options (Optional) Configuration options to override the defaultripple configuration\n */\n\n MdCheckboxInkRipple.$inject = [\"$mdInkRipple\"];\n angular.module('material.core')\n .factory('$mdCheckboxInkRipple', MdCheckboxInkRipple);\n\n function MdCheckboxInkRipple($mdInkRipple) {\n return {\n attach: attach\n };\n\n function attach(scope, element, options) {\n return $mdInkRipple.attach(scope, element, angular.extend({\n center: true,\n dimBackground: false,\n fitRipple: true\n }, options));\n }\n }\n})();\n\n})();\n(function(){\n\"use strict\";\n\n(function() {\n 'use strict';\n\n /**\n * @ngdoc service\n * @name $mdListInkRipple\n * @module material.core\n *\n * @description\n * Provides ripple effects for md-list. See $mdInkRipple service for all possible configuration options.\n *\n * @param {object=} scope Scope within the current context\n * @param {object=} element The element the ripple effect should be applied to\n * @param {object=} options (Optional) Configuration options to override the defaultripple configuration\n */\n\n MdListInkRipple.$inject = [\"$mdInkRipple\"];\n angular.module('material.core')\n .factory('$mdListInkRipple', MdListInkRipple);\n\n function MdListInkRipple($mdInkRipple) {\n return {\n attach: attach\n };\n\n function attach(scope, element, options) {\n return $mdInkRipple.attach(scope, element, angular.extend({\n center: false,\n dimBackground: true,\n outline: false,\n rippleSize: 'full'\n }, options));\n }\n }\n})();\n\n})();\n(function(){\n\"use strict\";\n\n/**\n * @ngdoc module\n * @name material.core.ripple\n * @description\n * Ripple\n */\nInkRippleCtrl.$inject = [\"$scope\", \"$element\", \"rippleOptions\", \"$window\", \"$timeout\", \"$mdUtil\", \"$mdColorUtil\"];\nInkRippleDirective.$inject = [\"$mdButtonInkRipple\", \"$mdCheckboxInkRipple\"];\nangular.module('material.core')\n .provider('$mdInkRipple', InkRippleProvider)\n .directive('mdInkRipple', InkRippleDirective)\n .directive('mdNoInk', attrNoDirective)\n .directive('mdNoBar', attrNoDirective)\n .directive('mdNoStretch', attrNoDirective);\n\nvar DURATION = 450;\n\n/**\n * @ngdoc directive\n * @name mdInkRipple\n * @module material.core.ripple\n *\n * @description\n * The `md-ink-ripple` directive allows you to specify the ripple color or if a ripple is allowed.\n *\n * @param {string|boolean} md-ink-ripple A color string `#FF0000` or boolean (`false` or `0`) for\n * preventing ripple\n *\n * @usage\n * ### String values\n *
\n * \n * Ripples in red\n * \n *\n * \n * Not rippling\n * \n * \n *\n * ### Interpolated values\n *
\n * \n * Ripples with the return value of 'randomColor' function\n * \n *\n * \n * Ripples if 'canRipple' function return value is not 'false' or '0'\n * \n * \n */\nfunction InkRippleDirective ($mdButtonInkRipple, $mdCheckboxInkRipple) {\n return {\n controller: angular.noop,\n link: function (scope, element, attr) {\n attr.hasOwnProperty('mdInkRippleCheckbox')\n ? $mdCheckboxInkRipple.attach(scope, element)\n : $mdButtonInkRipple.attach(scope, element);\n }\n };\n}\n\n/**\n * @ngdoc service\n * @name $mdInkRipple\n * @module material.core.ripple\n *\n * @description\n * `$mdInkRipple` is a service for adding ripples to any element.\n *\n * @usage\n *
\n * app.factory('$myElementInkRipple', function($mdInkRipple) {\n * return {\n * attach: function (scope, element, options) {\n * return $mdInkRipple.attach(scope, element, angular.extend({\n * center: false,\n * dimBackground: true\n * }, options));\n * }\n * };\n * });\n *\n * app.controller('myController', function ($scope, $element, $myElementInkRipple) {\n * $scope.onClick = function (ev) {\n * $myElementInkRipple.attach($scope, angular.element(ev.target), { center: true });\n * }\n * });\n * \n */\n\n/**\n * @ngdoc service\n * @name $mdInkRippleProvider\n * @module material.core.ripple\n *\n * @description\n * If you want to disable ink ripples globally, for all components, you can call the\n * `disableInkRipple` method in your app's config.\n *\n *\n * @usage\n *
\n * app.config(function ($mdInkRippleProvider) {\n * $mdInkRippleProvider.disableInkRipple();\n * });\n * \n */\n\nfunction InkRippleProvider () {\n var isDisabledGlobally = false;\n\n return {\n disableInkRipple: disableInkRipple,\n $get: [\"$injector\", function($injector) {\n return { attach: attach };\n\n /**\n * @ngdoc method\n * @name $mdInkRipple#attach\n *\n * @description\n * Attaching given scope, element and options to inkRipple controller\n *\n * @param {object=} scope Scope within the current context\n * @param {object=} element The element the ripple effect should be applied to\n * @param {object=} options (Optional) Configuration options to override the defaultRipple configuration\n * * `center` - Whether the ripple should start from the center of the container element\n * * `dimBackground` - Whether the background should be dimmed with the ripple color\n * * `colorElement` - The element the ripple should take its color from, defined by css property `color`\n * * `fitRipple` - Whether the ripple should fill the element\n */\n function attach (scope, element, options) {\n if (isDisabledGlobally || element.controller('mdNoInk')) return angular.noop;\n return $injector.instantiate(InkRippleCtrl, {\n $scope: scope,\n $element: element,\n rippleOptions: options\n });\n }\n }]\n };\n\n /**\n * @ngdoc method\n * @name $mdInkRippleProvider#disableInkRipple\n *\n * @description\n * A config-time method that, when called, disables ripples globally.\n */\n function disableInkRipple () {\n isDisabledGlobally = true;\n }\n}\n\n/**\n * Controller used by the ripple service in order to apply ripples\n * @ngInject\n */\nfunction InkRippleCtrl ($scope, $element, rippleOptions, $window, $timeout, $mdUtil, $mdColorUtil) {\n this.$window = $window;\n this.$timeout = $timeout;\n this.$mdUtil = $mdUtil;\n this.$mdColorUtil = $mdColorUtil;\n this.$scope = $scope;\n this.$element = $element;\n this.options = rippleOptions;\n this.mousedown = false;\n this.ripples = [];\n this.timeout = null; // Stores a reference to the most-recent ripple timeout\n this.lastRipple = null;\n\n $mdUtil.valueOnUse(this, 'container', this.createContainer);\n\n this.$element.addClass('md-ink-ripple');\n\n // attach method for unit tests\n ($element.controller('mdInkRipple') || {}).createRipple = angular.bind(this, this.createRipple);\n ($element.controller('mdInkRipple') || {}).setColor = angular.bind(this, this.color);\n\n this.bindEvents();\n}\n\n\n/**\n * Either remove or unlock any remaining ripples when the user mouses off of the element (either by\n * mouseup or mouseleave event)\n */\nfunction autoCleanup (self, cleanupFn) {\n if (self.mousedown || self.lastRipple) {\n self.mousedown = false;\n self.$mdUtil.nextTick(angular.bind(self, cleanupFn), false);\n }\n}\n\n\n/**\n * Returns the color that the ripple should be (either based on CSS or hard-coded)\n * @returns {string}\n */\nInkRippleCtrl.prototype.color = function (value) {\n var self = this;\n\n // If assigning a color value, apply it to background and the ripple color\n if (angular.isDefined(value)) {\n self._color = self._parseColor(value);\n }\n\n // If color lookup, use assigned, defined, or inherited\n return self._color || self._parseColor(self.inkRipple()) || self._parseColor(getElementColor());\n\n /**\n * Finds the color element and returns its text color for use as default ripple color\n * @returns {string}\n */\n function getElementColor () {\n var items = self.options && self.options.colorElement ? self.options.colorElement : [];\n var elem = items.length ? items[ 0 ] : self.$element[ 0 ];\n\n return elem ? self.$window.getComputedStyle(elem).color : 'rgb(0,0,0)';\n }\n};\n\n/**\n * Updating the ripple colors based on the current inkRipple value\n * or the element's computed style color\n */\nInkRippleCtrl.prototype.calculateColor = function () {\n return this.color();\n};\n\n\n/**\n * Takes a string color and converts it to RGBA format\n * @param {string} color\n * @param {number} multiplier\n * @returns {string}\n */\nInkRippleCtrl.prototype._parseColor = function parseColor (color, multiplier) {\n multiplier = multiplier || 1;\n var colorUtil = this.$mdColorUtil;\n\n if (!color) return;\n if (color.indexOf('rgba') === 0) return color.replace(/\\d?\\.?\\d*\\s*\\)\\s*$/, (0.1 * multiplier).toString() + ')');\n if (color.indexOf('rgb') === 0) return colorUtil.rgbToRgba(color);\n if (color.indexOf('#') === 0) return colorUtil.hexToRgba(color);\n\n};\n\n/**\n * Binds events to the root element for\n */\nInkRippleCtrl.prototype.bindEvents = function () {\n this.$element.on('mousedown', angular.bind(this, this.handleMousedown));\n this.$element.on('mouseup touchend', angular.bind(this, this.handleMouseup));\n this.$element.on('mouseleave', angular.bind(this, this.handleMouseup));\n this.$element.on('touchmove', angular.bind(this, this.handleTouchmove));\n};\n\n/**\n * Create a new ripple on every mousedown event from the root element\n * @param event {MouseEvent}\n */\nInkRippleCtrl.prototype.handleMousedown = function (event) {\n if (this.mousedown) return;\n\n // When jQuery is loaded, we have to get the original event\n if (event.hasOwnProperty('originalEvent')) event = event.originalEvent;\n this.mousedown = true;\n if (this.options.center) {\n this.createRipple(this.container.prop('clientWidth') / 2, this.container.prop('clientWidth') / 2);\n } else {\n\n // We need to calculate the relative coordinates if the target is a sublayer of the ripple element\n if (event.srcElement !== this.$element[0]) {\n var layerRect = this.$element[0].getBoundingClientRect();\n var layerX = event.clientX - layerRect.left;\n var layerY = event.clientY - layerRect.top;\n\n this.createRipple(layerX, layerY);\n } else {\n this.createRipple(event.offsetX, event.offsetY);\n }\n }\n};\n\n/**\n * Either remove or unlock any remaining ripples when the user mouses off of the element (either by\n * mouseup, touchend or mouseleave event)\n */\nInkRippleCtrl.prototype.handleMouseup = function () {\n this.$timeout(function () {\n autoCleanup(this, this.clearRipples);\n }.bind(this));\n};\n\n/**\n * Either remove or unlock any remaining ripples when the user mouses off of the element (by\n * touchmove)\n */\nInkRippleCtrl.prototype.handleTouchmove = function () {\n autoCleanup(this, this.deleteRipples);\n};\n\n/**\n * Cycles through all ripples and attempts to remove them.\n */\nInkRippleCtrl.prototype.deleteRipples = function () {\n for (var i = 0; i < this.ripples.length; i++) {\n this.ripples[ i ].remove();\n }\n};\n\n/**\n * Cycles through all ripples and attempts to remove them with fade.\n * Depending on logic within `fadeInComplete`, some removals will be postponed.\n */\nInkRippleCtrl.prototype.clearRipples = function () {\n for (var i = 0; i < this.ripples.length; i++) {\n this.fadeInComplete(this.ripples[ i ]);\n }\n};\n\n/**\n * Creates the ripple container element\n * @returns {*}\n */\nInkRippleCtrl.prototype.createContainer = function () {\n var container = angular.element('
');\n this.$element.append(container);\n return container;\n};\n\nInkRippleCtrl.prototype.clearTimeout = function () {\n if (this.timeout) {\n this.$timeout.cancel(this.timeout);\n this.timeout = null;\n }\n};\n\nInkRippleCtrl.prototype.isRippleAllowed = function () {\n var element = this.$element[0];\n do {\n if (!element.tagName || element.tagName === 'BODY') break;\n\n if (element && angular.isFunction(element.hasAttribute)) {\n if (element.hasAttribute('disabled')) return false;\n if (this.inkRipple() === 'false' || this.inkRipple() === '0') return false;\n }\n\n } while (element = element.parentNode);\n return true;\n};\n\n/**\n * The attribute `md-ink-ripple` may be a static or interpolated\n * color value OR a boolean indicator (used to disable ripples)\n */\nInkRippleCtrl.prototype.inkRipple = function () {\n return this.$element.attr('md-ink-ripple');\n};\n\n/**\n * Creates a new ripple and adds it to the container. Also tracks ripple in `this.ripples`.\n * @param left\n * @param top\n */\nInkRippleCtrl.prototype.createRipple = function (left, top) {\n if (!this.isRippleAllowed()) return;\n\n var ctrl = this;\n var colorUtil = ctrl.$mdColorUtil;\n var ripple = angular.element('
');\n var width = this.$element.prop('clientWidth');\n var height = this.$element.prop('clientHeight');\n var x = Math.max(Math.abs(width - left), left) * 2;\n var y = Math.max(Math.abs(height - top), top) * 2;\n var size = getSize(this.options.fitRipple, x, y);\n var color = this.calculateColor();\n\n ripple.css({\n left: left + 'px',\n top: top + 'px',\n background: 'black',\n width: size + 'px',\n height: size + 'px',\n backgroundColor: colorUtil.rgbaToRgb(color),\n borderColor: colorUtil.rgbaToRgb(color)\n });\n this.lastRipple = ripple;\n\n // we only want one timeout to be running at a time\n this.clearTimeout();\n this.timeout = this.$timeout(function () {\n ctrl.clearTimeout();\n if (!ctrl.mousedown) ctrl.fadeInComplete(ripple);\n }, DURATION * 0.35, false);\n\n if (this.options.dimBackground) this.container.css({ backgroundColor: color });\n this.container.append(ripple);\n this.ripples.push(ripple);\n ripple.addClass('md-ripple-placed');\n\n this.$mdUtil.nextTick(function () {\n\n ripple.addClass('md-ripple-scaled md-ripple-active');\n ctrl.$timeout(function () {\n ctrl.clearRipples();\n }, DURATION, false);\n\n }, false);\n\n function getSize (fit, x, y) {\n return fit\n ? Math.max(x, y)\n : Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));\n }\n};\n\n\n\n/**\n * After fadeIn finishes, either kicks off the fade-out animation or queues the element for removal on mouseup\n * @param ripple\n */\nInkRippleCtrl.prototype.fadeInComplete = function (ripple) {\n if (this.lastRipple === ripple) {\n if (!this.timeout && !this.mousedown) {\n this.removeRipple(ripple);\n }\n } else {\n this.removeRipple(ripple);\n }\n};\n\n/**\n * Kicks off the animation for removing a ripple\n * @param ripple {Element}\n */\nInkRippleCtrl.prototype.removeRipple = function (ripple) {\n var ctrl = this;\n var index = this.ripples.indexOf(ripple);\n if (index < 0) return;\n this.ripples.splice(this.ripples.indexOf(ripple), 1);\n ripple.removeClass('md-ripple-active');\n ripple.addClass('md-ripple-remove');\n if (this.ripples.length === 0) this.container.css({ backgroundColor: '' });\n // use a 2-second timeout in order to allow for the animation to finish\n // we don't actually care how long the animation takes\n this.$timeout(function () {\n ctrl.fadeOutComplete(ripple);\n }, DURATION, false);\n};\n\n/**\n * Removes the provided ripple from the DOM\n * @param ripple\n */\nInkRippleCtrl.prototype.fadeOutComplete = function (ripple) {\n ripple.remove();\n this.lastRipple = null;\n};\n\n/**\n * Used to create an empty directive. This is used to track flag-directives whose children may have\n * functionality based on them.\n *\n * Example: `md-no-ink` will potentially be used by all child directives.\n */\nfunction attrNoDirective () {\n return { controller: angular.noop };\n}\n\n})();\n(function(){\n\"use strict\";\n\n(function() {\n 'use strict';\n\n /**\n * @ngdoc service\n * @name $mdTabInkRipple\n * @module material.core\n *\n * @description\n * Provides ripple effects for md-tabs. See $mdInkRipple service for all possible configuration options.\n *\n * @param {object=} scope Scope within the current context\n * @param {object=} element The element the ripple effect should be applied to\n * @param {object=} options (Optional) Configuration options to override the defaultripple configuration\n */\n\n MdTabInkRipple.$inject = [\"$mdInkRipple\"];\n angular.module('material.core')\n .factory('$mdTabInkRipple', MdTabInkRipple);\n\n function MdTabInkRipple($mdInkRipple) {\n return {\n attach: attach\n };\n\n function attach(scope, element, options) {\n return $mdInkRipple.attach(scope, element, angular.extend({\n center: false,\n dimBackground: true,\n outline: false,\n rippleSize: 'full'\n }, options));\n }\n }\n})();\n\n})();\n(function(){\n\"use strict\";\n\nangular.module('material.core.theming.palette', [])\n.constant('$mdColorPalette', {\n 'red': {\n '50': '#ffebee',\n '100': '#ffcdd2',\n '200': '#ef9a9a',\n '300': '#e57373',\n '400': '#ef5350',\n '500': '#f44336',\n '600': '#e53935',\n '700': '#d32f2f',\n '800': '#c62828',\n '900': '#b71c1c',\n 'A100': '#ff8a80',\n 'A200': '#ff5252',\n 'A400': '#ff1744',\n 'A700': '#d50000',\n 'contrastDefaultColor': 'light',\n 'contrastDarkColors': '50 100 200 300 A100',\n 'contrastStrongLightColors': '400 500 600 700 A200 A400 A700'\n },\n 'pink': {\n '50': '#fce4ec',\n '100': '#f8bbd0',\n '200': '#f48fb1',\n '300': '#f06292',\n '400': '#ec407a',\n '500': '#e91e63',\n '600': '#d81b60',\n '700': '#c2185b',\n '800': '#ad1457',\n '900': '#880e4f',\n 'A100': '#ff80ab',\n 'A200': '#ff4081',\n 'A400': '#f50057',\n 'A700': '#c51162',\n 'contrastDefaultColor': 'light',\n 'contrastDarkColors': '50 100 200 A100',\n 'contrastStrongLightColors': '500 600 A200 A400 A700'\n },\n 'purple': {\n '50': '#f3e5f5',\n '100': '#e1bee7',\n '200': '#ce93d8',\n '300': '#ba68c8',\n '400': '#ab47bc',\n '500': '#9c27b0',\n '600': '#8e24aa',\n '700': '#7b1fa2',\n '800': '#6a1b9a',\n '900': '#4a148c',\n 'A100': '#ea80fc',\n 'A200': '#e040fb',\n 'A400': '#d500f9',\n 'A700': '#aa00ff',\n 'contrastDefaultColor': 'light',\n 'contrastDarkColors': '50 100 200 A100',\n 'contrastStrongLightColors': '300 400 A200 A400 A700'\n },\n 'deep-purple': {\n '50': '#ede7f6',\n '100': '#d1c4e9',\n '200': '#b39ddb',\n '300': '#9575cd',\n '400': '#7e57c2',\n '500': '#673ab7',\n '600': '#5e35b1',\n '700': '#512da8',\n '800': '#4527a0',\n '900': '#311b92',\n 'A100': '#b388ff',\n 'A200': '#7c4dff',\n 'A400': '#651fff',\n 'A700': '#6200ea',\n 'contrastDefaultColor': 'light',\n 'contrastDarkColors': '50 100 200 A100',\n 'contrastStrongLightColors': '300 400 A200'\n },\n 'indigo': {\n '50': '#e8eaf6',\n '100': '#c5cae9',\n '200': '#9fa8da',\n '300': '#7986cb',\n '400': '#5c6bc0',\n '500': '#3f51b5',\n '600': '#3949ab',\n '700': '#303f9f',\n '800': '#283593',\n '900': '#1a237e',\n 'A100': '#8c9eff',\n 'A200': '#536dfe',\n 'A400': '#3d5afe',\n 'A700': '#304ffe',\n 'contrastDefaultColor': 'light',\n 'contrastDarkColors': '50 100 200 A100',\n 'contrastStrongLightColors': '300 400 A200 A400'\n },\n 'blue': {\n '50': '#e3f2fd',\n '100': '#bbdefb',\n '200': '#90caf9',\n '300': '#64b5f6',\n '400': '#42a5f5',\n '500': '#2196f3',\n '600': '#1e88e5',\n '700': '#1976d2',\n '800': '#1565c0',\n '900': '#0d47a1',\n 'A100': '#82b1ff',\n 'A200': '#448aff',\n 'A400': '#2979ff',\n 'A700': '#2962ff',\n 'contrastDefaultColor': 'light',\n 'contrastDarkColors': '50 100 200 300 400 A100',\n 'contrastStrongLightColors': '500 600 700 A200 A400 A700'\n },\n 'light-blue': {\n '50': '#e1f5fe',\n '100': '#b3e5fc',\n '200': '#81d4fa',\n '300': '#4fc3f7',\n '400': '#29b6f6',\n '500': '#03a9f4',\n '600': '#039be5',\n '700': '#0288d1',\n '800': '#0277bd',\n '900': '#01579b',\n 'A100': '#80d8ff',\n 'A200': '#40c4ff',\n 'A400': '#00b0ff',\n 'A700': '#0091ea',\n 'contrastDefaultColor': 'dark',\n 'contrastLightColors': '600 700 800 900 A700',\n 'contrastStrongLightColors': '600 700 800 A700'\n },\n 'cyan': {\n '50': '#e0f7fa',\n '100': '#b2ebf2',\n '200': '#80deea',\n '300': '#4dd0e1',\n '400': '#26c6da',\n '500': '#00bcd4',\n '600': '#00acc1',\n '700': '#0097a7',\n '800': '#00838f',\n '900': '#006064',\n 'A100': '#84ffff',\n 'A200': '#18ffff',\n 'A400': '#00e5ff',\n 'A700': '#00b8d4',\n 'contrastDefaultColor': 'dark',\n 'contrastLightColors': '700 800 900',\n 'contrastStrongLightColors': '700 800 900'\n },\n 'teal': {\n '50': '#e0f2f1',\n '100': '#b2dfdb',\n '200': '#80cbc4',\n '300': '#4db6ac',\n '400': '#26a69a',\n '500': '#009688',\n '600': '#00897b',\n '700': '#00796b',\n '800': '#00695c',\n '900': '#004d40',\n 'A100': '#a7ffeb',\n 'A200': '#64ffda',\n 'A400': '#1de9b6',\n 'A700': '#00bfa5',\n 'contrastDefaultColor': 'dark',\n 'contrastLightColors': '500 600 700 800 900',\n 'contrastStrongLightColors': '500 600 700'\n },\n 'green': {\n '50': '#e8f5e9',\n '100': '#c8e6c9',\n '200': '#a5d6a7',\n '300': '#81c784',\n '400': '#66bb6a',\n '500': '#4caf50',\n '600': '#43a047',\n '700': '#388e3c',\n '800': '#2e7d32',\n '900': '#1b5e20',\n 'A100': '#b9f6ca',\n 'A200': '#69f0ae',\n 'A400': '#00e676',\n 'A700': '#00c853',\n 'contrastDefaultColor': 'dark',\n 'contrastLightColors': '500 600 700 800 900',\n 'contrastStrongLightColors': '500 600 700'\n },\n 'light-green': {\n '50': '#f1f8e9',\n '100': '#dcedc8',\n '200': '#c5e1a5',\n '300': '#aed581',\n '400': '#9ccc65',\n '500': '#8bc34a',\n '600': '#7cb342',\n '700': '#689f38',\n '800': '#558b2f',\n '900': '#33691e',\n 'A100': '#ccff90',\n 'A200': '#b2ff59',\n 'A400': '#76ff03',\n 'A700': '#64dd17',\n 'contrastDefaultColor': 'dark',\n 'contrastLightColors': '700 800 900',\n 'contrastStrongLightColors': '700 800 900'\n },\n 'lime': {\n '50': '#f9fbe7',\n '100': '#f0f4c3',\n '200': '#e6ee9c',\n '300': '#dce775',\n '400': '#d4e157',\n '500': '#cddc39',\n '600': '#c0ca33',\n '700': '#afb42b',\n '800': '#9e9d24',\n '900': '#827717',\n 'A100': '#f4ff81',\n 'A200': '#eeff41',\n 'A400': '#c6ff00',\n 'A700': '#aeea00',\n 'contrastDefaultColor': 'dark',\n 'contrastLightColors': '900',\n 'contrastStrongLightColors': '900'\n },\n 'yellow': {\n '50': '#fffde7',\n '100': '#fff9c4',\n '200': '#fff59d',\n '300': '#fff176',\n '400': '#ffee58',\n '500': '#ffeb3b',\n '600': '#fdd835',\n '700': '#fbc02d',\n '800': '#f9a825',\n '900': '#f57f17',\n 'A100': '#ffff8d',\n 'A200': '#ffff00',\n 'A400': '#ffea00',\n 'A700': '#ffd600',\n 'contrastDefaultColor': 'dark'\n },\n 'amber': {\n '50': '#fff8e1',\n '100': '#ffecb3',\n '200': '#ffe082',\n '300': '#ffd54f',\n '400': '#ffca28',\n '500': '#ffc107',\n '600': '#ffb300',\n '700': '#ffa000',\n '800': '#ff8f00',\n '900': '#ff6f00',\n 'A100': '#ffe57f',\n 'A200': '#ffd740',\n 'A400': '#ffc400',\n 'A700': '#ffab00',\n 'contrastDefaultColor': 'dark'\n },\n 'orange': {\n '50': '#fff3e0',\n '100': '#ffe0b2',\n '200': '#ffcc80',\n '300': '#ffb74d',\n '400': '#ffa726',\n '500': '#ff9800',\n '600': '#fb8c00',\n '700': '#f57c00',\n '800': '#ef6c00',\n '900': '#e65100',\n 'A100': '#ffd180',\n 'A200': '#ffab40',\n 'A400': '#ff9100',\n 'A700': '#ff6d00',\n 'contrastDefaultColor': 'dark',\n 'contrastLightColors': '800 900',\n 'contrastStrongLightColors': '800 900'\n },\n 'deep-orange': {\n '50': '#fbe9e7',\n '100': '#ffccbc',\n '200': '#ffab91',\n '300': '#ff8a65',\n '400': '#ff7043',\n '500': '#ff5722',\n '600': '#f4511e',\n '700': '#e64a19',\n '800': '#d84315',\n '900': '#bf360c',\n 'A100': '#ff9e80',\n 'A200': '#ff6e40',\n 'A400': '#ff3d00',\n 'A700': '#dd2c00',\n 'contrastDefaultColor': 'light',\n 'contrastDarkColors': '50 100 200 300 400 A100 A200',\n 'contrastStrongLightColors': '500 600 700 800 900 A400 A700'\n },\n 'brown': {\n '50': '#efebe9',\n '100': '#d7ccc8',\n '200': '#bcaaa4',\n '300': '#a1887f',\n '400': '#8d6e63',\n '500': '#795548',\n '600': '#6d4c41',\n '700': '#5d4037',\n '800': '#4e342e',\n '900': '#3e2723',\n 'A100': '#d7ccc8',\n 'A200': '#bcaaa4',\n 'A400': '#8d6e63',\n 'A700': '#5d4037',\n 'contrastDefaultColor': 'light',\n 'contrastDarkColors': '50 100 200 A100 A200',\n 'contrastStrongLightColors': '300 400'\n },\n 'grey': {\n '50': '#fafafa',\n '100': '#f5f5f5',\n '200': '#eeeeee',\n '300': '#e0e0e0',\n '400': '#bdbdbd',\n '500': '#9e9e9e',\n '600': '#757575',\n '700': '#616161',\n '800': '#424242',\n '900': '#212121',\n 'A100': '#ffffff',\n 'A200': '#000000',\n 'A400': '#303030',\n 'A700': '#616161',\n 'contrastDefaultColor': 'dark',\n 'contrastLightColors': '600 700 800 900 A200 A400 A700'\n },\n 'blue-grey': {\n '50': '#eceff1',\n '100': '#cfd8dc',\n '200': '#b0bec5',\n '300': '#90a4ae',\n '400': '#78909c',\n '500': '#607d8b',\n '600': '#546e7a',\n '700': '#455a64',\n '800': '#37474f',\n '900': '#263238',\n 'A100': '#cfd8dc',\n 'A200': '#b0bec5',\n 'A400': '#78909c',\n 'A700': '#455a64',\n 'contrastDefaultColor': 'light',\n 'contrastDarkColors': '50 100 200 300 A100 A200',\n 'contrastStrongLightColors': '400 500 700'\n }\n});\n\n})();\n(function(){\n\"use strict\";\n\n(function(angular) {\n 'use strict';\n/**\n * @ngdoc module\n * @name material.core.theming\n * @description\n * Theming\n */\ndetectDisabledThemes.$inject = [\"$mdThemingProvider\"];\nThemingDirective.$inject = [\"$mdTheming\", \"$interpolate\", \"$parse\", \"$mdUtil\", \"$q\", \"$log\"];\nThemableDirective.$inject = [\"$mdTheming\"];\nThemingProvider.$inject = [\"$mdColorPalette\", \"$$mdMetaProvider\"];\ngenerateAllThemes.$inject = [\"$injector\", \"$mdTheming\"];\nangular.module('material.core.theming', ['material.core.theming.palette', 'material.core.meta'])\n .directive('mdTheme', ThemingDirective)\n .directive('mdThemable', ThemableDirective)\n .directive('mdThemesDisabled', disableThemesDirective)\n .provider('$mdTheming', ThemingProvider)\n .config(detectDisabledThemes)\n .run(generateAllThemes);\n\n/**\n * Detect if the HTML or the BODY tags has a [md-themes-disabled] attribute\n * If yes, then immediately disable all theme stylesheet generation and DOM injection\n */\n/**\n * @ngInject\n */\nfunction detectDisabledThemes($mdThemingProvider) {\n var isDisabled = !!document.querySelector('[md-themes-disabled]');\n $mdThemingProvider.disableTheming(isDisabled);\n}\n\n/**\n * @ngdoc service\n * @name $mdThemingProvider\n * @module material.core.theming\n *\n * @description Provider to configure the `$mdTheming` service.\n *\n * ### Default Theme\n * The `$mdThemingProvider` uses by default the following theme configuration:\n *\n * - Primary Palette: `Blue`\n * - Accent Palette: `Pink`\n * - Warn Palette: `Deep-Orange`\n * - Background Palette: `Grey`\n *\n * If you don't want to use the `md-theme` directive on the elements itself, you may want to overwrite\n * the default theme.
\n * This can be done by using the following markup.\n *\n *
\n * myAppModule.config(function($mdThemingProvider) {\n * $mdThemingProvider\n * .theme('default')\n * .primaryPalette('blue')\n * .accentPalette('teal')\n * .warnPalette('red')\n * .backgroundPalette('grey');\n * });\n * \n *\n\n * ### Dynamic Themes\n *\n * By default, if you change a theme at runtime, the `$mdTheming` service will not detect those changes.
\n * If you have an application, which changes its theme on runtime, you have to enable theme watching.\n *\n *
\n * myAppModule.config(function($mdThemingProvider) {\n * // Enable theme watching.\n * $mdThemingProvider.alwaysWatchTheme(true);\n * });\n * \n *\n * ### Custom Theme Styles\n *\n * Sometimes you may want to use your own theme styles for some custom components.
\n * You are able to register your own styles by using the following markup.\n *\n *
\n * myAppModule.config(function($mdThemingProvider) {\n * // Register our custom stylesheet into the theming provider.\n * $mdThemingProvider.registerStyles(STYLESHEET);\n * });\n * \n *\n * The `registerStyles` method only accepts strings as value, so you're actually not able to load an external\n * stylesheet file into the `$mdThemingProvider`.\n *\n * If it's necessary to load an external stylesheet, we suggest using a bundler, which supports including raw content,\n * like [raw-loader](https://github.com/webpack/raw-loader) for `webpack`.\n *\n *
\n * myAppModule.config(function($mdThemingProvider) {\n * // Register your custom stylesheet into the theming provider.\n * $mdThemingProvider.registerStyles(require('../styles/my-component.theme.css'));\n * });\n * \n *\n * ### Browser color\n *\n * Enables browser header coloring\n * for more info please visit:\n * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color\n *\n * Options parameter:
\n * `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme.
\n * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',\n * 'accent', 'background' and 'warn'. Default is `primary`.
\n * `hue` - The hue from the selected palette. Default is `800`
\n *\n *
\n * myAppModule.config(function($mdThemingProvider) {\n * // Enable browser color\n * $mdThemingProvider.enableBrowserColor({\n * theme: 'myTheme', // Default is 'default'\n * palette: 'accent', // Default is 'primary', any basic material palette and extended palettes are available\n * hue: '200' // Default is '800'\n * });\n * });\n * \n */\n\n/**\n * Some Example Valid Theming Expressions\n * =======================================\n *\n * Intention group expansion: (valid for primary, accent, warn, background)\n *\n * {{primary-100}} - grab shade 100 from the primary palette\n * {{primary-100-0.7}} - grab shade 100, apply opacity of 0.7\n * {{primary-100-contrast}} - grab shade 100's contrast color\n * {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette\n * {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-1\n * {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue\n * {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules\n * {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue\n * {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules\n *\n * Foreground expansion: Applies rgba to black/white foreground text\n *\n * {{foreground-1}} - used for primary text\n * {{foreground-2}} - used for secondary text/divider\n * {{foreground-3}} - used for disabled text\n * {{foreground-4}} - used for dividers\n */\n\n// In memory generated CSS rules; registered by theme.name\nvar GENERATED = { };\n\n// In memory storage of defined themes and color palettes (both loaded by CSS, and user specified)\nvar PALETTES;\n\n// Text Colors on light and dark backgrounds\n// @see https://material.io/archive/guidelines/style/color.html#color-usability\nvar DARK_FOREGROUND = {\n name: 'dark',\n '1': 'rgba(0,0,0,0.87)',\n '2': 'rgba(0,0,0,0.54)',\n '3': 'rgba(0,0,0,0.38)',\n '4': 'rgba(0,0,0,0.12)'\n};\nvar LIGHT_FOREGROUND = {\n name: 'light',\n '1': 'rgba(255,255,255,1.0)',\n '2': 'rgba(255,255,255,0.7)',\n '3': 'rgba(255,255,255,0.5)',\n '4': 'rgba(255,255,255,0.12)'\n};\n\nvar DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)';\nvar LIGHT_SHADOW = '';\n\nvar DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)');\nvar LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87)');\nvar STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)');\n\nvar THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background'];\nvar DEFAULT_COLOR_TYPE = 'primary';\n\n// A color in a theme will use these hues by default, if not specified by user.\nvar LIGHT_DEFAULT_HUES = {\n 'accent': {\n 'default': 'A200',\n 'hue-1': 'A100',\n 'hue-2': 'A400',\n 'hue-3': 'A700'\n },\n 'background': {\n 'default': '50',\n 'hue-1': 'A100',\n 'hue-2': '100',\n 'hue-3': '300'\n }\n};\n\nvar DARK_DEFAULT_HUES = {\n 'background': {\n 'default': 'A400',\n 'hue-1': '800',\n 'hue-2': '900',\n 'hue-3': 'A200'\n }\n};\nTHEME_COLOR_TYPES.forEach(function(colorType) {\n // Color types with unspecified default hues will use these default hue values\n var defaultDefaultHues = {\n 'default': '500',\n 'hue-1': '300',\n 'hue-2': '800',\n 'hue-3': 'A100'\n };\n if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues;\n if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues;\n});\n\nvar VALID_HUE_VALUES = [\n '50', '100', '200', '300', '400', '500', '600',\n '700', '800', '900', 'A100', 'A200', 'A400', 'A700'\n];\n\nvar themeConfig = {\n disableTheming : false, // Generate our themes at run time; also disable stylesheet DOM injection\n generateOnDemand : false, // Whether or not themes are to be generated on-demand (vs. eagerly).\n registeredStyles : [], // Custom styles registered to be used in the theming of custom components.\n nonce : null // Nonce to be added as an attribute to the generated themes style tags.\n};\n\n/**\n *\n */\nfunction ThemingProvider($mdColorPalette, $$mdMetaProvider) {\n ThemingService.$inject = [\"$rootScope\", \"$mdUtil\", \"$q\", \"$log\"];\n PALETTES = { };\n var THEMES = { };\n\n var themingProvider;\n\n var alwaysWatchTheme = false;\n var defaultTheme = 'default';\n\n // Load JS Defined Palettes\n angular.extend(PALETTES, $mdColorPalette);\n\n // Default theme defined in core.js\n\n /**\n * Adds `theme-color` and `msapplication-navbutton-color` meta tags with the color parameter\n * @param {string} color Hex value of the wanted browser color\n * @returns {function} Remove function of the meta tags\n */\n var setBrowserColor = function (color) {\n // Chrome, Firefox OS and Opera\n var removeChrome = $$mdMetaProvider.setMeta('theme-color', color);\n // Windows Phone\n var removeWindows = $$mdMetaProvider.setMeta('msapplication-navbutton-color', color);\n\n return function () {\n removeChrome();\n removeWindows();\n };\n };\n\n /**\n * @ngdoc method\n * @name $mdThemingProvider#enableBrowserColor\n * @description\n * Enables browser header coloring. For more info please visit\n *
\n * Web Fundamentals.\n * @param {object=} options Options for the browser color, which include:
\n * - `theme` - `{string}`: A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme.
\n * - `palette` - `{string}`: Can be any one of the basic material design palettes, extended defined palettes, or `primary`,\n * `accent`, `background`, and `warn`. Default is `primary`.
\n * - `hue` - `{string}`: The hue from the selected palette. Default is `800`.
\n * @returns {function} Function that removes the browser coloring when called.\n */\n var enableBrowserColor = function (options) {\n options = angular.isObject(options) ? options : {};\n\n var theme = options.theme || 'default';\n var hue = options.hue || '800';\n\n var palette = PALETTES[options.palette] ||\n PALETTES[THEMES[theme].colors[options.palette || 'primary'].name];\n\n var color = angular.isObject(palette[hue]) ? palette[hue].hex : palette[hue];\n if (color.substr(0, 1) !== '#') color = '#' + color;\n\n return setBrowserColor(color);\n };\n\n return themingProvider = {\n definePalette: definePalette,\n extendPalette: extendPalette,\n theme: registerTheme,\n\n /**\n * return a read-only clone of the current theme configuration\n */\n configuration : function() {\n return angular.extend({ }, themeConfig, {\n defaultTheme : defaultTheme,\n alwaysWatchTheme : alwaysWatchTheme,\n registeredStyles : [].concat(themeConfig.registeredStyles)\n });\n },\n\n /**\n * @ngdoc method\n * @name $mdThemingProvider#disableTheming\n * @description\n * An easier way to disable theming without having to use `.constant(\"$MD_THEME_CSS\",\"\");`.\n * This disables all dynamic theme style sheet generations and injections.\n * @param {boolean=} isDisabled Disable all dynamic theme style sheet generations and injections\n * if `true` or `undefined`.\n */\n disableTheming: function(isDisabled) {\n themeConfig.disableTheming = angular.isUndefined(isDisabled) || !!isDisabled;\n },\n\n /**\n * @ngdoc method\n * @name $mdThemingProvider#registerStyles\n * @param {string} styles The styles to be appended to AngularJS Material's built in theme CSS.\n */\n registerStyles: function(styles) {\n themeConfig.registeredStyles.push(styles);\n },\n\n /**\n * @ngdoc method\n * @name $mdThemingProvider#setNonce\n * @param {string} nonceValue The nonce to be added as an attribute to the theme style tags.\n * Setting a value allows the use of CSP policy without using the `'unsafe-inline'` directive.\n * The string must already be base64 encoded. You can use `btoa(string)` to do this encoding.\n * In your CSP's `style-src`, you would then add an entry for `'nonce-nonceValue'`.\n */\n setNonce: function(nonceValue) {\n themeConfig.nonce = nonceValue;\n },\n\n generateThemesOnDemand: function(onDemand) {\n themeConfig.generateOnDemand = onDemand;\n },\n\n /**\n * @ngdoc method\n * @name $mdThemingProvider#setDefaultTheme\n * @param {string} theme Default theme name to be applied to elements. Default value is `default`.\n */\n setDefaultTheme: function(theme) {\n defaultTheme = theme;\n },\n\n /**\n * @ngdoc method\n * @name $mdThemingProvider#alwaysWatchTheme\n * @param {boolean} alwaysWatch Whether or not to always watch themes for changes and re-apply\n * classes when they change. Default is `false`. Enabling can reduce performance.\n */\n alwaysWatchTheme: function(alwaysWatch) {\n alwaysWatchTheme = alwaysWatch;\n },\n\n enableBrowserColor: enableBrowserColor,\n\n $get: ThemingService,\n _LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,\n _DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,\n _PALETTES: PALETTES,\n _THEMES: THEMES,\n _parseRules: parseRules,\n _rgba: rgba\n };\n\n /**\n * @ngdoc method\n * @name $mdThemingProvider#definePalette\n * @description\n * In the event that you need to define a custom color palette, you can use this function to\n * make it available to your theme for use in its intention groups.
\n * Note that you must specify all hues in the definition map.\n * @param {string} name Name of palette being defined\n * @param {object} map Palette definition that includes hue definitions and contrast colors:\n * - `'50'` - `{string}`: HEX color\n * - `'100'` - `{string}`: HEX color\n * - `'200'` - `{string}`: HEX color\n * - `'300'` - `{string}`: HEX color\n * - `'400'` - `{string}`: HEX color\n * - `'500'` - `{string}`: HEX color\n * - `'600'` - `{string}`: HEX color\n * - `'700'` - `{string}`: HEX color\n * - `'800'` - `{string}`: HEX color\n * - `'900'` - `{string}`: HEX color\n * - `'A100'` - `{string}`: HEX color\n * - `'A200'` - `{string}`: HEX color\n * - `'A400'` - `{string}`: HEX color\n * - `'A700'` - `{string}`: HEX color\n * - `'contrastDefaultColor'` - `{string}`: `light` or `dark`\n * - `'contrastDarkColors'` - `{string[]}`: Hues which should use dark contrast colors (i.e. raised button text).\n * For example: `['50', '100', '200', '300', '400', 'A100']`.\n * - `'contrastLightColors'` - `{string[]}`: Hues which should use light contrast colors (i.e. raised button text).\n * For example: `['500', '600', '700', '800', '900', 'A200', 'A400', 'A700']`.\n */\n function definePalette(name, map) {\n map = map || {};\n PALETTES[name] = checkPaletteValid(name, map);\n return themingProvider;\n }\n\n /**\n * @ngdoc method\n * @name $mdThemingProvider#extendPalette\n * @description\n * Sometimes it is easier to extend an existing color palette and then change a few properties,\n * rather than defining a whole new palette.\n * @param {string} name Name of palette being extended\n * @param {object} map Palette definition that includes optional hue definitions and contrast colors:\n * - `'50'` - `{string}`: HEX color\n * - `'100'` - `{string}`: HEX color\n * - `'200'` - `{string}`: HEX color\n * - `'300'` - `{string}`: HEX color\n * - `'400'` - `{string}`: HEX color\n * - `'500'` - `{string}`: HEX color\n * - `'600'` - `{string}`: HEX color\n * - `'700'` - `{string}`: HEX color\n * - `'800'` - `{string}`: HEX color\n * - `'900'` - `{string}`: HEX color\n * - `'A100'` - `{string}`: HEX color\n * - `'A200'` - `{string}`: HEX color\n * - `'A400'` - `{string}`: HEX color\n * - `'A700'` - `{string}`: HEX color\n * - `'contrastDefaultColor'` - `{string}`: `light` or `dark`\n * - `'contrastDarkColors'` - `{string[]}`: Hues which should use dark contrast colors (i.e. raised button text).\n * For example: `['50', '100', '200', '300', '400', 'A100']`.\n * - `'contrastLightColors'` - `{string[]}`: Hues which should use light contrast colors (i.e. raised button text).\n * For example: `['500', '600', '700', '800', '900', 'A200', 'A400', 'A700']`.\n * @returns {object} A new object which is a copy of the given palette, `name`,\n * with variables from `map` overwritten.\n */\n function extendPalette(name, map) {\n return checkPaletteValid(name, angular.extend({}, PALETTES[name] || {}, map));\n }\n\n // Make sure that palette has all required hues\n function checkPaletteValid(name, map) {\n var missingColors = VALID_HUE_VALUES.filter(function(field) {\n return !map[field];\n });\n if (missingColors.length) {\n throw new Error(\"Missing colors %1 in palette %2!\"\n .replace('%1', missingColors.join(', '))\n .replace('%2', name));\n }\n\n return map;\n }\n\n /**\n * @ngdoc method\n * @name $mdThemingProvider#theme\n * @description\n * Register a theme (which is a collection of color palettes); i.e. `warn`, `accent`,\n * `background`, and `primary`.
\n * Optionally inherit from an existing theme.\n * @param {string} name Name of theme being registered\n * @param {string=} inheritFrom Existing theme name to inherit from\n */\n function registerTheme(name, inheritFrom) {\n if (THEMES[name]) return THEMES[name];\n\n inheritFrom = inheritFrom || 'default';\n\n var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom;\n var theme = new Theme(name);\n\n if (parentTheme) {\n angular.forEach(parentTheme.colors, function(color, colorType) {\n theme.colors[colorType] = {\n name: color.name,\n // Make sure a COPY of the hues is given to the child color,\n // not the same reference.\n hues: angular.extend({}, color.hues)\n };\n });\n }\n THEMES[name] = theme;\n\n return theme;\n }\n\n function Theme(name) {\n var self = this;\n self.name = name;\n self.colors = {};\n\n self.dark = setDark;\n setDark(false);\n\n function setDark(isDark) {\n isDark = arguments.length === 0 ? true : !!isDark;\n\n // If no change, abort\n if (isDark === self.isDark) return;\n\n self.isDark = isDark;\n\n self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND;\n self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW;\n\n // Light and dark themes have different default hues.\n // Go through each existing color type for this theme, and for every\n // hue value that is still the default hue value from the previous light/dark setting,\n // set it to the default hue value from the new light/dark setting.\n var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES;\n var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES;\n angular.forEach(newDefaultHues, function(newDefaults, colorType) {\n var color = self.colors[colorType];\n var oldDefaults = oldDefaultHues[colorType];\n if (color) {\n for (var hueName in color.hues) {\n if (color.hues[hueName] === oldDefaults[hueName]) {\n color.hues[hueName] = newDefaults[hueName];\n }\n }\n }\n });\n\n return self;\n }\n\n THEME_COLOR_TYPES.forEach(function(colorType) {\n var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType];\n self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) {\n var color = self.colors[colorType] = {\n name: paletteName,\n hues: angular.extend({}, defaultHues, hues)\n };\n\n Object.keys(color.hues).forEach(function(name) {\n if (!defaultHues[name]) {\n throw new Error(\"Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4\"\n .replace('%1', name)\n .replace('%2', self.name)\n .replace('%3', paletteName)\n .replace('%4', Object.keys(defaultHues).join(', '))\n );\n }\n });\n Object.keys(color.hues).map(function(key) {\n return color.hues[key];\n }).forEach(function(hueValue) {\n if (VALID_HUE_VALUES.indexOf(hueValue) == -1) {\n throw new Error(\"Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5\"\n .replace('%1', hueValue)\n .replace('%2', self.name)\n .replace('%3', colorType)\n .replace('%4', paletteName)\n .replace('%5', VALID_HUE_VALUES.join(', '))\n );\n }\n });\n return self;\n };\n\n self[colorType + 'Color'] = function() {\n var args = Array.prototype.slice.call(arguments);\n // eslint-disable-next-line no-console\n console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' +\n 'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.');\n return self[colorType + 'Palette'].apply(self, args);\n };\n });\n }\n\n /**\n * @ngdoc service\n * @name $mdTheming\n * @module material.core.theming\n * @description\n * Service that makes an element apply theming related
classes to itself.\n *\n * For more information on the hue objects, their default values, as well as valid hue values, please visit
the custom hues section of Configuring a Theme.\n *\n *
\n * // Example component directive that we want to apply theming classes to.\n * app.directive('myFancyDirective', function($mdTheming) {\n * return {\n * restrict: 'AE',\n * link: function(scope, element, attrs) {\n * // Initialize the service using our directive's element\n * $mdTheming(element);\n *\n * $mdTheming.defineTheme('myTheme', {\n * primary: 'blue',\n * primaryHues: {\n * default: '500',\n * hue-1: '300',\n * hue-2: '900',\n * hue-3: 'A100'\n * },\n * accent: 'pink',\n * accentHues: {\n * default: '600',\n * hue-1: '300',\n * hue-2: '200',\n * hue-3: 'A500'\n * },\n * warn: 'red',\n * // It's not necessary to specify all hues in the object.\n * warnHues: {\n * default: '200',\n * hue-3: 'A100'\n * },\n * // It's not necessary to specify custom hues at all.\n * background: 'grey',\n * dark: true\n * });\n * // Your directive's custom code here.\n * }\n * };\n * });\n * \n * @param {element=} element Element that will have theming classes applied to it.\n */\n\n /**\n * @ngdoc property\n * @name $mdTheming#THEMES\n * @description\n * Property to get all the themes defined\n * @returns {object} All the themes defined with their properties.\n */\n\n /**\n * @ngdoc property\n * @name $mdTheming#PALETTES\n * @description\n * Property to get all the palettes defined\n * @returns {object} All the palettes defined with their colors.\n */\n\n /**\n * @ngdoc method\n * @name $mdTheming#registered\n * @description\n * Determine is specified theme name is a valid, registered theme\n * @param {string} themeName the theme to check if registered\n * @returns {boolean} whether the theme is registered or not\n */\n\n /**\n * @ngdoc method\n * @name $mdTheming#defaultTheme\n * @description\n * Returns the default theme\n * @returns {string} The default theme\n */\n\n /**\n * @ngdoc method\n * @name $mdTheming#generateTheme\n * @description\n * Lazy generate themes - by default, every theme is generated when defined.\n * You can disable this in the configuration section using the\n * `$mdThemingProvider.generateThemesOnDemand(true);`\n *\n * The theme name that is passed in must match the name of the theme that was defined as part of\n * the configuration block.\n *\n * @param {string} name theme name to generate\n */\n\n /**\n * @ngdoc method\n * @name $mdTheming#setBrowserColor\n * @description\n * Enables browser header coloring. For more info please visit\n *
\n * Web Fundamentals.\n * @param {object=} options Options for the browser color, which include:
\n * - `theme` - `{string}`: A defined theme via `$mdThemeProvider` to use the palettes from.\n * Default is `default` theme.
\n * - `palette` - `{string}`: Can be any one of the basic material design palettes, extended\n * defined palettes, or `primary`, `accent`, `background`, and `warn`. Default is `primary`.\n *
\n * - `hue` - `{string}`: The hue from the selected palette. Default is `800`.
\n * @returns {function} Function that removes the browser coloring when called.\n */\n\n /**\n * @ngdoc method\n * @name $mdTheming#defineTheme\n * @description\n * Dynamically define a theme by using an options object that contains palette names.\n *\n * @param {string} name Theme name to define\n * @param {object} options Theme definition options\n *\n * Options are:
\n * - `primary` - `{string}`: The name of the primary palette to use in the theme.
\n * - `primaryHues` - `{object=}`: Override hues for primary palette.
\n * - `accent` - `{string}`: The name of the accent palette to use in the theme.
\n * - `accentHues` - `{object=}`: Override hues for accent palette.
\n * - `warn` - `{string}`: The name of the warn palette to use in the theme.
\n * - `warnHues` - `{object=}`: Override hues for warn palette.
\n * - `background` - `{string}`: The name of the background palette to use in the theme.
\n * - `backgroundHues` - `{object=}`: Override hues for background palette.
\n * - `dark` - `{boolean}`: Indicates if it's a dark theme.
\n * @returns {Promise
} A resolved promise with the new theme name.\n */\n\n /* @ngInject */\n function ThemingService($rootScope, $mdUtil, $q, $log) {\n // Allow us to be invoked via a linking function signature.\n var applyTheme = function (scope, el) {\n if (el === undefined) { el = scope; scope = undefined; }\n if (scope === undefined) { scope = $rootScope; }\n applyTheme.inherit(el, el);\n };\n\n Object.defineProperty(applyTheme, 'THEMES', {\n get: function () {\n return angular.extend({}, THEMES);\n }\n });\n Object.defineProperty(applyTheme, 'PALETTES', {\n get: function () {\n return angular.extend({}, PALETTES);\n }\n });\n Object.defineProperty(applyTheme, 'ALWAYS_WATCH', {\n get: function () {\n return alwaysWatchTheme;\n }\n });\n applyTheme.inherit = inheritTheme;\n applyTheme.registered = registered;\n applyTheme.defaultTheme = function() { return defaultTheme; };\n applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };\n applyTheme.defineTheme = function(name, options) {\n options = options || {};\n\n var theme = registerTheme(name);\n\n if (options.primary) {\n theme.primaryPalette(options.primary, options.primaryHues);\n }\n if (options.accent) {\n theme.accentPalette(options.accent, options.accentHues);\n }\n if (options.warn) {\n theme.warnPalette(options.warn, options.warnHues);\n }\n if (options.background) {\n theme.backgroundPalette(options.background, options.backgroundHues);\n }\n if (options.dark){\n theme.dark();\n }\n\n this.generateTheme(name);\n\n return $q.resolve(name);\n };\n applyTheme.setBrowserColor = enableBrowserColor;\n\n return applyTheme;\n\n /**\n * Determine is specified theme name is a valid, registered theme\n */\n function registered(themeName) {\n if (themeName === undefined || themeName === '') return true;\n return applyTheme.THEMES[themeName] !== undefined;\n }\n\n /**\n * Get theme name for the element, then update with Theme CSS class\n */\n function inheritTheme (el, parent) {\n var ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');\n var scope = el.scope();\n\n updateThemeClass(lookupThemeName());\n\n if (ctrl) {\n var watchTheme = alwaysWatchTheme ||\n ctrl.$shouldWatch ||\n $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'));\n\n if (watchTheme || ctrl.isAsyncTheme) {\n var clearNameWatcher = function () {\n if (unwatch) {\n unwatch();\n unwatch = undefined;\n }\n };\n\n var unwatch = ctrl.registerChanges(function(name) {\n updateThemeClass(name);\n\n if (!watchTheme) {\n clearNameWatcher();\n }\n });\n\n if (scope) {\n scope.$on('$destroy', clearNameWatcher);\n } else {\n el.on('$destroy', clearNameWatcher);\n }\n }\n }\n\n /**\n * Find the theme name from the parent controller or element data\n */\n function lookupThemeName() {\n // As a few components (dialog) add their controllers later, we should also watch for a controller init.\n return ctrl && ctrl.$mdTheme || (defaultTheme === 'default' ? '' : defaultTheme);\n }\n\n /**\n * Remove old theme class and apply a new one\n * NOTE: if not a valid theme name, then the current name is not changed\n */\n function updateThemeClass(theme) {\n if (!theme) return;\n if (!registered(theme)) {\n $log.warn('Attempted to use unregistered theme \\'' + theme + '\\'. ' +\n 'Register it with $mdThemingProvider.theme().');\n }\n\n var oldTheme = el.data('$mdThemeName');\n if (oldTheme) el.removeClass('md-' + oldTheme +'-theme');\n el.addClass('md-' + theme + '-theme');\n el.data('$mdThemeName', theme);\n if (ctrl) {\n el.data('$mdThemeController', ctrl);\n }\n }\n }\n\n }\n}\n\nfunction ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {\n return {\n priority: 101, // has to be more than 100 to be before interpolation (issue on IE)\n link: {\n pre: function(scope, el, attrs) {\n var registeredCallbacks = [];\n\n var startSymbol = $interpolate.startSymbol();\n var endSymbol = $interpolate.endSymbol();\n\n var theme = attrs.mdTheme.trim();\n\n var hasInterpolation =\n theme.substr(0, startSymbol.length) === startSymbol &&\n theme.lastIndexOf(endSymbol) === theme.length - endSymbol.length;\n\n var oneTimeOperator = '::';\n var oneTimeBind = attrs.mdTheme\n .split(startSymbol).join('')\n .split(endSymbol).join('')\n .trim()\n .substr(0, oneTimeOperator.length) === oneTimeOperator;\n\n var getTheme = function () {\n var interpolation = $interpolate(attrs.mdTheme)(scope);\n return $parse(interpolation)(scope) || interpolation;\n };\n\n var ctrl = {\n isAsyncTheme: angular.isFunction(getTheme()) || angular.isFunction(getTheme().then),\n registerChanges: function (cb, context) {\n if (context) {\n cb = angular.bind(context, cb);\n }\n\n registeredCallbacks.push(cb);\n\n return function () {\n var index = registeredCallbacks.indexOf(cb);\n\n if (index > -1) {\n registeredCallbacks.splice(index, 1);\n }\n };\n },\n $setTheme: function (theme) {\n if (!$mdTheming.registered(theme)) {\n $log.warn('attempted to use unregistered theme \\'' + theme + '\\'');\n }\n\n ctrl.$mdTheme = theme;\n\n // Iterating backwards to support unregistering during iteration\n // http://stackoverflow.com/a/9882349/890293\n // we don't use `reverse()` of array because it mutates the array and we don't want it\n // to get re-indexed\n for (var i = registeredCallbacks.length; i--;) {\n registeredCallbacks[i](theme);\n }\n },\n $shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')) ||\n $mdTheming.ALWAYS_WATCH ||\n (hasInterpolation && !oneTimeBind)\n };\n\n el.data('$mdThemeController', ctrl);\n\n var setParsedTheme = function (theme) {\n if (typeof theme === 'string') {\n return ctrl.$setTheme(theme);\n }\n\n $q.when(angular.isFunction(theme) ? theme() : theme)\n .then(function(name) {\n ctrl.$setTheme(name);\n });\n };\n\n setParsedTheme(getTheme());\n\n var unwatch = scope.$watch(getTheme, function(theme) {\n if (theme) {\n setParsedTheme(theme);\n\n if (!ctrl.$shouldWatch) {\n unwatch();\n }\n }\n });\n }\n }\n };\n}\n\n/**\n * Special directive that will disable ALL runtime Theme style generation and DOM injection\n *\n * \n * \n *\n * \n * ...\n * \n *\n * Note: Using md-themes-css directive requires the developer to load external\n * theme stylesheets; e.g. custom themes from Material-Tools:\n *\n * `angular-material.themes.css`\n *\n * Another option is to use the ThemingProvider to configure and disable the attribute\n * conversions; this would obviate the use of the `md-themes-css` directive\n *\n */\nfunction disableThemesDirective() {\n themeConfig.disableTheming = true;\n\n // Return a 1x-only, first-match attribute directive\n return {\n restrict : 'A',\n priority : '900'\n };\n}\n\nfunction ThemableDirective($mdTheming) {\n return $mdTheming;\n}\n\nfunction parseRules(theme, colorType, rules) {\n checkValidPalette(theme, colorType);\n\n rules = rules.replace(/THEME_NAME/g, theme.name);\n var themeNameRegex = new RegExp('\\\\.md-' + theme.name + '-theme', 'g');\n var simpleVariableRegex = /'?\"?\\{\\{\\s*([a-zA-Z]+)-(A?\\d+|hue-[0-3]|shadow|default)-?(\\d\\.?\\d*)?(contrast)?\\s*\\}\\}'?\"?/g;\n\n // find and replace simple variables where we use a specific hue, not an entire palette\n // eg. \"{{primary-100}}\"\n // \\(' + THEME_COLOR_TYPES.join('\\|') + '\\)'\n rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity, contrast) {\n if (colorType === 'foreground') {\n if (hue == 'shadow') {\n return theme.foregroundShadow;\n } else {\n return theme.foregroundPalette[hue] || theme.foregroundPalette['1'];\n }\n }\n\n // `default` is also accepted as a hue-value, because the background palettes are\n // using it as a name for the default hue.\n if (hue.indexOf('hue') === 0 || hue === 'default') {\n hue = theme.colors[colorType].hues[hue];\n }\n\n return rgba((PALETTES[ theme.colors[colorType].name ][hue] || '')[contrast ? 'contrast' : 'value'], opacity);\n });\n\n // Matches '{{ primary-color }}', etc\n var hueRegex = new RegExp('(\\'|\")?{{\\\\s*([a-zA-Z]+)-(color|contrast)-?(\\\\d\\\\.?\\\\d*)?\\\\s*}}(\"|\\')?','g');\n var generatedRules = [];\n\n // For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3)\n angular.forEach(['default', 'hue-1', 'hue-2', 'hue-3'], function(hueName) {\n var newRule = rules\n .replace(hueRegex, function(match, _, matchedColorType, hueType, opacity) {\n var color = theme.colors[matchedColorType];\n var palette = PALETTES[color.name];\n var hueValue = color.hues[hueName];\n return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity);\n });\n if (hueName !== 'default') {\n newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName);\n }\n\n // Don't apply a selector rule to the default theme, making it easier to override\n // styles of the base-component\n if (theme.name == 'default') {\n var themeRuleRegex = /((?:\\s|>|\\.|\\w|-|:|\\(|\\)|\\[|\\]|\"|'|=)*)\\.md-default-theme((?:\\s|>|\\.|\\w|-|:|\\(|\\)|\\[|\\]|\"|'|=)*)/g;\n\n newRule = newRule.replace(themeRuleRegex, function(match, start, end) {\n return match + ', ' + start + end;\n });\n }\n generatedRules.push(newRule);\n });\n\n return generatedRules;\n}\n\nvar rulesByType = {};\n\n// Generate our themes at run time given the state of THEMES and PALETTES\nfunction generateAllThemes($injector, $mdTheming) {\n var head = document.head;\n var firstChild = head ? head.firstElementChild : null;\n var themeCss = !themeConfig.disableTheming && $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : '';\n\n // Append our custom registered styles to the theme stylesheet.\n themeCss += themeConfig.registeredStyles.join('');\n\n if (!firstChild) return;\n if (themeCss.length === 0) return; // no rules, so no point in running this expensive task\n\n // Expose contrast colors for palettes to ensure that text is always readable\n angular.forEach(PALETTES, sanitizePalette);\n\n // MD_THEME_CSS is a string generated by the build process that includes all the themable\n // components as templates\n\n // Break the CSS into individual rules\n var rules = themeCss\n .split(/\\}(?!(\\}|'|\"|;))/)\n .filter(function(rule) { return rule && rule.trim().length; })\n .map(function(rule) { return rule.trim() + '}'; });\n\n THEME_COLOR_TYPES.forEach(function(type) {\n rulesByType[type] = '';\n });\n\n // Sort the rules based on type, allowing us to do color substitution on a per-type basis\n rules.forEach(function(rule) {\n // First: test that if the rule has '.md-accent', it goes into the accent set of rules\n for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) {\n if (rule.indexOf('.md-' + type) > -1) {\n return rulesByType[type] += rule;\n }\n }\n\n // If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from\n // there\n for (i = 0; type = THEME_COLOR_TYPES[i]; i++) {\n if (rule.indexOf(type) > -1) {\n return rulesByType[type] += rule;\n }\n }\n\n // Default to the primary array\n return rulesByType[DEFAULT_COLOR_TYPE] += rule;\n });\n\n // If themes are being generated on-demand, quit here. The user will later manually\n // call generateTheme to do this on a theme-by-theme basis.\n if (themeConfig.generateOnDemand) return;\n\n angular.forEach($mdTheming.THEMES, function(theme) {\n if (!GENERATED[theme.name] && !($mdTheming.defaultTheme() !== 'default' && theme.name === 'default')) {\n generateTheme(theme, theme.name, themeConfig.nonce);\n }\n });\n\n\n // *************************\n // Internal functions\n // *************************\n\n // The user specifies a 'default' contrast color as either light or dark,\n // then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light)\n function sanitizePalette(palette, name) {\n var defaultContrast = palette.contrastDefaultColor;\n var lightColors = palette.contrastLightColors || [];\n var strongLightColors = palette.contrastStrongLightColors || [];\n var darkColors = palette.contrastDarkColors || [];\n\n // These colors are provided as space-separated lists\n if (typeof lightColors === 'string') lightColors = lightColors.split(' ');\n if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' ');\n if (typeof darkColors === 'string') darkColors = darkColors.split(' ');\n\n // Cleanup after ourselves\n delete palette.contrastDefaultColor;\n delete palette.contrastLightColors;\n delete palette.contrastStrongLightColors;\n delete palette.contrastDarkColors;\n\n // Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR }\n angular.forEach(palette, function(hueValue, hueName) {\n if (angular.isObject(hueValue)) return; // Already converted\n // Map everything to rgb colors\n var rgbValue = colorToRgbaArray(hueValue);\n if (!rgbValue) {\n throw new Error(\"Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected.\"\n .replace('%1', hueValue)\n .replace('%2', palette.name)\n .replace('%3', hueName));\n }\n\n palette[hueName] = {\n hex: palette[hueName],\n value: rgbValue,\n contrast: getContrastColor()\n };\n function getContrastColor() {\n if (defaultContrast === 'light') {\n if (darkColors.indexOf(hueName) > -1) {\n return DARK_CONTRAST_COLOR;\n } else {\n return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR\n : LIGHT_CONTRAST_COLOR;\n }\n } else {\n if (lightColors.indexOf(hueName) > -1) {\n return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR\n : LIGHT_CONTRAST_COLOR;\n } else {\n return DARK_CONTRAST_COLOR;\n }\n }\n }\n });\n }\n}\n\nfunction generateTheme(theme, name, nonce) {\n var head = document.head;\n var firstChild = head ? head.firstElementChild : null;\n\n if (!GENERATED[name]) {\n // For each theme, use the color palettes specified for\n // `primary`, `warn` and `accent` to generate CSS rules.\n THEME_COLOR_TYPES.forEach(function(colorType) {\n var styleStrings = parseRules(theme, colorType, rulesByType[colorType]);\n while (styleStrings.length) {\n var styleContent = styleStrings.shift();\n if (styleContent) {\n var style = document.createElement('style');\n style.setAttribute('md-theme-style', '');\n if (nonce) {\n style.setAttribute('nonce', nonce);\n }\n style.appendChild(document.createTextNode(styleContent));\n head.insertBefore(style, firstChild);\n }\n }\n });\n\n GENERATED[theme.name] = true;\n }\n\n}\n\n\nfunction checkValidPalette(theme, colorType) {\n // If theme attempts to use a palette that doesnt exist, throw error\n if (!PALETTES[ (theme.colors[colorType] || {}).name ]) {\n throw new Error(\n \"You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3\"\n .replace('%1', theme.name)\n .replace('%2', colorType)\n .replace('%3', Object.keys(PALETTES).join(', '))\n );\n }\n}\n\nfunction colorToRgbaArray(clr) {\n if (angular.isArray(clr) && clr.length == 3) return clr;\n if (/^rgb/.test(clr)) {\n return clr.replace(/(^\\s*rgba?\\(|\\)\\s*$)/g, '').split(',').map(function(value, i) {\n return i == 3 ? parseFloat(value, 10) : parseInt(value, 10);\n });\n }\n if (clr.charAt(0) == '#') clr = clr.substring(1);\n if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return;\n\n var dig = clr.length / 3;\n var red = clr.substr(0, dig);\n var grn = clr.substr(dig, dig);\n var blu = clr.substr(dig * 2);\n if (dig === 1) {\n red += red;\n grn += grn;\n blu += blu;\n }\n return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)];\n}\n\nfunction rgba(rgbArray, opacity) {\n if (!rgbArray) return \"rgb('0,0,0')\";\n\n if (rgbArray.length == 4) {\n rgbArray = angular.copy(rgbArray);\n opacity ? rgbArray.pop() : opacity = rgbArray.pop();\n }\n return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ?\n 'rgba(' + rgbArray.join(',') + ',' + opacity + ')' :\n 'rgb(' + rgbArray.join(',') + ')';\n}\n\n\n})(window.angular);\n\n})();\n(function(){\n\"use strict\";\n\n/**\n * @ngdoc module\n * @name material.components.autocomplete\n */\n/*\n * @see js folder for autocomplete implementation\n */\nangular.module('material.components.autocomplete', [\n 'material.core',\n 'material.components.icon',\n 'material.components.virtualRepeat'\n]);\n\n})();\n(function(){\n\"use strict\";\n\n\nMdAutocompleteCtrl.$inject = [\"$scope\", \"$element\", \"$mdUtil\", \"$mdConstant\", \"$mdTheming\", \"$window\", \"$animate\", \"$rootElement\", \"$attrs\", \"$q\", \"$log\", \"$mdLiveAnnouncer\"];angular\n .module('material.components.autocomplete')\n .controller('MdAutocompleteCtrl', MdAutocompleteCtrl);\n\nvar ITEM_HEIGHT = 48,\n MAX_ITEMS = 5,\n MENU_PADDING = 8,\n INPUT_PADDING = 2, // Padding provided by `md-input-container`\n MODE_STANDARD = 'standard',\n MODE_VIRTUAL = 'virtual';\n\nfunction MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, $window,\n $animate, $rootElement, $attrs, $q, $log, $mdLiveAnnouncer) {\n\n // Internal Variables.\n var ctrl = this,\n itemParts = $scope.itemsExpr.split(/ in /i),\n itemExpr = itemParts[ 1 ],\n elements = null,\n cache = {},\n noBlur = false,\n selectedItemWatchers = [],\n hasFocus = false,\n fetchesInProgress = 0,\n enableWrapScroll = null,\n inputModelCtrl = null,\n debouncedOnResize = $mdUtil.debounce(onWindowResize),\n mode = MODE_VIRTUAL; // default\n\n /**\n * The root document element. This is used for attaching a top-level click handler to\n * close the options panel when a click outside said panel occurs. We use `documentElement`\n * instead of body because, when scrolling is disabled, some browsers consider the body element\n * to be completely off the screen and propagate events directly to the html element.\n * @type {!Object} angular.JQLite\n */\n ctrl.documentElement = angular.element(document.documentElement);\n\n // Public Exported Variables with handlers\n defineProperty('hidden', handleHiddenChange, true);\n\n // Public Exported Variables\n ctrl.scope = $scope;\n ctrl.parent = $scope.$parent;\n ctrl.itemName = itemParts[0];\n ctrl.matches = [];\n ctrl.loading = false;\n ctrl.hidden = true;\n ctrl.index = -1;\n ctrl.activeOption = null;\n ctrl.id = $mdUtil.nextUid();\n ctrl.isDisabled = null;\n ctrl.isRequired = null;\n ctrl.isReadonly = null;\n ctrl.hasNotFound = false;\n ctrl.selectedMessage = $scope.selectedMessage || 'selected';\n\n // Public Exported Methods\n ctrl.keydown = keydown;\n ctrl.blur = blur;\n ctrl.focus = focus;\n ctrl.clear = clearValue;\n ctrl.select = select;\n ctrl.listEnter = onListEnter;\n ctrl.listLeave = onListLeave;\n ctrl.focusInput = focusInputElement;\n ctrl.getCurrentDisplayValue = getCurrentDisplayValue;\n ctrl.registerSelectedItemWatcher = registerSelectedItemWatcher;\n ctrl.unregisterSelectedItemWatcher = unregisterSelectedItemWatcher;\n ctrl.notFoundVisible = notFoundVisible;\n ctrl.loadingIsVisible = loadingIsVisible;\n ctrl.positionDropdown = positionDropdown;\n\n /**\n * Report types to be used for the $mdLiveAnnouncer\n * @enum {number} Unique flag id.\n */\n var ReportType = {\n Count: 1,\n Selected: 2\n };\n\n return init();\n\n // initialization methods\n\n /**\n * Initialize the controller, setup watchers, gather elements\n */\n function init () {\n\n $mdUtil.initOptionalProperties($scope, $attrs, {\n searchText: '',\n selectedItem: null,\n clearButton: false,\n disableVirtualRepeat: false,\n });\n\n $mdTheming($element);\n configureWatchers();\n $mdUtil.nextTick(function () {\n\n gatherElements();\n moveDropdown();\n\n // Touch devices often do not send a click event on tap. We still want to focus the input\n // and open the options pop-up in these cases.\n $element.on('touchstart', focusInputElement);\n\n // Forward all focus events to the input element when autofocus is enabled\n if ($scope.autofocus) {\n $element.on('focus', focusInputElement);\n }\n if ($scope.inputAriaDescribedBy) {\n elements.input.setAttribute('aria-describedby', $scope.inputAriaDescribedBy);\n }\n if (!$scope.floatingLabel) {\n if ($scope.inputAriaLabel) {\n elements.input.setAttribute('aria-label', $scope.inputAriaLabel);\n } else if ($scope.inputAriaLabelledBy) {\n elements.input.setAttribute('aria-labelledby', $scope.inputAriaLabelledBy);\n } else if ($scope.placeholder) {\n // If no aria-label or aria-labelledby references are defined, then just label using the\n // placeholder.\n elements.input.setAttribute('aria-label', $scope.placeholder);\n }\n }\n });\n }\n\n function updateModelValidators() {\n if (!$scope.requireMatch || !inputModelCtrl) return;\n\n inputModelCtrl.$setValidity('md-require-match', !!$scope.selectedItem || !$scope.searchText);\n }\n\n /**\n * Calculates the dropdown's position and applies the new styles to the menu element\n * @returns {*}\n */\n function positionDropdown () {\n if (!elements) {\n return $mdUtil.nextTick(positionDropdown, false, $scope);\n }\n\n var dropdownHeight = ($scope.dropdownItems || MAX_ITEMS) * ITEM_HEIGHT;\n var hrect = elements.wrap.getBoundingClientRect(),\n vrect = elements.snap.getBoundingClientRect(),\n root = elements.root.getBoundingClientRect(),\n top = vrect.bottom - root.top,\n bot = root.bottom - vrect.top,\n left = hrect.left - root.left,\n width = hrect.width,\n offset = getVerticalOffset(),\n position = $scope.dropdownPosition,\n styles, enoughBottomSpace, enoughTopSpace;\n var bottomSpace = root.bottom - vrect.bottom - MENU_PADDING + $mdUtil.getViewportTop();\n var topSpace = vrect.top - MENU_PADDING;\n\n // Automatically determine dropdown placement based on available space in viewport.\n if (!position) {\n enoughTopSpace = topSpace > dropdownHeight;\n enoughBottomSpace = bottomSpace > dropdownHeight;\n if (enoughBottomSpace) {\n position = 'bottom';\n } else if (enoughTopSpace) {\n position = 'top';\n } else {\n position = topSpace > bottomSpace ? 'top' : 'bottom';\n }\n }\n // Adjust the width to account for the padding provided by `md-input-container`\n if ($attrs.mdFloatingLabel) {\n left += INPUT_PADDING;\n width -= INPUT_PADDING * 2;\n }\n styles = {\n left: left + 'px',\n minWidth: width + 'px',\n maxWidth: Math.max(hrect.right - root.left, root.right - hrect.left) - MENU_PADDING + 'px'\n };\n\n if (position === 'top') {\n styles.top = 'auto';\n styles.bottom = bot + 'px';\n styles.maxHeight = Math.min(dropdownHeight, topSpace) + 'px';\n } else {\n bottomSpace = root.bottom - hrect.bottom - MENU_PADDING + $mdUtil.getViewportTop();\n\n styles.top = (top - offset) + 'px';\n styles.bottom = 'auto';\n styles.maxHeight = Math.min(dropdownHeight, bottomSpace) + 'px';\n }\n\n elements.$.scrollContainer.css(styles);\n $mdUtil.nextTick(correctHorizontalAlignment, false, $scope);\n\n /**\n * Calculates the vertical offset for floating label examples to account for ngMessages\n * @returns {number}\n */\n function getVerticalOffset () {\n var offset = 0;\n var inputContainer = $element.find('md-input-container');\n if (inputContainer.length) {\n var input = inputContainer.find('input');\n offset = inputContainer.prop('offsetHeight');\n offset -= input.prop('offsetTop');\n offset -= input.prop('offsetHeight');\n // add in the height left up top for the floating label text\n offset += inputContainer.prop('offsetTop');\n }\n return offset;\n }\n\n /**\n * Makes sure that the menu doesn't go off of the screen on either side.\n */\n function correctHorizontalAlignment () {\n var dropdown = elements.scrollContainer.getBoundingClientRect(),\n styles = {};\n if (dropdown.right > root.right) {\n styles.left = (hrect.right - dropdown.width) + 'px';\n }\n elements.$.scrollContainer.css(styles);\n }\n }\n\n /**\n * Moves the dropdown menu to the body tag in order to avoid z-index and overflow issues.\n */\n function moveDropdown () {\n if (!elements.$.root.length) return;\n $mdTheming(elements.$.scrollContainer);\n elements.$.scrollContainer.detach();\n elements.$.root.append(elements.$.scrollContainer);\n if ($animate.pin) $animate.pin(elements.$.scrollContainer, $rootElement);\n }\n\n /**\n * Sends focus to the input element.\n */\n function focusInputElement () {\n elements.input.focus();\n }\n\n /**\n * Update the activeOption based on the selected item in the listbox.\n * The activeOption is used in the template to set the aria-activedescendant attribute, which\n * enables screen readers to properly handle visual focus within the listbox and announce the\n * item's place in the list. I.e. \"List item 3 of 50\". Anytime that `ctrl.index` changes, this\n * function needs to be called to update the activeOption.\n */\n function updateActiveOption() {\n var selectedOption = elements.scroller.querySelector('.selected');\n if (selectedOption) {\n ctrl.activeOption = selectedOption.id;\n } else {\n ctrl.activeOption = null;\n }\n }\n\n /**\n * Sets up any watchers used by autocomplete\n */\n function configureWatchers () {\n var wait = parseInt($scope.delay, 10) || 0;\n\n $attrs.$observe('disabled', function (value) { ctrl.isDisabled = $mdUtil.parseAttributeBoolean(value, false); });\n $attrs.$observe('required', function (value) { ctrl.isRequired = $mdUtil.parseAttributeBoolean(value, false); });\n $attrs.$observe('readonly', function (value) { ctrl.isReadonly = $mdUtil.parseAttributeBoolean(value, false); });\n\n $scope.$watch('searchText', wait ? $mdUtil.debounce(handleSearchText, wait) : handleSearchText);\n $scope.$watch('selectedItem', selectedItemChange);\n\n angular.element($window).on('resize', debouncedOnResize);\n\n $scope.$on('$destroy', cleanup);\n }\n\n /**\n * Removes any events or leftover elements created by this controller\n */\n function cleanup () {\n if (!ctrl.hidden) {\n $mdUtil.enableScrolling();\n }\n\n angular.element($window).off('resize', debouncedOnResize);\n\n if (elements){\n var items = ['ul', 'scroller', 'scrollContainer', 'input'];\n angular.forEach(items, function(key){\n elements.$[key].remove();\n });\n }\n }\n\n /**\n * Event handler to be called whenever the window resizes.\n */\n function onWindowResize() {\n if (!ctrl.hidden) {\n positionDropdown();\n }\n }\n\n /**\n * Gathers all of the elements needed for this controller\n */\n function gatherElements () {\n\n var snapWrap = gatherSnapWrap();\n\n elements = {\n main: $element[0],\n scrollContainer: $element[0].querySelector('.md-virtual-repeat-container, .md-standard-list-container'),\n scroller: $element[0].querySelector('.md-virtual-repeat-scroller, .md-standard-list-scroller'),\n ul: $element.find('ul')[0],\n input: $element.find('input')[0],\n wrap: snapWrap.wrap,\n snap: snapWrap.snap,\n root: document.body,\n };\n\n elements.li = elements.ul.getElementsByTagName('li');\n elements.$ = getAngularElements(elements);\n mode = elements.scrollContainer.classList.contains('md-standard-list-container') ? MODE_STANDARD : MODE_VIRTUAL;\n inputModelCtrl = elements.$.input.controller('ngModel');\n }\n\n /**\n * Gathers the snap and wrap elements\n *\n */\n function gatherSnapWrap() {\n var element;\n var value;\n for (element = $element; element.length; element = element.parent()) {\n value = element.attr('md-autocomplete-snap');\n if (angular.isDefined(value)) break;\n }\n\n if (element.length) {\n return {\n snap: element[0],\n wrap: (value.toLowerCase() === 'width') ? element[0] : $element.find('md-autocomplete-wrap')[0]\n };\n }\n\n var wrap = $element.find('md-autocomplete-wrap')[0];\n return {\n snap: wrap,\n wrap: wrap\n };\n }\n\n /**\n * Gathers angular-wrapped versions of each element\n * @param elements\n * @returns {{}}\n */\n function getAngularElements (elements) {\n var obj = {};\n for (var key in elements) {\n if (elements.hasOwnProperty(key)) obj[ key ] = angular.element(elements[ key ]);\n }\n return obj;\n }\n\n // event/change handlers\n\n /**\n * @param {Event} $event\n */\n function preventDefault($event) {\n $event.preventDefault();\n }\n\n /**\n * @param {Event} $event\n */\n function stopPropagation($event) {\n $event.stopPropagation();\n }\n\n /**\n * Handles changes to the `hidden` property.\n * @param {boolean} hidden true to hide the options pop-up, false to show it.\n * @param {boolean} oldHidden the previous value of hidden\n */\n function handleHiddenChange (hidden, oldHidden) {\n var scrollContainerElement;\n\n if (elements) {\n scrollContainerElement = angular.element(elements.scrollContainer);\n }\n if (!hidden && oldHidden) {\n positionDropdown();\n\n // Report in polite mode, because the screen reader should finish the default description of\n // the input element.\n reportMessages(true, ReportType.Count | ReportType.Selected);\n\n if (elements) {\n $mdUtil.disableScrollAround(elements.scrollContainer);\n enableWrapScroll = disableElementScrollEvents(elements.wrap);\n if ($mdUtil.isIos) {\n ctrl.documentElement.on('touchend', handleTouchOutsidePanel);\n if (scrollContainerElement) {\n scrollContainerElement.on('touchstart touchmove touchend', stopPropagation);\n }\n }\n ctrl.index = getDefaultIndex();\n $mdUtil.nextTick(function() {\n updateActiveOption();\n updateScroll();\n });\n }\n } else if (hidden && !oldHidden) {\n if ($mdUtil.isIos) {\n ctrl.documentElement.off('touchend', handleTouchOutsidePanel);\n if (scrollContainerElement) {\n scrollContainerElement.off('touchstart touchmove touchend', stopPropagation);\n }\n }\n $mdUtil.enableScrolling();\n\n if (enableWrapScroll) {\n enableWrapScroll();\n enableWrapScroll = null;\n }\n }\n }\n\n /**\n * Handling touch events that bubble up to the document is required for closing the dropdown\n * panel on touch outside of the options pop-up panel on iOS.\n * @param {Event} $event\n */\n function handleTouchOutsidePanel($event) {\n ctrl.hidden = true;\n // iOS does not blur the pop-up for touches on the scroll mask, so we have to do it.\n doBlur(true);\n }\n\n /**\n * Disables scrolling for a specific element.\n * @param {!string|!DOMElement} element to disable scrolling\n * @return {Function} function to call to re-enable scrolling for the element\n */\n function disableElementScrollEvents(element) {\n var elementToDisable = angular.element(element);\n elementToDisable.on('wheel touchmove', preventDefault);\n\n return function() {\n elementToDisable.off('wheel touchmove', preventDefault);\n };\n }\n\n /**\n * When the user mouses over the dropdown menu, ignore blur events.\n */\n function onListEnter () {\n noBlur = true;\n }\n\n /**\n * When the user's mouse leaves the menu, blur events may hide the menu again.\n */\n function onListLeave () {\n if (!hasFocus && !ctrl.hidden) elements.input.focus();\n noBlur = false;\n ctrl.hidden = shouldHide();\n }\n\n /**\n * Handles changes to the selected item.\n * @param selectedItem\n * @param previousSelectedItem\n */\n function selectedItemChange (selectedItem, previousSelectedItem) {\n\n updateModelValidators();\n\n if (selectedItem) {\n getDisplayValue(selectedItem).then(function (val) {\n $scope.searchText = val;\n handleSelectedItemChange(selectedItem, previousSelectedItem);\n });\n } else if (previousSelectedItem && $scope.searchText) {\n getDisplayValue(previousSelectedItem).then(function(displayValue) {\n // Clear the searchText, when the selectedItem is set to null.\n // Do not clear the searchText, when the searchText isn't matching with the previous\n // selected item.\n if (angular.isString($scope.searchText)\n && displayValue.toString().toLowerCase() === $scope.searchText.toLowerCase()) {\n $scope.searchText = '';\n }\n });\n }\n\n if (selectedItem !== previousSelectedItem) {\n announceItemChange();\n }\n }\n\n /**\n * Use the user-defined expression to announce changes each time a new item is selected\n */\n function announceItemChange () {\n angular.isFunction($scope.itemChange) &&\n $scope.itemChange(getItemAsNameVal($scope.selectedItem));\n }\n\n /**\n * Use the user-defined expression to announce changes each time the search text is changed\n */\n function announceTextChange () {\n angular.isFunction($scope.textChange) && $scope.textChange();\n }\n\n /**\n * Calls any external watchers listening for the selected item. Used in conjunction with\n * `registerSelectedItemWatcher`.\n * @param selectedItem\n * @param previousSelectedItem\n */\n function handleSelectedItemChange (selectedItem, previousSelectedItem) {\n selectedItemWatchers.forEach(function (watcher) {\n watcher(selectedItem, previousSelectedItem);\n });\n }\n\n /**\n * Register a function to be called when the selected item changes.\n * @param cb\n */\n function registerSelectedItemWatcher (cb) {\n if (selectedItemWatchers.indexOf(cb) === -1) {\n selectedItemWatchers.push(cb);\n }\n }\n\n /**\n * Unregister a function previously registered for selected item changes.\n * @param cb\n */\n function unregisterSelectedItemWatcher (cb) {\n var i = selectedItemWatchers.indexOf(cb);\n if (i !== -1) {\n selectedItemWatchers.splice(i, 1);\n }\n }\n\n /**\n * Handles changes to the searchText property.\n * @param {string} searchText\n * @param {string} previousSearchText\n */\n function handleSearchText (searchText, previousSearchText) {\n ctrl.index = getDefaultIndex();\n\n // do nothing on init\n if (searchText === previousSearchText) return;\n\n updateModelValidators();\n\n getDisplayValue($scope.selectedItem).then(function (val) {\n // clear selected item if search text no longer matches it\n if (searchText !== val) {\n $scope.selectedItem = null;\n\n // trigger change event if available\n if (searchText !== previousSearchText) {\n announceTextChange();\n }\n\n // cancel results if search text is not long enough\n if (!isMinLengthMet()) {\n ctrl.matches = [];\n\n setLoading(false);\n reportMessages(true, ReportType.Count);\n\n } else {\n handleQuery();\n }\n }\n });\n\n }\n\n /**\n * Handles input blur event, determines if the dropdown should hide.\n */\n function blur($event) {\n hasFocus = false;\n\n if (!noBlur) {\n ctrl.hidden = shouldHide();\n evalAttr('ngBlur', { $event: $event });\n }\n }\n\n /**\n * Force blur on input element\n * @param {boolean} forceBlur\n */\n function doBlur(forceBlur) {\n if (forceBlur) {\n noBlur = false;\n hasFocus = false;\n }\n elements.input.blur();\n }\n\n /**\n * Handles input focus event, determines if the dropdown should show.\n */\n function focus($event) {\n hasFocus = true;\n\n if (isSearchable() && isMinLengthMet()) {\n handleQuery();\n }\n\n ctrl.hidden = shouldHide();\n\n evalAttr('ngFocus', { $event: $event });\n }\n\n /**\n * Handles keyboard input.\n * @param event\n */\n function keydown (event) {\n switch (event.keyCode) {\n case $mdConstant.KEY_CODE.DOWN_ARROW:\n if (ctrl.loading || hasSelection()) return;\n event.stopPropagation();\n event.preventDefault();\n ctrl.index = ctrl.index + 1 > ctrl.matches.length - 1 ? 0 : Math.min(ctrl.index + 1, ctrl.matches.length - 1);\n $mdUtil.nextTick(updateActiveOption);\n updateScroll();\n break;\n case $mdConstant.KEY_CODE.UP_ARROW:\n if (ctrl.loading || hasSelection()) return;\n event.stopPropagation();\n event.preventDefault();\n ctrl.index = ctrl.index - 1 < 0 ? ctrl.matches.length - 1 : Math.max(0, ctrl.index - 1);\n $mdUtil.nextTick(updateActiveOption);\n updateScroll();\n break;\n case $mdConstant.KEY_CODE.TAB:\n // If we hit tab, assume that we've left the list so it will close\n onListLeave();\n\n if (ctrl.hidden || ctrl.loading || ctrl.index < 0 || ctrl.matches.length < 1) return;\n select(ctrl.index);\n break;\n case $mdConstant.KEY_CODE.ENTER:\n if (ctrl.hidden || ctrl.loading || ctrl.index < 0 || ctrl.matches.length < 1) return;\n if (hasSelection()) return;\n event.stopImmediatePropagation();\n event.preventDefault();\n select(ctrl.index);\n break;\n case $mdConstant.KEY_CODE.ESCAPE:\n event.preventDefault(); // Prevent browser from always clearing input\n if (!shouldProcessEscape()) return;\n event.stopPropagation();\n\n clearSelectedItem();\n if ($scope.searchText && hasEscapeOption('clear')) {\n clearSearchText();\n }\n\n // Manually hide (needed for mdNotFound support)\n ctrl.hidden = true;\n\n if (hasEscapeOption('blur')) {\n // Force the component to blur if they hit escape\n doBlur(true);\n }\n\n break;\n default:\n }\n }\n\n // getters\n\n /**\n * Returns the minimum length needed to display the dropdown.\n * @returns {*}\n */\n function getMinLength () {\n return angular.isNumber($scope.minLength) ? $scope.minLength : 1;\n }\n\n /**\n * Returns the display value for an item.\n * @param {*} item\n * @returns {*}\n */\n function getDisplayValue (item) {\n return $q.when(getItemText(item) || item).then(function(itemText) {\n if (itemText && !angular.isString(itemText)) {\n $log.warn('md-autocomplete: Could not resolve display value to a string. ' +\n 'Please check the `md-item-text` attribute.');\n }\n\n return itemText;\n });\n\n /**\n * Getter function to invoke user-defined expression (in the directive)\n * to convert your object to a single string.\n * @param {*} item\n * @returns {string|null}\n */\n function getItemText (item) {\n return (item && $scope.itemText) ? $scope.itemText(getItemAsNameVal(item)) : null;\n }\n }\n\n /**\n * Returns the locals object for compiling item templates.\n * @param {*} item\n * @returns {Object|undefined}\n */\n function getItemAsNameVal (item) {\n if (!item) {\n return undefined;\n }\n\n var locals = {};\n if (ctrl.itemName) {\n locals[ ctrl.itemName ] = item;\n }\n\n return locals;\n }\n\n /**\n * Returns the default index based on whether or not autoselect is enabled.\n * @returns {number} 0 if autoselect is enabled, -1 if not.\n */\n function getDefaultIndex () {\n return $scope.autoselect ? 0 : -1;\n }\n\n /**\n * Sets the loading parameter and updates the hidden state.\n * @param value {boolean} Whether or not the component is currently loading.\n */\n function setLoading(value) {\n if (ctrl.loading !== value) {\n ctrl.loading = value;\n }\n\n // Always refresh the hidden variable as something else might have changed\n ctrl.hidden = shouldHide();\n }\n\n /**\n * Determines if the menu should be hidden.\n * @returns {boolean} true if the menu should be hidden\n */\n function shouldHide () {\n return !shouldShow();\n }\n\n /**\n * Determines whether the autocomplete is able to query within the current state.\n * @returns {boolean} true if the query can be run\n */\n function isSearchable() {\n if (ctrl.loading && !hasMatches()) {\n // No query when query is in progress.\n return false;\n } else if (hasSelection()) {\n // No query if there is already a selection\n return false;\n }\n else if (!hasFocus) {\n // No query if the input does not have focus\n return false;\n }\n return true;\n }\n\n /**\n * @returns {boolean} if the escape keydown should be processed, return true.\n * Otherwise return false.\n */\n function shouldProcessEscape() {\n return hasEscapeOption('blur') || !ctrl.hidden || ctrl.loading || hasEscapeOption('clear') && $scope.searchText;\n }\n\n /**\n * @param {string} option check if this option is set\n * @returns {boolean} if the specified escape option is set, return true. Return false otherwise.\n */\n function hasEscapeOption(option) {\n return !$scope.escapeOptions || $scope.escapeOptions.toLowerCase().indexOf(option) !== -1;\n }\n\n /**\n * Determines if the menu should be shown.\n * @returns {boolean} true if the menu should be shown\n */\n function shouldShow() {\n if (ctrl.isReadonly) {\n // Don't show if read only is set\n return false;\n } else if (!isSearchable()) {\n // Don't show if a query is in progress, there is already a selection,\n // or the input is not focused.\n return false;\n }\n return (isMinLengthMet() && hasMatches()) || notFoundVisible();\n }\n\n /**\n * @returns {boolean} true if the search text has matches.\n */\n function hasMatches() {\n return ctrl.matches.length ? true : false;\n }\n\n /**\n * @returns {boolean} true if the autocomplete has a valid selection.\n */\n function hasSelection() {\n return ctrl.scope.selectedItem ? true : false;\n }\n\n /**\n * @returns {boolean} true if the loading indicator is, or should be, visible.\n */\n function loadingIsVisible() {\n return ctrl.loading && !hasSelection();\n }\n\n /**\n * @returns {*} the display value of the current item.\n */\n function getCurrentDisplayValue () {\n return getDisplayValue(ctrl.matches[ ctrl.index ]);\n }\n\n /**\n * Determines if the minimum length is met by the search text.\n * @returns {*} true if the minimum length is met by the search text\n */\n function isMinLengthMet () {\n return ($scope.searchText || '').length >= getMinLength();\n }\n\n // actions\n\n /**\n * Defines a public property with a handler and a default value.\n * @param {string} key\n * @param {Function} handler function\n * @param {*} defaultValue default value\n */\n function defineProperty (key, handler, defaultValue) {\n Object.defineProperty(ctrl, key, {\n get: function () { return defaultValue; },\n set: function (newValue) {\n var oldValue = defaultValue;\n defaultValue = newValue;\n handler(newValue, oldValue);\n }\n });\n }\n\n /**\n * Selects the item at the given index.\n * @param {number} index to select\n */\n function select (index) {\n // force form to update state for validation\n $mdUtil.nextTick(function () {\n getDisplayValue(ctrl.matches[ index ]).then(function (val) {\n var ngModel = elements.$.input.controller('ngModel');\n $mdLiveAnnouncer.announce(val + ' ' + ctrl.selectedMessage, 'assertive');\n ngModel.$setViewValue(val);\n ngModel.$render();\n }).finally(function () {\n $scope.selectedItem = ctrl.matches[ index ];\n setLoading(false);\n });\n }, false);\n }\n\n /**\n * Clears the searchText value and selected item.\n * @param {Event} $event\n */\n function clearValue ($event) {\n if ($event) {\n $event.stopPropagation();\n }\n clearSelectedItem();\n clearSearchText();\n }\n\n /**\n * Clears the selected item\n */\n function clearSelectedItem () {\n // Reset our variables\n ctrl.index = -1;\n $mdUtil.nextTick(updateActiveOption);\n ctrl.matches = [];\n }\n\n /**\n * Clears the searchText value\n */\n function clearSearchText () {\n // Set the loading to true so we don't see flashes of content.\n // The flashing will only occur when an async request is running.\n // So the loading process will stop when the results had been retrieved.\n setLoading(true);\n\n $scope.searchText = '';\n\n // Normally, triggering the change / input event is unnecessary, because the browser detects it properly.\n // But some browsers are not detecting it properly, which means that we have to trigger the event.\n // Using the `input` is not working properly, because for example IE11 is not supporting the `input` event.\n // The `change` event is a good alternative and is supported by all supported browsers.\n var eventObj = document.createEvent('CustomEvent');\n eventObj.initCustomEvent('change', true, true, { value: '' });\n elements.input.dispatchEvent(eventObj);\n\n // For some reason, firing the above event resets the value of $scope.searchText if\n // $scope.searchText has a space character at the end, so we blank it one more time and then\n // focus.\n elements.input.blur();\n $scope.searchText = '';\n elements.input.focus();\n }\n\n /**\n * Fetches the results for the provided search text.\n * @param searchText\n */\n function fetchResults (searchText) {\n var items = $scope.$parent.$eval(itemExpr),\n term = searchText.toLowerCase(),\n isList = angular.isArray(items),\n isPromise = !!items.then; // Every promise should contain a `then` property\n\n if (isList) onResultsRetrieved(items);\n else if (isPromise) handleAsyncResults(items);\n\n function handleAsyncResults(items) {\n if (!items) return;\n\n items = $q.when(items);\n fetchesInProgress++;\n setLoading(true);\n\n $mdUtil.nextTick(function () {\n items\n .then(onResultsRetrieved)\n .finally(function(){\n if (--fetchesInProgress === 0) {\n setLoading(false);\n }\n });\n },true, $scope);\n }\n\n function onResultsRetrieved(matches) {\n cache[term] = matches;\n\n // Just cache the results if the request is now outdated.\n // The request becomes outdated, when the new searchText has changed during the result fetching.\n if ((searchText || '') !== ($scope.searchText || '')) {\n return;\n }\n\n handleResults(matches);\n }\n }\n\n\n /**\n * Reports given message types to supported screen readers.\n * @param {boolean} isPolite Whether the announcement should be polite.\n * @param {!number} types Message flags to be reported to the screen reader.\n */\n function reportMessages(isPolite, types) {\n var politeness = isPolite ? 'polite' : 'assertive';\n var messages = [];\n\n if (types & ReportType.Selected && ctrl.index !== -1) {\n messages.push(getCurrentDisplayValue());\n }\n\n if (types & ReportType.Count) {\n messages.push($q.resolve(getCountMessage()));\n }\n\n $q.all(messages).then(function(data) {\n $mdLiveAnnouncer.announce(data.join(' '), politeness);\n });\n }\n\n /**\n * @returns {string} the ARIA message for how many results match the current query.\n */\n function getCountMessage () {\n switch (ctrl.matches.length) {\n case 0:\n return 'There are no matches available.';\n case 1:\n return 'There is 1 match available.';\n default:\n return 'There are ' + ctrl.matches.length + ' matches available.';\n }\n }\n\n /**\n * Makes sure that the focused element is within view.\n */\n function updateScroll () {\n if (!elements.li[0]) return;\n if (mode === MODE_STANDARD) {\n updateStandardScroll();\n } else {\n updateVirtualScroll();\n }\n }\n\n function updateVirtualScroll() {\n // elements in virtual scroll have consistent heights\n var optionHeight = elements.li[0].offsetHeight,\n top = optionHeight * Math.max(0, ctrl.index),\n bottom = top + optionHeight,\n containerHeight = elements.scroller.clientHeight,\n scrollTop = elements.scroller.scrollTop;\n\n if (top < scrollTop) {\n scrollTo(top);\n } else if (bottom > scrollTop + containerHeight) {\n scrollTo(bottom - containerHeight);\n }\n }\n\n function updateStandardScroll() {\n // elements in standard scroll have variable heights\n var selected = elements.li[Math.max(0, ctrl.index)];\n var containerHeight = elements.scrollContainer.offsetHeight,\n top = selected && selected.offsetTop || 0,\n bottom = top + selected.clientHeight,\n scrollTop = elements.scrollContainer.scrollTop;\n\n if (top < scrollTop) {\n scrollTo(top);\n } else if (bottom > scrollTop + containerHeight) {\n scrollTo(bottom - containerHeight);\n }\n }\n\n function isPromiseFetching() {\n return fetchesInProgress !== 0;\n }\n\n function scrollTo (offset) {\n if (mode === MODE_STANDARD) {\n elements.scrollContainer.scrollTop = offset;\n } else {\n elements.$.scrollContainer.controller('mdVirtualRepeatContainer').scrollTo(offset);\n }\n }\n\n function notFoundVisible () {\n var textLength = (ctrl.scope.searchText || '').length;\n\n return ctrl.hasNotFound && !hasMatches() && (!ctrl.loading || isPromiseFetching()) && textLength >= getMinLength() && (hasFocus || noBlur) && !hasSelection();\n }\n\n /**\n * Starts the query to gather the results for the current searchText. Attempts to return cached\n * results first, then forwards the process to `fetchResults` if necessary.\n */\n function handleQuery () {\n var searchText = $scope.searchText || '';\n var term = searchText.toLowerCase();\n\n // If caching is enabled and the current searchText is stored in the cache\n if (!$scope.noCache && cache[term]) {\n // The results should be handled as same as a normal un-cached request does.\n handleResults(cache[term]);\n } else {\n fetchResults(searchText);\n }\n\n ctrl.hidden = shouldHide();\n }\n\n /**\n * Handles the retrieved results by showing them in the autocompletes dropdown.\n * @param results Retrieved results\n */\n function handleResults(results) {\n ctrl.matches = results;\n ctrl.hidden = shouldHide();\n\n // If loading is in progress, then we'll end the progress. This is needed for example,\n // when the `clear` button was clicked, because there we always show the loading process, to prevent flashing.\n if (ctrl.loading) setLoading(false);\n\n if ($scope.selectOnMatch) selectItemOnMatch();\n\n positionDropdown();\n reportMessages(true, ReportType.Count);\n }\n\n /**\n * If there is only one matching item and the search text matches its display value exactly,\n * automatically select that item. Note: This function is only called if the user uses the\n * `md-select-on-match` flag.\n */\n function selectItemOnMatch () {\n var searchText = $scope.searchText,\n matches = ctrl.matches,\n item = matches[ 0 ];\n if (matches.length === 1) getDisplayValue(item).then(function (displayValue) {\n var isMatching = searchText === displayValue;\n if ($scope.matchInsensitive && !isMatching) {\n isMatching = searchText.toLowerCase() === displayValue.toLowerCase();\n }\n\n if (isMatching) {\n select(0);\n }\n });\n }\n\n /**\n * Evaluates an attribute expression against the parent scope.\n * @param {String} attr Name of the attribute to be evaluated.\n * @param {Object?} locals Properties to be injected into the evaluation context.\n */\n function evalAttr(attr, locals) {\n if ($attrs[attr]) {\n $scope.$parent.$eval($attrs[attr], locals || {});\n }\n }\n\n}\n\n})();\n(function(){\n\"use strict\";\n\n\nMdAutocomplete.$inject = [\"$$mdSvgRegistry\"];angular\n .module('material.components.autocomplete')\n .directive('mdAutocomplete', MdAutocomplete);\n\n/**\n * @ngdoc directive\n * @name mdAutocomplete\n * @module material.components.autocomplete\n *\n * @description\n * `` is a special input component with a drop-down of all possible matches to a\n * custom query. This component allows you to provide real-time suggestions as the user types\n * in the input area.\n *\n * To start, you will need to specify the required parameters and provide a template for your\n * results. The content inside `md-autocomplete` will be treated as a template.\n *\n * In more complex cases, you may want to include other content such as a message to display when\n * no matches were found. You can do this by wrapping your template in `md-item-template` and\n * adding a tag for `md-not-found`. An example of this is shown below.\n *\n * To reset the displayed value you must clear both values for `md-search-text` and\n * `md-selected-item`.\n *\n * ### Validation\n *\n * You can use `ng-messages` to include validation the same way that you would normally validate;\n * however, if you want to replicate a standard input with a floating label, you will have to\n * do the following:\n *\n * - Make sure that your template is wrapped in `md-item-template`\n * - Add your `ng-messages` code inside of `md-autocomplete`\n * - Add your validation properties to `md-autocomplete` (ie. `required`)\n * - Add a `name` to `md-autocomplete` (to be used on the generated `input`)\n *\n * There is an example below of how this should look.\n *\n * ### Snapping Drop-Down\n *\n * You can cause the autocomplete drop-down to snap to an ancestor element by applying the\n * `md-autocomplete-snap` attribute to that element. You can also snap to the width of\n * the `md-autocomplete-snap` element by setting the attribute's value to `width`\n * (ie. `md-autocomplete-snap=\"width\"`).\n *\n * ### Notes\n *\n * **Autocomplete Dropdown Items Rendering**\n *\n * The `md-autocomplete` uses the the \n * mdVirtualRepeat directive for displaying the results inside of the dropdown.
\n *\n * > When encountering issues regarding the item template please take a look at the\n * VirtualRepeatContainer documentation.\n *\n * **Autocomplete inside of a Virtual Repeat**\n *\n * When using the `md-autocomplete` directive inside of a\n * VirtualRepeatContainer the dropdown items\n * might not update properly, because caching of the results is enabled by default.\n *\n * The autocomplete will then show invalid dropdown items, because the Virtual Repeat only updates\n * the scope bindings rather than re-creating the `md-autocomplete`. This means that the previous\n * cached results will be used.\n *\n * > To avoid such problems, ensure that the autocomplete does not cache any results via\n * `md-no-cache=\"true\"`:\n *\n * \n * \n * {{ item.display }}\n * \n * \n *\n *\n * @param {expression} md-items An expression in the format of `item in results` to iterate over\n * matches for your search.
\n * The `results` expression can be also a function, which returns the results synchronously\n * or asynchronously (per Promise).\n * @param {expression=} md-selected-item-change An expression to be run each time a new item is\n * selected.\n * @param {expression=} md-search-text-change An expression to be run each time the search text\n * updates.\n * @param {expression=} md-search-text A model to bind the search query text to.\n * @param {object=} md-selected-item A model to bind the selected item to.\n * @param {expression=} md-item-text An expression that will convert your object to a single string.\n * @param {string=} placeholder Placeholder text that will be forwarded to the input.\n * @param {boolean=} md-no-cache Disables the internal caching that happens in autocomplete.\n * @param {boolean=} ng-disabled Determines whether or not to disable the input field.\n * @param {boolean=} md-require-match When set to true, the autocomplete will add a validator,\n * which will evaluate to false, when no item is currently selected.\n * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will\n * make suggestions.\n * @param {number=} md-delay Specifies the amount of time (in milliseconds) to wait before looking\n * for results.\n * @param {boolean=} md-clear-button Whether the clear button for the autocomplete input should show\n * up or not. When `md-floating-label` is set, defaults to false, defaults to true otherwise.\n * @param {boolean=} md-autofocus If true, the autocomplete will be automatically focused when a\n * `$mdDialog`, `$mdBottomsheet` or `$mdSidenav`, which contains the autocomplete, is opening.\n *
\n * Also the autocomplete will immediately focus the input element.\n * @param {boolean=} md-no-asterisk When present, asterisk will not be appended to the floating\n * label.\n * @param {boolean=} md-autoselect If set to true, the first item will be automatically selected\n * in the dropdown upon open.\n * @param {string=} md-input-name The name attribute given to the input element to be used with\n * FormController.\n * @param {string=} md-menu-class This class will be applied to the dropdown menu for styling.\n * @param {string=} md-menu-container-class This class will be applied to the parent container\n * of the dropdown panel.\n * @param {string=} md-input-class This will be applied to the input for styling. This attribute\n * is only valid when a `md-floating-label` is defined.\n * @param {string=} md-floating-label This will add a floating label to autocomplete and wrap it in\n * `md-input-container`.\n * @param {string=} md-select-on-focus When present the input's text will be automatically selected\n * on focus.\n * @param {string=} md-input-id An ID to be added to the input element.\n * @param {number=} md-input-minlength The minimum length for the input's value for validation.\n * @param {number=} md-input-maxlength The maximum length for the input's value for validation.\n * @param {boolean=} md-select-on-match When set, autocomplete will automatically select\n * the item if the search text is an exact match.
\n * An exact match is when only one match is displayed.\n * @param {boolean=} md-match-case-insensitive When set and using `md-select-on-match`, autocomplete\n * will select on case-insensitive match.\n * @param {string=} md-escape-options Override escape key logic. Default is `blur clear`.
\n * Options: `blur`, `clear`, `none`.\n * @param {string=} md-dropdown-items Specifies the maximum amount of items to be shown in\n * the dropdown.
\n * When the dropdown doesn't fit into the viewport, the dropdown will shrink\n * as much as possible.\n * @param {string=} md-dropdown-position Overrides the default dropdown position. Options: `top`,\n * `bottom`.\n * @param {string=} input-aria-describedby A space-separated list of element IDs. This should\n * contain the IDs of any elements that describe this autocomplete. Screen readers will read the\n * content of these elements at the end of announcing that the autocomplete has been selected\n * and describing its current state. The descriptive elements do not need to be visible on the\n * page.\n * @param {string=} input-aria-labelledby A space-separated list of element IDs. The ideal use case\n * is that this would contain the ID of a ` ' : '';\n }\n\n function getRepeatType(repeatMode) {\n return isVirtualRepeatDisabled(repeatMode) ?\n 'ng-repeat' : 'md-virtual-repeat';\n }\n\n function isVirtualRepeatDisabled(repeatMode) {\n // ensure we have a valid repeat mode\n var correctedRepeatMode = getRepeatMode(repeatMode);\n return correctedRepeatMode !== REPEAT_VIRTUAL;\n }\n\n function getInputElement () {\n if (attr.mdFloatingLabel) {\n return '\\\n