` if one is not provided. Defaults to true. Can be disabled if you provide a\n * custom toast directive.\n * - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.\n * This scope will be destroyed when the toast is removed unless `preserveScope` is set to true.\n * - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false\n * - `hideDelay` - `{number=}`: How many milliseconds the toast should stay\n * active before automatically closing. Set to 0 or false to have the toast stay open until\n * closed manually. Default: 3000.\n * - `position` - `{string=}`: Sets the position of the toast.
\n * Available: any combination of `'bottom'`, `'left'`, `'top'`, `'right'`, `'end'` and `'start'`.\n * The properties `'end'` and `'start'` are dynamic and can be used for RTL support.
\n * Default combination: `'bottom left'`.\n * - `toastClass` - `{string=}`: A class to set on the toast element.\n * - `controller` - `{string=}`: The controller to associate with this toast.\n * The controller will be injected the local `$mdToast.hide( )`, which is a function\n * used to hide the toast.\n * - `locals` - `{string=}`: An object containing key/value pairs. The keys will\n * be used as names of values to inject into the controller. For example,\n * `locals: {three: 3}` would inject `three` into the controller with the value\n * of 3.\n * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.\n * - `resolve` - `{object=}`: Similar to locals, except it takes promises as values\n * and the toast will not open until the promises resolve.\n * - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.\n * - `parent` - `{element=}`: The element to append the toast to. Defaults to appending\n * to the root element of the application.\n *\n * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or\n * rejected with `$mdToast.cancel()`. `$mdToast.hide()` will resolve either with a Boolean\n * value == 'true' or the value passed as an argument to `$mdToast.hide()`.\n * And `$mdToast.cancel()` will resolve the promise with a Boolean value == 'false'\n */\n\n/**\n * @ngdoc method\n * @name $mdToast#hide\n *\n * @description\n * Hide an existing toast and resolve the promise returned from `$mdToast.show()`.\n *\n * @param {*=} response An argument for the resolved promise.\n *\n * @returns {promise} a promise that is called when the existing element is removed from the DOM.\n * The promise is resolved with either a Boolean value == 'true' or the value passed as the\n * argument to `.hide()`.\n *\n */\n\n/**\n * @ngdoc method\n * @name $mdToast#cancel\n *\n * @description\n * `DEPRECATED` - The promise returned from opening a toast is used only to notify about the closing of the toast.\n * As such, there isn't any reason to also allow that promise to be rejected,\n * since it's not clear what the difference between resolve and reject would be.\n *\n * Hide the existing toast and reject the promise returned from\n * `$mdToast.show()`.\n *\n * @param {*=} response An argument for the rejected promise.\n *\n * @returns {promise} a promise that is called when the existing element is removed from the DOM\n * The promise is resolved with a Boolean value == 'false'.\n *\n */\n\nfunction MdToastProvider($$interimElementProvider) {\n // Differentiate promise resolves: hide timeout (value == true) and hide action clicks (value == ok).\n toastDefaultOptions.$inject = [\"$animate\", \"$mdToast\", \"$mdUtil\", \"$mdMedia\"];\n var ACTION_RESOLVE = 'ok';\n\n var activeToastContent;\n var $mdToast = $$interimElementProvider('$mdToast')\n .setDefaults({\n methods: ['position', 'hideDelay', 'capsule', 'parent', 'position', 'toastClass'],\n options: toastDefaultOptions\n })\n .addPreset('simple', {\n argOption: 'textContent',\n methods: ['textContent', 'content', 'action', 'highlightAction', 'highlightClass', 'theme', 'parent' ],\n options: /* @ngInject */ [\"$mdToast\", \"$mdTheming\", function($mdToast, $mdTheming) {\n return {\n template:\n '
' +\n ' ' +\n ' ' +\n ' {{ toast.content }}' +\n ' ' +\n ' ' +\n ' {{ toast.action }}' +\n ' ' +\n '
' +\n '',\n controller: /* @ngInject */ [\"$scope\", function mdToastCtrl($scope) {\n var self = this;\n\n if (self.highlightAction) {\n $scope.highlightClasses = [\n 'md-highlight',\n self.highlightClass\n ]\n }\n\n $scope.$watch(function() { return activeToastContent; }, function() {\n self.content = activeToastContent;\n });\n\n this.resolve = function() {\n $mdToast.hide( ACTION_RESOLVE );\n };\n }],\n theme: $mdTheming.defaultTheme(),\n controllerAs: 'toast',\n bindToController: true\n };\n }]\n })\n .addMethod('updateTextContent', updateTextContent)\n .addMethod('updateContent', updateTextContent);\n\n function updateTextContent(newContent) {\n activeToastContent = newContent;\n }\n\n return $mdToast;\n\n /* @ngInject */\n function toastDefaultOptions($animate, $mdToast, $mdUtil, $mdMedia) {\n var SWIPE_EVENTS = '$md.swipeleft $md.swiperight $md.swipeup $md.swipedown';\n return {\n onShow: onShow,\n onRemove: onRemove,\n toastClass: '',\n position: 'bottom left',\n themable: true,\n hideDelay: 3000,\n autoWrap: true,\n transformTemplate: function(template, options) {\n var shouldAddWrapper = options.autoWrap && template && !/md-toast-content/g.test(template);\n\n if (shouldAddWrapper) {\n // Root element of template will be
. We need to wrap all of its content inside of\n // of . All templates provided here should be static, developer-controlled\n // content (meaning we're not attempting to guard against XSS).\n var templateRoot = document.createElement('md-template');\n templateRoot.innerHTML = template;\n\n // Iterate through all root children, to detect possible md-toast directives.\n for (var i = 0; i < templateRoot.children.length; i++) {\n if (templateRoot.children[i].nodeName === 'MD-TOAST') {\n var wrapper = angular.element('
');\n\n // Wrap the children of the `md-toast` directive in jqLite, to be able to append multiple\n // nodes with the same execution.\n wrapper.append(angular.element(templateRoot.children[i].childNodes));\n\n // Append the new wrapped element to the `md-toast` directive.\n templateRoot.children[i].appendChild(wrapper[0]);\n }\n }\n\n // We have to return the innerHTMl, because we do not want to have the `md-template` element to be\n // the root element of our interimElement.\n return templateRoot.innerHTML;\n }\n\n return template || '';\n }\n };\n\n function onShow(scope, element, options) {\n activeToastContent = options.textContent || options.content; // support deprecated #content method\n\n var isSmScreen = !$mdMedia('gt-sm');\n\n element = $mdUtil.extractElementByName(element, 'md-toast', true);\n options.element = element;\n\n options.onSwipe = function(ev, gesture) {\n //Add the relevant swipe class to the element so it can animate correctly\n var swipe = ev.type.replace('$md.','');\n var direction = swipe.replace('swipe', '');\n\n // If the swipe direction is down/up but the toast came from top/bottom don't fade away\n // Unless the screen is small, then the toast always on bottom\n if ((direction === 'down' && options.position.indexOf('top') != -1 && !isSmScreen) ||\n (direction === 'up' && (options.position.indexOf('bottom') != -1 || isSmScreen))) {\n return;\n }\n\n if ((direction === 'left' || direction === 'right') && isSmScreen) {\n return;\n }\n\n element.addClass('md-' + swipe);\n $mdUtil.nextTick($mdToast.cancel);\n };\n options.openClass = toastOpenClass(options.position);\n\n element.addClass(options.toastClass);\n\n // 'top left' -> 'md-top md-left'\n options.parent.addClass(options.openClass);\n\n // static is the default position\n if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {\n options.parent.css('position', 'relative');\n }\n\n element.on(SWIPE_EVENTS, options.onSwipe);\n element.addClass(isSmScreen ? 'md-bottom' : options.position.split(' ').map(function(pos) {\n return 'md-' + pos;\n }).join(' '));\n\n if (options.parent) options.parent.addClass('md-toast-animating');\n return $animate.enter(element, options.parent).then(function() {\n if (options.parent) options.parent.removeClass('md-toast-animating');\n });\n }\n\n function onRemove(scope, element, options) {\n element.off(SWIPE_EVENTS, options.onSwipe);\n if (options.parent) options.parent.addClass('md-toast-animating');\n if (options.openClass) options.parent.removeClass(options.openClass);\n\n return ((options.$destroy == true) ? element.remove() : $animate.leave(element))\n .then(function () {\n if (options.parent) options.parent.removeClass('md-toast-animating');\n if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {\n options.parent.css('position', '');\n }\n });\n }\n\n function toastOpenClass(position) {\n // For mobile, always open full-width on bottom\n if (!$mdMedia('gt-xs')) {\n return 'md-toast-open-bottom';\n }\n\n return 'md-toast-open-' +\n (position.indexOf('top') > -1 ? 'top' : 'bottom');\n }\n }\n\n}\n\n})();\n(function(){\n\"use strict\";\n\n/**\n * @ngdoc module\n * @name material.components.toolbar\n */\nmdToolbarDirective.$inject = [\"$$rAF\", \"$mdConstant\", \"$mdUtil\", \"$mdTheming\", \"$animate\"];\nangular.module('material.components.toolbar', [\n 'material.core',\n 'material.components.content'\n])\n .directive('mdToolbar', mdToolbarDirective);\n\n/**\n * @ngdoc directive\n * @name mdToolbar\n * @module material.components.toolbar\n * @restrict E\n * @description\n * `md-toolbar` is used to place a toolbar in your app.\n *\n * Toolbars are usually used above a content area to display the title of the\n * current page, and show relevant action buttons for that page.\n *\n * You can change the height of the toolbar by adding either the\n * `md-medium-tall` or `md-tall` class to the toolbar.\n *\n * @usage\n *
\n * \n *
\n *\n * \n *
My App's Title
\n *\n * \n * Right Bar Button\n * \n * \n *\n * \n *
\n * Hello!\n * \n *
\n * \n *\n *
Note: The code above shows usage with the `md-truncate` component which provides an\n * ellipsis if the title is longer than the width of the Toolbar.\n *\n * ## CSS & Styles\n *\n * The `
` provides a few custom CSS classes that you may use to enhance the\n * functionality of your toolbar.\n *\n * \n * \n *\n * \n * The `md-toolbar-tools` class provides quite a bit of automatic styling for your toolbar\n * buttons and text. When applied, it will center the buttons and text vertically for you.\n * \n *\n * \n *
\n *\n * ### Private Classes\n *\n * Currently, the only private class is the `md-toolbar-transitions` class. All other classes are\n * considered public.\n *\n * @param {boolean=} md-scroll-shrink Whether the header should shrink away as\n * the user scrolls down, and reveal itself as the user scrolls up.\n *\n * _**Note (1):** for scrollShrink to work, the toolbar must be a sibling of a\n * `md-content` element, placed before it. See the scroll shrink demo._\n *\n * _**Note (2):** The `md-scroll-shrink` attribute is only parsed on component\n * initialization, it does not watch for scope changes._\n *\n *\n * @param {number=} md-shrink-speed-factor How much to change the speed of the toolbar's\n * shrinking by. For example, if 0.25 is given then the toolbar will shrink\n * at one fourth the rate at which the user scrolls down. Default 0.5.\n *\n */\n\nfunction mdToolbarDirective($$rAF, $mdConstant, $mdUtil, $mdTheming, $animate) {\n var translateY = angular.bind(null, $mdUtil.supplant, 'translate3d(0,{0}px,0)');\n\n return {\n template: '',\n restrict: 'E',\n\n link: function(scope, element, attr) {\n\n element.addClass('_md'); // private md component indicator for styling\n $mdTheming(element);\n\n $mdUtil.nextTick(function () {\n element.addClass('_md-toolbar-transitions'); // adding toolbar transitions after digest\n }, false);\n\n if (angular.isDefined(attr.mdScrollShrink)) {\n setupScrollShrink();\n }\n\n function setupScrollShrink() {\n\n var toolbarHeight;\n var contentElement;\n var disableScrollShrink = angular.noop;\n\n // Current \"y\" position of scroll\n // Store the last scroll top position\n var y = 0;\n var prevScrollTop = 0;\n var shrinkSpeedFactor = attr.mdShrinkSpeedFactor || 0.5;\n\n var debouncedContentScroll = $$rAF.throttle(onContentScroll);\n var debouncedUpdateHeight = $mdUtil.debounce(updateToolbarHeight, 5 * 1000);\n\n // Wait for $mdContentLoaded event from mdContent directive.\n // If the mdContent element is a sibling of our toolbar, hook it up\n // to scroll events.\n\n scope.$on('$mdContentLoaded', onMdContentLoad);\n\n // If the toolbar is used inside an ng-if statement, we may miss the\n // $mdContentLoaded event, so we attempt to fake it if we have a\n // md-content close enough.\n\n attr.$observe('mdScrollShrink', onChangeScrollShrink);\n\n // If the toolbar has ngShow or ngHide we need to update height immediately as it changed\n // and not wait for $mdUtil.debounce to happen\n\n if (attr.ngShow) { scope.$watch(attr.ngShow, updateToolbarHeight); }\n if (attr.ngHide) { scope.$watch(attr.ngHide, updateToolbarHeight); }\n\n // If the scope is destroyed (which could happen with ng-if), make sure\n // to disable scroll shrinking again\n\n scope.$on('$destroy', disableScrollShrink);\n\n /**\n *\n */\n function onChangeScrollShrink(shrinkWithScroll) {\n var closestContent = element.parent().find('md-content');\n\n // If we have a content element, fake the call; this might still fail\n // if the content element isn't a sibling of the toolbar\n\n if (!contentElement && closestContent.length) {\n onMdContentLoad(null, closestContent);\n }\n\n // Evaluate the expression\n shrinkWithScroll = scope.$eval(shrinkWithScroll);\n\n // Disable only if the attribute's expression evaluates to false\n if (shrinkWithScroll === false) {\n disableScrollShrink();\n } else {\n disableScrollShrink = enableScrollShrink();\n }\n }\n\n /**\n *\n */\n function onMdContentLoad($event, newContentEl) {\n // Toolbar and content must be siblings\n if (newContentEl && element.parent()[0] === newContentEl.parent()[0]) {\n // unhook old content event listener if exists\n if (contentElement) {\n contentElement.off('scroll', debouncedContentScroll);\n }\n\n contentElement = newContentEl;\n disableScrollShrink = enableScrollShrink();\n }\n }\n\n /**\n *\n */\n function onContentScroll(e) {\n var scrollTop = e ? e.target.scrollTop : prevScrollTop;\n\n debouncedUpdateHeight();\n\n y = Math.min(\n toolbarHeight / shrinkSpeedFactor,\n Math.max(0, y + scrollTop - prevScrollTop)\n );\n\n element.css($mdConstant.CSS.TRANSFORM, translateY([-y * shrinkSpeedFactor]));\n contentElement.css($mdConstant.CSS.TRANSFORM, translateY([(toolbarHeight - y) * shrinkSpeedFactor]));\n\n prevScrollTop = scrollTop;\n\n $mdUtil.nextTick(function() {\n var hasWhiteFrame = element.hasClass('md-whiteframe-z1');\n\n if (hasWhiteFrame && !y) {\n $animate.removeClass(element, 'md-whiteframe-z1');\n } else if (!hasWhiteFrame && y) {\n $animate.addClass(element, 'md-whiteframe-z1');\n }\n });\n\n }\n\n /**\n *\n */\n function enableScrollShrink() {\n if (!contentElement) return angular.noop; // no md-content\n\n contentElement.on('scroll', debouncedContentScroll);\n contentElement.attr('scroll-shrink', 'true');\n\n $mdUtil.nextTick(updateToolbarHeight, false);\n\n return function disableScrollShrink() {\n contentElement.off('scroll', debouncedContentScroll);\n contentElement.attr('scroll-shrink', 'false');\n\n updateToolbarHeight();\n };\n }\n\n /**\n *\n */\n function updateToolbarHeight() {\n toolbarHeight = element.prop('offsetHeight');\n // Add a negative margin-top the size of the toolbar to the content el.\n // The content will start transformed down the toolbarHeight amount,\n // so everything looks normal.\n //\n // As the user scrolls down, the content will be transformed up slowly\n // to put the content underneath where the toolbar was.\n var margin = (-toolbarHeight * shrinkSpeedFactor) + 'px';\n\n contentElement.css({\n \"margin-top\": margin,\n \"margin-bottom\": margin\n });\n\n onContentScroll();\n }\n\n }\n\n }\n };\n\n}\n\n})();\n(function(){\n\"use strict\";\n\n/**\n * @ngdoc module\n * @name material.components.tooltip\n */\nMdTooltipDirective.$inject = [\"$timeout\", \"$window\", \"$$rAF\", \"$document\", \"$interpolate\", \"$mdUtil\", \"$mdPanel\", \"$$mdTooltipRegistry\"];\nangular\n .module('material.components.tooltip', [\n 'material.core',\n 'material.components.panel'\n ])\n .directive('mdTooltip', MdTooltipDirective)\n .service('$$mdTooltipRegistry', MdTooltipRegistry);\n\n\n/**\n * @ngdoc directive\n * @name mdTooltip\n * @module material.components.tooltip\n * @description\n * Tooltips are used to describe elements that are interactive and primarily\n * graphical (not textual).\n *\n * Place a `` as a child of the element it describes.\n *\n * A tooltip will activate when the user hovers over, focuses, or touches the\n * parent element.\n *\n * @usage\n * \n * \n * Play Music\n * \n * \n * \n *\n * @param {number=} md-z-index The visual level that the tooltip will appear\n * in comparison with the rest of the elements of the application.\n * @param {expression=} md-visible Boolean bound to whether the tooltip is\n * currently visible.\n * @param {number=} md-delay How many milliseconds to wait to show the tooltip\n * after the user hovers over, focuses, or touches the parent element.\n * Defaults to 0ms on non-touch devices and 75ms on touch.\n * @param {boolean=} md-autohide If present or provided with a boolean value,\n * the tooltip will hide on mouse leave, regardless of focus.\n * @param {string=} md-direction The direction that the tooltip is shown,\n * relative to the parent element. Supports top, right, bottom, and left.\n * Defaults to bottom.\n */\nfunction MdTooltipDirective($timeout, $window, $$rAF, $document, $interpolate,\n $mdUtil, $mdPanel, $$mdTooltipRegistry) {\n\n var ENTER_EVENTS = 'focus touchstart mouseenter';\n var LEAVE_EVENTS = 'blur touchcancel mouseleave';\n var TOOLTIP_DEFAULT_Z_INDEX = 100;\n var TOOLTIP_DEFAULT_SHOW_DELAY = 0;\n var TOOLTIP_DEFAULT_DIRECTION = 'bottom';\n var TOOLTIP_DIRECTIONS = {\n top: { x: $mdPanel.xPosition.CENTER, y: $mdPanel.yPosition.ABOVE },\n right: { x: $mdPanel.xPosition.OFFSET_END, y: $mdPanel.yPosition.CENTER },\n bottom: { x: $mdPanel.xPosition.CENTER, y: $mdPanel.yPosition.BELOW },\n left: { x: $mdPanel.xPosition.OFFSET_START, y: $mdPanel.yPosition.CENTER }\n };\n\n return {\n restrict: 'E',\n priority: 210, // Before ngAria\n scope: {\n mdZIndex: '=?mdZIndex',\n mdDelay: '=?mdDelay',\n mdVisible: '=?mdVisible',\n mdAutohide: '=?mdAutohide',\n mdDirection: '@?mdDirection' // Do not expect expressions.\n },\n link: linkFunc\n };\n\n function linkFunc(scope, element, attr) {\n // Set constants.\n var tooltipId = 'md-tooltip-' + $mdUtil.nextUid();\n var parent = $mdUtil.getParentWithPointerEvents(element);\n var debouncedOnResize = $$rAF.throttle(updatePosition);\n var mouseActive = false;\n var origin, position, panelPosition, panelRef, autohide, showTimeout,\n elementFocusedOnWindowBlur = null;\n\n // Set defaults\n setDefaults();\n\n // Set parent aria-label.\n addAriaLabel();\n\n // Remove the element from its current DOM position.\n element.detach();\n\n updatePosition();\n bindEvents();\n configureWatchers();\n\n function setDefaults() {\n scope.mdZIndex = scope.mdZIndex || TOOLTIP_DEFAULT_Z_INDEX;\n scope.mdDelay = scope.mdDelay || TOOLTIP_DEFAULT_SHOW_DELAY;\n if (!TOOLTIP_DIRECTIONS[scope.mdDirection]) {\n scope.mdDirection = TOOLTIP_DEFAULT_DIRECTION;\n }\n }\n\n function addAriaLabel(labelText) {\n // Only interpolate the text from the HTML element because otherwise the custom text could\n // be interpolated twice and cause XSS violations.\n var interpolatedText = labelText || $interpolate(element.text().trim())(scope.$parent);\n\n // Only add the `aria-label` to the parent if there isn't already one, if there isn't an\n // already present `aria-labelledby`, or if the previous `aria-label` was added by the\n // tooltip directive.\n if (\n (!parent.attr('aria-label') && !parent.attr('aria-labelledby')) ||\n parent.attr('md-labeled-by-tooltip')\n ) {\n parent.attr('aria-label', interpolatedText);\n\n // Set the `md-labeled-by-tooltip` attribute if it has not already been set.\n if (!parent.attr('md-labeled-by-tooltip')) {\n parent.attr('md-labeled-by-tooltip', tooltipId);\n }\n }\n }\n\n function updatePosition() {\n setDefaults();\n\n // If the panel has already been created, remove the current origin\n // class from the panel element.\n if (panelRef && panelRef.panelEl) {\n panelRef.panelEl.removeClass(origin);\n }\n\n // Set the panel element origin class based off of the current\n // mdDirection.\n origin = 'md-origin-' + scope.mdDirection;\n\n // Create the position of the panel based off of the mdDirection.\n position = TOOLTIP_DIRECTIONS[scope.mdDirection];\n\n // Using the newly created position object, use the MdPanel\n // panelPosition API to build the panel's position.\n panelPosition = $mdPanel.newPanelPosition()\n .relativeTo(parent)\n .addPanelPosition(position.x, position.y);\n\n // If the panel has already been created, add the new origin class to\n // the panel element and update it's position with the panelPosition.\n if (panelRef && panelRef.panelEl) {\n panelRef.panelEl.addClass(origin);\n panelRef.updatePosition(panelPosition);\n }\n }\n\n function bindEvents() {\n // Add a mutationObserver where there is support for it and the need\n // for it in the form of viable host(parent[0]).\n if (parent[0] && 'MutationObserver' in $window) {\n // Use a mutationObserver to tackle #2602.\n var attributeObserver = new MutationObserver(function(mutations) {\n if (isDisabledMutation(mutations)) {\n $mdUtil.nextTick(function() {\n setVisible(false);\n });\n }\n });\n\n attributeObserver.observe(parent[0], {\n attributes: true\n });\n }\n\n elementFocusedOnWindowBlur = false;\n\n $$mdTooltipRegistry.register('scroll', windowScrollEventHandler, true);\n $$mdTooltipRegistry.register('blur', windowBlurEventHandler);\n $$mdTooltipRegistry.register('resize', debouncedOnResize);\n\n scope.$on('$destroy', onDestroy);\n\n // To avoid 'synthetic clicks', we listen to mousedown instead of\n // 'click'.\n parent.on('mousedown', mousedownEventHandler);\n parent.on(ENTER_EVENTS, enterEventHandler);\n\n function isDisabledMutation(mutations) {\n mutations.some(function(mutation) {\n return mutation.attributeName === 'disabled' && parent[0].disabled;\n });\n return false;\n }\n\n function windowScrollEventHandler() {\n setVisible(false);\n }\n\n function windowBlurEventHandler() {\n elementFocusedOnWindowBlur = document.activeElement === parent[0];\n }\n\n function enterEventHandler($event) {\n // Prevent the tooltip from showing when the window is receiving\n // focus.\n if ($event.type === 'focus' && elementFocusedOnWindowBlur) {\n elementFocusedOnWindowBlur = false;\n } else if (!scope.mdVisible) {\n parent.on(LEAVE_EVENTS, leaveEventHandler);\n setVisible(true);\n\n // If the user is on a touch device, we should bind the tap away\n // after the 'touched' in order to prevent the tooltip being\n // removed immediately.\n if ($event.type === 'touchstart') {\n parent.one('touchend', function() {\n $mdUtil.nextTick(function() {\n $document.one('touchend', leaveEventHandler);\n }, false);\n });\n }\n }\n }\n\n function leaveEventHandler() {\n autohide = scope.hasOwnProperty('mdAutohide') ?\n scope.mdAutohide :\n attr.hasOwnProperty('mdAutohide');\n\n if (autohide || mouseActive ||\n $document[0].activeElement !== parent[0]) {\n // When a show timeout is currently in progress, then we have\n // to cancel it, otherwise the tooltip will remain showing\n // without focus or hover.\n if (showTimeout) {\n $timeout.cancel(showTimeout);\n setVisible.queued = false;\n showTimeout = null;\n }\n\n parent.off(LEAVE_EVENTS, leaveEventHandler);\n parent.triggerHandler('blur');\n setVisible(false);\n }\n mouseActive = false;\n }\n\n function mousedownEventHandler() {\n mouseActive = true;\n }\n\n function onDestroy() {\n $$mdTooltipRegistry.deregister('scroll', windowScrollEventHandler, true);\n $$mdTooltipRegistry.deregister('blur', windowBlurEventHandler);\n $$mdTooltipRegistry.deregister('resize', debouncedOnResize);\n\n parent\n .off(ENTER_EVENTS, enterEventHandler)\n .off(LEAVE_EVENTS, leaveEventHandler)\n .off('mousedown', mousedownEventHandler);\n\n // Trigger the handler in case any of the tooltips are\n // still visible.\n leaveEventHandler();\n attributeObserver && attributeObserver.disconnect();\n }\n }\n\n function configureWatchers() {\n if (element[0] && 'MutationObserver' in $window) {\n var attributeObserver = new MutationObserver(function(mutations) {\n mutations.forEach(function(mutation) {\n if (mutation.attributeName === 'md-visible' &&\n !scope.visibleWatcher ) {\n scope.visibleWatcher = scope.$watch('mdVisible',\n onVisibleChanged);\n }\n });\n });\n\n attributeObserver.observe(element[0], {\n attributes: true\n });\n\n // Build watcher only if mdVisible is being used.\n if (attr.hasOwnProperty('mdVisible')) {\n scope.visibleWatcher = scope.$watch('mdVisible',\n onVisibleChanged);\n }\n } else {\n // MutationObserver not supported\n scope.visibleWatcher = scope.$watch('mdVisible', onVisibleChanged);\n }\n\n // Direction watcher\n scope.$watch('mdDirection', updatePosition);\n\n // Clean up if the element or parent was removed via jqLite's .remove.\n // A couple of notes:\n // - In these cases the scope might not have been destroyed, which\n // is why we destroy it manually. An example of this can be having\n // `md-visible=\"false\"` and adding tooltips while they're\n // invisible. If `md-visible` becomes true, at some point, you'd\n // usually get a lot of tooltips.\n // - We use `.one`, not `.on`, because this only needs to fire once.\n // If we were using `.on`, it would get thrown into an infinite\n // loop.\n // - This kicks off the scope's `$destroy` event which finishes the\n // cleanup.\n element.one('$destroy', onElementDestroy);\n parent.one('$destroy', onElementDestroy);\n scope.$on('$destroy', function() {\n setVisible(false);\n panelRef && panelRef.destroy();\n attributeObserver && attributeObserver.disconnect();\n element.remove();\n });\n\n // Updates the aria-label when the element text changes. This watch\n // doesn't need to be set up if the element doesn't have any data\n // bindings.\n if (element.text().indexOf($interpolate.startSymbol()) > -1) {\n scope.$watch(function() {\n return element.text().trim();\n }, addAriaLabel);\n }\n\n function onElementDestroy() {\n scope.$destroy();\n }\n }\n\n function setVisible(value) {\n // Break if passed value is already in queue or there is no queue and\n // passed value is current in the controller.\n if (setVisible.queued && setVisible.value === !!value ||\n !setVisible.queued && scope.mdVisible === !!value) {\n return;\n }\n setVisible.value = !!value;\n\n if (!setVisible.queued) {\n if (value) {\n setVisible.queued = true;\n showTimeout = $timeout(function() {\n scope.mdVisible = setVisible.value;\n setVisible.queued = false;\n showTimeout = null;\n if (!scope.visibleWatcher) {\n onVisibleChanged(scope.mdVisible);\n }\n }, scope.mdDelay);\n } else {\n $mdUtil.nextTick(function() {\n scope.mdVisible = false;\n if (!scope.visibleWatcher) {\n onVisibleChanged(false);\n }\n });\n }\n }\n }\n\n function onVisibleChanged(isVisible) {\n isVisible ? showTooltip() : hideTooltip();\n }\n\n function showTooltip() {\n // Do not show the tooltip if the text is empty.\n if (!element[0].textContent.trim()) {\n throw new Error('Text for the tooltip has not been provided. ' +\n 'Please include text within the mdTooltip element.');\n }\n\n if (!panelRef) {\n var attachTo = angular.element(document.body);\n var panelAnimation = $mdPanel.newPanelAnimation()\n .openFrom(parent)\n .closeTo(parent)\n .withAnimation({\n open: 'md-show',\n close: 'md-hide'\n });\n\n var panelConfig = {\n id: tooltipId,\n attachTo: attachTo,\n contentElement: element,\n propagateContainerEvents: true,\n panelClass: 'md-tooltip ' + origin,\n animation: panelAnimation,\n position: panelPosition,\n zIndex: scope.mdZIndex,\n focusOnOpen: false\n };\n\n panelRef = $mdPanel.create(panelConfig);\n }\n\n panelRef.open().then(function() {\n panelRef.panelEl.attr('role', 'tooltip');\n });\n }\n\n function hideTooltip() {\n panelRef && panelRef.close();\n }\n }\n\n}\n\n\n/**\n * Service that is used to reduce the amount of listeners that are being\n * registered on the `window` by the tooltip component. Works by collecting\n * the individual event handlers and dispatching them from a global handler.\n *\n * @ngInject\n */\nfunction MdTooltipRegistry() {\n var listeners = {};\n var ngWindow = angular.element(window);\n\n return {\n register: register,\n deregister: deregister\n };\n\n /**\n * Global event handler that dispatches the registered handlers in the\n * service.\n * @param {!Event} event Event object passed in by the browser\n */\n function globalEventHandler(event) {\n if (listeners[event.type]) {\n listeners[event.type].forEach(function(currentHandler) {\n currentHandler.call(this, event);\n }, this);\n }\n }\n\n /**\n * Registers a new handler with the service.\n * @param {string} type Type of event to be registered.\n * @param {!Function} handler Event handler.\n * @param {boolean} useCapture Whether to use event capturing.\n */\n function register(type, handler, useCapture) {\n var handlers = listeners[type] = listeners[type] || [];\n\n if (!handlers.length) {\n useCapture ? window.addEventListener(type, globalEventHandler, true) :\n ngWindow.on(type, globalEventHandler);\n }\n\n if (handlers.indexOf(handler) === -1) {\n handlers.push(handler);\n }\n }\n\n /**\n * Removes an event handler from the service.\n * @param {string} type Type of event handler.\n * @param {!Function} handler The event handler itself.\n * @param {boolean} useCapture Whether the event handler used event capturing.\n */\n function deregister(type, handler, useCapture) {\n var handlers = listeners[type];\n var index = handlers ? handlers.indexOf(handler) : -1;\n\n if (index > -1) {\n handlers.splice(index, 1);\n\n if (handlers.length === 0) {\n useCapture ? window.removeEventListener(type, globalEventHandler, true) :\n ngWindow.off(type, globalEventHandler);\n }\n }\n }\n}\n\n})();\n(function(){\n\"use strict\";\n\n/**\n * @ngdoc module\n * @name material.components.truncate\n */\nMdTruncateController.$inject = [\"$element\"];\nangular.module('material.components.truncate', ['material.core'])\n .directive('mdTruncate', MdTruncateDirective);\n\n/**\n * @ngdoc directive\n * @name mdTruncate\n * @module material.components.truncate\n * @restrict AE\n * @description\n *\n * The `md-truncate` component displays a label that will automatically clip text which is wider\n * than the component. By default, it displays an ellipsis, but you may apply the `md-clip` CSS\n * class to override this default and use a standard \"clipping\" approach.\n *\n * Note: The `md-truncate` component does not automatically adjust it's width. You must\n * provide the `flex` attribute, or some other CSS-based width management. See the\n * demos for examples.\n *\n * @usage\n *\n * ### As an Element\n *\n * \n * \n * Back\n *\n * Chapter 1 - The Way of the Old West\n *\n * Forward\n *
\n * \n *\n * ### As an Attribute\n *\n * \n * Some Title With a Lot of Text
\n * \n *\n * ## CSS & Styles\n *\n * `` provides two CSS classes that you may use to control the type of clipping.\n *\n * Note: The `md-truncate` also applies a setting of `width: 0;` when used with the `flex`\n * attribute to fix an issue with the flex element not shrinking properly.\n *\n * \n * \n *\n * \n * Assigns the \"ellipsis\" behavior (default) which will cut off mid-word and append an ellipsis\n * (…) to the end of the text.\n * \n *\n * \n * Assigns the \"clipping\" behavior which will simply chop off the text. This may happen\n * mid-word or even mid-character.\n * \n *\n * \n *
\n */\nfunction MdTruncateDirective() {\n return {\n restrict: 'AE',\n\n controller: MdTruncateController,\n controllerAs: '$ctrl',\n bindToController: true\n }\n}\n\n/**\n * Controller for the component.\n *\n * @param $element The md-truncate element.\n *\n * @constructor\n * @ngInject\n */\nfunction MdTruncateController($element) {\n $element.addClass('md-truncate');\n}\n\n})();\n(function(){\n\"use strict\";\n\n/**\n * @ngdoc module\n * @name material.components.virtualRepeat\n */\nVirtualRepeatContainerController.$inject = [\"$$rAF\", \"$mdUtil\", \"$mdConstant\", \"$parse\", \"$rootScope\", \"$window\", \"$scope\", \"$element\", \"$attrs\"];\nVirtualRepeatController.$inject = [\"$scope\", \"$element\", \"$attrs\", \"$browser\", \"$document\", \"$rootScope\", \"$$rAF\", \"$mdUtil\"];\nVirtualRepeatDirective.$inject = [\"$parse\"];\nangular.module('material.components.virtualRepeat', [\n 'material.core',\n 'material.components.showHide'\n])\n.directive('mdVirtualRepeatContainer', VirtualRepeatContainerDirective)\n.directive('mdVirtualRepeat', VirtualRepeatDirective);\n\n\n/**\n * @ngdoc directive\n * @name mdVirtualRepeatContainer\n * @module material.components.virtualRepeat\n * @restrict E\n * @description\n * `md-virtual-repeat-container` provides the scroll container for md-virtual-repeat.\n *\n * VirtualRepeat is a limited substitute for ng-repeat that renders only\n * enough DOM nodes to fill the container and recycling them as the user scrolls.\n *\n * Once an element is not visible anymore, the VirtualRepeat recycles it and will reuse it for\n * another visible item by replacing the previous dataset with the new one.\n *\n * ### Common Issues\n *\n * - When having one-time bindings inside of the view template, the VirtualRepeat will not properly\n * update the bindings for new items, since the view will be recycled.\n *\n * - Directives inside of a VirtualRepeat will be only compiled (linked) once, because those\n * are will be recycled items and used for other items.\n * The VirtualRepeat just updates the scope bindings.\n *\n *\n * ### Notes\n *\n * > The VirtualRepeat is a similar implementation to the Android\n * [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)\n *\n * \n *\n * > Please also review the VirtualRepeat\n * documentation for more information.\n *\n *\n * @usage\n * \n *\n * \n * Hello {{i}}!
\n * \n * \n *\n * @param {number=} md-top-index Binds the index of the item that is at the top of the scroll\n * container to $scope. It can both read and set the scroll position.\n * @param {boolean=} md-orient-horizontal Whether the container should scroll horizontally\n * (defaults to orientation and scrolling vertically).\n * @param {boolean=} md-auto-shrink When present, the container will shrink to fit\n * the number of items when that number is less than its original size.\n * @param {number=} md-auto-shrink-min Minimum number of items that md-auto-shrink\n * will shrink to (default: 0).\n */\nfunction VirtualRepeatContainerDirective() {\n return {\n controller: VirtualRepeatContainerController,\n template: virtualRepeatContainerTemplate,\n compile: function virtualRepeatContainerCompile($element, $attrs) {\n $element\n .addClass('md-virtual-repeat-container')\n .addClass($attrs.hasOwnProperty('mdOrientHorizontal')\n ? 'md-orient-horizontal'\n : 'md-orient-vertical');\n }\n };\n}\n\n\nfunction virtualRepeatContainerTemplate($element) {\n return '';\n}\n\n/**\n * Number of additional elements to render above and below the visible area inside\n * of the virtual repeat container. A higher number results in less flicker when scrolling\n * very quickly in Safari, but comes with a higher rendering and dirty-checking cost.\n * @const {number}\n */\nvar NUM_EXTRA = 3;\n\n/** @ngInject */\nfunction VirtualRepeatContainerController($$rAF, $mdUtil, $mdConstant, $parse, $rootScope, $window, $scope,\n $element, $attrs) {\n this.$rootScope = $rootScope;\n this.$scope = $scope;\n this.$element = $element;\n this.$attrs = $attrs;\n\n /** @type {number} The width or height of the container */\n this.size = 0;\n /** @type {number} The scroll width or height of the scroller */\n this.scrollSize = 0;\n /** @type {number} The scrollLeft or scrollTop of the scroller */\n this.scrollOffset = 0;\n /** @type {boolean} Whether the scroller is oriented horizontally */\n this.horizontal = this.$attrs.hasOwnProperty('mdOrientHorizontal');\n /** @type {!VirtualRepeatController} The repeater inside of this container */\n this.repeater = null;\n /** @type {boolean} Whether auto-shrink is enabled */\n this.autoShrink = this.$attrs.hasOwnProperty('mdAutoShrink');\n /** @type {number} Minimum number of items to auto-shrink to */\n this.autoShrinkMin = parseInt(this.$attrs.mdAutoShrinkMin, 10) || 0;\n /** @type {?number} Original container size when shrank */\n this.originalSize = null;\n /** @type {number} Amount to offset the total scroll size by. */\n this.offsetSize = parseInt(this.$attrs.mdOffsetSize, 10) || 0;\n /** @type {?string} height or width element style on the container prior to auto-shrinking. */\n this.oldElementSize = null;\n /** @type {!number} Maximum amount of pixels allowed for a single DOM element */\n this.maxElementPixels = $mdConstant.ELEMENT_MAX_PIXELS;\n\n if (this.$attrs.mdTopIndex) {\n /** @type {function(angular.Scope): number} Binds to topIndex on AngularJS scope */\n this.bindTopIndex = $parse(this.$attrs.mdTopIndex);\n /** @type {number} The index of the item that is at the top of the scroll container */\n this.topIndex = this.bindTopIndex(this.$scope);\n\n if (!angular.isDefined(this.topIndex)) {\n this.topIndex = 0;\n this.bindTopIndex.assign(this.$scope, 0);\n }\n\n this.$scope.$watch(this.bindTopIndex, angular.bind(this, function(newIndex) {\n if (newIndex !== this.topIndex) {\n this.scrollToIndex(newIndex);\n }\n }));\n } else {\n this.topIndex = 0;\n }\n\n this.scroller = $element[0].querySelector('.md-virtual-repeat-scroller');\n this.sizer = this.scroller.querySelector('.md-virtual-repeat-sizer');\n this.offsetter = this.scroller.querySelector('.md-virtual-repeat-offsetter');\n\n // After the dom stablizes, measure the initial size of the container and\n // make a best effort at re-measuring as it changes.\n var boundUpdateSize = angular.bind(this, this.updateSize);\n\n $$rAF(angular.bind(this, function() {\n boundUpdateSize();\n\n var debouncedUpdateSize = $mdUtil.debounce(boundUpdateSize, 10, null, false);\n var jWindow = angular.element($window);\n\n // Make one more attempt to get the size if it is 0.\n // This is not by any means a perfect approach, but there's really no\n // silver bullet here.\n if (!this.size) {\n debouncedUpdateSize();\n }\n\n jWindow.on('resize', debouncedUpdateSize);\n $scope.$on('$destroy', function() {\n jWindow.off('resize', debouncedUpdateSize);\n });\n\n $scope.$emit('$md-resize-enable');\n $scope.$on('$md-resize', boundUpdateSize);\n }));\n}\n\n\n/** Called by the md-virtual-repeat inside of the container at startup. */\nVirtualRepeatContainerController.prototype.register = function(repeaterCtrl) {\n this.repeater = repeaterCtrl;\n\n angular.element(this.scroller)\n .on('scroll wheel touchmove touchend', angular.bind(this, this.handleScroll_));\n};\n\n\n/** @return {boolean} Whether the container is configured for horizontal scrolling. */\nVirtualRepeatContainerController.prototype.isHorizontal = function() {\n return this.horizontal;\n};\n\n\n/** @return {number} The size (width or height) of the container. */\nVirtualRepeatContainerController.prototype.getSize = function() {\n return this.size;\n};\n\n\n/**\n * Resizes the container.\n * @private\n * @param {number} size The new size to set.\n */\nVirtualRepeatContainerController.prototype.setSize_ = function(size) {\n var dimension = this.getDimensionName_();\n\n this.size = size;\n this.$element[0].style[dimension] = size + 'px';\n};\n\n\nVirtualRepeatContainerController.prototype.unsetSize_ = function() {\n this.$element[0].style[this.getDimensionName_()] = this.oldElementSize;\n this.oldElementSize = null;\n};\n\n\n/** Instructs the container to re-measure its size. */\nVirtualRepeatContainerController.prototype.updateSize = function() {\n // If the original size is already determined, we can skip the update.\n if (this.originalSize) return;\n\n this.size = this.isHorizontal()\n ? this.$element[0].clientWidth\n : this.$element[0].clientHeight;\n\n // Recheck the scroll position after updating the size. This resolves\n // problems that can result if the scroll position was measured while the\n // element was display: none or detached from the document.\n this.handleScroll_();\n\n this.repeater && this.repeater.containerUpdated();\n};\n\n\n/** @return {number} The container's scrollHeight or scrollWidth. */\nVirtualRepeatContainerController.prototype.getScrollSize = function() {\n return this.scrollSize;\n};\n\n\nVirtualRepeatContainerController.prototype.getDimensionName_ = function() {\n return this.isHorizontal() ? 'width' : 'height';\n};\n\n\n/**\n * Sets the scroller element to the specified size.\n * @private\n * @param {number} size The new size.\n */\nVirtualRepeatContainerController.prototype.sizeScroller_ = function(size) {\n var dimension = this.getDimensionName_();\n var crossDimension = this.isHorizontal() ? 'height' : 'width';\n\n // Clear any existing dimensions.\n this.sizer.innerHTML = '';\n\n // If the size falls within the browser's maximum explicit size for a single element, we can\n // set the size and be done. Otherwise, we have to create children that add up the the desired\n // size.\n if (size < this.maxElementPixels) {\n this.sizer.style[dimension] = size + 'px';\n } else {\n this.sizer.style[dimension] = 'auto';\n this.sizer.style[crossDimension] = 'auto';\n\n // Divide the total size we have to render into N max-size pieces.\n var numChildren = Math.floor(size / this.maxElementPixels);\n\n // Element template to clone for each max-size piece.\n var sizerChild = document.createElement('div');\n sizerChild.style[dimension] = this.maxElementPixels + 'px';\n sizerChild.style[crossDimension] = '1px';\n\n for (var i = 0; i < numChildren; i++) {\n this.sizer.appendChild(sizerChild.cloneNode(false));\n }\n\n // Re-use the element template for the remainder.\n sizerChild.style[dimension] = (size - (numChildren * this.maxElementPixels)) + 'px';\n this.sizer.appendChild(sizerChild);\n }\n};\n\n\n/**\n * If auto-shrinking is enabled, shrinks or unshrinks as appropriate.\n * @private\n * @param {number} size The new size.\n */\nVirtualRepeatContainerController.prototype.autoShrink_ = function(size) {\n var shrinkSize = Math.max(size, this.autoShrinkMin * this.repeater.getItemSize());\n\n if (this.autoShrink && shrinkSize !== this.size) {\n if (this.oldElementSize === null) {\n this.oldElementSize = this.$element[0].style[this.getDimensionName_()];\n }\n\n var currentSize = this.originalSize || this.size;\n\n if (!currentSize || shrinkSize < currentSize) {\n if (!this.originalSize) {\n this.originalSize = this.size;\n }\n\n // Now we update the containers size, because shrinking is enabled.\n this.setSize_(shrinkSize);\n } else if (this.originalSize !== null) {\n // Set the size back to our initial size.\n this.unsetSize_();\n\n var _originalSize = this.originalSize;\n this.originalSize = null;\n\n // We determine the repeaters size again, if the original size was zero.\n // The originalSize needs to be null, to be able to determine the size.\n if (!_originalSize) this.updateSize();\n\n // Apply the original size or the determined size back to the container, because\n // it has been overwritten before, in the shrink block.\n this.setSize_(_originalSize || this.size);\n }\n\n this.repeater.containerUpdated();\n }\n};\n\n\n/**\n * Sets the scrollHeight or scrollWidth. Called by the repeater based on\n * its item count and item size.\n * @param {number} itemsSize The total size of the items.\n */\nVirtualRepeatContainerController.prototype.setScrollSize = function(itemsSize) {\n var size = itemsSize + this.offsetSize;\n if (this.scrollSize === size) return;\n\n this.sizeScroller_(size);\n this.autoShrink_(size);\n this.scrollSize = size;\n};\n\n\n/** @return {number} The container's current scroll offset. */\nVirtualRepeatContainerController.prototype.getScrollOffset = function() {\n return this.scrollOffset;\n};\n\n/**\n * Scrolls to a given scrollTop position.\n * @param {number} position\n */\nVirtualRepeatContainerController.prototype.scrollTo = function(position) {\n this.scroller[this.isHorizontal() ? 'scrollLeft' : 'scrollTop'] = position;\n this.handleScroll_();\n};\n\n/**\n * Scrolls the item with the given index to the top of the scroll container.\n * @param {number} index\n */\nVirtualRepeatContainerController.prototype.scrollToIndex = function(index) {\n var itemSize = this.repeater.getItemSize();\n var itemsLength = this.repeater.itemsLength;\n if(index > itemsLength) {\n index = itemsLength - 1;\n }\n this.scrollTo(itemSize * index);\n};\n\nVirtualRepeatContainerController.prototype.resetScroll = function() {\n this.scrollTo(0);\n};\n\n\nVirtualRepeatContainerController.prototype.handleScroll_ = function() {\n var ltr = document.dir != 'rtl' && document.body.dir != 'rtl';\n if(!ltr && !this.maxSize) {\n this.scroller.scrollLeft = this.scrollSize;\n this.maxSize = this.scroller.scrollLeft;\n }\n var offset = this.isHorizontal() ?\n (ltr?this.scroller.scrollLeft : this.maxSize - this.scroller.scrollLeft)\n : this.scroller.scrollTop;\n if (offset === this.scrollOffset || offset > this.scrollSize - this.size) return;\n\n var itemSize = this.repeater.getItemSize();\n if (!itemSize) return;\n\n var numItems = Math.max(0, Math.floor(offset / itemSize) - NUM_EXTRA);\n\n var transform = (this.isHorizontal() ? 'translateX(' : 'translateY(') +\n (!this.isHorizontal() || ltr ? (numItems * itemSize) : - (numItems * itemSize)) + 'px)';\n\n this.scrollOffset = offset;\n this.offsetter.style.webkitTransform = transform;\n this.offsetter.style.transform = transform;\n\n if (this.bindTopIndex) {\n var topIndex = Math.floor(offset / itemSize);\n if (topIndex !== this.topIndex && topIndex < this.repeater.getItemCount()) {\n this.topIndex = topIndex;\n this.bindTopIndex.assign(this.$scope, topIndex);\n if (!this.$rootScope.$$phase) this.$scope.$digest();\n }\n }\n\n this.repeater.containerUpdated();\n};\n\n\n/**\n * @ngdoc directive\n * @name mdVirtualRepeat\n * @module material.components.virtualRepeat\n * @restrict A\n * @priority 1000\n * @description\n * `md-virtual-repeat` specifies an element to repeat using virtual scrolling.\n *\n * Virtual repeat is a limited substitute for ng-repeat that renders only\n * enough DOM nodes to fill the container and recycling them as the user scrolls.\n *\n * Arrays, but not objects are supported for iteration.\n * Track by, as alias, and (key, value) syntax are not supported.\n *\n * ### On-Demand Async Item Loading\n *\n * When using the `md-on-demand` attribute and loading some asynchronous data, the `getItemAtIndex` function will\n * mostly return nothing.\n *\n * \n * DynamicItems.prototype.getItemAtIndex = function(index) {\n * if (this.pages[index]) {\n * return this.pages[index];\n * } else {\n * // This is an asynchronous action and does not return any value.\n * this.loadPage(index);\n * }\n * };\n * \n *\n * This means that the VirtualRepeat will not have any value for the given index.
\n * After the data loading completed, the user expects the VirtualRepeat to recognize the change.\n *\n * To make sure that the VirtualRepeat properly detects any change, you need to run the operation\n * in another digest.\n *\n * \n * DynamicItems.prototype.loadPage = function(index) {\n * var self = this;\n *\n * // Trigger a new digest by using $timeout\n * $timeout(function() {\n * self.pages[index] = Data;\n * });\n * };\n * \n *\n * > Note: Please also review the\n * VirtualRepeatContainer documentation\n * for more information.\n *\n * @usage\n * \n * \n * Hello {{i}}!
\n * \n *\n * \n * Hello {{i}}!
\n * \n * \n *\n * @param {number=} md-item-size The height or width of the repeated elements (which must be\n * identical for each element). Optional. Will attempt to read the size from the dom if missing,\n * but still assumes that all repeated nodes have same height or width.\n * @param {string=} md-extra-name Evaluates to an additional name to which the current iterated item\n * can be assigned on the repeated scope (needed for use in `md-autocomplete`).\n * @param {boolean=} md-on-demand When present, treats the md-virtual-repeat argument as an object\n * that can fetch rows rather than an array.\n *\n * **NOTE:** This object must implement the following interface with two (2) methods:\n *\n * - `getItemAtIndex: function(index) [object]` The item at that index or null if it is not yet\n * loaded (it should start downloading the item in that case).\n * - `getLength: function() [number]` The data length to which the repeater container\n * should be sized. Ideally, when the count is known, this method should return it.\n * Otherwise, return a higher number than the currently loaded items to produce an\n * infinite-scroll behavior.\n */\nfunction VirtualRepeatDirective($parse) {\n return {\n controller: VirtualRepeatController,\n priority: 1000,\n require: ['mdVirtualRepeat', '^^mdVirtualRepeatContainer'],\n restrict: 'A',\n terminal: true,\n transclude: 'element',\n compile: function VirtualRepeatCompile($element, $attrs) {\n var expression = $attrs.mdVirtualRepeat;\n var match = expression.match(/^\\s*([\\s\\S]+?)\\s+in\\s+([\\s\\S]+?)\\s*$/);\n var repeatName = match[1];\n var repeatListExpression = $parse(match[2]);\n var extraName = $attrs.mdExtraName && $parse($attrs.mdExtraName);\n\n return function VirtualRepeatLink($scope, $element, $attrs, ctrl, $transclude) {\n ctrl[0].link_(ctrl[1], $transclude, repeatName, repeatListExpression, extraName);\n };\n }\n };\n}\n\n\n/** @ngInject */\nfunction VirtualRepeatController($scope, $element, $attrs, $browser, $document, $rootScope,\n $$rAF, $mdUtil) {\n this.$scope = $scope;\n this.$element = $element;\n this.$attrs = $attrs;\n this.$browser = $browser;\n this.$document = $document;\n this.$rootScope = $rootScope;\n this.$$rAF = $$rAF;\n\n /** @type {boolean} Whether we are in on-demand mode. */\n this.onDemand = $mdUtil.parseAttributeBoolean($attrs.mdOnDemand);\n /** @type {!Function} Backup reference to $browser.$$checkUrlChange */\n this.browserCheckUrlChange = $browser.$$checkUrlChange;\n /** @type {number} Most recent starting repeat index (based on scroll offset) */\n this.newStartIndex = 0;\n /** @type {number} Most recent ending repeat index (based on scroll offset) */\n this.newEndIndex = 0;\n /** @type {number} Most recent end visible index (based on scroll offset) */\n this.newVisibleEnd = 0;\n /** @type {number} Previous starting repeat index (based on scroll offset) */\n this.startIndex = 0;\n /** @type {number} Previous ending repeat index (based on scroll offset) */\n this.endIndex = 0;\n // TODO: measure width/height of first element from dom if not provided.\n // getComputedStyle?\n /** @type {?number} Height/width of repeated elements. */\n this.itemSize = $scope.$eval($attrs.mdItemSize) || null;\n\n /** @type {boolean} Whether this is the first time that items are rendered. */\n this.isFirstRender = true;\n\n /**\n * @private {boolean} Whether the items in the list are already being updated. Used to prevent\n * nested calls to virtualRepeatUpdate_.\n */\n this.isVirtualRepeatUpdating_ = false;\n\n /** @type {number} Most recently seen length of items. */\n this.itemsLength = 0;\n\n /**\n * @type {!Function} Unwatch callback for item size (when md-items-size is\n * not specified), or angular.noop otherwise.\n */\n this.unwatchItemSize_ = angular.noop;\n\n /**\n * Presently rendered blocks by repeat index.\n * @type {Object} A pool of presently unused blocks. */\n this.pooledBlocks = [];\n\n $scope.$on('$destroy', angular.bind(this, this.cleanupBlocks_));\n}\n\n\n/**\n * An object representing a repeated item.\n * @typedef {{element: !jqLite, new: boolean, scope: !angular.Scope}}\n */\nVirtualRepeatController.Block;\n\n\n/**\n * Called at startup by the md-virtual-repeat postLink function.\n * @param {!VirtualRepeatContainerController} container The container's controller.\n * @param {!Function} transclude The repeated element's bound transclude function.\n * @param {string} repeatName The left hand side of the repeat expression, indicating\n * the name for each item in the array.\n * @param {!Function} repeatListExpression A compiled expression based on the right hand side\n * of the repeat expression. Points to the array to repeat over.\n * @param {string|undefined} extraName The optional extra repeatName.\n */\nVirtualRepeatController.prototype.link_ =\n function(container, transclude, repeatName, repeatListExpression, extraName) {\n this.container = container;\n this.transclude = transclude;\n this.repeatName = repeatName;\n this.rawRepeatListExpression = repeatListExpression;\n this.extraName = extraName;\n this.sized = false;\n\n this.repeatListExpression = angular.bind(this, this.repeatListExpression_);\n\n this.container.register(this);\n};\n\n\n/** @private Cleans up unused blocks. */\nVirtualRepeatController.prototype.cleanupBlocks_ = function() {\n angular.forEach(this.pooledBlocks, function cleanupBlock(block) {\n block.element.remove();\n });\n};\n\n\n/** @private Attempts to set itemSize by measuring a repeated element in the dom */\nVirtualRepeatController.prototype.readItemSize_ = function() {\n if (this.itemSize) {\n // itemSize was successfully read in a different asynchronous call.\n return;\n }\n\n this.items = this.repeatListExpression(this.$scope);\n this.parentNode = this.$element[0].parentNode;\n var block = this.getBlock_(0);\n if (!block.element[0].parentNode) {\n this.parentNode.appendChild(block.element[0]);\n }\n\n this.itemSize = block.element[0][\n this.container.isHorizontal() ? 'offsetWidth' : 'offsetHeight'] || null;\n\n this.blocks[0] = block;\n this.poolBlock_(0);\n\n if (this.itemSize) {\n this.containerUpdated();\n }\n};\n\n\n/**\n * Returns the user-specified repeat list, transforming it into an array-like\n * object in the case of infinite scroll/dynamic load mode.\n * @param {!angular.Scope} The scope.\n * @return {!Array|!Object} An array or array-like object for iteration.\n */\nVirtualRepeatController.prototype.repeatListExpression_ = function(scope) {\n var repeatList = this.rawRepeatListExpression(scope);\n\n if (this.onDemand && repeatList) {\n var virtualList = new VirtualRepeatModelArrayLike(repeatList);\n virtualList.$$includeIndexes(this.newStartIndex, this.newVisibleEnd);\n return virtualList;\n } else {\n return repeatList;\n }\n};\n\n\n/**\n * Called by the container. Informs us that the containers scroll or size has\n * changed.\n */\nVirtualRepeatController.prototype.containerUpdated = function() {\n // If itemSize is unknown, attempt to measure it.\n if (!this.itemSize) {\n // Make sure to clean up watchers if we can (see #8178)\n if(this.unwatchItemSize_ && this.unwatchItemSize_ !== angular.noop){\n this.unwatchItemSize_();\n }\n this.unwatchItemSize_ = this.$scope.$watchCollection(\n this.repeatListExpression,\n angular.bind(this, function(items) {\n if (items && items.length) {\n this.readItemSize_();\n }\n }));\n if (!this.$rootScope.$$phase) this.$scope.$digest();\n\n return;\n } else if (!this.sized) {\n this.items = this.repeatListExpression(this.$scope);\n }\n\n if (!this.sized) {\n this.unwatchItemSize_();\n this.sized = true;\n this.$scope.$watchCollection(this.repeatListExpression,\n angular.bind(this, function(items, oldItems) {\n if (!this.isVirtualRepeatUpdating_) {\n this.virtualRepeatUpdate_(items, oldItems);\n }\n }));\n }\n\n this.updateIndexes_();\n\n if (this.newStartIndex !== this.startIndex ||\n this.newEndIndex !== this.endIndex ||\n this.container.getScrollOffset() > this.container.getScrollSize()) {\n if (this.items instanceof VirtualRepeatModelArrayLike) {\n this.items.$$includeIndexes(this.newStartIndex, this.newEndIndex);\n }\n this.virtualRepeatUpdate_(this.items, this.items);\n }\n};\n\n\n/**\n * Called by the container. Returns the size of a single repeated item.\n * @return {?number} Size of a repeated item.\n */\nVirtualRepeatController.prototype.getItemSize = function() {\n return this.itemSize;\n};\n\n\n/**\n * Called by the container. Returns the size of a single repeated item.\n * @return {?number} Size of a repeated item.\n */\nVirtualRepeatController.prototype.getItemCount = function() {\n return this.itemsLength;\n};\n\n\n/**\n * Updates the order and visible offset of repeated blocks in response to scrolling\n * or items updates.\n * @private\n */\nVirtualRepeatController.prototype.virtualRepeatUpdate_ = function(items, oldItems) {\n this.isVirtualRepeatUpdating_ = true;\n\n var itemsLength = items && items.length || 0;\n var lengthChanged = false;\n\n // If the number of items shrank, keep the scroll position.\n if (this.items && itemsLength < this.items.length && this.container.getScrollOffset() !== 0) {\n this.items = items;\n var previousScrollOffset = this.container.getScrollOffset();\n this.container.resetScroll();\n this.container.scrollTo(previousScrollOffset);\n }\n\n if (itemsLength !== this.itemsLength) {\n lengthChanged = true;\n this.itemsLength = itemsLength;\n }\n\n this.items = items;\n if (items !== oldItems || lengthChanged) {\n this.updateIndexes_();\n }\n\n this.parentNode = this.$element[0].parentNode;\n\n if (lengthChanged) {\n this.container.setScrollSize(itemsLength * this.itemSize);\n }\n\n var cleanupFirstRender = false, firstRenderStartIndex;\n if (this.isFirstRender) {\n cleanupFirstRender = true;\n this.isFirstRender = false;\n firstRenderStartIndex = this.$attrs.mdStartIndex ?\n this.$scope.$eval(this.$attrs.mdStartIndex) :\n this.container.topIndex;\n this.container.scrollToIndex(firstRenderStartIndex);\n }\n\n // Detach and pool any blocks that are no longer in the viewport.\n Object.keys(this.blocks).forEach(function(blockIndex) {\n var index = parseInt(blockIndex, 10);\n if (index < this.newStartIndex || index >= this.newEndIndex) {\n this.poolBlock_(index);\n }\n }, this);\n\n // Add needed blocks.\n // For performance reasons, temporarily block browser url checks as we digest\n // the restored block scopes ($$checkUrlChange reads window.location to\n // check for changes and trigger route change, etc, which we don't need when\n // trying to scroll at 60fps).\n this.$browser.$$checkUrlChange = angular.noop;\n\n var i, block,\n newStartBlocks = [],\n newEndBlocks = [];\n\n // Collect blocks at the top.\n for (i = this.newStartIndex; i < this.newEndIndex && this.blocks[i] == null; i++) {\n block = this.getBlock_(i);\n this.updateBlock_(block, i);\n newStartBlocks.push(block);\n }\n\n // Update blocks that are already rendered.\n for (; this.blocks[i] != null; i++) {\n this.updateBlock_(this.blocks[i], i);\n }\n var maxIndex = i - 1;\n\n // Collect blocks at the end.\n for (; i < this.newEndIndex; i++) {\n block = this.getBlock_(i);\n this.updateBlock_(block, i);\n newEndBlocks.push(block);\n }\n\n // Attach collected blocks to the document.\n if (newStartBlocks.length) {\n this.parentNode.insertBefore(\n this.domFragmentFromBlocks_(newStartBlocks),\n this.$element[0].nextSibling);\n }\n if (newEndBlocks.length) {\n this.parentNode.insertBefore(\n this.domFragmentFromBlocks_(newEndBlocks),\n this.blocks[maxIndex] && this.blocks[maxIndex].element[0].nextSibling);\n }\n\n // DOM manipulation may have altered scroll, so scroll again\n if (cleanupFirstRender) {\n this.container.scrollToIndex(firstRenderStartIndex);\n }\n // Restore $$checkUrlChange.\n this.$browser.$$checkUrlChange = this.browserCheckUrlChange;\n\n this.startIndex = this.newStartIndex;\n this.endIndex = this.newEndIndex;\n\n this.isVirtualRepeatUpdating_ = false;\n};\n\n\n/**\n * @param {number} index Where the block is to be in the repeated list.\n * @return {!VirtualRepeatController.Block} A new or pooled block to place at the specified index.\n * @private\n */\nVirtualRepeatController.prototype.getBlock_ = function(index) {\n if (this.pooledBlocks.length) {\n return this.pooledBlocks.pop();\n }\n\n var block;\n this.transclude(angular.bind(this, function(clone, scope) {\n block = {\n element: clone,\n new: true,\n scope: scope\n };\n\n this.updateScope_(scope, index);\n this.parentNode.appendChild(clone[0]);\n }));\n\n return block;\n};\n\n\n/**\n * Updates and if not in a digest cycle, digests the specified block's scope to the data\n * at the specified index.\n * @param {!VirtualRepeatController.Block} block The block whose scope should be updated.\n * @param {number} index The index to set.\n * @private\n */\nVirtualRepeatController.prototype.updateBlock_ = function(block, index) {\n this.blocks[index] = block;\n\n if (!block.new &&\n (block.scope.$index === index && block.scope[this.repeatName] === this.items[index])) {\n return;\n }\n block.new = false;\n\n // Update and digest the block's scope.\n this.updateScope_(block.scope, index);\n\n // Perform digest before reattaching the block.\n // Any resulting synchronous dom mutations should be much faster as a result.\n // This might break some directives, but I'm going to try it for now.\n if (!this.$rootScope.$$phase) {\n block.scope.$digest();\n }\n};\n\n\n/**\n * Updates scope to the data at the specified index.\n * @param {!angular.Scope} scope The scope which should be updated.\n * @param {number} index The index to set.\n * @private\n */\nVirtualRepeatController.prototype.updateScope_ = function(scope, index) {\n scope.$index = index;\n scope[this.repeatName] = this.items && this.items[index];\n if (this.extraName) scope[this.extraName(this.$scope)] = this.items[index];\n};\n\n\n/**\n * Pools the block at the specified index (Pulls its element out of the dom and stores it).\n * @param {number} index The index at which the block to pool is stored.\n * @private\n */\nVirtualRepeatController.prototype.poolBlock_ = function(index) {\n this.pooledBlocks.push(this.blocks[index]);\n this.parentNode.removeChild(this.blocks[index].element[0]);\n delete this.blocks[index];\n};\n\n\n/**\n * Produces a dom fragment containing the elements from the list of blocks.\n * @param {!Array} blocks The blocks whose elements\n * should be added to the document fragment.\n * @return {DocumentFragment}\n * @private\n */\nVirtualRepeatController.prototype.domFragmentFromBlocks_ = function(blocks) {\n var fragment = this.$document[0].createDocumentFragment();\n blocks.forEach(function(block) {\n fragment.appendChild(block.element[0]);\n });\n return fragment;\n};\n\n\n/**\n * Updates start and end indexes based on length of repeated items and container size.\n * @private\n */\nVirtualRepeatController.prototype.updateIndexes_ = function() {\n var itemsLength = this.items ? this.items.length : 0;\n var containerLength = Math.ceil(this.container.getSize() / this.itemSize);\n\n this.newStartIndex = Math.max(0, Math.min(\n itemsLength - containerLength,\n Math.floor(this.container.getScrollOffset() / this.itemSize)));\n this.newVisibleEnd = this.newStartIndex + containerLength + NUM_EXTRA;\n this.newEndIndex = Math.min(itemsLength, this.newVisibleEnd);\n this.newStartIndex = Math.max(0, this.newStartIndex - NUM_EXTRA);\n};\n\n/**\n * This VirtualRepeatModelArrayLike class enforces the interface requirements\n * for infinite scrolling within a mdVirtualRepeatContainer. An object with this\n * interface must implement the following interface with two (2) methods:\n *\n * getItemAtIndex: function(index) -> item at that index or null if it is not yet\n * loaded (It should start downloading the item in that case).\n *\n * getLength: function() -> number The data legnth to which the repeater container\n * should be sized. Ideally, when the count is known, this method should return it.\n * Otherwise, return a higher number than the currently loaded items to produce an\n * infinite-scroll behavior.\n *\n * @usage\n * \n * \n * \n * Hello {{i}}!\n *
\n * \n * \n *\n */\nfunction VirtualRepeatModelArrayLike(model) {\n if (!angular.isFunction(model.getItemAtIndex) ||\n !angular.isFunction(model.getLength)) {\n throw Error('When md-on-demand is enabled, the Object passed to md-virtual-repeat must implement ' +\n 'functions getItemAtIndex() and getLength() ');\n }\n\n this.model = model;\n}\n\n\nVirtualRepeatModelArrayLike.prototype.$$includeIndexes = function(start, end) {\n for (var i = start; i < end; i++) {\n if (!this.hasOwnProperty(i)) {\n this[i] = this.model.getItemAtIndex(i);\n }\n }\n this.length = this.model.getLength();\n};\n\n})();\n(function(){\n\"use strict\";\n\n/**\n * @ngdoc module\n * @name material.components.whiteframe\n */\nMdWhiteframeDirective.$inject = [\"$log\"];\nangular\n .module('material.components.whiteframe', ['material.core'])\n .directive('mdWhiteframe', MdWhiteframeDirective);\n\n/**\n * @ngdoc directive\n * @module material.components.whiteframe\n * @name mdWhiteframe\n *\n * @description\n * The md-whiteframe directive allows you to apply an elevation shadow to an element.\n *\n * The attribute values needs to be a number between 1 and 24 or -1.\n * When set to -1 no style is applied.\n *\n * ### Notes\n * - If there is no value specified it defaults to 4dp.\n * - If the value is not valid it defaults to 4dp.\n\n * @usage\n * \n * \n * Elevation of 3dp\n *
\n * \n *\n * \n * \n * No elevation shadow applied\n *
\n * \n *\n * \n * \n * Elevation of 5dp with an interpolated value\n *
\n * \n */\nfunction MdWhiteframeDirective($log) {\n var DISABLE_DP = -1;\n var MIN_DP = 1;\n var MAX_DP = 24;\n var DEFAULT_DP = 4;\n\n return {\n link: postLink\n };\n\n function postLink(scope, element, attr) {\n var oldClass = '';\n\n attr.$observe('mdWhiteframe', function(elevation) {\n elevation = parseInt(elevation, 10) || DEFAULT_DP;\n\n if (elevation != DISABLE_DP && (elevation > MAX_DP || elevation < MIN_DP)) {\n $log.warn('md-whiteframe attribute value is invalid. It should be a number between ' + MIN_DP + ' and ' + MAX_DP, element[0]);\n elevation = DEFAULT_DP;\n }\n\n var newClass = elevation == DISABLE_DP ? '' : 'md-whiteframe-' + elevation + 'dp';\n attr.$updateClass(newClass, oldClass);\n oldClass = newClass;\n });\n }\n}\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\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\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 = null;\n ctrl.id = $mdUtil.nextUid();\n ctrl.isDisabled = null;\n ctrl.isRequired = null;\n ctrl.isReadonly = null;\n ctrl.hasNotFound = false;\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.mouseUp = onMouseup;\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 });\n\n $mdTheming($element);\n configureWatchers();\n $mdUtil.nextTick(function () {\n\n gatherElements();\n moveDropdown();\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 });\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\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;\n\n // Automatically determine dropdown placement based on available space in viewport.\n if (!position) {\n position = (top > bot && root.height - top - MENU_PADDING < dropdownHeight) ? 'top' : 'bottom';\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, hrect.top - root.top - MENU_PADDING) + 'px';\n } else {\n var 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);\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 - MENU_PADDING) {\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 * 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'),\n scroller: $element[0].querySelector('.md-virtual-repeat-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\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 * Handles changes to the `hidden` property.\n * @param hidden\n * @param oldHidden\n */\n function handleHiddenChange (hidden, oldHidden) {\n if (!hidden && oldHidden) {\n positionDropdown();\n\n // Report in polite mode, because the screenreader 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.ul);\n enableWrapScroll = disableElementScrollEvents(angular.element(elements.wrap));\n }\n } else if (hidden && !oldHidden) {\n $mdUtil.enableScrolling();\n\n if (enableWrapScroll) {\n enableWrapScroll();\n enableWrapScroll = null;\n }\n }\n }\n\n /**\n * Disables scrolling for a specific element\n */\n function disableElementScrollEvents(element) {\n\n function preventDefault(e) {\n e.preventDefault();\n }\n\n element.on('wheel', preventDefault);\n element.on('touchmove', preventDefault);\n\n return function() {\n element.off('wheel', preventDefault);\n element.off('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 * When the mouse button is released, send focus back to the input field.\n */\n function onMouseup () {\n elements.input.focus();\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) announceItemChange();\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) && $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) { watcher(selectedItem, previousSelectedItem); });\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 searchText\n * @param 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\n // trigger change event if available\n if (searchText !== previousSearchText) announceTextChange();\n\n // cancel results if search text is not long enough\n if (!isMinLengthMet()) {\n ctrl.matches = [];\n\n setLoading(false);\n reportMessages(false, 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 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) return;\n event.stopPropagation();\n event.preventDefault();\n ctrl.index = Math.min(ctrl.index + 1, ctrl.matches.length - 1);\n updateScroll();\n reportMessages(false, ReportType.Selected);\n break;\n case $mdConstant.KEY_CODE.UP_ARROW:\n if (ctrl.loading) return;\n event.stopPropagation();\n event.preventDefault();\n ctrl.index = ctrl.index < 0 ? ctrl.matches.length - 1 : Math.max(0, ctrl.index - 1);\n updateScroll();\n reportMessages(false, ReportType.Selected);\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.stopPropagation();\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 */\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 {{}}\n */\n function getItemAsNameVal (item) {\n if (!item) return undefined;\n\n var locals = {};\n if (ctrl.itemName) locals[ ctrl.itemName ] = item;\n\n return locals;\n }\n\n /**\n * Returns the default index based on whether or not autoselect is enabled.\n * @returns {number}\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}\n */\n function shouldHide () {\n if (!isSearchable()) return true; // Hide when not able to query\n else return !shouldShow(); // Hide when the dropdown is not able to show.\n }\n\n /**\n * Determines whether the autocomplete is able to query within the current state.\n * @returns {boolean}\n */\n function isSearchable() {\n if (ctrl.loading && !hasMatches()) return false; // No query when query is in progress.\n else if (hasSelection()) return false; // No query if there is already a selection\n else if (!hasFocus) return false; // No query if the input does not have focus\n return true;\n }\n\n /**\n * Determines if the escape keydown should be processed\n * @returns {boolean}\n */\n function shouldProcessEscape() {\n return hasEscapeOption('blur') || !ctrl.hidden || ctrl.loading || hasEscapeOption('clear') && $scope.searchText;\n }\n\n /**\n * Determines if an escape option is set\n * @returns {boolean}\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}\n */\n function shouldShow() {\n return (isMinLengthMet() && hasMatches()) || notFoundVisible();\n }\n\n /**\n * Returns true if the search text has matches.\n * @returns {boolean}\n */\n function hasMatches() {\n return ctrl.matches.length ? true : false;\n }\n\n /**\n * Returns true if the autocomplete has a valid selection.\n * @returns {boolean}\n */\n function hasSelection() {\n return ctrl.scope.selectedItem ? true : false;\n }\n\n /**\n * Returns true if the loading indicator is, or should be, visible.\n * @returns {boolean}\n */\n function loadingIsVisible() {\n return ctrl.loading && !hasSelection();\n }\n\n /**\n * Returns the display value of the current item.\n * @returns {*}\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 {*}\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 key\n * @param handler\n * @param value\n */\n function defineProperty (key, handler, value) {\n Object.defineProperty(ctrl, key, {\n get: function () { return value; },\n set: function (newValue) {\n var oldValue = value;\n value = newValue;\n handler(newValue, oldValue);\n }\n });\n }\n\n /**\n * Selects the item at the given index.\n * @param index\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 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 */\n function clearValue () {\n clearSelectedItem();\n clearSearchText();\n }\n\n /**\n * Clears the selected item\n */\n function clearSelectedItem () {\n // Reset our variables\n ctrl.index = 0;\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 screenreaders.\n * @param {boolean} isPolite Whether the announcement should be polite.\n * @param {!number} types Message flags to be reported to the screenreader.\n */\n function reportMessages(isPolite, types) {\n\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 /**\n * Returns the ARIA message for how many results match the current query.\n * @returns {*}\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 var height = elements.li[0].offsetHeight,\n top = height * ctrl.index,\n bot = top + height,\n hgt = elements.scroller.clientHeight,\n scrollTop = elements.scroller.scrollTop;\n if (top < scrollTop) {\n scrollTo(top);\n } else if (bot > scrollTop + hgt) {\n scrollTo(bot - hgt);\n }\n }\n\n function isPromiseFetching() {\n return fetchesInProgress !== 0;\n }\n\n function scrollTo (offset) {\n elements.$.scrollContainer.controller('mdVirtualRepeatContainer').scrollTo(offset);\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) select(0);\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 `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 VirtualRepeat\n * 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 might\n * not update properly, because caching of the results is enabled by default.\n *\n * The autocomplete will then show invalid dropdown items, because the VirtualRepeat only updates the\n * scope bindings, rather than re-creating the `md-autocomplete` and the previous cached results will be used.\n *\n * > To avoid such problems ensure that the autocomplete does not cache any results.\n *\n * \n * \n * {{ item.display }}\n * \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 up or not.\n * @param {boolean=} md-autofocus If true, the autocomplete will be automatically focused when a `$mdDialog`,\n * `$mdBottomsheet` or `$mdSidenav`, which contains the autocomplete, is opening.
\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 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-menu-class This will be applied to the dropdown menu for styling\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-input-name The name attribute given to the input element to be used with\n * FormController\n * @param {string=} md-select-on-focus When present the inputs 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 exact\n * the item if the search text is an exact match.
\n * Exact match means that there is only one match showing up.\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 less as possible.\n * @param {string=} md-dropdown-position Overrides the default dropdown position. Options: `top`, `bottom`.\n * @param {string=} ng-trim If set to false, the search text will be not trimmed automatically.\n * Defaults to true.\n * @param {string=} ng-pattern Adds the pattern validator to the ngModel of the search text.\n * [ngPattern Directive](https://docs.angularjs.org/api/ng/directive/ngPattern)\n *\n * @usage\n * ### Basic Example\n * \n * \n * {{item.display}}\n * \n * \n *\n * ### Example with \"not found\" message\n * \n * \n * \n * {{item.display}}\n * \n * \n * No matches found.\n * \n * \n * \n *\n * In this example, our code utilizes `md-item-template` and `md-not-found` to specify the\n * different parts that make up our component.\n *\n * ### Clear button for the input\n * By default, for floating label autocomplete's the clear button is not showing up\n * ([See specs](https://material.google.com/components/text-fields.html#text-fields-auto-complete-text-field))\n *\n * Nevertheless, developers are able to explicitly toggle the clear button for all types of autocomplete's.\n *\n * \n * \n * \n * \n *\n * ### Example with validation\n * \n * \n * \n *\n * In this example, our code utilizes `md-item-template` and `ng-messages` to specify\n * input validation for the field.\n *\n * ### Asynchronous Results\n * The autocomplete items expression also supports promises, which will resolve with the query results.\n *\n * \n * function AppController($scope, $http) {\n * $scope.query = function(searchText) {\n * return $http\n * .get(BACKEND_URL + '/items/' + searchText)\n * .then(function(data) {\n * // Map the response object to the data object.\n * return data;\n * });\n * };\n * }\n * \n *\n * \n * \n * \n * {{item}}\n * \n * \n * \n *\n */\n\nfunction MdAutocomplete ($$mdSvgRegistry) {\n\n return {\n controller: 'MdAutocompleteCtrl',\n controllerAs: '$mdAutocompleteCtrl',\n scope: {\n inputName: '@mdInputName',\n inputMinlength: '@mdInputMinlength',\n inputMaxlength: '@mdInputMaxlength',\n searchText: '=?mdSearchText',\n selectedItem: '=?mdSelectedItem',\n itemsExpr: '@mdItems',\n itemText: '&mdItemText',\n placeholder: '@placeholder',\n noCache: '=?mdNoCache',\n requireMatch: '=?mdRequireMatch',\n selectOnMatch: '=?mdSelectOnMatch',\n matchInsensitive: '=?mdMatchCaseInsensitive',\n itemChange: '&?mdSelectedItemChange',\n textChange: '&?mdSearchTextChange',\n minLength: '=?mdMinLength',\n delay: '=?mdDelay',\n autofocus: '=?mdAutofocus',\n floatingLabel: '@?mdFloatingLabel',\n autoselect: '=?mdAutoselect',\n menuClass: '@?mdMenuClass',\n inputId: '@?mdInputId',\n escapeOptions: '@?mdEscapeOptions',\n dropdownItems: '=?mdDropdownItems',\n dropdownPosition: '@?mdDropdownPosition',\n clearButton: '=?mdClearButton'\n },\n compile: function(tElement, tAttrs) {\n var attributes = ['md-select-on-focus', 'md-no-asterisk', 'ng-trim', 'ng-pattern'];\n var input = tElement.find('input');\n\n attributes.forEach(function(attribute) {\n var attrValue = tAttrs[tAttrs.$normalize(attribute)];\n\n if (attrValue !== null) {\n input.attr(attribute, attrValue);\n }\n });\n\n return function(scope, element, attrs, ctrl) {\n // Retrieve the state of using a md-not-found template by using our attribute, which will\n // be added to the element in the template function.\n ctrl.hasNotFound = !!element.attr('md-has-not-found');\n\n // By default the inset autocomplete should show the clear button when not explicitly overwritten.\n if (!angular.isDefined(attrs.mdClearButton) && !scope.floatingLabel) {\n scope.clearButton = true;\n }\n }\n },\n template: function (element, attr) {\n var noItemsTemplate = getNoItemsTemplate(),\n itemTemplate = getItemTemplate(),\n leftover = element.html(),\n tabindex = attr.tabindex;\n\n // Set our attribute for the link function above which runs later.\n // We will set an attribute, because otherwise the stored variables will be trashed when\n // removing the element is hidden while retrieving the template. For example when using ngIf.\n if (noItemsTemplate) element.attr('md-has-not-found', true);\n\n // Always set our tabindex of the autocomplete directive to -1, because our input\n // will hold the actual tabindex.\n element.attr('tabindex', '-1');\n\n return '\\\n \\\n ' + getInputElement() + '\\\n ' + getClearButton() + '\\\n \\\n \\\n \\\n - \\\n ' + itemTemplate + '\\\n
' + noItemsTemplate + '\\\n
\\\n \\\n ';\n\n function getItemTemplate() {\n var templateTag = element.find('md-item-template').detach(),\n html = templateTag.length ? templateTag.html() : element.html();\n if (!templateTag.length) element.empty();\n return '' + html + '';\n }\n\n function getNoItemsTemplate() {\n var templateTag = element.find('md-not-found').detach(),\n template = templateTag.length ? templateTag.html() : '';\n return template\n ? '' + template + ''\n : '';\n\n }\n\n function getInputElement () {\n if (attr.mdFloatingLabel) {\n return '\\\n \\\n \\\n \\\n ' + leftover + '
\\\n ';\n } else {\n return '\\\n ';\n }\n }\n\n function getClearButton() {\n return '' +\n '';\n }\n }\n };\n}\n\n})();\n(function(){\n\"use strict\";\n\n\nMdAutocompleteItemScopeDirective.$inject = [\"$compile\", \"$mdUtil\"];angular\n .module('material.components.autocomplete')\n .directive('mdAutocompleteParentScope', MdAutocompleteItemScopeDirective);\n\nfunction MdAutocompleteItemScopeDirective($compile, $mdUtil) {\n return {\n restrict: 'AE',\n compile: compile,\n terminal: true,\n transclude: 'element'\n };\n\n function compile(tElement, tAttr, transclude) {\n return function postLink(scope, element, attr) {\n var ctrl = scope.$mdAutocompleteCtrl;\n var newScope = ctrl.parent.$new();\n var itemName = ctrl.itemName;\n\n // Watch for changes to our scope's variables and copy them to the new scope\n watchVariable('$index', '$index');\n watchVariable('item', itemName);\n\n // Ensure that $digest calls on our scope trigger $digest on newScope.\n connectScopes();\n\n // Link the element against newScope.\n transclude(newScope, function(clone) {\n element.after(clone);\n });\n\n /**\n * Creates a watcher for variables that are copied from the parent scope\n * @param variable\n * @param alias\n */\n function watchVariable(variable, alias) {\n newScope[alias] = scope[variable];\n\n scope.$watch(variable, function(value) {\n $mdUtil.nextTick(function() {\n newScope[alias] = value;\n });\n });\n }\n\n /**\n * Creates watchers on scope and newScope that ensure that for any\n * $digest of scope, newScope is also $digested.\n */\n function connectScopes() {\n var scopeDigesting = false;\n var newScopeDigesting = false;\n\n scope.$watch(function() {\n if (newScopeDigesting || scopeDigesting) {\n return;\n }\n\n scopeDigesting = true;\n scope.$$postDigest(function() {\n if (!newScopeDigesting) {\n newScope.$digest();\n }\n\n scopeDigesting = newScopeDigesting = false;\n });\n });\n\n newScope.$watch(function() {\n newScopeDigesting = true;\n });\n }\n };\n }\n}\n})();\n(function(){\n\"use strict\";\n\n\nMdHighlightCtrl.$inject = [\"$scope\", \"$element\", \"$attrs\"];angular\n .module('material.components.autocomplete')\n .controller('MdHighlightCtrl', MdHighlightCtrl);\n\nfunction MdHighlightCtrl ($scope, $element, $attrs) {\n this.$scope = $scope;\n this.$element = $element;\n this.$attrs = $attrs;\n\n // Cache the Regex to avoid rebuilding each time.\n this.regex = null;\n}\n\nMdHighlightCtrl.prototype.init = function(unsafeTermFn, unsafeContentFn) {\n\n this.flags = this.$attrs.mdHighlightFlags || '';\n\n this.unregisterFn = this.$scope.$watch(function($scope) {\n return {\n term: unsafeTermFn($scope),\n contentText: unsafeContentFn($scope)\n };\n }.bind(this), this.onRender.bind(this), true);\n\n this.$element.on('$destroy', this.unregisterFn);\n};\n\n/**\n * Triggered once a new change has been recognized and the highlighted\n * text needs to be updated.\n */\nMdHighlightCtrl.prototype.onRender = function(state, prevState) {\n\n var contentText = state.contentText;\n\n /* Update the regex if it's outdated, because we don't want to rebuilt it constantly. */\n if (this.regex === null || state.term !== prevState.term) {\n this.regex = this.createRegex(state.term, this.flags);\n }\n\n /* If a term is available apply the regex to the content */\n if (state.term) {\n this.applyRegex(contentText);\n } else {\n this.$element.text(contentText);\n }\n\n};\n\n/**\n * Decomposes the specified text into different tokens (whether match or not).\n * Breaking down the string guarantees proper XSS protection due to the native browser\n * escaping of unsafe text.\n */\nMdHighlightCtrl.prototype.applyRegex = function(text) {\n var tokens = this.resolveTokens(text);\n\n this.$element.empty();\n\n tokens.forEach(function (token) {\n\n if (token.isMatch) {\n var tokenEl = angular.element('').text(token.text);\n\n this.$element.append(tokenEl);\n } else {\n this.$element.append(document.createTextNode(token));\n }\n\n }.bind(this));\n\n};\n\n /**\n * Decomposes the specified text into different tokens by running the regex against the text.\n */\nMdHighlightCtrl.prototype.resolveTokens = function(string) {\n var tokens = [];\n var lastIndex = 0;\n\n // Use replace here, because it supports global and single regular expressions at same time.\n string.replace(this.regex, function(match, index) {\n appendToken(lastIndex, index);\n\n tokens.push({\n text: match,\n isMatch: true\n });\n\n lastIndex = index + match.length;\n });\n\n // Append the missing text as a token.\n appendToken(lastIndex);\n\n return tokens;\n\n function appendToken(from, to) {\n var targetText = string.slice(from, to);\n targetText && tokens.push(targetText);\n }\n};\n\n/** Creates a regex for the specified text with the given flags. */\nMdHighlightCtrl.prototype.createRegex = function(term, flags) {\n var startFlag = '', endFlag = '';\n var regexTerm = this.sanitizeRegex(term);\n\n if (flags.indexOf('^') >= 0) startFlag = '^';\n if (flags.indexOf('$') >= 0) endFlag = '$';\n\n return new RegExp(startFlag + regexTerm + endFlag, flags.replace(/[$\\^]/g, ''));\n};\n\n/** Sanitizes a regex by removing all common RegExp identifiers */\nMdHighlightCtrl.prototype.sanitizeRegex = function(term) {\n return term && term.toString().replace(/[\\\\\\^\\$\\*\\+\\?\\.\\(\\)\\|\\{}\\[\\]]/g, '\\\\$&');\n};\n\n})();\n(function(){\n\"use strict\";\n\n\nMdHighlight.$inject = [\"$interpolate\", \"$parse\"];angular\n .module('material.components.autocomplete')\n .directive('mdHighlightText', MdHighlight);\n\n/**\n * @ngdoc directive\n * @name mdHighlightText\n * @module material.components.autocomplete\n *\n * @description\n * The `md-highlight-text` directive allows you to specify text that should be highlighted within\n * an element. Highlighted text will be wrapped in `` which can\n * be styled through CSS. Please note that child elements may not be used with this directive.\n *\n * @param {string} md-highlight-text A model to be searched for\n * @param {string=} md-highlight-flags A list of flags (loosely based on JavaScript RexExp flags).\n * #### **Supported flags**:\n * - `g`: Find all matches within the provided text\n * - `i`: Ignore case when searching for matches\n * - `$`: Only match if the text ends with the search term\n * - `^`: Only match if the text begins with the search term\n *\n * @usage\n * \n * \n * \n * - \n * {{result.text}}\n *
\n *
\n * \n */\n\nfunction MdHighlight ($interpolate, $parse) {\n return {\n terminal: true,\n controller: 'MdHighlightCtrl',\n compile: function mdHighlightCompile(tElement, tAttr) {\n var termExpr = $parse(tAttr.mdHighlightText);\n var unsafeContentExpr = $interpolate(tElement.html());\n\n return function mdHighlightLink(scope, element, attr, ctrl) {\n ctrl.init(termExpr, unsafeContentExpr);\n };\n }\n };\n}\n\n})();\n(function(){\n\"use strict\";\n\n\nMdChipCtrl.$inject = [\"$scope\", \"$element\", \"$mdConstant\", \"$timeout\", \"$mdUtil\"];angular\n .module('material.components.chips')\n .controller('MdChipCtrl', MdChipCtrl);\n\n/**\n * Controller for the MdChip component. Responsible for handling keyboard\n * events and editting the chip if needed.\n *\n * @param $scope\n * @param $element\n * @param $mdConstant\n * @param $timeout\n * @param $mdUtil\n * @constructor\n */\nfunction MdChipCtrl ($scope, $element, $mdConstant, $timeout, $mdUtil) {\n /**\n * @type {$scope}\n */\n this.$scope = $scope;\n\n /**\n * @type {$element}\n */\n this.$element = $element;\n\n /**\n * @type {$mdConstant}\n */\n this.$mdConstant = $mdConstant;\n\n /**\n * @type {$timeout}\n */\n this.$timeout = $timeout;\n\n /**\n * @type {$mdUtil}\n */\n this.$mdUtil = $mdUtil;\n\n /**\n * @type {boolean}\n */\n this.isEditting = false;\n\n /**\n * @type {MdChipsCtrl}\n */\n this.parentController = undefined;\n\n /**\n * @type {boolean}\n */\n this.enableChipEdit = false;\n}\n\n\n/**\n * @param {MdChipsCtrl} controller\n */\nMdChipCtrl.prototype.init = function(controller) {\n this.parentController = controller;\n this.enableChipEdit = this.parentController.enableChipEdit;\n\n if (this.enableChipEdit) {\n this.$element.on('keydown', this.chipKeyDown.bind(this));\n this.$element.on('mousedown', this.chipMouseDown.bind(this));\n this.getChipContent().addClass('_md-chip-content-edit-is-enabled');\n }\n};\n\n\n/**\n * @return {Object}\n */\nMdChipCtrl.prototype.getChipContent = function() {\n var chipContents = this.$element[0].getElementsByClassName('md-chip-content');\n return angular.element(chipContents[0]);\n};\n\n\n/**\n * @return {Object}\n */\nMdChipCtrl.prototype.getContentElement = function() {\n return angular.element(this.getChipContent().children()[0]);\n};\n\n\n/**\n * @return {number}\n */\nMdChipCtrl.prototype.getChipIndex = function() {\n return parseInt(this.$element.attr('index'));\n};\n\n\n/**\n * Presents an input element to edit the contents of the chip.\n */\nMdChipCtrl.prototype.goOutOfEditMode = function() {\n if (!this.isEditting) return;\n\n this.isEditting = false;\n this.$element.removeClass('_md-chip-editing');\n this.getChipContent()[0].contentEditable = 'false';\n var chipIndex = this.getChipIndex();\n\n var content = this.getContentElement().text();\n if (content) {\n this.parentController.updateChipContents(\n chipIndex,\n this.getContentElement().text()\n );\n\n this.$mdUtil.nextTick(function() {\n if (this.parentController.selectedChip === chipIndex) {\n this.parentController.focusChip(chipIndex);\n }\n }.bind(this));\n } else {\n this.parentController.removeChipAndFocusInput(chipIndex);\n }\n};\n\n\n/**\n * Given an HTML element. Selects contents of it.\n * @param node\n */\nMdChipCtrl.prototype.selectNodeContents = function(node) {\n var range, selection;\n if (document.body.createTextRange) {\n range = document.body.createTextRange();\n range.moveToElementText(node);\n range.select();\n } else if (window.getSelection) {\n selection = window.getSelection();\n range = document.createRange();\n range.selectNodeContents(node);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n};\n\n\n/**\n * Presents an input element to edit the contents of the chip.\n */\nMdChipCtrl.prototype.goInEditMode = function() {\n this.isEditting = true;\n this.$element.addClass('_md-chip-editing');\n this.getChipContent()[0].contentEditable = 'true';\n this.getChipContent().on('blur', function() {\n this.goOutOfEditMode();\n }.bind(this));\n\n this.selectNodeContents(this.getChipContent()[0]);\n};\n\n\n/**\n * Handles the keydown event on the chip element. If enable-chip-edit attribute is\n * set to true, space or enter keys can trigger going into edit mode. Enter can also\n * trigger submitting if the chip is already being edited.\n * @param event\n */\nMdChipCtrl.prototype.chipKeyDown = function(event) {\n if (!this.isEditting &&\n (event.keyCode === this.$mdConstant.KEY_CODE.ENTER ||\n event.keyCode === this.$mdConstant.KEY_CODE.SPACE)) {\n event.preventDefault();\n this.goInEditMode();\n } else if (this.isEditting &&\n event.keyCode === this.$mdConstant.KEY_CODE.ENTER) {\n event.preventDefault();\n this.goOutOfEditMode();\n }\n};\n\n\n/**\n * Handles the double click event\n */\nMdChipCtrl.prototype.chipMouseDown = function() {\n if(this.getChipIndex() == this.parentController.selectedChip &&\n this.enableChipEdit &&\n !this.isEditting) {\n this.goInEditMode();\n }\n};\n\n})();\n(function(){\n\"use strict\";\n\n\nMdChip.$inject = [\"$mdTheming\", \"$mdUtil\", \"$compile\", \"$timeout\"];angular\n .module('material.components.chips')\n .directive('mdChip', MdChip);\n\n/**\n * @ngdoc directive\n * @name mdChip\n * @module material.components.chips\n *\n * @description\n * `` is a component used within `` and is responsible for rendering individual\n * chips.\n *\n *\n * @usage\n * \n * {{$chip}}\n * \n *\n */\n\n// This hint text is hidden within a chip but used by screen readers to\n// inform the user how they can interact with a chip.\nvar DELETE_HINT_TEMPLATE = '\\\n \\\n {{$mdChipsCtrl.deleteHint}}\\\n ';\n\n/**\n * MDChip Directive Definition\n *\n * @param $mdTheming\n * @param $mdUtil\n * @ngInject\n */\nfunction MdChip($mdTheming, $mdUtil, $compile, $timeout) {\n var deleteHintTemplate = $mdUtil.processTemplate(DELETE_HINT_TEMPLATE);\n\n return {\n restrict: 'E',\n require: ['^?mdChips', 'mdChip'],\n link: postLink,\n controller: 'MdChipCtrl'\n };\n\n function postLink(scope, element, attr, ctrls) {\n var chipsController = ctrls.shift();\n var chipController = ctrls.shift();\n var chipContentElement = angular.element(element[0].querySelector('.md-chip-content'));\n\n $mdTheming(element);\n\n if (chipsController) {\n chipController.init(chipsController);\n\n // Append our delete hint to the div.md-chip-content (which does not exist at compile time)\n chipContentElement.append($compile(deleteHintTemplate)(scope));\n\n // When a chip is blurred, make sure to unset (or reset) the selected chip so that tabbing\n // through elements works properly\n chipContentElement.on('blur', function() {\n chipsController.resetSelectedChip();\n chipsController.$scope.$applyAsync();\n });\n }\n\n // Use $timeout to ensure we run AFTER the element has been added to the DOM so we can focus it.\n $timeout(function() {\n if (!chipsController) {\n return;\n }\n\n if (chipsController.shouldFocusLastChip) {\n chipsController.focusLastChipThenInput();\n }\n });\n }\n}\n\n})();\n(function(){\n\"use strict\";\n\n\nMdChipRemove.$inject = [\"$timeout\"];angular\n .module('material.components.chips')\n .directive('mdChipRemove', MdChipRemove);\n\n/**\n * @ngdoc directive\n * @name mdChipRemove\n * @restrict A\n * @module material.components.chips\n *\n * @description\n * Designates an element to be used as the delete button for a chip.
\n * This element is passed as a child of the `md-chips` element.\n *\n * The designated button will be just appended to the chip and removes the given chip on click.
\n * By default the button is not being styled by the `md-chips` component.\n *\n * @usage\n * \n * \n * \n * \n * \n */\n\n\n/**\n * MdChipRemove Directive Definition.\n * \n * @param $timeout\n * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}\n * @constructor\n */\nfunction MdChipRemove ($timeout) {\n return {\n restrict: 'A',\n require: '^mdChips',\n scope: false,\n link: postLink\n };\n\n function postLink(scope, element, attr, ctrl) {\n element.on('click', function(event) {\n scope.$apply(function() {\n ctrl.removeChip(scope.$$replacedScope.$index);\n });\n });\n\n // Child elements aren't available until after a $timeout tick as they are hidden by an\n // `ng-if`. see http://goo.gl/zIWfuw\n $timeout(function() {\n element.attr({ tabindex: -1, 'aria-hidden': true });\n element.find('button').attr('tabindex', '-1');\n });\n }\n}\n\n})();\n(function(){\n\"use strict\";\n\n\nMdChipTransclude.$inject = [\"$compile\"];angular\n .module('material.components.chips')\n .directive('mdChipTransclude', MdChipTransclude);\n\nfunction MdChipTransclude ($compile) {\n return {\n restrict: 'EA',\n terminal: true,\n link: link,\n scope: false\n };\n function link (scope, element, attr) {\n var ctrl = scope.$parent.$mdChipsCtrl,\n newScope = ctrl.parent.$new(false, ctrl.parent);\n newScope.$$replacedScope = scope;\n newScope.$chip = scope.$chip;\n newScope.$index = scope.$index;\n newScope.$mdChipsCtrl = ctrl;\n\n var newHtml = ctrl.$scope.$eval(attr.mdChipTransclude);\n\n element.html(newHtml);\n $compile(element.contents())(newScope);\n }\n}\n\n})();\n(function(){\n\"use strict\";\n\n/**\n * The default chip append delay.\n *\n * @type {number}\n */\nMdChipsCtrl.$inject = [\"$scope\", \"$attrs\", \"$mdConstant\", \"$log\", \"$element\", \"$timeout\", \"$mdUtil\"];\nvar DEFAULT_CHIP_APPEND_DELAY = 300;\n\nangular\n .module('material.components.chips')\n .controller('MdChipsCtrl', MdChipsCtrl);\n\n/**\n * Controller for the MdChips component. Responsible for adding to and\n * removing from the list of chips, marking chips as selected, and binding to\n * the models of various input components.\n *\n * @param $scope\n * @param $attrs\n * @param $mdConstant\n * @param $log\n * @param $element\n * @param $timeout\n * @param $mdUtil\n * @constructor\n */\nfunction MdChipsCtrl ($scope, $attrs, $mdConstant, $log, $element, $timeout, $mdUtil) {\n /** @type {$timeout} **/\n this.$timeout = $timeout;\n\n /** @type {Object} */\n this.$mdConstant = $mdConstant;\n\n /** @type {angular.$scope} */\n this.$scope = $scope;\n\n /** @type {angular.$scope} */\n this.parent = $scope.$parent;\n\n /** @type {$mdUtil} */\n this.$mdUtil = $mdUtil;\n\n /** @type {$log} */\n this.$log = $log;\n\n /** @type {$element} */\n this.$element = $element;\n\n /** @type {$attrs} */\n this.$attrs = $attrs;\n\n /** @type {angular.NgModelController} */\n this.ngModelCtrl = null;\n\n /** @type {angular.NgModelController} */\n this.userInputNgModelCtrl = null;\n\n /** @type {MdAutocompleteCtrl} */\n this.autocompleteCtrl = null;\n\n /** @type {Element} */\n this.userInputElement = null;\n\n /** @type {Array.