diff options
Diffstat (limited to 'ecomp-portal-FE/client/bower_components/angular-material/modules/closure/core/core.js')
-rw-r--r-- | ecomp-portal-FE/client/bower_components/angular-material/modules/closure/core/core.js | 3779 |
1 files changed, 3779 insertions, 0 deletions
diff --git a/ecomp-portal-FE/client/bower_components/angular-material/modules/closure/core/core.js b/ecomp-portal-FE/client/bower_components/angular-material/modules/closure/core/core.js new file mode 100644 index 00000000..27c23d2c --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/angular-material/modules/closure/core/core.js @@ -0,0 +1,3779 @@ +/*! + * Angular Material Design + * https://github.com/angular/material + * @license MIT + * v0.9.8 + */ +goog.provide('ng.material.core'); + + + +/** + * Initialization function that validates environment + * requirements. + */ +angular + .module('material.core', [ 'material.core.gestures', 'material.core.theming' ]) + .config( MdCoreConfigure ); + + +function MdCoreConfigure($provide, $mdThemingProvider) { + + $provide.decorator('$$rAF', ["$delegate", rAFDecorator]); + + $mdThemingProvider.theme('default') + .primaryPalette('indigo') + .accentPalette('pink') + .warnPalette('red') + .backgroundPalette('grey'); +} +MdCoreConfigure.$inject = ["$provide", "$mdThemingProvider"]; + +function rAFDecorator( $delegate ) { + /** + * Use this to throttle events that come in often. + * The throttled function will always use the *last* invocation before the + * coming frame. + * + * For example, window resize events that fire many times a second: + * If we set to use an raf-throttled callback on window resize, then + * our callback will only be fired once per frame, with the last resize + * event that happened before that frame. + * + * @param {function} callback function to debounce + */ + $delegate.throttle = function(cb) { + var queueArgs, alreadyQueued, queueCb, context; + return function debounced() { + queueArgs = arguments; + context = this; + queueCb = cb; + if (!alreadyQueued) { + alreadyQueued = true; + $delegate(function() { + queueCb.apply(context, queueArgs); + alreadyQueued = false; + }); + } + }; + }; + return $delegate; +} + +angular.module('material.core') +.factory('$mdConstant', MdConstantFactory); + +function MdConstantFactory($$rAF, $sniffer) { + + var webkit = /webkit/i.test($sniffer.vendorPrefix); + function vendorProperty(name) { + return webkit ? ('webkit' + name.charAt(0).toUpperCase() + name.substring(1)) : name; + } + + return { + KEY_CODE: { + ENTER: 13, + ESCAPE: 27, + SPACE: 32, + LEFT_ARROW : 37, + UP_ARROW : 38, + RIGHT_ARROW : 39, + DOWN_ARROW : 40, + TAB : 9, + BACKSPACE: 8, + DELETE: 46 + }, + CSS: { + /* Constants */ + TRANSITIONEND: 'transitionend' + (webkit ? ' webkitTransitionEnd' : ''), + ANIMATIONEND: 'animationend' + (webkit ? ' webkitAnimationEnd' : ''), + + TRANSFORM: vendorProperty('transform'), + TRANSFORM_ORIGIN: vendorProperty('transformOrigin'), + TRANSITION: vendorProperty('transition'), + TRANSITION_DURATION: vendorProperty('transitionDuration'), + ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'), + ANIMATION_DURATION: vendorProperty('animationDuration'), + ANIMATION_NAME: vendorProperty('animationName'), + ANIMATION_TIMING: vendorProperty('animationTimingFunction'), + ANIMATION_DIRECTION: vendorProperty('animationDirection') + }, + MEDIA: { + 'sm': '(max-width: 600px)', + 'gt-sm': '(min-width: 600px)', + 'md': '(min-width: 600px) and (max-width: 960px)', + 'gt-md': '(min-width: 960px)', + 'lg': '(min-width: 960px) and (max-width: 1200px)', + 'gt-lg': '(min-width: 1200px)' + }, + MEDIA_PRIORITY: [ + 'gt-lg', + 'lg', + 'gt-md', + 'md', + 'gt-sm', + 'sm' + ] + }; +} +MdConstantFactory.$inject = ["$$rAF", "$sniffer"]; + + angular + .module('material.core') + .config( ["$provide", function($provide){ + $provide.decorator('$mdUtil', ['$delegate', function ($delegate){ + /** + * Inject the iterator facade to easily support iteration and accessors + * @see iterator below + */ + $delegate.iterator = MdIterator; + + return $delegate; + } + ]); + }]); + + /** + * iterator is a list facade to easily support iteration and accessors + * + * @param items Array list which this iterator will enumerate + * @param reloop Boolean enables iterator to consider the list as an endless reloop + */ + function MdIterator(items, reloop) { + var trueFn = function() { return true; }; + + if (items && !angular.isArray(items)) { + items = Array.prototype.slice.call(items); + } + + reloop = !!reloop; + var _items = items || [ ]; + + // Published API + return { + items: getItems, + count: count, + + inRange: inRange, + contains: contains, + indexOf: indexOf, + itemAt: itemAt, + + findBy: findBy, + + add: add, + remove: remove, + + first: first, + last: last, + next: angular.bind(null, findSubsequentItem, false), + previous: angular.bind(null, findSubsequentItem, true), + + hasPrevious: hasPrevious, + hasNext: hasNext + + }; + + /** + * Publish copy of the enumerable set + * @returns {Array|*} + */ + function getItems() { + return [].concat(_items); + } + + /** + * Determine length of the list + * @returns {Array.length|*|number} + */ + function count() { + return _items.length; + } + + /** + * Is the index specified valid + * @param index + * @returns {Array.length|*|number|boolean} + */ + function inRange(index) { + return _items.length && ( index > -1 ) && (index < _items.length ); + } + + /** + * Can the iterator proceed to the next item in the list; relative to + * the specified item. + * + * @param item + * @returns {Array.length|*|number|boolean} + */ + function hasNext(item) { + return item ? inRange(indexOf(item) + 1) : false; + } + + /** + * Can the iterator proceed to the previous item in the list; relative to + * the specified item. + * + * @param item + * @returns {Array.length|*|number|boolean} + */ + function hasPrevious(item) { + return item ? inRange(indexOf(item) - 1) : false; + } + + /** + * Get item at specified index/position + * @param index + * @returns {*} + */ + function itemAt(index) { + return inRange(index) ? _items[index] : null; + } + + /** + * Find all elements matching the key/value pair + * otherwise return null + * + * @param val + * @param key + * + * @return array + */ + function findBy(key, val) { + return _items.filter(function(item) { + return item[key] === val; + }); + } + + /** + * Add item to list + * @param item + * @param index + * @returns {*} + */ + function add(item, index) { + if ( !item ) return -1; + + if (!angular.isNumber(index)) { + index = _items.length; + } + + _items.splice(index, 0, item); + + return indexOf(item); + } + + /** + * Remove item from list... + * @param item + */ + function remove(item) { + if ( contains(item) ){ + _items.splice(indexOf(item), 1); + } + } + + /** + * Get the zero-based index of the target item + * @param item + * @returns {*} + */ + function indexOf(item) { + return _items.indexOf(item); + } + + /** + * Boolean existence check + * @param item + * @returns {boolean} + */ + function contains(item) { + return item && (indexOf(item) > -1); + } + + /** + * Return first item in the list + * @returns {*} + */ + function first() { + return _items.length ? _items[0] : null; + } + + /** + * Return last item in the list... + * @returns {*} + */ + function last() { + return _items.length ? _items[_items.length - 1] : null; + } + + /** + * Find the next item. If reloop is true and at the end of the list, it will go back to the + * first item. If given, the `validate` callback will be used to determine whether the next item + * is valid. If not valid, it will try to find the next item again. + * + * @param {boolean} backwards Specifies the direction of searching (forwards/backwards) + * @param {*} item The item whose subsequent item we are looking for + * @param {Function=} validate The `validate` function + * @param {integer=} limit The recursion limit + * + * @returns {*} The subsequent item or null + */ + function findSubsequentItem(backwards, item, validate, limit) { + validate = validate || trueFn; + + var curIndex = indexOf(item); + while (true) { + if (!inRange(curIndex)) return null; + + var nextIndex = curIndex + (backwards ? -1 : 1); + var foundItem = null; + if (inRange(nextIndex)) { + foundItem = _items[nextIndex]; + } else if (reloop) { + foundItem = backwards ? last() : first(); + nextIndex = indexOf(foundItem); + } + + if ((foundItem === null) || (nextIndex === limit)) return null; + if (validate(foundItem)) return foundItem; + + if (angular.isUndefined(limit)) limit = nextIndex; + + curIndex = nextIndex; + } + } + } + + +angular.module('material.core') +.factory('$mdMedia', mdMediaFactory); + +/** + * @ngdoc service + * @name $mdMedia + * @module material.core + * + * @description + * `$mdMedia` is used to evaluate whether a given media query is true or false given the + * current device's screen / window size. The media query will be re-evaluated on resize, allowing + * you to register a watch. + * + * `$mdMedia` also has pre-programmed support for media queries that match the layout breakpoints. + * (`sm`, `gt-sm`, `md`, `gt-md`, `lg`, `gt-lg`). + * + * @returns {boolean} a boolean representing whether or not the given media query is true or false. + * + * @usage + * <hljs lang="js"> + * app.controller('MyController', function($mdMedia, $scope) { + * $scope.$watch(function() { return $mdMedia('lg'); }, function(big) { + * $scope.bigScreen = big; + * }); + * + * $scope.screenIsSmall = $mdMedia('sm'); + * $scope.customQuery = $mdMedia('(min-width: 1234px)'); + * $scope.anotherCustom = $mdMedia('max-width: 300px'); + * }); + * </hljs> + */ + +function mdMediaFactory($mdConstant, $rootScope, $window) { + var queries = {}; + var mqls = {}; + var results = {}; + var normalizeCache = {}; + + $mdMedia.getResponsiveAttribute = getResponsiveAttribute; + $mdMedia.getQuery = getQuery; + $mdMedia.watchResponsiveAttributes = watchResponsiveAttributes; + + return $mdMedia; + + function $mdMedia(query) { + var validated = queries[query]; + if (angular.isUndefined(validated)) { + validated = queries[query] = validate(query); + } + + var result = results[validated]; + if (angular.isUndefined(result)) { + result = add(validated); + } + + return result; + } + + function validate(query) { + return $mdConstant.MEDIA[query] || + ((query.charAt(0) !== '(') ? ('(' + query + ')') : query); + } + + function add(query) { + var result = mqls[query] = $window.matchMedia(query); + result.addListener(onQueryChange); + return (results[result.media] = !!result.matches); + } + + function onQueryChange(query) { + $rootScope.$evalAsync(function() { + results[query.media] = !!query.matches; + }); + } + + function getQuery(name) { + return mqls[name]; + } + + function getResponsiveAttribute(attrs, attrName) { + for (var i = 0; i < $mdConstant.MEDIA_PRIORITY.length; i++) { + var mediaName = $mdConstant.MEDIA_PRIORITY[i]; + if (!mqls[queries[mediaName]].matches) { + continue; + } + + var normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName); + if (attrs[normalizedName]) { + return attrs[normalizedName]; + } + } + + // fallback on unprefixed + return attrs[getNormalizedName(attrs, attrName)]; + } + + function watchResponsiveAttributes(attrNames, attrs, watchFn) { + var unwatchFns = []; + attrNames.forEach(function(attrName) { + var normalizedName = getNormalizedName(attrs, attrName); + if (attrs[normalizedName]) { + unwatchFns.push( + attrs.$observe(normalizedName, angular.bind(void 0, watchFn, null))); + } + + for (var mediaName in $mdConstant.MEDIA) { + normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName); + if (!attrs[normalizedName]) { + return; + } + + unwatchFns.push(attrs.$observe(normalizedName, angular.bind(void 0, watchFn, mediaName))); + } + }); + + return function unwatch() { + unwatchFns.forEach(function(fn) { fn(); }) + }; + } + + // Improves performance dramatically + function getNormalizedName(attrs, attrName) { + return normalizeCache[attrName] || + (normalizeCache[attrName] = attrs.$normalize(attrName)); + } +} +mdMediaFactory.$inject = ["$mdConstant", "$rootScope", "$window"]; + +/* + * This var has to be outside the angular factory, otherwise when + * there are multiple material apps on the same page, each app + * will create its own instance of this array and the app's IDs + * will not be unique. + */ +var nextUniqueId = 0; + +angular.module('material.core') +.factory('$mdUtil', ["$cacheFactory", "$document", "$timeout", "$q", "$window", "$mdConstant", function($cacheFactory, $document, $timeout, $q, $window, $mdConstant) { + var Util; + + function getNode(el) { + return el[0] || el; + } + + return Util = { + now: window.performance ? + angular.bind(window.performance, window.performance.now) : + Date.now, + + clientRect: function(element, offsetParent, isOffsetRect) { + var node = getNode(element); + offsetParent = getNode(offsetParent || node.offsetParent || document.body); + var nodeRect = node.getBoundingClientRect(); + + // The user can ask for an offsetRect: a rect relative to the offsetParent, + // or a clientRect: a rect relative to the page + var offsetRect = isOffsetRect ? + offsetParent.getBoundingClientRect() : + { left: 0, top: 0, width: 0, height: 0 }; + return { + left: nodeRect.left - offsetRect.left, + top: nodeRect.top - offsetRect.top, + width: nodeRect.width, + height: nodeRect.height + }; + }, + offsetRect: function(element, offsetParent) { + return Util.clientRect(element, offsetParent, true); + }, + // Disables scroll around the passed element. Goes up the DOM to find a + // disableTarget (a md-content that is scrolling, or the body as a fallback) + // and uses CSS/JS to prevent it from scrolling + disableScrollAround: function(element) { + element = element instanceof angular.element ? element[0] : element; + var parentEl = element; + var disableTarget; + + // Find the highest level scrolling md-content + while (parentEl = this.getClosest(parentEl, 'MD-CONTENT', true)) { + if (isScrolling(parentEl)) { + disableTarget = angular.element(parentEl)[0]; + } + } + + // Default to the body if no scrolling md-content + if (!disableTarget) { + disableTarget = $document[0].body; + if (!isScrolling(disableTarget)) return angular.noop; + } + + if (disableTarget.nodeName == 'BODY') { + return disableBodyScroll(); + } else { + return disableElementScroll(); + } + + // Creates a virtual scrolling mask to absorb touchmove, keyboard, scrollbar clicking, and wheel events + function disableElementScroll() { + var scrollMask = angular.element('<div class="md-scroll-mask"><div class="md-scroll-mask-bar"></div></div>'); + var computedStyle = $window.getComputedStyle(disableTarget); + var disableRect = disableTarget.getBoundingClientRect(); + var scrollWidth = disableRect.width - disableTarget.clientWidth; + applyStyles(scrollMask[0], { + zIndex: computedStyle.zIndex == 'auto' ? 2 : computedStyle.zIndex + 1, + width: disableRect.width + 'px', + height: disableRect.height + 'px', + top: disableRect.top + 'px', + left: disableRect.left + 'px' + }); + scrollMask[0].firstElementChild.style.width = scrollWidth + 'px'; + $document[0].body.appendChild(scrollMask[0]); + + scrollMask.on('wheel', preventDefault); + scrollMask.on('touchmove', preventDefault); + $document.on('keydown', disableKeyNav); + + return function restoreScroll() { + scrollMask.off('wheel'); + scrollMask.off('touchmove'); + scrollMask[0].parentNode.removeChild(scrollMask[0]); + $document.off('keydown', disableKeyNav); + }; + + // Prevent keypresses from elements inside the disableTarget + // used to stop the keypresses that could cause the page to scroll + // (arrow keys, spacebar, tab, etc). + function disableKeyNav(e) { + if (disableTarget.contains(e.target)) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + + function preventDefault(e) { + e.preventDefault(); + } + } + + // Converts the disableTarget (body) to a position fixed block and translate it to the propper scroll position + function disableBodyScroll() { + var restoreStyle = disableTarget.getAttribute('style') || ''; + var scrollOffset = disableTarget.scrollTop; + + applyStyles(disableTarget, { + position: 'fixed', + width: '100%', + overflowY: 'scroll', + top: -scrollOffset + 'px' + }); + + return function restoreScroll() { + disableTarget.setAttribute('style', restoreStyle); + disableTarget.scrollTop = scrollOffset; + }; + } + + function applyStyles (el, styles) { + for (var key in styles) { + el.style[key] = styles[key]; + } + } + + function isScrolling(el) { + if (el instanceof angular.element) el = el[0]; + return el.scrollHeight > el.offsetHeight; + } + }, + + floatingScrollbars: function() { + if (this.floatingScrollbars.cached === undefined) { + var tempNode = angular.element('<div style="width: 100%; z-index: -1; position: absolute; height: 35px; overflow-y: scroll"><div style="height: 60;"></div></div>'); + $document[0].body.appendChild(tempNode[0]); + this.floatingScrollbars.cached = (tempNode[0].offsetWidth == tempNode[0].childNodes[0].offsetWidth); + tempNode.remove(); + } + return this.floatingScrollbars.cached; + }, + + // Mobile safari only allows you to set focus in click event listeners... + forceFocus: function(element) { + var node = element[0] || element; + + document.addEventListener('click', function focusOnClick(ev) { + if (ev.target === node && ev.$focus) { + node.focus(); + ev.stopImmediatePropagation(); + ev.preventDefault(); + node.removeEventListener('click', focusOnClick); + } + }, true); + + var newEvent = document.createEvent('MouseEvents'); + newEvent.initMouseEvent('click', false, true, window, {}, 0, 0, 0, 0, + false, false, false, false, 0, null); + newEvent.$material = true; + newEvent.$focus = true; + node.dispatchEvent(newEvent); + }, + + transitionEndPromise: function(element, opts) { + opts = opts || {}; + var deferred = $q.defer(); + element.on($mdConstant.CSS.TRANSITIONEND, finished); + function finished(ev) { + // Make sure this transitionend didn't bubble up from a child + if (!ev || ev.target === element[0]) { + element.off($mdConstant.CSS.TRANSITIONEND, finished); + deferred.resolve(); + } + } + if (opts.timeout) $timeout(finished, opts.timeout); + return deferred.promise; + }, + + fakeNgModel: function() { + return { + $fake: true, + $setTouched: angular.noop, + $setViewValue: function(value) { + this.$viewValue = value; + this.$render(value); + this.$viewChangeListeners.forEach(function(cb) { cb(); }); + }, + $isEmpty: function(value) { + return ('' + value).length === 0; + }, + $parsers: [], + $formatters: [], + $viewChangeListeners: [], + $render: angular.noop + }; + }, + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. + // @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs + // @param invokeApply should the $timeout trigger $digest() dirty checking + debounce: function (func, wait, scope, invokeApply) { + var timer; + + return function debounced() { + var context = scope, + args = Array.prototype.slice.call(arguments); + + $timeout.cancel(timer); + timer = $timeout(function() { + + timer = undefined; + func.apply(context, args); + + }, wait || 10, invokeApply ); + }; + }, + + // Returns a function that can only be triggered every `delay` milliseconds. + // In other words, the function will not be called unless it has been more + // than `delay` milliseconds since the last call. + throttle: function throttle(func, delay) { + var recent; + return function throttled() { + var context = this; + var args = arguments; + var now = Util.now(); + + if (!recent || (now - recent > delay)) { + func.apply(context, args); + recent = now; + } + }; + }, + + /** + * Measures the number of milliseconds taken to run the provided callback + * function. Uses a high-precision timer if available. + */ + time: function time(cb) { + var start = Util.now(); + cb(); + return Util.now() - start; + }, + + /** + * Get a unique ID. + * + * @returns {string} an unique numeric string + */ + nextUid: function() { + return '' + nextUniqueId++; + }, + + // Stop watchers and events from firing on a scope without destroying it, + // by disconnecting it from its parent and its siblings' linked lists. + disconnectScope: function disconnectScope(scope) { + if (!scope) return; + + // we can't destroy the root scope or a scope that has been already destroyed + if (scope.$root === scope) return; + if (scope.$$destroyed ) return; + + var parent = scope.$parent; + scope.$$disconnected = true; + + // See Scope.$destroy + if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling; + if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling; + if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling; + if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling; + + scope.$$nextSibling = scope.$$prevSibling = null; + + }, + + // Undo the effects of disconnectScope above. + reconnectScope: function reconnectScope(scope) { + if (!scope) return; + + // we can't disconnect the root node or scope already disconnected + if (scope.$root === scope) return; + if (!scope.$$disconnected) return; + + var child = scope; + + var parent = child.$parent; + child.$$disconnected = false; + // See Scope.$new for this logic... + child.$$prevSibling = parent.$$childTail; + if (parent.$$childHead) { + parent.$$childTail.$$nextSibling = child; + parent.$$childTail = child; + } else { + parent.$$childHead = parent.$$childTail = child; + } + }, + + /* + * getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName + * + * @param el Element to start walking the DOM from + * @param tagName Tag name to find closest to el, such as 'form' + */ + getClosest: function getClosest(el, tagName, onlyParent) { + if (el instanceof angular.element) el = el[0]; + tagName = tagName.toUpperCase(); + if (onlyParent) el = el.parentNode; + if (!el) return null; + do { + if (el.nodeName === tagName) { + return el; + } + } while (el = el.parentNode); + return null; + }, + + /** + * Functional equivalent for $element.filter(‘md-bottom-sheet’) + * useful with interimElements where the element and its container are important... + */ + extractElementByName: function (element, nodeName) { + for (var i = 0, len = element.length; i < len; i++) { + if (element[i].nodeName.toLowerCase() === nodeName){ + return angular.element(element[i]); + } + } + return element; + }, + + /** + * Give optional properties with no value a boolean true by default + */ + initOptionalProperties: function (scope, attr, defaults ) { + defaults = defaults || { }; + angular.forEach(scope.$$isolateBindings, function (binding, key) { + if (binding.optional && angular.isUndefined(scope[key])) { + var hasKey = attr.hasOwnProperty(attr.$normalize(binding.attrName)); + + scope[key] = angular.isDefined(defaults[key]) ? defaults[key] : hasKey; + } + }); + } + + }; + +}]); + +/* + * Since removing jQuery from the demos, some code that uses `element.focus()` is broken. + * + * We need to add `element.focus()`, because it's testable unlike `element[0].focus`. + * + * TODO(ajoslin): This should be added in a better place later. + */ + +angular.element.prototype.focus = angular.element.prototype.focus || function() { + if (this.length) { + this[0].focus(); + } + return this; +}; +angular.element.prototype.blur = angular.element.prototype.blur || function() { + if (this.length) { + this[0].blur(); + } + return this; +}; + + +angular.module('material.core') + .service('$mdAria', AriaService); + +/* + * ngInject + */ +function AriaService($$rAF, $log, $window) { + + return { + expect: expect, + expectAsync: expectAsync, + expectWithText: expectWithText + }; + + /** + * Check if expected attribute has been specified on the target element or child + * @param element + * @param attrName + * @param {optional} defaultValue What to set the attr to if no value is found + */ + function expect(element, attrName, defaultValue) { + var node = element[0] || element; + + // if node exists and neither it nor its children have the attribute + if (node && + ((!node.hasAttribute(attrName) || + node.getAttribute(attrName).length === 0) && + !childHasAttribute(node, attrName))) { + + defaultValue = angular.isString(defaultValue) ? defaultValue.trim() : ''; + if (defaultValue.length) { + element.attr(attrName, defaultValue); + } else { + $log.warn('ARIA: Attribute "', attrName, '", required for accessibility, is missing on node:', node); + } + + } + } + + function expectAsync(element, attrName, defaultValueGetter) { + // Problem: when retrieving the element's contents synchronously to find the label, + // the text may not be defined yet in the case of a binding. + // There is a higher chance that a binding will be defined if we wait one frame. + $$rAF(function() { + expect(element, attrName, defaultValueGetter()); + }); + } + + function expectWithText(element, attrName) { + expectAsync(element, attrName, function() { + return getText(element); + }); + } + + function getText(element) { + return element.text().trim(); + } + + function childHasAttribute(node, attrName) { + var hasChildren = node.hasChildNodes(), + hasAttr = false; + + function isHidden(el) { + var style = el.currentStyle ? el.currentStyle : $window.getComputedStyle(el); + return (style.display === 'none'); + } + + if(hasChildren) { + var children = node.childNodes; + for(var i=0; i<children.length; i++){ + var child = children[i]; + if(child.nodeType === 1 && child.hasAttribute(attrName)) { + if(!isHidden(child)){ + hasAttr = true; + } + } + } + } + return hasAttr; + } +} +AriaService.$inject = ["$$rAF", "$log", "$window"]; + +angular.module('material.core') + .service('$mdCompiler', mdCompilerService); + +function mdCompilerService($q, $http, $injector, $compile, $controller, $templateCache) { + /* jshint validthis: true */ + + /* + * @ngdoc service + * @name $mdCompiler + * @module material.core + * @description + * The $mdCompiler service is an abstraction of angular's compiler, that allows the developer + * to easily compile an element with a templateUrl, controller, and locals. + * + * @usage + * <hljs lang="js"> + * $mdCompiler.compile({ + * templateUrl: 'modal.html', + * controller: 'ModalCtrl', + * locals: { + * modal: myModalInstance; + * } + * }).then(function(compileData) { + * compileData.element; // modal.html's template in an element + * compileData.link(myScope); //attach controller & scope to element + * }); + * </hljs> + */ + + /* + * @ngdoc method + * @name $mdCompiler#compile + * @description A helper to compile an HTML template/templateUrl with a given controller, + * locals, and scope. + * @param {object} options An options object, with the following properties: + * + * - `controller` - `{(string=|function()=}` Controller fn that should be associated with + * newly created scope or the name of a registered controller if passed as a string. + * - `controllerAs` - `{string=}` A controller alias name. If present the controller will be + * published to scope under the `controllerAs` name. + * - `template` - `{string=}` An html template as a string. + * - `templateUrl` - `{string=}` A path to an html template. + * - `transformTemplate` - `{function(template)=}` A function which transforms the template after + * it is loaded. It will be given the template string as a parameter, and should + * return a a new string representing the transformed template. + * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should + * be injected into the controller. If any of these dependencies are promises, the compiler + * will wait for them all to be resolved, or if one is rejected before the controller is + * instantiated `compile()` will fail.. + * * `key` - `{string}`: a name of a dependency to be injected into the controller. + * * `factory` - `{string|function}`: If `string` then it is an alias for a service. + * Otherwise if function, then it is injected and the return value is treated as the + * dependency. If the result is a promise, it is resolved before its value is + * injected into the controller. + * + * @returns {object=} promise A promise, which will be resolved with a `compileData` object. + * `compileData` has the following properties: + * + * - `element` - `{element}`: an uncompiled element matching the provided template. + * - `link` - `{function(scope)}`: A link function, which, when called, will compile + * the element and instantiate the provided controller (if given). + * - `locals` - `{object}`: The locals which will be passed into the controller once `link` is + * called. If `bindToController` is true, they will be coppied to the ctrl instead + * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in. + */ + this.compile = function(options) { + var templateUrl = options.templateUrl; + var template = options.template || ''; + var controller = options.controller; + var controllerAs = options.controllerAs; + var resolve = options.resolve || {}; + var locals = options.locals || {}; + var transformTemplate = options.transformTemplate || angular.identity; + var bindToController = options.bindToController; + + // Take resolve values and invoke them. + // Resolves can either be a string (value: 'MyRegisteredAngularConst'), + // or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {}) + angular.forEach(resolve, function(value, key) { + if (angular.isString(value)) { + resolve[key] = $injector.get(value); + } else { + resolve[key] = $injector.invoke(value); + } + }); + //Add the locals, which are just straight values to inject + //eg locals: { three: 3 }, will inject three into the controller + angular.extend(resolve, locals); + + if (templateUrl) { + resolve.$template = $http.get(templateUrl, {cache: $templateCache}) + .then(function(response) { + return response.data; + }); + } else { + resolve.$template = $q.when(template); + } + + // Wait for all the resolves to finish if they are promises + return $q.all(resolve).then(function(locals) { + + var template = transformTemplate(locals.$template); + var element = options.element || angular.element('<div>').html(template.trim()).contents(); + var linkFn = $compile(element); + + //Return a linking function that can be used later when the element is ready + return { + locals: locals, + element: element, + link: function link(scope) { + locals.$scope = scope; + + //Instantiate controller if it exists, because we have scope + if (controller) { + var invokeCtrl = $controller(controller, locals, true); + if (bindToController) { + angular.extend(invokeCtrl.instance, locals); + } + var ctrl = invokeCtrl(); + //See angular-route source for this logic + element.data('$ngControllerController', ctrl); + element.children().data('$ngControllerController', ctrl); + + if (controllerAs) { + scope[controllerAs] = ctrl; + } + } + return linkFn(scope); + } + }; + }); + + }; +} +mdCompilerService.$inject = ["$q", "$http", "$injector", "$compile", "$controller", "$templateCache"]; + +angular.module('material.core') + .provider('$$interimElement', InterimElementProvider); + +/* + * @ngdoc service + * @name $$interimElement + * @module material.core + * + * @description + * + * Factory that contructs `$$interimElement.$service` services. + * Used internally in material design for elements that appear on screen temporarily. + * The service provides a promise-like API for interacting with the temporary + * elements. + * + * ```js + * app.service('$mdToast', function($$interimElement) { + * var $mdToast = $$interimElement(toastDefaultOptions); + * return $mdToast; + * }); + * ``` + * @param {object=} defaultOptions Options used by default for the `show` method on the service. + * + * @returns {$$interimElement.$service} + * + */ + +function InterimElementProvider() { + createInterimElementProvider.$get = InterimElementFactory; + InterimElementFactory.$inject = ["$document", "$q", "$rootScope", "$timeout", "$rootElement", "$animate", "$interpolate", "$mdCompiler", "$mdTheming"]; + return createInterimElementProvider; + + /** + * Returns a new provider which allows configuration of a new interimElement + * service. Allows configuration of default options & methods for options, + * as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method) + */ + function createInterimElementProvider(interimFactoryName) { + var EXPOSED_METHODS = ['onHide', 'onShow', 'onRemove']; + + var customMethods = {}; + var providerConfig = { + presets: {} + }; + + var provider = { + setDefaults: setDefaults, + addPreset: addPreset, + addMethod: addMethod, + $get: factory + }; + + /** + * all interim elements will come with the 'build' preset + */ + provider.addPreset('build', { + methods: ['controller', 'controllerAs', 'resolve', + 'template', 'templateUrl', 'themable', 'transformTemplate', 'parent'] + }); + + factory.$inject = ["$$interimElement", "$animate", "$injector"]; + return provider; + + /** + * Save the configured defaults to be used when the factory is instantiated + */ + function setDefaults(definition) { + providerConfig.optionsFactory = definition.options; + providerConfig.methods = (definition.methods || []).concat(EXPOSED_METHODS); + return provider; + } + + /** + * Add a method to the factory that isn't specific to any interim element operations + */ + + function addMethod(name, fn) { + customMethods[name] = fn; + return provider; + } + + /** + * Save the configured preset to be used when the factory is instantiated + */ + function addPreset(name, definition) { + definition = definition || {}; + definition.methods = definition.methods || []; + definition.options = definition.options || function() { return {}; }; + + if (/^cancel|hide|show$/.test(name)) { + throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!"); + } + if (definition.methods.indexOf('_options') > -1) { + throw new Error("Method '_options' in " + interimFactoryName + " is reserved!"); + } + providerConfig.presets[name] = { + methods: definition.methods.concat(EXPOSED_METHODS), + optionsFactory: definition.options, + argOption: definition.argOption + }; + return provider; + } + + /** + * Create a factory that has the given methods & defaults implementing interimElement + */ + /* ngInject */ + function factory($$interimElement, $animate, $injector) { + var defaultMethods; + var defaultOptions; + var interimElementService = $$interimElement(); + + /* + * publicService is what the developer will be using. + * It has methods hide(), cancel(), show(), build(), and any other + * presets which were set during the config phase. + */ + var publicService = { + hide: interimElementService.hide, + cancel: interimElementService.cancel, + show: showInterimElement + }; + + defaultMethods = providerConfig.methods || []; + // This must be invoked after the publicService is initialized + defaultOptions = invokeFactory(providerConfig.optionsFactory, {}); + + // Copy over the simple custom methods + angular.forEach(customMethods, function(fn, name) { + publicService[name] = fn; + }); + + angular.forEach(providerConfig.presets, function(definition, name) { + var presetDefaults = invokeFactory(definition.optionsFactory, {}); + var presetMethods = (definition.methods || []).concat(defaultMethods); + + // Every interimElement built with a preset has a field called `$type`, + // which matches the name of the preset. + // Eg in preset 'confirm', options.$type === 'confirm' + angular.extend(presetDefaults, { $type: name }); + + // This creates a preset class which has setter methods for every + // method given in the `.addPreset()` function, as well as every + // method given in the `.setDefaults()` function. + // + // @example + // .setDefaults({ + // methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'], + // options: dialogDefaultOptions + // }) + // .addPreset('alert', { + // methods: ['title', 'ok'], + // options: alertDialogOptions + // }) + // + // Set values will be passed to the options when interimElemnt.show() is called. + function Preset(opts) { + this._options = angular.extend({}, presetDefaults, opts); + } + angular.forEach(presetMethods, function(name) { + Preset.prototype[name] = function(value) { + this._options[name] = value; + return this; + }; + }); + + // Create shortcut method for one-linear methods + if (definition.argOption) { + var methodName = 'show' + name.charAt(0).toUpperCase() + name.slice(1); + publicService[methodName] = function(arg) { + var config = publicService[name](arg); + return publicService.show(config); + }; + } + + // eg $mdDialog.alert() will return a new alert preset + publicService[name] = function(arg) { + // If argOption is supplied, eg `argOption: 'content'`, then we assume + // if the argument is not an options object then it is the `argOption` option. + // + // @example `$mdToast.simple('hello')` // sets options.content to hello + // // because argOption === 'content' + if (arguments.length && definition.argOption && !angular.isObject(arg) && + !angular.isArray(arg)) { + return (new Preset())[definition.argOption](arg); + } else { + return new Preset(arg); + } + + }; + }); + + return publicService; + + function showInterimElement(opts) { + // opts is either a preset which stores its options on an _options field, + // or just an object made up of options + if (opts && opts._options) opts = opts._options; + return interimElementService.show( + angular.extend({}, defaultOptions, opts) + ); + } + + /** + * Helper to call $injector.invoke with a local of the factory name for + * this provider. + * If an $mdDialog is providing options for a dialog and tries to inject + * $mdDialog, a circular dependency error will happen. + * We get around that by manually injecting $mdDialog as a local. + */ + function invokeFactory(factory, defaultVal) { + var locals = {}; + locals[interimFactoryName] = publicService; + return $injector.invoke(factory || function() { return defaultVal; }, {}, locals); + } + + } + + } + + /* ngInject */ + function InterimElementFactory($document, $q, $rootScope, $timeout, $rootElement, $animate, + $interpolate, $mdCompiler, $mdTheming ) { + var startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}')), + processTemplate = usesStandardSymbols ? angular.identity : replaceInterpolationSymbols; + + return function createInterimElementService() { + /* + * @ngdoc service + * @name $$interimElement.$service + * + * @description + * A service used to control inserting and removing an element into the DOM. + * + */ + var stack = []; + var service; + return service = { + show: show, + hide: hide, + cancel: cancel + }; + + /* + * @ngdoc method + * @name $$interimElement.$service#show + * @kind function + * + * @description + * Adds the `$interimElement` to the DOM and returns a promise that will be resolved or rejected + * with hide or cancel, respectively. + * + * @param {*} options is hashMap of settings + * @returns a Promise + * + */ + function show(options) { + if (stack.length) { + return service.cancel().then(function() { + return show(options); + }); + } else { + var interimElement = new InterimElement(options); + stack.push(interimElement); + return interimElement.show().then(function() { + return interimElement.deferred.promise; + }); + } + } + + /* + * @ngdoc method + * @name $$interimElement.$service#hide + * @kind function + * + * @description + * Removes the `$interimElement` from the DOM and resolves the promise returned from `show` + * + * @param {*} resolveParam Data to resolve the promise with + * @returns a Promise that will be resolved after the element has been removed. + * + */ + function hide(response) { + var interimElement = stack.shift(); + return interimElement && interimElement.remove().then(function() { + interimElement.deferred.resolve(response); + }); + } + + /* + * @ngdoc method + * @name $$interimElement.$service#cancel + * @kind function + * + * @description + * Removes the `$interimElement` from the DOM and rejects the promise returned from `show` + * + * @param {*} reason Data to reject the promise with + * @returns Promise that will be resolved after the element has been removed. + * + */ + function cancel(reason) { + var interimElement = stack.shift(); + return $q.when(interimElement && interimElement.remove().then(function() { + interimElement.deferred.reject(reason); + })); + } + + + /* + * Internal Interim Element Object + * Used internally to manage the DOM element and related data + */ + function InterimElement(options) { + var self; + var hideTimeout, element, showDone, removeDone; + + options = options || {}; + options = angular.extend({ + preserveScope: false, + scope: options.scope || $rootScope.$new(options.isolateScope), + onShow: function(scope, element, options) { + return $animate.enter(element, options.parent); + }, + onRemove: function(scope, element, options) { + // Element could be undefined if a new element is shown before + // the old one finishes compiling. + return element && $animate.leave(element) || $q.when(); + } + }, options); + + if (options.template) { + options.template = processTemplate(options.template); + } + + return self = { + options: options, + deferred: $q.defer(), + show: function() { + var compilePromise; + if (options.skipCompile) { + compilePromise = $q(function(resolve) { + resolve({ + locals: {}, + link: function() { return options.element; } + }); + }); + } else { + compilePromise = $mdCompiler.compile(options); + } + + return showDone = compilePromise.then(function(compileData) { + angular.extend(compileData.locals, self.options); + + element = compileData.link(options.scope); + + // Search for parent at insertion time, if not specified + if (angular.isFunction(options.parent)) { + options.parent = options.parent(options.scope, element, options); + } else if (angular.isString(options.parent)) { + options.parent = angular.element($document[0].querySelector(options.parent)); + } + + // If parent querySelector/getter function fails, or it's just null, + // find a default. + if (!(options.parent || {}).length) { + var el; + if ($rootElement[0] && $rootElement[0].querySelector) { + el = $rootElement[0].querySelector(':not(svg) > body'); + } + if (!el) el = $rootElement[0]; + if (el.nodeName == '#comment') { + el = $document[0].body; + } + options.parent = angular.element(el); + } + + if (options.themable) $mdTheming(element); + var ret = options.onShow(options.scope, element, options); + return $q.when(ret) + .then(function(){ + // Issue onComplete callback when the `show()` finishes + (options.onComplete || angular.noop)(options.scope, element, options); + startHideTimeout(); + }); + + function startHideTimeout() { + if (options.hideDelay) { + hideTimeout = $timeout(service.cancel, options.hideDelay) ; + } + } + }, function(reason) { showDone = true; self.deferred.reject(reason); }); + }, + cancelTimeout: function() { + if (hideTimeout) { + $timeout.cancel(hideTimeout); + hideTimeout = undefined; + } + }, + remove: function() { + self.cancelTimeout(); + return removeDone = $q.when(showDone).then(function() { + var ret = element ? options.onRemove(options.scope, element, options) : true; + return $q.when(ret).then(function() { + if (!options.preserveScope) options.scope.$destroy(); + removeDone = true; + }); + }); + } + }; + } + }; + + /* + * Replace `{{` and `}}` in a string (usually a template) with the actual start-/endSymbols used + * for interpolation. This allows pre-defined templates (for components such as dialog, toast etc) + * to continue to work in apps that use custom interpolation start-/endSymbols. + * + * @param {string} text The text in which to replace `{{` / `}}` + * @returns {string} The modified string using the actual interpolation start-/endSymbols + */ + function replaceInterpolationSymbols(text) { + if (!text || !angular.isString(text)) return text; + return text.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); + } + } + +} + + var HANDLERS = {}; + /* The state of the current 'pointer' + * The pointer represents the state of the current touch. + * It contains normalized x and y coordinates from DOM events, + * as well as other information abstracted from the DOM. + */ + var pointer, lastPointer, forceSkipClickHijack = false; + + // Used to attach event listeners once when multiple ng-apps are running. + var isInitialized = false; + + angular + .module('material.core.gestures', [ ]) + .provider('$mdGesture', MdGestureProvider) + .factory('$$MdGestureHandler', MdGestureHandler) + .run( attachToDocument ); + + /** + * @ngdoc service + * @name $mdGestureProvider + * @module material.core.gestures + * + * @description + * In some scenarios on Mobile devices (without jQuery), the click events should NOT be hijacked. + * `$mdGestureProvider` is used to configure the Gesture module to ignore or skip click hijacking on mobile + * devices. + * + * <hljs lang="js"> + * app.config(function($mdGestureProvider) { + * + * // For mobile devices without jQuery loaded, do not + * // intercept click events during the capture phase. + * $mdGestureProvider.skipClickHijack(); + * + * }); + * </hljs> + * + */ + function MdGestureProvider() { } + + MdGestureProvider.prototype = { + + // Publish access to setter to configure a variable BEFORE the + // $mdGesture service is instantiated... + skipClickHijack: function() { + return forceSkipClickHijack = true; + }, + + /** + * $get is used to build an instance of $mdGesture + * ngInject + */ + $get : ["$$MdGestureHandler", "$$rAF", "$timeout", function($$MdGestureHandler, $$rAF, $timeout) { + return new MdGesture($$MdGestureHandler, $$rAF, $timeout); + }] + }; + + + + /** + * MdGesture factory construction function + * ngInject + */ + function MdGesture($$MdGestureHandler, $$rAF, $timeout) { + var userAgent = navigator.userAgent || navigator.vendor || window.opera; + var isIos = userAgent.match(/ipad|iphone|ipod/i); + var isAndroid = userAgent.match(/android/i); + var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery); + + var self = { + handler: addHandler, + register: register, + // On mobile w/out jQuery, we normally intercept clicks. Should we skip that? + isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack + }; + + if (self.isHijackingClicks) { + self.handler('click', { + options: { + maxDistance: 6 + }, + onEnd: function (ev, pointer) { + if (pointer.distance < this.state.options.maxDistance) { + this.dispatchEvent(ev, 'click'); + } + } + }); + } + + /* + * Register an element to listen for a handler. + * This allows an element to override the default options for a handler. + * Additionally, some handlers like drag and hold only dispatch events if + * the domEvent happens inside an element that's registered to listen for these events. + * + * @see GestureHandler for how overriding of default options works. + * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horziontal: false }) + */ + function register(element, handlerName, options) { + var handler = HANDLERS[handlerName.replace(/^\$md./, '')]; + if (!handler) { + throw new Error('Failed to register element with handler ' + handlerName + '. ' + + 'Available handlers: ' + Object.keys(HANDLERS).join(', ')); + } + return handler.registerElement(element, options); + } + + /* + * add a handler to $mdGesture. see below. + */ + function addHandler(name, definition) { + var handler = new $$MdGestureHandler(name); + angular.extend(handler, definition); + HANDLERS[name] = handler; + + return self; + } + + /* + * Register handlers. These listen to touch/start/move events, interpret them, + * and dispatch gesture events depending on options & conditions. These are all + * instances of GestureHandler. + * @see GestureHandler + */ + return self + /* + * The press handler dispatches an event on touchdown/touchend. + * It's a simple abstraction of touch/mouse/pointer start and end. + */ + .handler('press', { + onStart: function (ev, pointer) { + this.dispatchEvent(ev, '$md.pressdown'); + }, + onEnd: function (ev, pointer) { + this.dispatchEvent(ev, '$md.pressup'); + } + }) + + /* + * The hold handler dispatches an event if the user keeps their finger within + * the same <maxDistance> area for <delay> ms. + * The hold handler will only run if a parent of the touch target is registered + * to listen for hold events through $mdGesture.register() + */ + .handler('hold', { + options: { + maxDistance: 6, + delay: 500 + }, + onCancel: function () { + $timeout.cancel(this.state.timeout); + }, + onStart: function (ev, pointer) { + // For hold, require a parent to be registered with $mdGesture.register() + // Because we prevent scroll events, this is necessary. + if (!this.state.registeredParent) return this.cancel(); + + this.state.pos = {x: pointer.x, y: pointer.y}; + this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() { + this.dispatchEvent(ev, '$md.hold'); + this.cancel(); //we're done! + }), this.state.options.delay, false); + }, + onMove: function (ev, pointer) { + // Don't scroll while waiting for hold. + // If we don't preventDefault touchmove events here, Android will assume we don't + // want to listen to anymore touch events. It will start scrolling and stop sending + // touchmove events. + ev.preventDefault(); + + // If the user moves greater than <maxDistance> pixels, stop the hold timer + // set in onStart + var dx = this.state.pos.x - pointer.x; + var dy = this.state.pos.y - pointer.y; + if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) { + this.cancel(); + } + }, + onEnd: function () { + this.onCancel(); + } + }) + + /* + * The drag handler dispatches a drag event if the user holds and moves his finger greater than + * <minDistance> px in the x or y direction, depending on options.horizontal. + * The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in + * the perpindicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier> + * pixels vertically, this handler won't consider the move part of a drag. + */ + .handler('drag', { + options: { + minDistance: 6, + horizontal: true, + cancelMultiplier: 1.5 + }, + onStart: function (ev) { + // For drag, require a parent to be registered with $mdGesture.register() + if (!this.state.registeredParent) this.cancel(); + }, + onMove: function (ev, pointer) { + var shouldStartDrag, shouldCancel; + // Don't scroll while deciding if this touchmove qualifies as a drag event. + // If we don't preventDefault touchmove events here, Android will assume we don't + // want to listen to anymore touch events. It will start scrolling and stop sending + // touchmove events. + ev.preventDefault(); + + if (!this.state.dragPointer) { + if (this.state.options.horizontal) { + shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance; + shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier; + } else { + shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance; + shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier; + } + + if (shouldStartDrag) { + // Create a new pointer representing this drag, starting at this point where the drag started. + this.state.dragPointer = makeStartPointer(ev); + updatePointerState(ev, this.state.dragPointer); + this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer); + + } else if (shouldCancel) { + this.cancel(); + } + } else { + this.dispatchDragMove(ev); + } + }, + // Only dispatch dragmove events every frame; any more is unnecessray + dispatchDragMove: $$rAF.throttle(function (ev) { + // Make sure the drag didn't stop while waiting for the next frame + if (this.state.isRunning) { + updatePointerState(ev, this.state.dragPointer); + this.dispatchEvent(ev, '$md.drag', this.state.dragPointer); + } + }), + onEnd: function (ev, pointer) { + if (this.state.dragPointer) { + updatePointerState(ev, this.state.dragPointer); + this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer); + } + } + }) + + /* + * The swipe handler will dispatch a swipe event if, on the end of a touch, + * the velocity and distance were high enough. + * TODO: add vertical swiping with a `horizontal` option similar to the drag handler. + */ + .handler('swipe', { + options: { + minVelocity: 0.65, + minDistance: 10 + }, + onEnd: function (ev, pointer) { + if (Math.abs(pointer.velocityX) > this.state.options.minVelocity && + Math.abs(pointer.distanceX) > this.state.options.minDistance) { + var eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight'; + this.dispatchEvent(ev, eventType); + } + } + }); + + } + MdGesture.$inject = ["$$MdGestureHandler", "$$rAF", "$timeout"]; + + /** + * MdGestureHandler + * A GestureHandler is an object which is able to dispatch custom dom events + * based on native dom {touch,pointer,mouse}{start,move,end} events. + * + * A gesture will manage its lifecycle through the start,move,end, and cancel + * functions, which are called by native dom events. + * + * A gesture has the concept of 'options' (eg a swipe's required velocity), which can be + * overridden by elements registering through $mdGesture.register() + */ + function GestureHandler (name) { + this.name = name; + this.state = {}; + } + + function MdGestureHandler() { + var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery); + + GestureHandler.prototype = { + options: {}, + // jQuery listeners don't work with custom DOMEvents, so we have to dispatch events + // differently when jQuery is loaded + dispatchEvent: hasJQuery ? jQueryDispatchEvent : nativeDispatchEvent, + + // These are overridden by the registered handler + onStart: angular.noop, + onMove: angular.noop, + onEnd: angular.noop, + onCancel: angular.noop, + + // onStart sets up a new state for the handler, which includes options from the + // nearest registered parent element of ev.target. + start: function (ev, pointer) { + if (this.state.isRunning) return; + var parentTarget = this.getNearestParent(ev.target); + // Get the options from the nearest registered parent + var parentTargetOptions = parentTarget && parentTarget.$mdGesture[this.name] || {}; + + this.state = { + isRunning: true, + // Override the default options with the nearest registered parent's options + options: angular.extend({}, this.options, parentTargetOptions), + // Pass in the registered parent node to the state so the onStart listener can use + registeredParent: parentTarget + }; + this.onStart(ev, pointer); + }, + move: function (ev, pointer) { + if (!this.state.isRunning) return; + this.onMove(ev, pointer); + }, + end: function (ev, pointer) { + if (!this.state.isRunning) return; + this.onEnd(ev, pointer); + this.state.isRunning = false; + }, + cancel: function (ev, pointer) { + this.onCancel(ev, pointer); + this.state = {}; + }, + + // Find and return the nearest parent element that has been registered to + // listen for this handler via $mdGesture.register(element, 'handlerName'). + getNearestParent: function (node) { + var current = node; + while (current) { + if ((current.$mdGesture || {})[this.name]) { + return current; + } + current = current.parentNode; + } + return null; + }, + + // Called from $mdGesture.register when an element reigsters itself with a handler. + // Store the options the user gave on the DOMElement itself. These options will + // be retrieved with getNearestParent when the handler starts. + registerElement: function (element, options) { + var self = this; + element[0].$mdGesture = element[0].$mdGesture || {}; + element[0].$mdGesture[this.name] = options || {}; + element.on('$destroy', onDestroy); + + return onDestroy; + + function onDestroy() { + delete element[0].$mdGesture[self.name]; + element.off('$destroy', onDestroy); + } + } + }; + + return GestureHandler; + + /* + * Dispatch an event with jQuery + * TODO: Make sure this sends bubbling events + * + * @param srcEvent the original DOM touch event that started this. + * @param eventType the name of the custom event to send (eg 'click' or '$md.drag') + * @param eventPointer the pointer object that matches this event. + */ + function jQueryDispatchEvent(srcEvent, eventType, eventPointer) { + eventPointer = eventPointer || pointer; + var eventObj = new angular.element.Event(eventType); + + eventObj.$material = true; + eventObj.pointer = eventPointer; + eventObj.srcEvent = srcEvent; + + angular.extend(eventObj, { + clientX: eventPointer.x, + clientY: eventPointer.y, + screenX: eventPointer.x, + screenY: eventPointer.y, + pageX: eventPointer.x, + pageY: eventPointer.y, + ctrlKey: srcEvent.ctrlKey, + altKey: srcEvent.altKey, + shiftKey: srcEvent.shiftKey, + metaKey: srcEvent.metaKey + }); + angular.element(eventPointer.target).trigger(eventObj); + } + + /* + * NOTE: nativeDispatchEvent is very performance sensitive. + * @param srcEvent the original DOM touch event that started this. + * @param eventType the name of the custom event to send (eg 'click' or '$md.drag') + * @param eventPointer the pointer object that matches this event. + */ + function nativeDispatchEvent(srcEvent, eventType, eventPointer) { + eventPointer = eventPointer || pointer; + var eventObj; + + if (eventType === 'click') { + eventObj = document.createEvent('MouseEvents'); + eventObj.initMouseEvent( + 'click', true, true, window, srcEvent.detail, + eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y, + srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey, + srcEvent.button, srcEvent.relatedTarget || null + ); + + } else { + eventObj = document.createEvent('CustomEvent'); + eventObj.initCustomEvent(eventType, true, true, {}); + } + eventObj.$material = true; + eventObj.pointer = eventPointer; + eventObj.srcEvent = srcEvent; + eventPointer.target.dispatchEvent(eventObj); + } + + } + + /** + * Attach Gestures: hook document and check shouldHijack clicks + * ngInject + */ + function attachToDocument( $mdGesture, $$MdGestureHandler ) { + + // Polyfill document.contains for IE11. + // TODO: move to util + document.contains || (document.contains = function (node) { + return document.body.contains(node); + }); + + if (!isInitialized && $mdGesture.isHijackingClicks ) { + /* + * If hijack clicks is true, we preventDefault any click that wasn't + * sent by ngMaterial. This is because on older Android & iOS, a false, or 'ghost', + * click event will be sent ~400ms after a touchend event happens. + * The only way to know if this click is real is to prevent any normal + * click events, and add a flag to events sent by material so we know not to prevent those. + * + * Two exceptions to click events that should be prevented are: + * - click events sent by the keyboard (eg form submit) + * - events that originate from an Ionic app + */ + document.addEventListener('click', function clickHijacker(ev) { + var isKeyClick = ev.clientX === 0 && ev.clientY === 0; + if (!isKeyClick && !ev.$material && !ev.isIonicTap) { + ev.preventDefault(); + ev.stopPropagation(); + } + }, true); + + isInitialized = true; + } + + // Listen to all events to cover all platforms. + var START_EVENTS = 'mousedown touchstart pointerdown'; + var MOVE_EVENTS = 'mousemove touchmove pointermove'; + var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel'; + + angular.element(document) + .on(START_EVENTS, gestureStart) + .on(MOVE_EVENTS, gestureMove) + .on(END_EVENTS, gestureEnd) + // For testing + .on('$$mdGestureReset', function gestureClearCache () { + lastPointer = pointer = null; + }); + + /* + * When a DOM event happens, run all registered gesture handlers' lifecycle + * methods which match the DOM event. + * Eg when a 'touchstart' event happens, runHandlers('start') will call and + * run `handler.cancel()` and `handler.start()` on all registered handlers. + */ + function runHandlers(handlerEvent, event) { + var handler; + for (var name in HANDLERS) { + handler = HANDLERS[name]; + if( handler instanceof $$MdGestureHandler ) { + + if (handlerEvent === 'start') { + // Run cancel to reset any handlers' state + handler.cancel(); + } + handler[handlerEvent](event, pointer); + + } + } + } + + /* + * gestureStart vets if a start event is legitimate (and not part of a 'ghost click' from iOS/Android) + * If it is legitimate, we initiate the pointer state and mark the current pointer's type + * For example, for a touchstart event, mark the current pointer as a 'touch' pointer, so mouse events + * won't effect it. + */ + function gestureStart(ev) { + // If we're already touched down, abort + if (pointer) return; + + var now = +Date.now(); + + // iOS & old android bug: after a touch event, a click event is sent 350 ms later. + // If <400ms have passed, don't allow an event of a different type than the previous event + if (lastPointer && !typesMatch(ev, lastPointer) && (now - lastPointer.endTime < 1500)) { + return; + } + + pointer = makeStartPointer(ev); + + runHandlers('start', ev); + } + /* + * If a move event happens of the right type, update the pointer and run all the move handlers. + * "of the right type": if a mousemove happens but our pointer started with a touch event, do nothing. + */ + function gestureMove(ev) { + if (!pointer || !typesMatch(ev, pointer)) return; + + updatePointerState(ev, pointer); + runHandlers('move', ev); + } + /* + * If an end event happens of the right type, update the pointer, run endHandlers, and save the pointer as 'lastPointer' + */ + function gestureEnd(ev) { + if (!pointer || !typesMatch(ev, pointer)) return; + + updatePointerState(ev, pointer); + pointer.endTime = +Date.now(); + + runHandlers('end', ev); + + lastPointer = pointer; + pointer = null; + } + + } + attachToDocument.$inject = ["$mdGesture", "$$MdGestureHandler"]; + + // ******************** + // Module Functions + // ******************** + + /* + * Initiate the pointer. x, y, and the pointer's type. + */ + function makeStartPointer(ev) { + var point = getEventPoint(ev); + var startPointer = { + startTime: +Date.now(), + target: ev.target, + // 'p' for pointer events, 'm' for mouse, 't' for touch + type: ev.type.charAt(0) + }; + startPointer.startX = startPointer.x = point.pageX; + startPointer.startY = startPointer.y = point.pageY; + return startPointer; + } + + /* + * return whether the pointer's type matches the event's type. + * Eg if a touch event happens but the pointer has a mouse type, return false. + */ + function typesMatch(ev, pointer) { + return ev && pointer && ev.type.charAt(0) === pointer.type; + } + + /* + * Update the given pointer based upon the given DOMEvent. + * Distance, velocity, direction, duration, etc + */ + function updatePointerState(ev, pointer) { + var point = getEventPoint(ev); + var x = pointer.x = point.pageX; + var y = pointer.y = point.pageY; + + pointer.distanceX = x - pointer.startX; + pointer.distanceY = y - pointer.startY; + pointer.distance = Math.sqrt( + pointer.distanceX * pointer.distanceX + pointer.distanceY * pointer.distanceY + ); + + pointer.directionX = pointer.distanceX > 0 ? 'right' : pointer.distanceX < 0 ? 'left' : ''; + pointer.directionY = pointer.distanceY > 0 ? 'up' : pointer.distanceY < 0 ? 'down' : ''; + + pointer.duration = +Date.now() - pointer.startTime; + pointer.velocityX = pointer.distanceX / pointer.duration; + pointer.velocityY = pointer.distanceY / pointer.duration; + } + + /* + * Normalize the point where the DOM event happened whether it's touch or mouse. + * @returns point event obj with pageX and pageY on it. + */ + function getEventPoint(ev) { + ev = ev.originalEvent || ev; // support jQuery events + return (ev.touches && ev.touches[0]) || + (ev.changedTouches && ev.changedTouches[0]) || + ev; + } + + /** + * @ngdoc module + * @name material.core.componentRegistry + * + * @description + * A component instance registration service. + * Note: currently this as a private service in the SideNav component. + */ + angular.module('material.core') + .factory('$mdComponentRegistry', ComponentRegistry); + + /* + * @private + * @ngdoc factory + * @name ComponentRegistry + * @module material.core.componentRegistry + * + */ + function ComponentRegistry($log, $q) { + + var self; + var instances = [ ]; + var pendings = { }; + + return self = { + /** + * Used to print an error when an instance for a handle isn't found. + */ + notFoundError: function(handle) { + $log.error('No instance found for handle', handle); + }, + /** + * Return all registered instances as an array. + */ + getInstances: function() { + return instances; + }, + + /** + * Get a registered instance. + * @param handle the String handle to look up for a registered instance. + */ + get: function(handle) { + if ( !isValidID(handle) ) return null; + + var i, j, instance; + for(i = 0, j = instances.length; i < j; i++) { + instance = instances[i]; + if(instance.$$mdHandle === handle) { + return instance; + } + } + return null; + }, + + /** + * Register an instance. + * @param instance the instance to register + * @param handle the handle to identify the instance under. + */ + register: function(instance, handle) { + if ( !handle ) return angular.noop; + + instance.$$mdHandle = handle; + instances.push(instance); + resolveWhen(); + + return deregister; + + /** + * Remove registration for an instance + */ + function deregister() { + var index = instances.indexOf(instance); + if (index !== -1) { + instances.splice(index, 1); + } + } + + /** + * Resolve any pending promises for this instance + */ + function resolveWhen() { + var dfd = pendings[handle]; + if ( dfd ) { + dfd.resolve( instance ); + delete pendings[handle]; + } + } + }, + + /** + * Async accessor to registered component instance + * If not available then a promise is created to notify + * all listeners when the instance is registered. + */ + when : function(handle) { + if ( isValidID(handle) ) { + var deferred = $q.defer(); + var instance = self.get(handle); + + if ( instance ) { + deferred.resolve( instance ); + } else { + pendings[handle] = deferred; + } + + return deferred.promise; + } + return $q.reject("Invalid `md-component-id` value."); + } + + }; + + function isValidID(handle){ + return handle && (handle !== ""); + } + + } + ComponentRegistry.$inject = ["$log", "$q"]; + +(function() { + 'use strict'; + + /** + * @ngdoc service + * @name $mdButtonInkRipple + * @module material.core + * + * @description + * Provides ripple effects for md-button. See $mdInkRipple service for all possible configuration options. + * + * @param {object=} scope Scope within the current context + * @param {object=} element The element the ripple effect should be applied to + * @param {object=} options (Optional) Configuration options to override the defaultripple configuration + */ + + angular.module('material.core') + .factory('$mdButtonInkRipple', MdButtonInkRipple); + + function MdButtonInkRipple($mdInkRipple) { + return { + attach: attach + }; + + function attach(scope, element, options) { + var elementOptions = optionsForElement(element); + return $mdInkRipple.attach(scope, element, angular.extend(elementOptions, options)); + }; + + function optionsForElement(element) { + if (element.hasClass('md-icon-button')) { + return { + isMenuItem: element.hasClass('md-menu-item'), + fitRipple: true, + center: true + }; + } else { + return { + isMenuItem: element.hasClass('md-menu-item'), + dimBackground: true + } + } + }; + } + MdButtonInkRipple.$inject = ["$mdInkRipple"];; +})(); + +(function() { + 'use strict'; + + /** + * @ngdoc service + * @name $mdCheckboxInkRipple + * @module material.core + * + * @description + * Provides ripple effects for md-checkbox. See $mdInkRipple service for all possible configuration options. + * + * @param {object=} scope Scope within the current context + * @param {object=} element The element the ripple effect should be applied to + * @param {object=} options (Optional) Configuration options to override the defaultripple configuration + */ + + angular.module('material.core') + .factory('$mdCheckboxInkRipple', MdCheckboxInkRipple); + + function MdCheckboxInkRipple($mdInkRipple) { + return { + attach: attach + }; + + function attach(scope, element, options) { + return $mdInkRipple.attach(scope, element, angular.extend({ + center: true, + dimBackground: false, + fitRipple: true + }, options)); + }; + } + MdCheckboxInkRipple.$inject = ["$mdInkRipple"];; +})(); + +(function() { + 'use strict'; + + /** + * @ngdoc service + * @name $mdListInkRipple + * @module material.core + * + * @description + * Provides ripple effects for md-list. See $mdInkRipple service for all possible configuration options. + * + * @param {object=} scope Scope within the current context + * @param {object=} element The element the ripple effect should be applied to + * @param {object=} options (Optional) Configuration options to override the defaultripple configuration + */ + + angular.module('material.core') + .factory('$mdListInkRipple', MdListInkRipple); + + function MdListInkRipple($mdInkRipple) { + return { + attach: attach + }; + + function attach(scope, element, options) { + return $mdInkRipple.attach(scope, element, angular.extend({ + center: false, + dimBackground: true, + outline: false, + rippleSize: 'full' + }, options)); + }; + } + MdListInkRipple.$inject = ["$mdInkRipple"];; +})(); + +angular.module('material.core') + .factory('$mdInkRipple', InkRippleService) + .directive('mdInkRipple', InkRippleDirective) + .directive('mdNoInk', attrNoDirective()) + .directive('mdNoBar', attrNoDirective()) + .directive('mdNoStretch', attrNoDirective()); + +function InkRippleDirective($mdButtonInkRipple, $mdCheckboxInkRipple) { + return { + controller: angular.noop, + link: function (scope, element, attr) { + if (attr.hasOwnProperty('mdInkRippleCheckbox')) { + $mdCheckboxInkRipple.attach(scope, element); + } else { + $mdButtonInkRipple.attach(scope, element); + } + } + }; +} +InkRippleDirective.$inject = ["$mdButtonInkRipple", "$mdCheckboxInkRipple"]; + +function InkRippleService($window, $timeout) { + + return { + attach: attach + }; + + function attach(scope, element, options) { + if (element.controller('mdNoInk')) return angular.noop; + + options = angular.extend({ + colorElement: element, + mousedown: true, + hover: true, + focus: true, + center: false, + mousedownPauseTime: 150, + dimBackground: false, + outline: false, + fullRipple: true, + isMenuItem: false, + fitRipple: false + }, options); + + var rippleSize, + controller = element.controller('mdInkRipple') || {}, + counter = 0, + ripples = [], + states = [], + isActiveExpr = element.attr('md-highlight'), + isActive = false, + isHeld = false, + node = element[0], + rippleSizeSetting = element.attr('md-ripple-size'), + color = parseColor(element.attr('md-ink-ripple')) || parseColor(options.colorElement.length && $window.getComputedStyle(options.colorElement[0]).color || 'rgb(0, 0, 0)'); + + switch (rippleSizeSetting) { + case 'full': + options.fullRipple = true; + break; + case 'partial': + options.fullRipple = false; + break; + } + + // expose onInput for ripple testing + if (options.mousedown) { + element.on('$md.pressdown', onPressDown) + .on('$md.pressup', onPressUp); + } + + controller.createRipple = createRipple; + + if (isActiveExpr) { + scope.$watch(isActiveExpr, function watchActive(newValue) { + isActive = newValue; + if (isActive && !ripples.length) { + $timeout(function () { createRipple(0, 0); }, 0, false); + } + angular.forEach(ripples, updateElement); + }); + } + + // Publish self-detach method if desired... + return function detach() { + element.off('$md.pressdown', onPressDown) + .off('$md.pressup', onPressUp); + getRippleContainer().remove(); + }; + + /** + * Gets the current ripple container + * If there is no ripple container, it creates one and returns it + * + * @returns {angular.element} ripple container element + */ + function getRippleContainer() { + var container = element.data('$mdRippleContainer'); + if (container) return container; + container = angular.element('<div class="md-ripple-container">'); + element.append(container); + element.data('$mdRippleContainer', container); + return container; + } + + function parseColor(color) { + if (!color) return; + if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, '0.1)'); + if (color.indexOf('rgb') === 0) return rgbToRGBA(color); + if (color.indexOf('#') === 0) return hexToRGBA(color); + + /** + * Converts a hex value to an rgba string + * + * @param {string} hex value (3 or 6 digits) to be converted + * + * @returns {string} rgba color with 0.1 alpha + */ + function hexToRGBA(color) { + var hex = color.charAt(0) === '#' ? color.substr(1) : color, + dig = hex.length / 3, + red = hex.substr(0, dig), + grn = hex.substr(dig, dig), + blu = hex.substr(dig * 2); + if (dig === 1) { + red += red; + grn += grn; + blu += blu; + } + return 'rgba(' + parseInt(red, 16) + ',' + parseInt(grn, 16) + ',' + parseInt(blu, 16) + ',0.1)'; + } + + /** + * Converts rgb value to rgba string + * + * @param {string} rgb color string + * + * @returns {string} rgba color with 0.1 alpha + */ + function rgbToRGBA(color) { + return color.replace(')', ', 0.1)').replace('(', 'a('); + } + + } + + function removeElement(elem, wait) { + ripples.splice(ripples.indexOf(elem), 1); + if (ripples.length === 0) { + getRippleContainer().css({ backgroundColor: '' }); + } + $timeout(function () { elem.remove(); }, wait, false); + } + + function updateElement(elem) { + var index = ripples.indexOf(elem), + state = states[index] || {}, + elemIsActive = ripples.length > 1 ? false : isActive, + elemIsHeld = ripples.length > 1 ? false : isHeld; + if (elemIsActive || state.animating || elemIsHeld) { + elem.addClass('md-ripple-visible'); + } else if (elem) { + elem.removeClass('md-ripple-visible'); + if (options.outline) { + elem.css({ + width: rippleSize + 'px', + height: rippleSize + 'px', + marginLeft: (rippleSize * -1) + 'px', + marginTop: (rippleSize * -1) + 'px' + }); + } + removeElement(elem, options.outline ? 450 : 650); + } + } + + /** + * Creates a ripple at the provided coordinates + * + * @param {number} left cursor position + * @param {number} top cursor position + * + * @returns {angular.element} the generated ripple element + */ + function createRipple(left, top) { + + color = parseColor(element.attr('md-ink-ripple')) || parseColor($window.getComputedStyle(options.colorElement[0]).color || 'rgb(0, 0, 0)'); + + var container = getRippleContainer(), + size = getRippleSize(left, top), + css = getRippleCss(size, left, top), + elem = getRippleElement(css), + index = ripples.indexOf(elem), + state = states[index] || {}; + + rippleSize = size; + + state.animating = true; + + $timeout(function () { + if (options.dimBackground) { + container.css({ backgroundColor: color }); + } + elem.addClass('md-ripple-placed md-ripple-scaled'); + if (options.outline) { + elem.css({ + borderWidth: (size * 0.5) + 'px', + marginLeft: (size * -0.5) + 'px', + marginTop: (size * -0.5) + 'px' + }); + } else { + elem.css({ left: '50%', top: '50%' }); + } + updateElement(elem); + $timeout(function () { + state.animating = false; + updateElement(elem); + }, (options.outline ? 450 : 225), false); + }, 0, false); + + return elem; + + /** + * Creates the ripple element with the provided css + * + * @param {object} css properties to be applied + * + * @returns {angular.element} the generated ripple element + */ + function getRippleElement(css) { + var elem = angular.element('<div class="md-ripple" data-counter="' + counter++ + '">'); + ripples.unshift(elem); + states.unshift({ animating: true }); + container.append(elem); + css && elem.css(css); + return elem; + } + + /** + * Calculate the ripple size + * + * @returns {number} calculated ripple diameter + */ + function getRippleSize(left, top) { + var width = container.prop('offsetWidth'), + height = container.prop('offsetHeight'), + multiplier, size, rect; + if (options.isMenuItem) { + size = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + } else if (options.outline) { + rect = node.getBoundingClientRect(); + left -= rect.left; + top -= rect.top; + width = Math.max(left, width - left); + height = Math.max(top, height - top); + size = 2 * Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + } else { + multiplier = options.fullRipple ? 1.1 : 0.8; + size = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) * multiplier; + if (options.fitRipple) { + size = Math.min(height, width, size); + } + } + return size; + } + + /** + * Generates the ripple css + * + * @param {number} the diameter of the ripple + * @param {number} the left cursor offset + * @param {number} the top cursor offset + * + * @returns {{backgroundColor: string, borderColor: string, width: string, height: string}} + */ + function getRippleCss(size, left, top) { + var rect = node.getBoundingClientRect(), + css = { + backgroundColor: rgbaToRGB(color), + borderColor: rgbaToRGB(color), + width: size + 'px', + height: size + 'px' + }; + + if (options.outline) { + css.width = 0; + css.height = 0; + } else { + css.marginLeft = css.marginTop = (size * -0.5) + 'px'; + } + + if (options.center) { + css.left = css.top = '50%'; + } else { + css.left = Math.round((left - rect.left) / container.prop('offsetWidth') * 100) + '%'; + css.top = Math.round((top - rect.top) / container.prop('offsetHeight') * 100) + '%'; + } + + return css; + + /** + * Converts rgba string to rgb, removing the alpha value + * + * @param {string} rgba color + * + * @returns {string} rgb color + */ + function rgbaToRGB(color) { + return color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')'); + } + } + } + + /** + * Handles user input start and stop events + * + */ + function onPressDown(ev) { + if (!isRippleAllowed()) return; + + createRipple(ev.pointer.x, ev.pointer.y); + isHeld = true; + } + function onPressUp() { + isHeld = false; + var ripple = ripples[ ripples.length - 1 ]; + $timeout(function () { updateElement(ripple); }, 0, false); + } + + /** + * Determines if the ripple is allowed + * + * @returns {boolean} true if the ripple is allowed, false if not + */ + function isRippleAllowed() { + var parent = node.parentNode; + var grandparent = parent && parent.parentNode; + var ancestor = grandparent && grandparent.parentNode; + return !isDisabled(node) && !isDisabled(parent) && !isDisabled(grandparent) && !isDisabled(ancestor); + function isDisabled (elem) { + return elem && elem.hasAttribute && elem.hasAttribute('disabled'); + } + } + + } +} +InkRippleService.$inject = ["$window", "$timeout"]; + +/** + * noink/nobar/nostretch directive: make any element that has one of + * these attributes be given a controller, so that other directives can + * `require:` these and see if there is a `no<xxx>` parent attribute. + * + * @usage + * <hljs lang="html"> + * <parent md-no-ink> + * <child detect-no> + * </child> + * </parent> + * </hljs> + * + * <hljs lang="js"> + * myApp.directive('detectNo', function() { + * return { + * require: ['^?mdNoInk', ^?mdNoBar'], + * link: function(scope, element, attr, ctrls) { + * var noinkCtrl = ctrls[0]; + * var nobarCtrl = ctrls[1]; + * if (noInkCtrl) { + * alert("the md-no-ink flag has been specified on an ancestor!"); + * } + * if (nobarCtrl) { + * alert("the md-no-bar flag has been specified on an ancestor!"); + * } + * } + * }; + * }); + * </hljs> + */ +function attrNoDirective() { + return function() { + return { + controller: angular.noop + }; + }; +} + +(function() { + 'use strict'; + + /** + * @ngdoc service + * @name $mdTabInkRipple + * @module material.core + * + * @description + * Provides ripple effects for md-tabs. See $mdInkRipple service for all possible configuration options. + * + * @param {object=} scope Scope within the current context + * @param {object=} element The element the ripple effect should be applied to + * @param {object=} options (Optional) Configuration options to override the defaultripple configuration + */ + + angular.module('material.core') + .factory('$mdTabInkRipple', MdTabInkRipple); + + function MdTabInkRipple($mdInkRipple) { + return { + attach: attach + }; + + function attach(scope, element, options) { + return $mdInkRipple.attach(scope, element, angular.extend({ + center: false, + dimBackground: true, + outline: false, + rippleSize: 'full' + }, options)); + }; + } + MdTabInkRipple.$inject = ["$mdInkRipple"];; +})(); + +angular.module('material.core.theming.palette', []) +.constant('$mdColorPalette', { + 'red': { + '50': '#ffebee', + '100': '#ffcdd2', + '200': '#ef9a9a', + '300': '#e57373', + '400': '#ef5350', + '500': '#f44336', + '600': '#e53935', + '700': '#d32f2f', + '800': '#c62828', + '900': '#b71c1c', + 'A100': '#ff8a80', + 'A200': '#ff5252', + 'A400': '#ff1744', + 'A700': '#d50000', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 300 400 A100', + 'contrastStrongLightColors': '500 600 700 A200 A400 A700' + }, + 'pink': { + '50': '#fce4ec', + '100': '#f8bbd0', + '200': '#f48fb1', + '300': '#f06292', + '400': '#ec407a', + '500': '#e91e63', + '600': '#d81b60', + '700': '#c2185b', + '800': '#ad1457', + '900': '#880e4f', + 'A100': '#ff80ab', + 'A200': '#ff4081', + 'A400': '#f50057', + 'A700': '#c51162', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 300 400 A100', + 'contrastStrongLightColors': '500 600 A200 A400 A700' + }, + 'purple': { + '50': '#f3e5f5', + '100': '#e1bee7', + '200': '#ce93d8', + '300': '#ba68c8', + '400': '#ab47bc', + '500': '#9c27b0', + '600': '#8e24aa', + '700': '#7b1fa2', + '800': '#6a1b9a', + '900': '#4a148c', + 'A100': '#ea80fc', + 'A200': '#e040fb', + 'A400': '#d500f9', + 'A700': '#aa00ff', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 A100', + 'contrastStrongLightColors': '300 400 A200 A400 A700' + }, + 'deep-purple': { + '50': '#ede7f6', + '100': '#d1c4e9', + '200': '#b39ddb', + '300': '#9575cd', + '400': '#7e57c2', + '500': '#673ab7', + '600': '#5e35b1', + '700': '#512da8', + '800': '#4527a0', + '900': '#311b92', + 'A100': '#b388ff', + 'A200': '#7c4dff', + 'A400': '#651fff', + 'A700': '#6200ea', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 A100', + 'contrastStrongLightColors': '300 400 A200' + }, + 'indigo': { + '50': '#e8eaf6', + '100': '#c5cae9', + '200': '#9fa8da', + '300': '#7986cb', + '400': '#5c6bc0', + '500': '#3f51b5', + '600': '#3949ab', + '700': '#303f9f', + '800': '#283593', + '900': '#1a237e', + 'A100': '#8c9eff', + 'A200': '#536dfe', + 'A400': '#3d5afe', + 'A700': '#304ffe', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 A100', + 'contrastStrongLightColors': '300 400 A200 A400' + }, + 'blue': { + '50': '#e3f2fd', + '100': '#bbdefb', + '200': '#90caf9', + '300': '#64b5f6', + '400': '#42a5f5', + '500': '#2196f3', + '600': '#1e88e5', + '700': '#1976d2', + '800': '#1565c0', + '900': '#0d47a1', + 'A100': '#82b1ff', + 'A200': '#448aff', + 'A400': '#2979ff', + 'A700': '#2962ff', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '100 200 300 400 A100', + 'contrastStrongLightColors': '500 600 700 A200 A400 A700' + }, + 'light-blue': { + '50': '#e1f5fe', + '100': '#b3e5fc', + '200': '#81d4fa', + '300': '#4fc3f7', + '400': '#29b6f6', + '500': '#03a9f4', + '600': '#039be5', + '700': '#0288d1', + '800': '#0277bd', + '900': '#01579b', + 'A100': '#80d8ff', + 'A200': '#40c4ff', + 'A400': '#00b0ff', + 'A700': '#0091ea', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '500 600 700 800 900 A700', + 'contrastStrongLightColors': '500 600 700 800 A700' + }, + 'cyan': { + '50': '#e0f7fa', + '100': '#b2ebf2', + '200': '#80deea', + '300': '#4dd0e1', + '400': '#26c6da', + '500': '#00bcd4', + '600': '#00acc1', + '700': '#0097a7', + '800': '#00838f', + '900': '#006064', + 'A100': '#84ffff', + 'A200': '#18ffff', + 'A400': '#00e5ff', + 'A700': '#00b8d4', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '500 600 700 800 900', + 'contrastStrongLightColors': '500 600 700 800' + }, + 'teal': { + '50': '#e0f2f1', + '100': '#b2dfdb', + '200': '#80cbc4', + '300': '#4db6ac', + '400': '#26a69a', + '500': '#009688', + '600': '#00897b', + '700': '#00796b', + '800': '#00695c', + '900': '#004d40', + 'A100': '#a7ffeb', + 'A200': '#64ffda', + 'A400': '#1de9b6', + 'A700': '#00bfa5', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '500 600 700 800 900', + 'contrastStrongLightColors': '500 600 700' + }, + 'green': { + '50': '#e8f5e9', + '100': '#c8e6c9', + '200': '#a5d6a7', + '300': '#81c784', + '400': '#66bb6a', + '500': '#4caf50', + '600': '#43a047', + '700': '#388e3c', + '800': '#2e7d32', + '900': '#1b5e20', + 'A100': '#b9f6ca', + 'A200': '#69f0ae', + 'A400': '#00e676', + 'A700': '#00c853', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '500 600 700 800 900', + 'contrastStrongLightColors': '500 600 700' + }, + 'light-green': { + '50': '#f1f8e9', + '100': '#dcedc8', + '200': '#c5e1a5', + '300': '#aed581', + '400': '#9ccc65', + '500': '#8bc34a', + '600': '#7cb342', + '700': '#689f38', + '800': '#558b2f', + '900': '#33691e', + 'A100': '#ccff90', + 'A200': '#b2ff59', + 'A400': '#76ff03', + 'A700': '#64dd17', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '800 900', + 'contrastStrongLightColors': '800 900' + }, + 'lime': { + '50': '#f9fbe7', + '100': '#f0f4c3', + '200': '#e6ee9c', + '300': '#dce775', + '400': '#d4e157', + '500': '#cddc39', + '600': '#c0ca33', + '700': '#afb42b', + '800': '#9e9d24', + '900': '#827717', + 'A100': '#f4ff81', + 'A200': '#eeff41', + 'A400': '#c6ff00', + 'A700': '#aeea00', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '900', + 'contrastStrongLightColors': '900' + }, + 'yellow': { + '50': '#fffde7', + '100': '#fff9c4', + '200': '#fff59d', + '300': '#fff176', + '400': '#ffee58', + '500': '#ffeb3b', + '600': '#fdd835', + '700': '#fbc02d', + '800': '#f9a825', + '900': '#f57f17', + 'A100': '#ffff8d', + 'A200': '#ffff00', + 'A400': '#ffea00', + 'A700': '#ffd600', + 'contrastDefaultColor': 'dark' + }, + 'amber': { + '50': '#fff8e1', + '100': '#ffecb3', + '200': '#ffe082', + '300': '#ffd54f', + '400': '#ffca28', + '500': '#ffc107', + '600': '#ffb300', + '700': '#ffa000', + '800': '#ff8f00', + '900': '#ff6f00', + 'A100': '#ffe57f', + 'A200': '#ffd740', + 'A400': '#ffc400', + 'A700': '#ffab00', + 'contrastDefaultColor': 'dark' + }, + 'orange': { + '50': '#fff3e0', + '100': '#ffe0b2', + '200': '#ffcc80', + '300': '#ffb74d', + '400': '#ffa726', + '500': '#ff9800', + '600': '#fb8c00', + '700': '#f57c00', + '800': '#ef6c00', + '900': '#e65100', + 'A100': '#ffd180', + 'A200': '#ffab40', + 'A400': '#ff9100', + 'A700': '#ff6d00', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '800 900', + 'contrastStrongLightColors': '800 900' + }, + 'deep-orange': { + '50': '#fbe9e7', + '100': '#ffccbc', + '200': '#ffab91', + '300': '#ff8a65', + '400': '#ff7043', + '500': '#ff5722', + '600': '#f4511e', + '700': '#e64a19', + '800': '#d84315', + '900': '#bf360c', + 'A100': '#ff9e80', + 'A200': '#ff6e40', + 'A400': '#ff3d00', + 'A700': '#dd2c00', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 300 400 A100 A200', + 'contrastStrongLightColors': '500 600 700 800 900 A400 A700' + }, + 'brown': { + '50': '#efebe9', + '100': '#d7ccc8', + '200': '#bcaaa4', + '300': '#a1887f', + '400': '#8d6e63', + '500': '#795548', + '600': '#6d4c41', + '700': '#5d4037', + '800': '#4e342e', + '900': '#3e2723', + 'A100': '#d7ccc8', + 'A200': '#bcaaa4', + 'A400': '#8d6e63', + 'A700': '#5d4037', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200', + 'contrastStrongLightColors': '300 400' + }, + 'grey': { + '50': '#fafafa', + '100': '#f5f5f5', + '200': '#eeeeee', + '300': '#e0e0e0', + '400': '#bdbdbd', + '500': '#9e9e9e', + '600': '#757575', + '700': '#616161', + '800': '#424242', + '900': '#212121', + '1000': '#000000', + 'A100': '#ffffff', + 'A200': '#eeeeee', + 'A400': '#bdbdbd', + 'A700': '#616161', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '600 700 800 900' + }, + 'blue-grey': { + '50': '#eceff1', + '100': '#cfd8dc', + '200': '#b0bec5', + '300': '#90a4ae', + '400': '#78909c', + '500': '#607d8b', + '600': '#546e7a', + '700': '#455a64', + '800': '#37474f', + '900': '#263238', + 'A100': '#cfd8dc', + 'A200': '#b0bec5', + 'A400': '#78909c', + 'A700': '#455a64', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 300', + 'contrastStrongLightColors': '400 500' + } +}); + +angular.module('material.core.theming', ['material.core.theming.palette']) + .directive('mdTheme', ThemingDirective) + .directive('mdThemable', ThemableDirective) + .provider('$mdTheming', ThemingProvider) + .run(generateThemes); + +/** + * @ngdoc provider + * @name $mdThemingProvider + * @module material.core + * + * @description Provider to configure the `$mdTheming` service. + */ + +/** + * @ngdoc method + * @name $mdThemingProvider#setDefaultTheme + * @param {string} themeName Default theme name to be applied to elements. Default value is `default`. + */ + +/** + * @ngdoc method + * @name $mdThemingProvider#alwaysWatchTheme + * @param {boolean} watch Whether or not to always watch themes for changes and re-apply + * classes when they change. Default is `false`. Enabling can reduce performance. + */ + +/* Some Example Valid Theming Expressions + * ======================================= + * + * Intention group expansion: (valid for primary, accent, warn, background) + * + * {{primary-100}} - grab shade 100 from the primary palette + * {{primary-100-0.7}} - grab shade 100, apply opacity of 0.7 + * {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette + * {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-1 + * {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue + * {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules + * {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue + * {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules + * + * Foreground expansion: Applies rgba to black/white foreground text + * + * {{foreground-1}} - used for primary text + * {{foreground-2}} - used for secondary text/divider + * {{foreground-3}} - used for disabled text + * {{foreground-4}} - used for dividers + * + */ + +// In memory generated CSS rules; registered by theme.name +var GENERATED = { }; + +// In memory storage of defined themes and color palettes (both loaded by CSS, and user specified) +var PALETTES; +var THEMES; + +var DARK_FOREGROUND = { + name: 'dark', + '1': 'rgba(0,0,0,0.87)', + '2': 'rgba(0,0,0,0.54)', + '3': 'rgba(0,0,0,0.26)', + '4': 'rgba(0,0,0,0.12)' +}; +var LIGHT_FOREGROUND = { + name: 'light', + '1': 'rgba(255,255,255,1.0)', + '2': 'rgba(255,255,255,0.7)', + '3': 'rgba(255,255,255,0.3)', + '4': 'rgba(255,255,255,0.12)' +}; + +var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)'; +var LIGHT_SHADOW = ''; + +var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)'); +var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87'); +var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)'); + +var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background']; +var DEFAULT_COLOR_TYPE = 'primary'; + +// A color in a theme will use these hues by default, if not specified by user. +var LIGHT_DEFAULT_HUES = { + 'accent': { + 'default': 'A200', + 'hue-1': 'A100', + 'hue-2': 'A400', + 'hue-3': 'A700' + }, + 'background': { + 'default': 'A100', + 'hue-1': '300', + 'hue-2': '800', + 'hue-3': '900' + } +}; + +var DARK_DEFAULT_HUES = { + 'background': { + 'default': '800', + 'hue-1': '300', + 'hue-2': '600', + 'hue-3': '900' + } +}; +THEME_COLOR_TYPES.forEach(function(colorType) { + // Color types with unspecified default hues will use these default hue values + var defaultDefaultHues = { + 'default': '500', + 'hue-1': '300', + 'hue-2': '800', + 'hue-3': 'A100' + }; + if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues; + if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues; +}); + +var VALID_HUE_VALUES = [ + '50', '100', '200', '300', '400', '500', '600', + '700', '800', '900', 'A100', 'A200', 'A400', 'A700' +]; + +function ThemingProvider($mdColorPalette) { + PALETTES = { }; + THEMES = { }; + + var themingProvider; + var defaultTheme = 'default'; + var alwaysWatchTheme = false; + + // Load JS Defined Palettes + angular.extend(PALETTES, $mdColorPalette); + + // Default theme defined in core.js + + ThemingService.$inject = ["$rootScope", "$log"]; + return themingProvider = { + definePalette: definePalette, + extendPalette: extendPalette, + theme: registerTheme, + + setDefaultTheme: function(theme) { + defaultTheme = theme; + }, + alwaysWatchTheme: function(alwaysWatch) { + alwaysWatchTheme = alwaysWatch; + }, + $get: ThemingService, + _LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES, + _DARK_DEFAULT_HUES: DARK_DEFAULT_HUES, + _PALETTES: PALETTES, + _THEMES: THEMES, + _parseRules: parseRules, + _rgba: rgba + }; + + // Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... }); + function definePalette(name, map) { + map = map || {}; + PALETTES[name] = checkPaletteValid(name, map); + return themingProvider; + } + + // Returns an new object which is a copy of a given palette `name` with variables from + // `map` overwritten + // Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' }); + function extendPalette(name, map) { + return checkPaletteValid(name, angular.extend({}, PALETTES[name] || {}, map) ); + } + + // Make sure that palette has all required hues + function checkPaletteValid(name, map) { + var missingColors = VALID_HUE_VALUES.filter(function(field) { + return !map[field]; + }); + if (missingColors.length) { + throw new Error("Missing colors %1 in palette %2!" + .replace('%1', missingColors.join(', ')) + .replace('%2', name)); + } + + return map; + } + + // Register a theme (which is a collection of color palettes to use with various states + // ie. warn, accent, primary ) + // Optionally inherit from an existing theme + // $mdThemingProvider.theme('custom-theme').primaryPalette('red'); + function registerTheme(name, inheritFrom) { + if (THEMES[name]) return THEMES[name]; + + inheritFrom = inheritFrom || 'default'; + + var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom; + var theme = new Theme(name); + + if (parentTheme) { + angular.forEach(parentTheme.colors, function(color, colorType) { + theme.colors[colorType] = { + name: color.name, + // Make sure a COPY of the hues is given to the child color, + // not the same reference. + hues: angular.extend({}, color.hues) + }; + }); + } + THEMES[name] = theme; + + return theme; + } + + function Theme(name) { + var self = this; + self.name = name; + self.colors = {}; + + self.dark = setDark; + setDark(false); + + function setDark(isDark) { + isDark = arguments.length === 0 ? true : !!isDark; + + // If no change, abort + if (isDark === self.isDark) return; + + self.isDark = isDark; + + self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND; + self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW; + + // Light and dark themes have different default hues. + // Go through each existing color type for this theme, and for every + // hue value that is still the default hue value from the previous light/dark setting, + // set it to the default hue value from the new light/dark setting. + var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES; + var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES; + angular.forEach(newDefaultHues, function(newDefaults, colorType) { + var color = self.colors[colorType]; + var oldDefaults = oldDefaultHues[colorType]; + if (color) { + for (var hueName in color.hues) { + if (color.hues[hueName] === oldDefaults[hueName]) { + color.hues[hueName] = newDefaults[hueName]; + } + } + } + }); + + return self; + } + + THEME_COLOR_TYPES.forEach(function(colorType) { + var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType]; + self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) { + var color = self.colors[colorType] = { + name: paletteName, + hues: angular.extend({}, defaultHues, hues) + }; + + Object.keys(color.hues).forEach(function(name) { + if (!defaultHues[name]) { + throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4" + .replace('%1', name) + .replace('%2', self.name) + .replace('%3', paletteName) + .replace('%4', Object.keys(defaultHues).join(', ')) + ); + } + }); + Object.keys(color.hues).map(function(key) { + return color.hues[key]; + }).forEach(function(hueValue) { + if (VALID_HUE_VALUES.indexOf(hueValue) == -1) { + throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5" + .replace('%1', hueValue) + .replace('%2', self.name) + .replace('%3', colorType) + .replace('%4', paletteName) + .replace('%5', VALID_HUE_VALUES.join(', ')) + ); + } + }); + return self; + }; + + self[colorType + 'Color'] = function() { + var args = Array.prototype.slice.call(arguments); + console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' + + 'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.'); + return self[colorType + 'Palette'].apply(self, args); + }; + }); + } + + /** + * @ngdoc service + * @name $mdTheming + * + * @description + * + * Service that makes an element apply theming related classes to itself. + * + * ```js + * app.directive('myFancyDirective', function($mdTheming) { + * return { + * restrict: 'e', + * link: function(scope, el, attrs) { + * $mdTheming(el); + * } + * }; + * }); + * ``` + * @param {el=} element to apply theming to + */ + /* ngInject */ + function ThemingService($rootScope, $log) { + + applyTheme.inherit = function(el, parent) { + var ctrl = parent.controller('mdTheme'); + + var attrThemeValue = el.attr('md-theme-watch'); + if ( (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false') { + var deregisterWatch = $rootScope.$watch(function() { + return ctrl && ctrl.$mdTheme || defaultTheme; + }, changeTheme); + el.on('$destroy', deregisterWatch); + } else { + var theme = ctrl && ctrl.$mdTheme || defaultTheme; + changeTheme(theme); + } + + function changeTheme(theme) { + if (!registered(theme)) { + $log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' + + 'Register it with $mdThemingProvider.theme().'); + } + var oldTheme = el.data('$mdThemeName'); + if (oldTheme) el.removeClass('md-' + oldTheme +'-theme'); + el.addClass('md-' + theme + '-theme'); + el.data('$mdThemeName', theme); + } + }; + + applyTheme.THEMES = angular.extend({}, THEMES); + applyTheme.defaultTheme = function() { return defaultTheme; }; + applyTheme.registered = registered; + + return applyTheme; + + function registered(themeName) { + if (themeName === undefined || themeName === '') return true; + return applyTheme.THEMES[themeName] !== undefined; + } + + function applyTheme(scope, el) { + // Allow us to be invoked via a linking function signature. + if (el === undefined) { + el = scope; + scope = undefined; + } + if (scope === undefined) { + scope = $rootScope; + } + applyTheme.inherit(el, el); + } + } +} +ThemingProvider.$inject = ["$mdColorPalette"]; + +function ThemingDirective($mdTheming, $interpolate, $log) { + return { + priority: 100, + link: { + pre: function(scope, el, attrs) { + var ctrl = { + $setTheme: function(theme) { + if (!$mdTheming.registered(theme)) { + $log.warn('attempted to use unregistered theme \'' + theme + '\''); + } + ctrl.$mdTheme = theme; + } + }; + el.data('$mdThemeController', ctrl); + ctrl.$setTheme($interpolate(attrs.mdTheme)(scope)); + attrs.$observe('mdTheme', ctrl.$setTheme); + } + } + }; +} +ThemingDirective.$inject = ["$mdTheming", "$interpolate", "$log"]; + +function ThemableDirective($mdTheming) { + return $mdTheming; +} +ThemableDirective.$inject = ["$mdTheming"]; + +function parseRules(theme, colorType, rules) { + checkValidPalette(theme, colorType); + + rules = rules.replace(/THEME_NAME/g, theme.name); + var generatedRules = []; + var color = theme.colors[colorType]; + + var themeNameRegex = new RegExp('.md-' + theme.name + '-theme', 'g'); + // Matches '{{ primary-color }}', etc + var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g'); + var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow)-?(\d\.?\d*)?\s*\}\}'?"?/g; + var palette = PALETTES[color.name]; + + // find and replace simple variables where we use a specific hue, not an entire palette + // eg. "{{primary-100}}" + //\(' + THEME_COLOR_TYPES.join('\|') + '\)' + rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity) { + if (colorType === 'foreground') { + if (hue == 'shadow') { + return theme.foregroundShadow; + } else { + return theme.foregroundPalette[hue] || theme.foregroundPalette['1']; + } + } + if (hue.indexOf('hue') === 0) { + hue = theme.colors[colorType].hues[hue]; + } + return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '').value, opacity ); + }); + + // For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3) + angular.forEach(color.hues, function(hueValue, hueName) { + var newRule = rules + .replace(hueRegex, function(match, _, colorType, hueType, opacity) { + return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity); + }); + if (hueName !== 'default') { + newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName); + } + + // Don't apply a selector rule to the default theme, making it easier to override + // styles of the base-component + if (theme.name == 'default') { + newRule = newRule.replace(/\.md-default-theme/g, ''); + } + generatedRules.push(newRule); + }); + + return generatedRules; +} + +// Generate our themes at run time given the state of THEMES and PALETTES +function generateThemes($injector) { + + var head = document.getElementsByTagName('head')[0]; + var firstChild = head ? head.firstElementChild : null; + var themeCss = $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : ''; + + if ( !firstChild ) return; + if (themeCss.length === 0) return; // no rules, so no point in running this expensive task + + // Expose contrast colors for palettes to ensure that text is always readable + angular.forEach(PALETTES, sanitizePalette); + + // MD_THEME_CSS is a string generated by the build process that includes all the themable + // components as templates + + // Break the CSS into individual rules + var rulesByType = {}; + var rules = themeCss + .split(/\}(?!(\}|'|"|;))/) + .filter(function(rule) { return rule && rule.length; }) + .map(function(rule) { return rule.trim() + '}'; }); + + + var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g'); + + THEME_COLOR_TYPES.forEach(function(type) { + rulesByType[type] = ''; + }); + + + // Sort the rules based on type, allowing us to do color substitution on a per-type basis + rules.forEach(function(rule) { + var match = rule.match(ruleMatchRegex); + // First: test that if the rule has '.md-accent', it goes into the accent set of rules + for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) { + if (rule.indexOf('.md-' + type) > -1) { + return rulesByType[type] += rule; + } + } + + // If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from + // there + for (i = 0; type = THEME_COLOR_TYPES[i]; i++) { + if (rule.indexOf(type) > -1) { + return rulesByType[type] += rule; + } + } + + // Default to the primary array + return rulesByType[DEFAULT_COLOR_TYPE] += rule; + }); + + // For each theme, use the color palettes specified for + // `primary`, `warn` and `accent` to generate CSS rules. + + angular.forEach(THEMES, function(theme) { + if ( !GENERATED[theme.name] ) { + + + THEME_COLOR_TYPES.forEach(function(colorType) { + var styleStrings = parseRules(theme, colorType, rulesByType[colorType]); + while (styleStrings.length) { + var style = document.createElement('style'); + style.setAttribute('type', 'text/css'); + style.appendChild(document.createTextNode(styleStrings.shift())); + head.insertBefore(style, firstChild); + } + }); + + + if (theme.colors.primary.name == theme.colors.accent.name) { + console.warn("$mdThemingProvider: Using the same palette for primary and" + + " accent. This violates the material design spec."); + } + + GENERATED[theme.name] = true; + } + }); + + + // ************************* + // Internal functions + // ************************* + + // The user specifies a 'default' contrast color as either light or dark, + // then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light) + function sanitizePalette(palette) { + var defaultContrast = palette.contrastDefaultColor; + var lightColors = palette.contrastLightColors || []; + var strongLightColors = palette.contrastStrongLightColors || []; + var darkColors = palette.contrastDarkColors || []; + + // These colors are provided as space-separated lists + if (typeof lightColors === 'string') lightColors = lightColors.split(' '); + if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' '); + if (typeof darkColors === 'string') darkColors = darkColors.split(' '); + + // Cleanup after ourselves + delete palette.contrastDefaultColor; + delete palette.contrastLightColors; + delete palette.contrastStrongLightColors; + delete palette.contrastDarkColors; + + // Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR } + angular.forEach(palette, function(hueValue, hueName) { + if (angular.isObject(hueValue)) return; // Already converted + // Map everything to rgb colors + var rgbValue = colorToRgbaArray(hueValue); + if (!rgbValue) { + throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected." + .replace('%1', hueValue) + .replace('%2', palette.name) + .replace('%3', hueName)); + } + + palette[hueName] = { + value: rgbValue, + contrast: getContrastColor() + }; + function getContrastColor() { + if (defaultContrast === 'light') { + if (darkColors.indexOf(hueName) > -1) { + return DARK_CONTRAST_COLOR; + } else { + return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR + : LIGHT_CONTRAST_COLOR; + } + } else { + if (lightColors.indexOf(hueName) > -1) { + return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR + : LIGHT_CONTRAST_COLOR; + } else { + return DARK_CONTRAST_COLOR; + } + } + } + }); + } + + +} +generateThemes.$inject = ["$injector"]; + +function checkValidPalette(theme, colorType) { + // If theme attempts to use a palette that doesnt exist, throw error + if (!PALETTES[ (theme.colors[colorType] || {}).name ]) { + throw new Error( + "You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3" + .replace('%1', theme.name) + .replace('%2', colorType) + .replace('%3', Object.keys(PALETTES).join(', ')) + ); + } +} + +function colorToRgbaArray(clr) { + if (angular.isArray(clr) && clr.length == 3) return clr; + if (/^rgb/.test(clr)) { + return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) { + return i == 3 ? parseFloat(value, 10) : parseInt(value, 10); + }); + } + if (clr.charAt(0) == '#') clr = clr.substring(1); + if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return; + + var dig = clr.length / 3; + var red = clr.substr(0, dig); + var grn = clr.substr(dig, dig); + var blu = clr.substr(dig * 2); + if (dig === 1) { + red += red; + grn += grn; + blu += blu; + } + return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)]; +} + +function rgba(rgbArray, opacity) { + if ( !rgbArray ) return "rgb('0,0,0')"; + + if (rgbArray.length == 4) { + rgbArray = angular.copy(rgbArray); + opacity ? rgbArray.pop() : opacity = rgbArray.pop(); + } + return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ? + 'rgba(' + rgbArray.join(',') + ',' + opacity + ')' : + 'rgb(' + rgbArray.join(',') + ')'; +} + + +(function(){ +angular.module("material.core").constant("$MD_THEME_CSS", "/* mixin definition ; sets LTR and RTL within the same style call */md-autocomplete.md-THEME_NAME-theme { background: '{{background-50}}'; } md-autocomplete.md-THEME_NAME-theme[disabled] { background: '{{background-100}}'; } md-autocomplete.md-THEME_NAME-theme button md-icon path { fill: '{{background-600}}'; } md-autocomplete.md-THEME_NAME-theme button:after { background: '{{background-600-0.3}}'; }.md-autocomplete-suggestions.md-THEME_NAME-theme { background: '{{background-50}}'; } .md-autocomplete-suggestions.md-THEME_NAME-theme li { color: '{{background-900}}'; } .md-autocomplete-suggestions.md-THEME_NAME-theme li .highlight { color: '{{background-600}}'; } .md-autocomplete-suggestions.md-THEME_NAME-theme li:hover, .md-autocomplete-suggestions.md-THEME_NAME-theme li.selected { background: '{{background-200}}'; }md-backdrop.md-opaque.md-THEME_NAME-theme { background-color: '{{foreground-4-0.5}}'; }md-bottom-sheet.md-THEME_NAME-theme { background-color: '{{background-50}}'; border-top-color: '{{background-300}}'; } md-bottom-sheet.md-THEME_NAME-theme.md-list md-list-item { color: '{{foreground-1}}'; } md-bottom-sheet.md-THEME_NAME-theme .md-subheader { background-color: '{{background-50}}'; } md-bottom-sheet.md-THEME_NAME-theme .md-subheader { color: '{{foreground-1}}'; }a.md-button.md-THEME_NAME-theme, .md-button.md-THEME_NAME-theme { border-radius: 3px; } a.md-button.md-THEME_NAME-theme:not([disabled]):hover, .md-button.md-THEME_NAME-theme:not([disabled]):hover { background-color: '{{background-500-0.2}}'; } a.md-button.md-THEME_NAME-theme:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme:not([disabled]).md-focused { background-color: '{{background-500-0.2}}'; } a.md-button.md-THEME_NAME-theme:not([disabled]).md-icon-button:hover, .md-button.md-THEME_NAME-theme:not([disabled]).md-icon-button:hover { background-color: transparent; } a.md-button.md-THEME_NAME-theme.md-fab, .md-button.md-THEME_NAME-theme.md-fab { border-radius: 50%; background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } a.md-button.md-THEME_NAME-theme.md-fab md-icon, .md-button.md-THEME_NAME-theme.md-fab md-icon { color: '{{accent-contrast}}'; } a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover { background-color: '{{accent-color}}'; } a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused { background-color: '{{accent-A700}}'; } a.md-button.md-THEME_NAME-theme.md-icon-button, .md-button.md-THEME_NAME-theme.md-icon-button { border-radius: 50%; } a.md-button.md-THEME_NAME-theme.md-primary, .md-button.md-THEME_NAME-theme.md-primary { color: '{{primary-color}}'; } a.md-button.md-THEME_NAME-theme.md-primary.md-raised, a.md-button.md-THEME_NAME-theme.md-primary.md-fab, .md-button.md-THEME_NAME-theme.md-primary.md-raised, .md-button.md-THEME_NAME-theme.md-primary.md-fab { color: '{{primary-contrast}}'; background-color: '{{primary-color}}'; } a.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]) md-icon, a.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]) md-icon { color: '{{primary-contrast}}'; } a.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]):hover, a.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]):hover { background-color: '{{primary-color}}'; } a.md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]).md-focused, a.md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]).md-focused { background-color: '{{primary-600}}'; } a.md-button.md-THEME_NAME-theme.md-primary:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-primary:not([disabled]) md-icon { color: '{{primary-color}}'; } a.md-button.md-THEME_NAME-theme.md-fab, .md-button.md-THEME_NAME-theme.md-fab { border-radius: 50%; background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]) .md-icon, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]) .md-icon { color: '{{accent-contrast}}'; } a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover { background-color: '{{accent-color}}'; } a.md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused { background-color: '{{accent-A700}}'; } a.md-button.md-THEME_NAME-theme.md-raised, .md-button.md-THEME_NAME-theme.md-raised { color: '{{background-contrast}}'; background-color: '{{background-50}}'; } a.md-button.md-THEME_NAME-theme.md-raised:not([disabled]) .md-icon, .md-button.md-THEME_NAME-theme.md-raised:not([disabled]) .md-icon { color: '{{background-contrast}}'; } a.md-button.md-THEME_NAME-theme.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-raised:not([disabled]):hover { background-color: '{{background-50}}'; } a.md-button.md-THEME_NAME-theme.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-raised:not([disabled]).md-focused { background-color: '{{background-200}}'; } a.md-button.md-THEME_NAME-theme.md-warn, .md-button.md-THEME_NAME-theme.md-warn { color: '{{warn-color}}'; } a.md-button.md-THEME_NAME-theme.md-warn.md-raised, a.md-button.md-THEME_NAME-theme.md-warn.md-fab, .md-button.md-THEME_NAME-theme.md-warn.md-raised, .md-button.md-THEME_NAME-theme.md-warn.md-fab { color: '{{warn-contrast}}'; background-color: '{{warn-color}}'; } a.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]) md-icon, a.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]) md-icon { color: '{{warn-contrast}}'; } a.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]):hover, a.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]):hover { background-color: '{{warn-color}}'; } a.md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]).md-focused, a.md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]).md-focused { background-color: '{{warn-700}}'; } a.md-button.md-THEME_NAME-theme.md-warn:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-warn:not([disabled]) md-icon { color: '{{warn-color}}'; } a.md-button.md-THEME_NAME-theme.md-accent, .md-button.md-THEME_NAME-theme.md-accent { color: '{{accent-color}}'; } a.md-button.md-THEME_NAME-theme.md-accent.md-raised, a.md-button.md-THEME_NAME-theme.md-accent.md-fab, .md-button.md-THEME_NAME-theme.md-accent.md-raised, .md-button.md-THEME_NAME-theme.md-accent.md-fab { color: '{{accent-contrast}}'; background-color: '{{accent-color}}'; } a.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]) md-icon, a.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]) md-icon { color: '{{accent-contrast}}'; } a.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]):hover, a.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]):hover { background-color: '{{accent-color}}'; } a.md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]).md-focused, a.md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]).md-focused { background-color: '{{accent-700}}'; } a.md-button.md-THEME_NAME-theme.md-accent:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-accent:not([disabled]) md-icon { color: '{{accent-color}}'; } a.md-button.md-THEME_NAME-theme[disabled], a.md-button.md-THEME_NAME-theme.md-raised[disabled], a.md-button.md-THEME_NAME-theme.md-fab[disabled], a.md-button.md-THEME_NAME-theme.md-accent[disabled], a.md-button.md-THEME_NAME-theme.md-warn[disabled], .md-button.md-THEME_NAME-theme[disabled], .md-button.md-THEME_NAME-theme.md-raised[disabled], .md-button.md-THEME_NAME-theme.md-fab[disabled], .md-button.md-THEME_NAME-theme.md-accent[disabled], .md-button.md-THEME_NAME-theme.md-warn[disabled] { color: '{{foreground-3}}'; cursor: not-allowed; } a.md-button.md-THEME_NAME-theme[disabled] md-icon, a.md-button.md-THEME_NAME-theme.md-raised[disabled] md-icon, a.md-button.md-THEME_NAME-theme.md-fab[disabled] md-icon, a.md-button.md-THEME_NAME-theme.md-accent[disabled] md-icon, a.md-button.md-THEME_NAME-theme.md-warn[disabled] md-icon, .md-button.md-THEME_NAME-theme[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-raised[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-fab[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-accent[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-warn[disabled] md-icon { color: '{{foreground-3}}'; } a.md-button.md-THEME_NAME-theme.md-raised[disabled], a.md-button.md-THEME_NAME-theme.md-fab[disabled], .md-button.md-THEME_NAME-theme.md-raised[disabled], .md-button.md-THEME_NAME-theme.md-fab[disabled] { background-color: '{{foreground-4}}'; } a.md-button.md-THEME_NAME-theme[disabled], .md-button.md-THEME_NAME-theme[disabled] { background-color: transparent; }md-card.md-THEME_NAME-theme { background-color: '{{background-color}}'; border-radius: 2px; } md-card.md-THEME_NAME-theme .md-card-image { border-radius: 2px 2px 0 0; }md-checkbox.md-THEME_NAME-theme .md-ripple { color: '{{accent-600}}'; }md-checkbox.md-THEME_NAME-theme.md-checked .md-ripple { color: '{{background-600}}'; }md-checkbox.md-THEME_NAME-theme.md-checked.md-focused .md-container:before { background-color: '{{accent-color-0.26}}'; }md-checkbox.md-THEME_NAME-theme .md-icon { border-color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme.md-checked .md-icon { background-color: '{{accent-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme.md-checked .md-icon:after { border-color: '{{background-200}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-ripple { color: '{{primary-600}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ripple { color: '{{background-600}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-icon { border-color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-icon { background-color: '{{primary-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked.md-focused .md-container:before { background-color: '{{primary-color-0.26}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-icon:after { border-color: '{{background-200}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-ripple { color: '{{warn-600}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-icon { border-color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-icon { background-color: '{{warn-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before { background-color: '{{warn-color-0.26}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-icon:after { border-color: '{{background-200}}'; }md-checkbox.md-THEME_NAME-theme[disabled] .md-icon { border-color: '{{foreground-3}}'; }md-checkbox.md-THEME_NAME-theme[disabled].md-checked .md-icon { background-color: '{{foreground-3}}'; }md-checkbox.md-THEME_NAME-theme[disabled] .md-label { color: '{{foreground-3}}'; }md-chips.md-THEME_NAME-theme .md-chips { box-shadow: 0 1px '{{background-300}}'; } md-chips.md-THEME_NAME-theme .md-chips.md-focused { box-shadow: 0 2px '{{primary-color}}'; }md-chips.md-THEME_NAME-theme .md-chip { background: '{{background-300}}'; color: '{{background-800}}'; } md-chips.md-THEME_NAME-theme .md-chip.md-focused { background: '{{primary-color}}'; color: '{{primary-contrast}}'; } md-chips.md-THEME_NAME-theme .md-chip.md-focused md-icon { color: '{{primary-contrast}}'; }md-chips.md-THEME_NAME-theme md-chip-remove .md-button md-icon path { fill: '{{background-500}}'; }.md-contact-suggestion span.md-contact-email { color: '{{background-400}}'; }md-dialog.md-THEME_NAME-theme { border-radius: 4px; background-color: '{{background-color}}'; } md-dialog.md-THEME_NAME-theme.md-content-overflow .md-actions { border-top-color: '{{foreground-4}}'; }md-content.md-THEME_NAME-theme { background-color: '{{background-color}}'; }md-divider.md-THEME_NAME-theme { border-top-color: '{{foreground-4}}'; }md-input-container.md-THEME_NAME-theme .md-input { color: '{{foreground-1}}'; border-color: '{{foreground-4}}'; text-shadow: '{{foreground-shadow}}'; } md-input-container.md-THEME_NAME-theme .md-input::-webkit-input-placeholder, md-input-container.md-THEME_NAME-theme .md-input::-moz-placeholder, md-input-container.md-THEME_NAME-theme .md-input:-moz-placeholder, md-input-container.md-THEME_NAME-theme .md-input:-ms-input-placeholder { color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme > md-icon { color: '{{foreground-1}}'; }md-input-container.md-THEME_NAME-theme label, md-input-container.md-THEME_NAME-theme .md-placeholder { text-shadow: '{{foreground-shadow}}'; color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme ng-messages, md-input-container.md-THEME_NAME-theme [ng-message], md-input-container.md-THEME_NAME-theme [data-ng-message], md-input-container.md-THEME_NAME-theme [x-ng-message] { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-has-value label { color: '{{foreground-2}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused .md-input { border-color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused label { color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused md-icon { color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input { border-color: '{{accent-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent label { color: '{{accent-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input { border-color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn label { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input { border-color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid.md-input-focused label { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid data-ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid x-ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid [ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid [data-ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid [x-ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid .md-char-counter { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme .md-input[disabled], [disabled] md-input-container.md-THEME_NAME-theme .md-input { border-bottom-color: transparent; color: '{{foreground-3}}'; background-image: linear-gradient(to right, '{{foreground-3}}' 0%, '{{foreground-3}}' 33%, transparent 0%); background-image: -ms-linear-gradient(left, transparent 0%, '{{foreground-3}}' 100%); }md-icon.md-THEME_NAME-theme { color: '{{foreground-2}}'; } md-icon.md-THEME_NAME-theme.md-primary { color: '{{primary-color}}'; } md-icon.md-THEME_NAME-theme.md-accent { color: '{{accent-color}}'; } md-icon.md-THEME_NAME-theme.md-warn { color: '{{warn-color}}'; }md-progress-circular.md-THEME_NAME-theme { background-color: transparent; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-gap { border-top-color: '{{primary-color}}'; border-bottom-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme .md-inner .md-right .md-half-circle { border-top-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-right .md-half-circle { border-right-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-left .md-half-circle { border-left-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-gap { border-top-color: '{{warn-color}}'; border-bottom-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-right .md-half-circle { border-top-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-right .md-half-circle { border-right-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-left .md-half-circle { border-left-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-gap { border-top-color: '{{accent-color}}'; border-bottom-color: '{{accent-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-right .md-half-circle { border-top-color: '{{accent-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-right .md-half-circle { border-right-color: '{{accent-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-left .md-half-circle { border-left-color: '{{accent-color}}'; }md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h3, md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h4, md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h3, md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h4 { color: '{{foreground-1}}'; }md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text p, md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text p { color: '{{foreground-2}}'; }md-list.md-THEME_NAME-theme .md-proxy-focus.md-focused div.md-no-style { background-color: '{{background-100}}'; }md-list.md-THEME_NAME-theme md-list-item > md-icon { color: '{{foreground-2}}'; } md-list.md-THEME_NAME-theme md-list-item > md-icon.md-highlight { color: '{{primary-color}}'; } md-list.md-THEME_NAME-theme md-list-item > md-icon.md-highlight.md-accent { color: '{{accent-color}}'; }md-list.md-THEME_NAME-theme md-list-item button { background-color: '{{background-color}}'; } md-list.md-THEME_NAME-theme md-list-item button.md-button:not([disabled]):hover { background-color: '{{background-color}}'; }md-progress-linear.md-THEME_NAME-theme .md-container { background-color: '{{primary-100}}'; }md-progress-linear.md-THEME_NAME-theme .md-bar { background-color: '{{primary-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn .md-container { background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn .md-bar { background-color: '{{warn-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent .md-container { background-color: '{{accent-100}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent .md-bar { background-color: '{{accent-color}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-bar1 { background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-dashed:before { background: radial-gradient('{{warn-100}}' 0%, '{{warn-100}}' 16%, transparent 42%); }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-bar1 { background-color: '{{accent-100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-dashed:before { background: radial-gradient('{{accent-100}}' 0%, '{{accent-100}}' 16%, transparent 42%); }md-radio-button.md-THEME_NAME-theme .md-off { border-color: '{{foreground-2}}'; }md-radio-button.md-THEME_NAME-theme .md-on { background-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked .md-off { border-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked .md-ink-ripple { color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme .md-container .md-ripple { color: '{{accent-600}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-on { background-color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off { border-color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple { color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple { color: '{{primary-600}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-on { background-color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off { border-color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple { color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple { color: '{{warn-600}}'; }md-radio-group.md-THEME_NAME-theme[disabled], md-radio-button.md-THEME_NAME-theme[disabled] { color: '{{foreground-3}}'; } md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-off, md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-off { border-color: '{{foreground-3}}'; } md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-on, md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-on { border-color: '{{foreground-3}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked .md-container:before { background-color: '{{accent-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked:not([disabled]).md-primary .md-container:before { background-color: '{{primary-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-primary .md-container:before { background-color: '{{warn-color-0.26}}'; }md-select.md-THEME_NAME-theme.ng-invalid.ng-dirty .md-select-label { color: '{{warn-500}}' !important; border-bottom-color: '{{warn-500}}' !important; }md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-label { border-bottom-color: '{{primary-color}}'; color: '{{ foreground-1 }}'; } md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-label.md-placeholder { color: '{{ foreground-1 }}'; }md-select.md-THEME_NAME-theme:not([disabled]):focus.md-accent .md-select-label { border-bottom-color: '{{accent-color}}'; }md-select.md-THEME_NAME-theme:not([disabled]):focus.md-warn .md-select-label { border-bottom-color: '{{warn-color}}'; }md-select.md-THEME_NAME-theme[disabled] .md-select-label { color: '{{foreground-3}}'; } md-select.md-THEME_NAME-theme[disabled] .md-select-label.md-placeholder { color: '{{foreground-3}}'; }md-select.md-THEME_NAME-theme .md-select-label { border-bottom-color: '{{foreground-4}}'; } md-select.md-THEME_NAME-theme .md-select-label.md-placeholder { color: '{{foreground-2}}'; }md-select-menu.md-THEME_NAME-theme md-optgroup { color: '{{foreground-2}}'; } md-select-menu.md-THEME_NAME-theme md-optgroup md-option { color: '{{foreground-1}}'; }md-select-menu.md-THEME_NAME-theme md-option[selected] { color: '{{primary-500}}'; } md-select-menu.md-THEME_NAME-theme md-option[selected]:focus { color: '{{primary-600}}'; } md-select-menu.md-THEME_NAME-theme md-option[selected].md-accent { color: '{{accent-500}}'; } md-select-menu.md-THEME_NAME-theme md-option[selected].md-accent:focus { color: '{{accent-600}}'; }md-select-menu.md-THEME_NAME-theme md-option:focus:not([selected]) { background: '{{background-200}}'; }md-sidenav.md-THEME_NAME-theme { background-color: '{{background-color}}'; }md-slider.md-THEME_NAME-theme .md-track { background-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme .md-track-ticks { background-color: '{{foreground-4}}'; }md-slider.md-THEME_NAME-theme .md-focus-thumb { background-color: '{{foreground-2}}'; }md-slider.md-THEME_NAME-theme .md-focus-ring { border-color: '{{foreground-4}}'; }md-slider.md-THEME_NAME-theme .md-disabled-thumb { border-color: '{{background-color}}'; }md-slider.md-THEME_NAME-theme.md-min .md-thumb:after { background-color: '{{background-color}}'; }md-slider.md-THEME_NAME-theme .md-track.md-track-fill { background-color: '{{accent-color}}'; }md-slider.md-THEME_NAME-theme .md-thumb:after { border-color: '{{accent-color}}'; background-color: '{{accent-color}}'; }md-slider.md-THEME_NAME-theme .md-sign { background-color: '{{accent-color}}'; } md-slider.md-THEME_NAME-theme .md-sign:after { border-top-color: '{{accent-color}}'; }md-slider.md-THEME_NAME-theme .md-thumb-text { color: '{{accent-contrast}}'; }md-slider.md-THEME_NAME-theme.md-warn .md-track.md-track-fill { background-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn .md-thumb:after { border-color: '{{warn-color}}'; background-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn .md-sign { background-color: '{{warn-color}}'; } md-slider.md-THEME_NAME-theme.md-warn .md-sign:after { border-top-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn .md-thumb-text { color: '{{warn-contrast}}'; }md-slider.md-THEME_NAME-theme.md-primary .md-track.md-track-fill { background-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary .md-thumb:after { border-color: '{{primary-color}}'; background-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary .md-sign { background-color: '{{primary-color}}'; } md-slider.md-THEME_NAME-theme.md-primary .md-sign:after { border-top-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary .md-thumb-text { color: '{{primary-contrast}}'; }md-slider.md-THEME_NAME-theme[disabled] .md-thumb:after { border-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme[disabled]:not(.md-min) .md-thumb:after { background-color: '{{foreground-3}}'; }.md-subheader.md-THEME_NAME-theme { color: '{{ foreground-2-0.23 }}'; background-color: '{{background-color}}'; } .md-subheader.md-THEME_NAME-theme.md-primary { color: '{{primary-color}}'; } .md-subheader.md-THEME_NAME-theme.md-accent { color: '{{accent-color}}'; } .md-subheader.md-THEME_NAME-theme.md-warn { color: '{{warn-color}}'; }md-switch.md-THEME_NAME-theme .md-thumb { background-color: '{{background-50}}'; }md-switch.md-THEME_NAME-theme .md-bar { background-color: '{{background-500}}'; }md-switch.md-THEME_NAME-theme.md-checked .md-thumb { background-color: '{{accent-color}}'; }md-switch.md-THEME_NAME-theme.md-checked .md-bar { background-color: '{{accent-color-0.5}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-focused .md-thumb:before { background-color: '{{accent-color-0.26}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-primary .md-thumb { background-color: '{{primary-color}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-primary .md-bar { background-color: '{{primary-color-0.5}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-primary.md-focused .md-thumb:before { background-color: '{{primary-color-0.26}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-warn .md-thumb { background-color: '{{warn-color}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-warn .md-bar { background-color: '{{warn-color-0.5}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-warn.md-focused .md-thumb:before { background-color: '{{warn-color-0.26}}'; }md-switch.md-THEME_NAME-theme[disabled] .md-thumb { background-color: '{{background-400}}'; }md-switch.md-THEME_NAME-theme[disabled] .md-bar { background-color: '{{foreground-4}}'; }md-tabs.md-THEME_NAME-theme md-tabs-wrapper { background-color: transparent; border-color: '{{foreground-4}}'; }md-tabs.md-THEME_NAME-theme .md-paginator md-icon { color: '{{primary-color}}'; }md-tabs.md-THEME_NAME-theme md-ink-bar { color: '{{accent-color}}'; background: '{{accent-color}}'; }md-tabs.md-THEME_NAME-theme .md-tab { color: '{{foreground-2}}'; } md-tabs.md-THEME_NAME-theme .md-tab[disabled] { color: '{{foreground-3}}'; } md-tabs.md-THEME_NAME-theme .md-tab.md-active, md-tabs.md-THEME_NAME-theme .md-tab.md-focused { color: '{{primary-color}}'; } md-tabs.md-THEME_NAME-theme .md-tab.md-focused { background: '{{primary-color-0.1}}'; } md-tabs.md-THEME_NAME-theme .md-tab .md-ripple-container { color: '{{accent-100}}'; }md-tabs.md-THEME_NAME-theme.md-accent md-tabs-wrapper { background-color: '{{accent-color}}'; }md-tabs.md-THEME_NAME-theme.md-accent md-tab-item:not([disabled]) { color: '{{accent-100}}'; } md-tabs.md-THEME_NAME-theme.md-accent md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-accent md-tab-item:not([disabled]).md-focused { color: '{{accent-contrast}}'; } md-tabs.md-THEME_NAME-theme.md-accent md-tab-item:not([disabled]).md-focused { background: '{{accent-contrast-0.1}}'; }md-tabs.md-THEME_NAME-theme.md-accent md-ink-bar { color: '{{primary-600-1}}'; background: '{{primary-600-1}}'; }md-tabs.md-THEME_NAME-theme.md-primary md-tabs-wrapper { background-color: '{{primary-color}}'; }md-tabs.md-THEME_NAME-theme.md-primary md-tab-item:not([disabled]) { color: '{{primary-100}}'; } md-tabs.md-THEME_NAME-theme.md-primary md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-primary md-tab-item:not([disabled]).md-focused { color: '{{primary-contrast}}'; } md-tabs.md-THEME_NAME-theme.md-primary md-tab-item:not([disabled]).md-focused { background: '{{primary-contrast-0.1}}'; }md-tabs.md-THEME_NAME-theme.md-warn md-tabs-wrapper { background-color: '{{warn-color}}'; }md-tabs.md-THEME_NAME-theme.md-warn md-tab-item:not([disabled]) { color: '{{warn-100}}'; } md-tabs.md-THEME_NAME-theme.md-warn md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-warn md-tab-item:not([disabled]).md-focused { color: '{{warn-contrast}}'; } md-tabs.md-THEME_NAME-theme.md-warn md-tab-item:not([disabled]).md-focused { background: '{{warn-contrast-0.1}}'; }md-toolbar > md-tabs.md-THEME_NAME-theme md-tabs-wrapper { background-color: '{{primary-color}}'; }md-toolbar > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]) { color: '{{primary-100}}'; } md-toolbar > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-active, md-toolbar > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused { color: '{{primary-contrast}}'; } md-toolbar > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused { background: '{{primary-contrast-0.1}}'; }md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme md-tabs-wrapper { background-color: '{{accent-color}}'; }md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]) { color: '{{accent-100}}'; } md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-active, md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused { color: '{{accent-contrast}}'; } md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused { background: '{{accent-contrast-0.1}}'; }md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme md-ink-bar { color: '{{primary-600-1}}'; background: '{{primary-600-1}}'; }md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme md-tabs-wrapper { background-color: '{{warn-color}}'; }md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]) { color: '{{warn-100}}'; } md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-active, md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused { color: '{{warn-contrast}}'; } md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme md-tab-item:not([disabled]).md-focused { background: '{{warn-contrast-0.1}}'; }md-toast.md-THEME_NAME-theme { background-color: #323232; color: '{{background-50}}'; } md-toast.md-THEME_NAME-theme .md-button { color: '{{background-50}}'; } md-toast.md-THEME_NAME-theme .md-button.md-highlight { color: '{{primary-A200}}'; } md-toast.md-THEME_NAME-theme .md-button.md-highlight.md-accent { color: '{{accent-A200}}'; } md-toast.md-THEME_NAME-theme .md-button.md-highlight.md-warn { color: '{{warn-A200}}'; }md-toolbar.md-THEME_NAME-theme { background-color: '{{primary-color}}'; color: '{{primary-contrast}}'; } md-toolbar.md-THEME_NAME-theme md-icon { color: '{{primary-contrast}}'; } md-toolbar.md-THEME_NAME-theme .md-button { color: '{{primary-contrast}}'; } md-toolbar.md-THEME_NAME-theme.md-accent { background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } md-toolbar.md-THEME_NAME-theme.md-warn { background-color: '{{warn-color}}'; color: '{{warn-contrast}}'; }md-tooltip.md-THEME_NAME-theme { color: '{{background-A100}}'; } md-tooltip.md-THEME_NAME-theme .md-background { background-color: '{{foreground-2}}'; }"); +})(); + + +ng.material.core = angular.module("material.core");
\ No newline at end of file |