2 * Angular Material Design
3 * https://github.com/angular/material
7 (function( window, angular, undefined ){
12 * Initialization function that validates environment
16 .module('material.core', [ 'material.core.gestures', 'material.core.theming' ])
17 .config( MdCoreConfigure );
20 function MdCoreConfigure($provide, $mdThemingProvider) {
22 $provide.decorator('$$rAF', ["$delegate", rAFDecorator]);
24 $mdThemingProvider.theme('default')
25 .primaryPalette('indigo')
26 .accentPalette('pink')
28 .backgroundPalette('grey');
30 MdCoreConfigure.$inject = ["$provide", "$mdThemingProvider"];
32 function rAFDecorator( $delegate ) {
34 * Use this to throttle events that come in often.
35 * The throttled function will always use the *last* invocation before the
38 * For example, window resize events that fire many times a second:
39 * If we set to use an raf-throttled callback on window resize, then
40 * our callback will only be fired once per frame, with the last resize
41 * event that happened before that frame.
43 * @param {function} callback function to debounce
45 $delegate.throttle = function(cb) {
46 var queueArgs, alreadyQueued, queueCb, context;
47 return function debounced() {
48 queueArgs = arguments;
53 $delegate(function() {
54 queueCb.apply(context, queueArgs);
55 alreadyQueued = false;
63 angular.module('material.core')
64 .factory('$mdConstant', MdConstantFactory);
66 function MdConstantFactory($$rAF, $sniffer) {
68 var webkit = /webkit/i.test($sniffer.vendorPrefix);
69 function vendorProperty(name) {
70 return webkit ? ('webkit' + name.charAt(0).toUpperCase() + name.substring(1)) : name;
88 TRANSITIONEND: 'transitionend' + (webkit ? ' webkitTransitionEnd' : ''),
89 ANIMATIONEND: 'animationend' + (webkit ? ' webkitAnimationEnd' : ''),
91 TRANSFORM: vendorProperty('transform'),
92 TRANSFORM_ORIGIN: vendorProperty('transformOrigin'),
93 TRANSITION: vendorProperty('transition'),
94 TRANSITION_DURATION: vendorProperty('transitionDuration'),
95 ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'),
96 ANIMATION_DURATION: vendorProperty('animationDuration'),
97 ANIMATION_NAME: vendorProperty('animationName'),
98 ANIMATION_TIMING: vendorProperty('animationTimingFunction'),
99 ANIMATION_DIRECTION: vendorProperty('animationDirection')
102 'sm': '(max-width: 600px)',
103 'gt-sm': '(min-width: 600px)',
104 'md': '(min-width: 600px) and (max-width: 960px)',
105 'gt-md': '(min-width: 960px)',
106 'lg': '(min-width: 960px) and (max-width: 1200px)',
107 'gt-lg': '(min-width: 1200px)'
119 MdConstantFactory.$inject = ["$$rAF", "$sniffer"];
122 .module('material.core')
123 .config( ["$provide", function($provide){
124 $provide.decorator('$mdUtil', ['$delegate', function ($delegate){
126 * Inject the iterator facade to easily support iteration and accessors
127 * @see iterator below
129 $delegate.iterator = MdIterator;
137 * iterator is a list facade to easily support iteration and accessors
139 * @param items Array list which this iterator will enumerate
140 * @param reloop Boolean enables iterator to consider the list as an endless reloop
142 function MdIterator(items, reloop) {
143 var trueFn = function() { return true; };
145 if (items && !angular.isArray(items)) {
146 items = Array.prototype.slice.call(items);
150 var _items = items || [ ];
169 next: angular.bind(null, findSubsequentItem, false),
170 previous: angular.bind(null, findSubsequentItem, true),
172 hasPrevious: hasPrevious,
178 * Publish copy of the enumerable set
181 function getItems() {
182 return [].concat(_items);
186 * Determine length of the list
187 * @returns {Array.length|*|number}
190 return _items.length;
194 * Is the index specified valid
196 * @returns {Array.length|*|number|boolean}
198 function inRange(index) {
199 return _items.length && ( index > -1 ) && (index < _items.length );
203 * Can the iterator proceed to the next item in the list; relative to
204 * the specified item.
207 * @returns {Array.length|*|number|boolean}
209 function hasNext(item) {
210 return item ? inRange(indexOf(item) + 1) : false;
214 * Can the iterator proceed to the previous item in the list; relative to
215 * the specified item.
218 * @returns {Array.length|*|number|boolean}
220 function hasPrevious(item) {
221 return item ? inRange(indexOf(item) - 1) : false;
225 * Get item at specified index/position
229 function itemAt(index) {
230 return inRange(index) ? _items[index] : null;
234 * Find all elements matching the key/value pair
235 * otherwise return null
242 function findBy(key, val) {
243 return _items.filter(function(item) {
244 return item[key] === val;
254 function add(item, index) {
255 if ( !item ) return -1;
257 if (!angular.isNumber(index)) {
258 index = _items.length;
261 _items.splice(index, 0, item);
263 return indexOf(item);
267 * Remove item from list...
270 function remove(item) {
271 if ( contains(item) ){
272 _items.splice(indexOf(item), 1);
277 * Get the zero-based index of the target item
281 function indexOf(item) {
282 return _items.indexOf(item);
286 * Boolean existence check
290 function contains(item) {
291 return item && (indexOf(item) > -1);
295 * Return first item in the list
299 return _items.length ? _items[0] : null;
303 * Return last item in the list...
307 return _items.length ? _items[_items.length - 1] : null;
311 * Find the next item. If reloop is true and at the end of the list, it will go back to the
312 * first item. If given, the `validate` callback will be used to determine whether the next item
313 * is valid. If not valid, it will try to find the next item again.
315 * @param {boolean} backwards Specifies the direction of searching (forwards/backwards)
316 * @param {*} item The item whose subsequent item we are looking for
317 * @param {Function=} validate The `validate` function
318 * @param {integer=} limit The recursion limit
320 * @returns {*} The subsequent item or null
322 function findSubsequentItem(backwards, item, validate, limit) {
323 validate = validate || trueFn;
325 var curIndex = indexOf(item);
327 if (!inRange(curIndex)) return null;
329 var nextIndex = curIndex + (backwards ? -1 : 1);
330 var foundItem = null;
331 if (inRange(nextIndex)) {
332 foundItem = _items[nextIndex];
334 foundItem = backwards ? last() : first();
335 nextIndex = indexOf(foundItem);
338 if ((foundItem === null) || (nextIndex === limit)) return null;
339 if (validate(foundItem)) return foundItem;
341 if (angular.isUndefined(limit)) limit = nextIndex;
343 curIndex = nextIndex;
349 angular.module('material.core')
350 .factory('$mdMedia', mdMediaFactory);
355 * @module material.core
358 * `$mdMedia` is used to evaluate whether a given media query is true or false given the
359 * current device's screen / window size. The media query will be re-evaluated on resize, allowing
360 * you to register a watch.
362 * `$mdMedia` also has pre-programmed support for media queries that match the layout breakpoints.
363 * (`sm`, `gt-sm`, `md`, `gt-md`, `lg`, `gt-lg`).
365 * @returns {boolean} a boolean representing whether or not the given media query is true or false.
369 * app.controller('MyController', function($mdMedia, $scope) {
370 * $scope.$watch(function() { return $mdMedia('lg'); }, function(big) {
371 * $scope.bigScreen = big;
374 * $scope.screenIsSmall = $mdMedia('sm');
375 * $scope.customQuery = $mdMedia('(min-width: 1234px)');
376 * $scope.anotherCustom = $mdMedia('max-width: 300px');
381 function mdMediaFactory($mdConstant, $rootScope, $window) {
385 var normalizeCache = {};
387 $mdMedia.getResponsiveAttribute = getResponsiveAttribute;
388 $mdMedia.getQuery = getQuery;
389 $mdMedia.watchResponsiveAttributes = watchResponsiveAttributes;
393 function $mdMedia(query) {
394 var validated = queries[query];
395 if (angular.isUndefined(validated)) {
396 validated = queries[query] = validate(query);
399 var result = results[validated];
400 if (angular.isUndefined(result)) {
401 result = add(validated);
407 function validate(query) {
408 return $mdConstant.MEDIA[query] ||
409 ((query.charAt(0) !== '(') ? ('(' + query + ')') : query);
412 function add(query) {
413 var result = mqls[query] = $window.matchMedia(query);
414 result.addListener(onQueryChange);
415 return (results[result.media] = !!result.matches);
418 function onQueryChange(query) {
419 $rootScope.$evalAsync(function() {
420 results[query.media] = !!query.matches;
424 function getQuery(name) {
428 function getResponsiveAttribute(attrs, attrName) {
429 for (var i = 0; i < $mdConstant.MEDIA_PRIORITY.length; i++) {
430 var mediaName = $mdConstant.MEDIA_PRIORITY[i];
431 if (!mqls[queries[mediaName]].matches) {
435 var normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
436 if (attrs[normalizedName]) {
437 return attrs[normalizedName];
441 // fallback on unprefixed
442 return attrs[getNormalizedName(attrs, attrName)];
445 function watchResponsiveAttributes(attrNames, attrs, watchFn) {
447 attrNames.forEach(function(attrName) {
448 var normalizedName = getNormalizedName(attrs, attrName);
449 if (attrs[normalizedName]) {
451 attrs.$observe(normalizedName, angular.bind(void 0, watchFn, null)));
454 for (var mediaName in $mdConstant.MEDIA) {
455 normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
456 if (!attrs[normalizedName]) {
460 unwatchFns.push(attrs.$observe(normalizedName, angular.bind(void 0, watchFn, mediaName)));
464 return function unwatch() {
465 unwatchFns.forEach(function(fn) { fn(); })
469 // Improves performance dramatically
470 function getNormalizedName(attrs, attrName) {
471 return normalizeCache[attrName] ||
472 (normalizeCache[attrName] = attrs.$normalize(attrName));
475 mdMediaFactory.$inject = ["$mdConstant", "$rootScope", "$window"];
478 * This var has to be outside the angular factory, otherwise when
479 * there are multiple material apps on the same page, each app
480 * will create its own instance of this array and the app's IDs
481 * will not be unique.
483 var nextUniqueId = 0;
485 angular.module('material.core')
486 .factory('$mdUtil', ["$cacheFactory", "$document", "$timeout", "$q", "$window", "$mdConstant", function($cacheFactory, $document, $timeout, $q, $window, $mdConstant) {
489 function getNode(el) {
494 now: window.performance ?
495 angular.bind(window.performance, window.performance.now) :
498 clientRect: function(element, offsetParent, isOffsetRect) {
499 var node = getNode(element);
500 offsetParent = getNode(offsetParent || node.offsetParent || document.body);
501 var nodeRect = node.getBoundingClientRect();
503 // The user can ask for an offsetRect: a rect relative to the offsetParent,
504 // or a clientRect: a rect relative to the page
505 var offsetRect = isOffsetRect ?
506 offsetParent.getBoundingClientRect() :
507 { left: 0, top: 0, width: 0, height: 0 };
509 left: nodeRect.left - offsetRect.left,
510 top: nodeRect.top - offsetRect.top,
511 width: nodeRect.width,
512 height: nodeRect.height
515 offsetRect: function(element, offsetParent) {
516 return Util.clientRect(element, offsetParent, true);
518 // Disables scroll around the passed element. Goes up the DOM to find a
519 // disableTarget (a md-content that is scrolling, or the body as a fallback)
520 // and uses CSS/JS to prevent it from scrolling
521 disableScrollAround: function(element) {
522 element = element instanceof angular.element ? element[0] : element;
523 var parentEl = element;
526 // Find the highest level scrolling md-content
527 while (parentEl = this.getClosest(parentEl, 'MD-CONTENT', true)) {
528 if (isScrolling(parentEl)) {
529 disableTarget = angular.element(parentEl)[0];
533 // Default to the body if no scrolling md-content
534 if (!disableTarget) {
535 disableTarget = $document[0].body;
536 if (!isScrolling(disableTarget)) return angular.noop;
539 if (disableTarget.nodeName == 'BODY') {
540 return disableBodyScroll();
542 return disableElementScroll();
545 // Creates a virtual scrolling mask to absorb touchmove, keyboard, scrollbar clicking, and wheel events
546 function disableElementScroll() {
547 var scrollMask = angular.element('<div class="md-scroll-mask"><div class="md-scroll-mask-bar"></div></div>');
548 var computedStyle = $window.getComputedStyle(disableTarget);
549 var disableRect = disableTarget.getBoundingClientRect();
550 var scrollWidth = disableRect.width - disableTarget.clientWidth;
551 applyStyles(scrollMask[0], {
552 zIndex: computedStyle.zIndex == 'auto' ? 2 : computedStyle.zIndex + 1,
553 width: disableRect.width + 'px',
554 height: disableRect.height + 'px',
555 top: disableRect.top + 'px',
556 left: disableRect.left + 'px'
558 scrollMask[0].firstElementChild.style.width = scrollWidth + 'px';
559 $document[0].body.appendChild(scrollMask[0]);
561 scrollMask.on('wheel', preventDefault);
562 scrollMask.on('touchmove', preventDefault);
563 $document.on('keydown', disableKeyNav);
565 return function restoreScroll() {
566 scrollMask.off('wheel');
567 scrollMask.off('touchmove');
568 scrollMask[0].parentNode.removeChild(scrollMask[0]);
569 $document.off('keydown', disableKeyNav);
572 // Prevent keypresses from elements inside the disableTarget
573 // used to stop the keypresses that could cause the page to scroll
574 // (arrow keys, spacebar, tab, etc).
575 function disableKeyNav(e) {
576 if (disableTarget.contains(e.target)) {
578 e.stopImmediatePropagation();
582 function preventDefault(e) {
587 // Converts the disableTarget (body) to a position fixed block and translate it to the propper scroll position
588 function disableBodyScroll() {
589 var restoreStyle = disableTarget.getAttribute('style') || '';
590 var scrollOffset = disableTarget.scrollTop;
592 applyStyles(disableTarget, {
596 top: -scrollOffset + 'px'
599 return function restoreScroll() {
600 disableTarget.setAttribute('style', restoreStyle);
601 disableTarget.scrollTop = scrollOffset;
605 function applyStyles (el, styles) {
606 for (var key in styles) {
607 el.style[key] = styles[key];
611 function isScrolling(el) {
612 if (el instanceof angular.element) el = el[0];
613 return el.scrollHeight > el.offsetHeight;
617 floatingScrollbars: function() {
618 if (this.floatingScrollbars.cached === undefined) {
619 var tempNode = angular.element('<div style="width: 100%; z-index: -1; position: absolute; height: 35px; overflow-y: scroll"><div style="height: 60;"></div></div>');
620 $document[0].body.appendChild(tempNode[0]);
621 this.floatingScrollbars.cached = (tempNode[0].offsetWidth == tempNode[0].childNodes[0].offsetWidth);
624 return this.floatingScrollbars.cached;
627 // Mobile safari only allows you to set focus in click event listeners...
628 forceFocus: function(element) {
629 var node = element[0] || element;
631 document.addEventListener('click', function focusOnClick(ev) {
632 if (ev.target === node && ev.$focus) {
634 ev.stopImmediatePropagation();
636 node.removeEventListener('click', focusOnClick);
640 var newEvent = document.createEvent('MouseEvents');
641 newEvent.initMouseEvent('click', false, true, window, {}, 0, 0, 0, 0,
642 false, false, false, false, 0, null);
643 newEvent.$material = true;
644 newEvent.$focus = true;
645 node.dispatchEvent(newEvent);
648 transitionEndPromise: function(element, opts) {
650 var deferred = $q.defer();
651 element.on($mdConstant.CSS.TRANSITIONEND, finished);
652 function finished(ev) {
653 // Make sure this transitionend didn't bubble up from a child
654 if (!ev || ev.target === element[0]) {
655 element.off($mdConstant.CSS.TRANSITIONEND, finished);
659 if (opts.timeout) $timeout(finished, opts.timeout);
660 return deferred.promise;
663 fakeNgModel: function() {
666 $setTouched: angular.noop,
667 $setViewValue: function(value) {
668 this.$viewValue = value;
670 this.$viewChangeListeners.forEach(function(cb) { cb(); });
672 $isEmpty: function(value) {
673 return ('' + value).length === 0;
677 $viewChangeListeners: [],
678 $render: angular.noop
682 // Returns a function, that, as long as it continues to be invoked, will not
683 // be triggered. The function will be called after it stops being called for
685 // @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs
686 // @param invokeApply should the $timeout trigger $digest() dirty checking
687 debounce: function (func, wait, scope, invokeApply) {
690 return function debounced() {
692 args = Array.prototype.slice.call(arguments);
694 $timeout.cancel(timer);
695 timer = $timeout(function() {
698 func.apply(context, args);
700 }, wait || 10, invokeApply );
704 // Returns a function that can only be triggered every `delay` milliseconds.
705 // In other words, the function will not be called unless it has been more
706 // than `delay` milliseconds since the last call.
707 throttle: function throttle(func, delay) {
709 return function throttled() {
711 var args = arguments;
712 var now = Util.now();
714 if (!recent || (now - recent > delay)) {
715 func.apply(context, args);
722 * Measures the number of milliseconds taken to run the provided callback
723 * function. Uses a high-precision timer if available.
725 time: function time(cb) {
726 var start = Util.now();
728 return Util.now() - start;
734 * @returns {string} an unique numeric string
736 nextUid: function() {
737 return '' + nextUniqueId++;
740 // Stop watchers and events from firing on a scope without destroying it,
741 // by disconnecting it from its parent and its siblings' linked lists.
742 disconnectScope: function disconnectScope(scope) {
745 // we can't destroy the root scope or a scope that has been already destroyed
746 if (scope.$root === scope) return;
747 if (scope.$$destroyed ) return;
749 var parent = scope.$parent;
750 scope.$$disconnected = true;
752 // See Scope.$destroy
753 if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling;
754 if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling;
755 if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
756 if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
758 scope.$$nextSibling = scope.$$prevSibling = null;
762 // Undo the effects of disconnectScope above.
763 reconnectScope: function reconnectScope(scope) {
766 // we can't disconnect the root node or scope already disconnected
767 if (scope.$root === scope) return;
768 if (!scope.$$disconnected) return;
772 var parent = child.$parent;
773 child.$$disconnected = false;
774 // See Scope.$new for this logic...
775 child.$$prevSibling = parent.$$childTail;
776 if (parent.$$childHead) {
777 parent.$$childTail.$$nextSibling = child;
778 parent.$$childTail = child;
780 parent.$$childHead = parent.$$childTail = child;
785 * getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName
787 * @param el Element to start walking the DOM from
788 * @param tagName Tag name to find closest to el, such as 'form'
790 getClosest: function getClosest(el, tagName, onlyParent) {
791 if (el instanceof angular.element) el = el[0];
792 tagName = tagName.toUpperCase();
793 if (onlyParent) el = el.parentNode;
794 if (!el) return null;
796 if (el.nodeName === tagName) {
799 } while (el = el.parentNode);
804 * Functional equivalent for $element.filter(‘md-bottom-sheet’)
805 * useful with interimElements where the element and its container are important...
807 extractElementByName: function (element, nodeName) {
808 for (var i = 0, len = element.length; i < len; i++) {
809 if (element[i].nodeName.toLowerCase() === nodeName){
810 return angular.element(element[i]);
817 * Give optional properties with no value a boolean true by default
819 initOptionalProperties: function (scope, attr, defaults ) {
820 defaults = defaults || { };
821 angular.forEach(scope.$$isolateBindings, function (binding, key) {
822 if (binding.optional && angular.isUndefined(scope[key])) {
823 var hasKey = attr.hasOwnProperty(attr.$normalize(binding.attrName));
825 scope[key] = angular.isDefined(defaults[key]) ? defaults[key] : hasKey;
835 * Since removing jQuery from the demos, some code that uses `element.focus()` is broken.
837 * We need to add `element.focus()`, because it's testable unlike `element[0].focus`.
839 * TODO(ajoslin): This should be added in a better place later.
842 angular.element.prototype.focus = angular.element.prototype.focus || function() {
848 angular.element.prototype.blur = angular.element.prototype.blur || function() {
856 angular.module('material.core')
857 .service('$mdAria', AriaService);
862 function AriaService($$rAF, $log, $window) {
866 expectAsync: expectAsync,
867 expectWithText: expectWithText
871 * Check if expected attribute has been specified on the target element or child
874 * @param {optional} defaultValue What to set the attr to if no value is found
876 function expect(element, attrName, defaultValue) {
877 var node = element[0] || element;
879 // if node exists and neither it nor its children have the attribute
881 ((!node.hasAttribute(attrName) ||
882 node.getAttribute(attrName).length === 0) &&
883 !childHasAttribute(node, attrName))) {
885 defaultValue = angular.isString(defaultValue) ? defaultValue.trim() : '';
886 if (defaultValue.length) {
887 element.attr(attrName, defaultValue);
889 $log.warn('ARIA: Attribute "', attrName, '", required for accessibility, is missing on node:', node);
895 function expectAsync(element, attrName, defaultValueGetter) {
896 // Problem: when retrieving the element's contents synchronously to find the label,
897 // the text may not be defined yet in the case of a binding.
898 // There is a higher chance that a binding will be defined if we wait one frame.
900 expect(element, attrName, defaultValueGetter());
904 function expectWithText(element, attrName) {
905 expectAsync(element, attrName, function() {
906 return getText(element);
910 function getText(element) {
911 return element.text().trim();
914 function childHasAttribute(node, attrName) {
915 var hasChildren = node.hasChildNodes(),
918 function isHidden(el) {
919 var style = el.currentStyle ? el.currentStyle : $window.getComputedStyle(el);
920 return (style.display === 'none');
924 var children = node.childNodes;
925 for(var i=0; i<children.length; i++){
926 var child = children[i];
927 if(child.nodeType === 1 && child.hasAttribute(attrName)) {
928 if(!isHidden(child)){
937 AriaService.$inject = ["$$rAF", "$log", "$window"];
939 angular.module('material.core')
940 .service('$mdCompiler', mdCompilerService);
942 function mdCompilerService($q, $http, $injector, $compile, $controller, $templateCache) {
943 /* jshint validthis: true */
948 * @module material.core
950 * The $mdCompiler service is an abstraction of angular's compiler, that allows the developer
951 * to easily compile an element with a templateUrl, controller, and locals.
955 * $mdCompiler.compile({
956 * templateUrl: 'modal.html',
957 * controller: 'ModalCtrl',
959 * modal: myModalInstance;
961 * }).then(function(compileData) {
962 * compileData.element; // modal.html's template in an element
963 * compileData.link(myScope); //attach controller & scope to element
970 * @name $mdCompiler#compile
971 * @description A helper to compile an HTML template/templateUrl with a given controller,
973 * @param {object} options An options object, with the following properties:
975 * - `controller` - `{(string=|function()=}` Controller fn that should be associated with
976 * newly created scope or the name of a registered controller if passed as a string.
977 * - `controllerAs` - `{string=}` A controller alias name. If present the controller will be
978 * published to scope under the `controllerAs` name.
979 * - `template` - `{string=}` An html template as a string.
980 * - `templateUrl` - `{string=}` A path to an html template.
981 * - `transformTemplate` - `{function(template)=}` A function which transforms the template after
982 * it is loaded. It will be given the template string as a parameter, and should
983 * return a a new string representing the transformed template.
984 * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
985 * be injected into the controller. If any of these dependencies are promises, the compiler
986 * will wait for them all to be resolved, or if one is rejected before the controller is
987 * instantiated `compile()` will fail..
988 * * `key` - `{string}`: a name of a dependency to be injected into the controller.
989 * * `factory` - `{string|function}`: If `string` then it is an alias for a service.
990 * Otherwise if function, then it is injected and the return value is treated as the
991 * dependency. If the result is a promise, it is resolved before its value is
992 * injected into the controller.
994 * @returns {object=} promise A promise, which will be resolved with a `compileData` object.
995 * `compileData` has the following properties:
997 * - `element` - `{element}`: an uncompiled element matching the provided template.
998 * - `link` - `{function(scope)}`: A link function, which, when called, will compile
999 * the element and instantiate the provided controller (if given).
1000 * - `locals` - `{object}`: The locals which will be passed into the controller once `link` is
1001 * called. If `bindToController` is true, they will be coppied to the ctrl instead
1002 * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
1004 this.compile = function(options) {
1005 var templateUrl = options.templateUrl;
1006 var template = options.template || '';
1007 var controller = options.controller;
1008 var controllerAs = options.controllerAs;
1009 var resolve = options.resolve || {};
1010 var locals = options.locals || {};
1011 var transformTemplate = options.transformTemplate || angular.identity;
1012 var bindToController = options.bindToController;
1014 // Take resolve values and invoke them.
1015 // Resolves can either be a string (value: 'MyRegisteredAngularConst'),
1016 // or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {})
1017 angular.forEach(resolve, function(value, key) {
1018 if (angular.isString(value)) {
1019 resolve[key] = $injector.get(value);
1021 resolve[key] = $injector.invoke(value);
1024 //Add the locals, which are just straight values to inject
1025 //eg locals: { three: 3 }, will inject three into the controller
1026 angular.extend(resolve, locals);
1029 resolve.$template = $http.get(templateUrl, {cache: $templateCache})
1030 .then(function(response) {
1031 return response.data;
1034 resolve.$template = $q.when(template);
1037 // Wait for all the resolves to finish if they are promises
1038 return $q.all(resolve).then(function(locals) {
1040 var template = transformTemplate(locals.$template);
1041 var element = options.element || angular.element('<div>').html(template.trim()).contents();
1042 var linkFn = $compile(element);
1044 //Return a linking function that can be used later when the element is ready
1048 link: function link(scope) {
1049 locals.$scope = scope;
1051 //Instantiate controller if it exists, because we have scope
1053 var invokeCtrl = $controller(controller, locals, true);
1054 if (bindToController) {
1055 angular.extend(invokeCtrl.instance, locals);
1057 var ctrl = invokeCtrl();
1058 //See angular-route source for this logic
1059 element.data('$ngControllerController', ctrl);
1060 element.children().data('$ngControllerController', ctrl);
1063 scope[controllerAs] = ctrl;
1066 return linkFn(scope);
1073 mdCompilerService.$inject = ["$q", "$http", "$injector", "$compile", "$controller", "$templateCache"];
1076 /* The state of the current 'pointer'
1077 * The pointer represents the state of the current touch.
1078 * It contains normalized x and y coordinates from DOM events,
1079 * as well as other information abstracted from the DOM.
1081 var pointer, lastPointer, forceSkipClickHijack = false;
1083 // Used to attach event listeners once when multiple ng-apps are running.
1084 var isInitialized = false;
1087 .module('material.core.gestures', [ ])
1088 .provider('$mdGesture', MdGestureProvider)
1089 .factory('$$MdGestureHandler', MdGestureHandler)
1090 .run( attachToDocument );
1094 * @name $mdGestureProvider
1095 * @module material.core.gestures
1098 * In some scenarios on Mobile devices (without jQuery), the click events should NOT be hijacked.
1099 * `$mdGestureProvider` is used to configure the Gesture module to ignore or skip click hijacking on mobile
1103 * app.config(function($mdGestureProvider) {
1105 * // For mobile devices without jQuery loaded, do not
1106 * // intercept click events during the capture phase.
1107 * $mdGestureProvider.skipClickHijack();
1113 function MdGestureProvider() { }
1115 MdGestureProvider.prototype = {
1117 // Publish access to setter to configure a variable BEFORE the
1118 // $mdGesture service is instantiated...
1119 skipClickHijack: function() {
1120 return forceSkipClickHijack = true;
1124 * $get is used to build an instance of $mdGesture
1127 $get : ["$$MdGestureHandler", "$$rAF", "$timeout", function($$MdGestureHandler, $$rAF, $timeout) {
1128 return new MdGesture($$MdGestureHandler, $$rAF, $timeout);
1135 * MdGesture factory construction function
1138 function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
1139 var userAgent = navigator.userAgent || navigator.vendor || window.opera;
1140 var isIos = userAgent.match(/ipad|iphone|ipod/i);
1141 var isAndroid = userAgent.match(/android/i);
1142 var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
1145 handler: addHandler,
1147 // On mobile w/out jQuery, we normally intercept clicks. Should we skip that?
1148 isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack
1151 if (self.isHijackingClicks) {
1152 self.handler('click', {
1156 onEnd: function (ev, pointer) {
1157 if (pointer.distance < this.state.options.maxDistance) {
1158 this.dispatchEvent(ev, 'click');
1165 * Register an element to listen for a handler.
1166 * This allows an element to override the default options for a handler.
1167 * Additionally, some handlers like drag and hold only dispatch events if
1168 * the domEvent happens inside an element that's registered to listen for these events.
1170 * @see GestureHandler for how overriding of default options works.
1171 * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horziontal: false })
1173 function register(element, handlerName, options) {
1174 var handler = HANDLERS[handlerName.replace(/^\$md./, '')];
1176 throw new Error('Failed to register element with handler ' + handlerName + '. ' +
1177 'Available handlers: ' + Object.keys(HANDLERS).join(', '));
1179 return handler.registerElement(element, options);
1183 * add a handler to $mdGesture. see below.
1185 function addHandler(name, definition) {
1186 var handler = new $$MdGestureHandler(name);
1187 angular.extend(handler, definition);
1188 HANDLERS[name] = handler;
1194 * Register handlers. These listen to touch/start/move events, interpret them,
1195 * and dispatch gesture events depending on options & conditions. These are all
1196 * instances of GestureHandler.
1197 * @see GestureHandler
1201 * The press handler dispatches an event on touchdown/touchend.
1202 * It's a simple abstraction of touch/mouse/pointer start and end.
1205 onStart: function (ev, pointer) {
1206 this.dispatchEvent(ev, '$md.pressdown');
1208 onEnd: function (ev, pointer) {
1209 this.dispatchEvent(ev, '$md.pressup');
1214 * The hold handler dispatches an event if the user keeps their finger within
1215 * the same <maxDistance> area for <delay> ms.
1216 * The hold handler will only run if a parent of the touch target is registered
1217 * to listen for hold events through $mdGesture.register()
1224 onCancel: function () {
1225 $timeout.cancel(this.state.timeout);
1227 onStart: function (ev, pointer) {
1228 // For hold, require a parent to be registered with $mdGesture.register()
1229 // Because we prevent scroll events, this is necessary.
1230 if (!this.state.registeredParent) return this.cancel();
1232 this.state.pos = {x: pointer.x, y: pointer.y};
1233 this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() {
1234 this.dispatchEvent(ev, '$md.hold');
1235 this.cancel(); //we're done!
1236 }), this.state.options.delay, false);
1238 onMove: function (ev, pointer) {
1239 // Don't scroll while waiting for hold.
1240 // If we don't preventDefault touchmove events here, Android will assume we don't
1241 // want to listen to anymore touch events. It will start scrolling and stop sending
1242 // touchmove events.
1243 ev.preventDefault();
1245 // If the user moves greater than <maxDistance> pixels, stop the hold timer
1247 var dx = this.state.pos.x - pointer.x;
1248 var dy = this.state.pos.y - pointer.y;
1249 if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) {
1253 onEnd: function () {
1259 * The drag handler dispatches a drag event if the user holds and moves his finger greater than
1260 * <minDistance> px in the x or y direction, depending on options.horizontal.
1261 * The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in
1262 * the perpindicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>
1263 * pixels vertically, this handler won't consider the move part of a drag.
1269 cancelMultiplier: 1.5
1271 onStart: function (ev) {
1272 // For drag, require a parent to be registered with $mdGesture.register()
1273 if (!this.state.registeredParent) this.cancel();
1275 onMove: function (ev, pointer) {
1276 var shouldStartDrag, shouldCancel;
1277 // Don't scroll while deciding if this touchmove qualifies as a drag event.
1278 // If we don't preventDefault touchmove events here, Android will assume we don't
1279 // want to listen to anymore touch events. It will start scrolling and stop sending
1280 // touchmove events.
1281 ev.preventDefault();
1283 if (!this.state.dragPointer) {
1284 if (this.state.options.horizontal) {
1285 shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance;
1286 shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier;
1288 shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance;
1289 shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier;
1292 if (shouldStartDrag) {
1293 // Create a new pointer representing this drag, starting at this point where the drag started.
1294 this.state.dragPointer = makeStartPointer(ev);
1295 updatePointerState(ev, this.state.dragPointer);
1296 this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer);
1298 } else if (shouldCancel) {
1302 this.dispatchDragMove(ev);
1305 // Only dispatch dragmove events every frame; any more is unnecessray
1306 dispatchDragMove: $$rAF.throttle(function (ev) {
1307 // Make sure the drag didn't stop while waiting for the next frame
1308 if (this.state.isRunning) {
1309 updatePointerState(ev, this.state.dragPointer);
1310 this.dispatchEvent(ev, '$md.drag', this.state.dragPointer);
1313 onEnd: function (ev, pointer) {
1314 if (this.state.dragPointer) {
1315 updatePointerState(ev, this.state.dragPointer);
1316 this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer);
1322 * The swipe handler will dispatch a swipe event if, on the end of a touch,
1323 * the velocity and distance were high enough.
1324 * TODO: add vertical swiping with a `horizontal` option similar to the drag handler.
1331 onEnd: function (ev, pointer) {
1332 if (Math.abs(pointer.velocityX) > this.state.options.minVelocity &&
1333 Math.abs(pointer.distanceX) > this.state.options.minDistance) {
1334 var eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight';
1335 this.dispatchEvent(ev, eventType);
1341 MdGesture.$inject = ["$$MdGestureHandler", "$$rAF", "$timeout"];
1345 * A GestureHandler is an object which is able to dispatch custom dom events
1346 * based on native dom {touch,pointer,mouse}{start,move,end} events.
1348 * A gesture will manage its lifecycle through the start,move,end, and cancel
1349 * functions, which are called by native dom events.
1351 * A gesture has the concept of 'options' (eg a swipe's required velocity), which can be
1352 * overridden by elements registering through $mdGesture.register()
1354 function GestureHandler (name) {
1359 function MdGestureHandler() {
1360 var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
1362 GestureHandler.prototype = {
1364 // jQuery listeners don't work with custom DOMEvents, so we have to dispatch events
1365 // differently when jQuery is loaded
1366 dispatchEvent: hasJQuery ? jQueryDispatchEvent : nativeDispatchEvent,
1368 // These are overridden by the registered handler
1369 onStart: angular.noop,
1370 onMove: angular.noop,
1371 onEnd: angular.noop,
1372 onCancel: angular.noop,
1374 // onStart sets up a new state for the handler, which includes options from the
1375 // nearest registered parent element of ev.target.
1376 start: function (ev, pointer) {
1377 if (this.state.isRunning) return;
1378 var parentTarget = this.getNearestParent(ev.target);
1379 // Get the options from the nearest registered parent
1380 var parentTargetOptions = parentTarget && parentTarget.$mdGesture[this.name] || {};
1384 // Override the default options with the nearest registered parent's options
1385 options: angular.extend({}, this.options, parentTargetOptions),
1386 // Pass in the registered parent node to the state so the onStart listener can use
1387 registeredParent: parentTarget
1389 this.onStart(ev, pointer);
1391 move: function (ev, pointer) {
1392 if (!this.state.isRunning) return;
1393 this.onMove(ev, pointer);
1395 end: function (ev, pointer) {
1396 if (!this.state.isRunning) return;
1397 this.onEnd(ev, pointer);
1398 this.state.isRunning = false;
1400 cancel: function (ev, pointer) {
1401 this.onCancel(ev, pointer);
1405 // Find and return the nearest parent element that has been registered to
1406 // listen for this handler via $mdGesture.register(element, 'handlerName').
1407 getNearestParent: function (node) {
1410 if ((current.$mdGesture || {})[this.name]) {
1413 current = current.parentNode;
1418 // Called from $mdGesture.register when an element reigsters itself with a handler.
1419 // Store the options the user gave on the DOMElement itself. These options will
1420 // be retrieved with getNearestParent when the handler starts.
1421 registerElement: function (element, options) {
1423 element[0].$mdGesture = element[0].$mdGesture || {};
1424 element[0].$mdGesture[this.name] = options || {};
1425 element.on('$destroy', onDestroy);
1429 function onDestroy() {
1430 delete element[0].$mdGesture[self.name];
1431 element.off('$destroy', onDestroy);
1436 return GestureHandler;
1439 * Dispatch an event with jQuery
1440 * TODO: Make sure this sends bubbling events
1442 * @param srcEvent the original DOM touch event that started this.
1443 * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
1444 * @param eventPointer the pointer object that matches this event.
1446 function jQueryDispatchEvent(srcEvent, eventType, eventPointer) {
1447 eventPointer = eventPointer || pointer;
1448 var eventObj = new angular.element.Event(eventType);
1450 eventObj.$material = true;
1451 eventObj.pointer = eventPointer;
1452 eventObj.srcEvent = srcEvent;
1454 angular.extend(eventObj, {
1455 clientX: eventPointer.x,
1456 clientY: eventPointer.y,
1457 screenX: eventPointer.x,
1458 screenY: eventPointer.y,
1459 pageX: eventPointer.x,
1460 pageY: eventPointer.y,
1461 ctrlKey: srcEvent.ctrlKey,
1462 altKey: srcEvent.altKey,
1463 shiftKey: srcEvent.shiftKey,
1464 metaKey: srcEvent.metaKey
1466 angular.element(eventPointer.target).trigger(eventObj);
1470 * NOTE: nativeDispatchEvent is very performance sensitive.
1471 * @param srcEvent the original DOM touch event that started this.
1472 * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')
1473 * @param eventPointer the pointer object that matches this event.
1475 function nativeDispatchEvent(srcEvent, eventType, eventPointer) {
1476 eventPointer = eventPointer || pointer;
1479 if (eventType === 'click') {
1480 eventObj = document.createEvent('MouseEvents');
1481 eventObj.initMouseEvent(
1482 'click', true, true, window, srcEvent.detail,
1483 eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y,
1484 srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey,
1485 srcEvent.button, srcEvent.relatedTarget || null
1489 eventObj = document.createEvent('CustomEvent');
1490 eventObj.initCustomEvent(eventType, true, true, {});
1492 eventObj.$material = true;
1493 eventObj.pointer = eventPointer;
1494 eventObj.srcEvent = srcEvent;
1495 eventPointer.target.dispatchEvent(eventObj);
1501 * Attach Gestures: hook document and check shouldHijack clicks
1504 function attachToDocument( $mdGesture, $$MdGestureHandler ) {
1506 // Polyfill document.contains for IE11.
1507 // TODO: move to util
1508 document.contains || (document.contains = function (node) {
1509 return document.body.contains(node);
1512 if (!isInitialized && $mdGesture.isHijackingClicks ) {
1514 * If hijack clicks is true, we preventDefault any click that wasn't
1515 * sent by ngMaterial. This is because on older Android & iOS, a false, or 'ghost',
1516 * click event will be sent ~400ms after a touchend event happens.
1517 * The only way to know if this click is real is to prevent any normal
1518 * click events, and add a flag to events sent by material so we know not to prevent those.
1520 * Two exceptions to click events that should be prevented are:
1521 * - click events sent by the keyboard (eg form submit)
1522 * - events that originate from an Ionic app
1524 document.addEventListener('click', function clickHijacker(ev) {
1525 var isKeyClick = ev.clientX === 0 && ev.clientY === 0;
1526 if (!isKeyClick && !ev.$material && !ev.isIonicTap) {
1527 ev.preventDefault();
1528 ev.stopPropagation();
1532 isInitialized = true;
1535 // Listen to all events to cover all platforms.
1536 var START_EVENTS = 'mousedown touchstart pointerdown';
1537 var MOVE_EVENTS = 'mousemove touchmove pointermove';
1538 var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel';
1540 angular.element(document)
1541 .on(START_EVENTS, gestureStart)
1542 .on(MOVE_EVENTS, gestureMove)
1543 .on(END_EVENTS, gestureEnd)
1545 .on('$$mdGestureReset', function gestureClearCache () {
1546 lastPointer = pointer = null;
1550 * When a DOM event happens, run all registered gesture handlers' lifecycle
1551 * methods which match the DOM event.
1552 * Eg when a 'touchstart' event happens, runHandlers('start') will call and
1553 * run `handler.cancel()` and `handler.start()` on all registered handlers.
1555 function runHandlers(handlerEvent, event) {
1557 for (var name in HANDLERS) {
1558 handler = HANDLERS[name];
1559 if( handler instanceof $$MdGestureHandler ) {
1561 if (handlerEvent === 'start') {
1562 // Run cancel to reset any handlers' state
1565 handler[handlerEvent](event, pointer);
1572 * gestureStart vets if a start event is legitimate (and not part of a 'ghost click' from iOS/Android)
1573 * If it is legitimate, we initiate the pointer state and mark the current pointer's type
1574 * For example, for a touchstart event, mark the current pointer as a 'touch' pointer, so mouse events
1577 function gestureStart(ev) {
1578 // If we're already touched down, abort
1579 if (pointer) return;
1581 var now = +Date.now();
1583 // iOS & old android bug: after a touch event, a click event is sent 350 ms later.
1584 // If <400ms have passed, don't allow an event of a different type than the previous event
1585 if (lastPointer && !typesMatch(ev, lastPointer) && (now - lastPointer.endTime < 1500)) {
1589 pointer = makeStartPointer(ev);
1591 runHandlers('start', ev);
1594 * If a move event happens of the right type, update the pointer and run all the move handlers.
1595 * "of the right type": if a mousemove happens but our pointer started with a touch event, do nothing.
1597 function gestureMove(ev) {
1598 if (!pointer || !typesMatch(ev, pointer)) return;
1600 updatePointerState(ev, pointer);
1601 runHandlers('move', ev);
1604 * If an end event happens of the right type, update the pointer, run endHandlers, and save the pointer as 'lastPointer'
1606 function gestureEnd(ev) {
1607 if (!pointer || !typesMatch(ev, pointer)) return;
1609 updatePointerState(ev, pointer);
1610 pointer.endTime = +Date.now();
1612 runHandlers('end', ev);
1614 lastPointer = pointer;
1619 attachToDocument.$inject = ["$mdGesture", "$$MdGestureHandler"];
1621 // ********************
1623 // ********************
1626 * Initiate the pointer. x, y, and the pointer's type.
1628 function makeStartPointer(ev) {
1629 var point = getEventPoint(ev);
1630 var startPointer = {
1631 startTime: +Date.now(),
1633 // 'p' for pointer events, 'm' for mouse, 't' for touch
1634 type: ev.type.charAt(0)
1636 startPointer.startX = startPointer.x = point.pageX;
1637 startPointer.startY = startPointer.y = point.pageY;
1638 return startPointer;
1642 * return whether the pointer's type matches the event's type.
1643 * Eg if a touch event happens but the pointer has a mouse type, return false.
1645 function typesMatch(ev, pointer) {
1646 return ev && pointer && ev.type.charAt(0) === pointer.type;
1650 * Update the given pointer based upon the given DOMEvent.
1651 * Distance, velocity, direction, duration, etc
1653 function updatePointerState(ev, pointer) {
1654 var point = getEventPoint(ev);
1655 var x = pointer.x = point.pageX;
1656 var y = pointer.y = point.pageY;
1658 pointer.distanceX = x - pointer.startX;
1659 pointer.distanceY = y - pointer.startY;
1660 pointer.distance = Math.sqrt(
1661 pointer.distanceX * pointer.distanceX + pointer.distanceY * pointer.distanceY
1664 pointer.directionX = pointer.distanceX > 0 ? 'right' : pointer.distanceX < 0 ? 'left' : '';
1665 pointer.directionY = pointer.distanceY > 0 ? 'up' : pointer.distanceY < 0 ? 'down' : '';
1667 pointer.duration = +Date.now() - pointer.startTime;
1668 pointer.velocityX = pointer.distanceX / pointer.duration;
1669 pointer.velocityY = pointer.distanceY / pointer.duration;
1673 * Normalize the point where the DOM event happened whether it's touch or mouse.
1674 * @returns point event obj with pageX and pageY on it.
1676 function getEventPoint(ev) {
1677 ev = ev.originalEvent || ev; // support jQuery events
1678 return (ev.touches && ev.touches[0]) ||
1679 (ev.changedTouches && ev.changedTouches[0]) ||
1683 angular.module('material.core')
1684 .provider('$$interimElement', InterimElementProvider);
1688 * @name $$interimElement
1689 * @module material.core
1693 * Factory that contructs `$$interimElement.$service` services.
1694 * Used internally in material design for elements that appear on screen temporarily.
1695 * The service provides a promise-like API for interacting with the temporary
1699 * app.service('$mdToast', function($$interimElement) {
1700 * var $mdToast = $$interimElement(toastDefaultOptions);
1704 * @param {object=} defaultOptions Options used by default for the `show` method on the service.
1706 * @returns {$$interimElement.$service}
1710 function InterimElementProvider() {
1711 createInterimElementProvider.$get = InterimElementFactory;
1712 InterimElementFactory.$inject = ["$document", "$q", "$rootScope", "$timeout", "$rootElement", "$animate", "$interpolate", "$mdCompiler", "$mdTheming"];
1713 return createInterimElementProvider;
1716 * Returns a new provider which allows configuration of a new interimElement
1717 * service. Allows configuration of default options & methods for options,
1718 * as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method)
1720 function createInterimElementProvider(interimFactoryName) {
1721 var EXPOSED_METHODS = ['onHide', 'onShow', 'onRemove'];
1723 var customMethods = {};
1724 var providerConfig = {
1729 setDefaults: setDefaults,
1730 addPreset: addPreset,
1731 addMethod: addMethod,
1736 * all interim elements will come with the 'build' preset
1738 provider.addPreset('build', {
1739 methods: ['controller', 'controllerAs', 'resolve',
1740 'template', 'templateUrl', 'themable', 'transformTemplate', 'parent']
1743 factory.$inject = ["$$interimElement", "$animate", "$injector"];
1747 * Save the configured defaults to be used when the factory is instantiated
1749 function setDefaults(definition) {
1750 providerConfig.optionsFactory = definition.options;
1751 providerConfig.methods = (definition.methods || []).concat(EXPOSED_METHODS);
1756 * Add a method to the factory that isn't specific to any interim element operations
1759 function addMethod(name, fn) {
1760 customMethods[name] = fn;
1765 * Save the configured preset to be used when the factory is instantiated
1767 function addPreset(name, definition) {
1768 definition = definition || {};
1769 definition.methods = definition.methods || [];
1770 definition.options = definition.options || function() { return {}; };
1772 if (/^cancel|hide|show$/.test(name)) {
1773 throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!");
1775 if (definition.methods.indexOf('_options') > -1) {
1776 throw new Error("Method '_options' in " + interimFactoryName + " is reserved!");
1778 providerConfig.presets[name] = {
1779 methods: definition.methods.concat(EXPOSED_METHODS),
1780 optionsFactory: definition.options,
1781 argOption: definition.argOption
1787 * Create a factory that has the given methods & defaults implementing interimElement
1790 function factory($$interimElement, $animate, $injector) {
1793 var interimElementService = $$interimElement();
1796 * publicService is what the developer will be using.
1797 * It has methods hide(), cancel(), show(), build(), and any other
1798 * presets which were set during the config phase.
1800 var publicService = {
1801 hide: interimElementService.hide,
1802 cancel: interimElementService.cancel,
1803 show: showInterimElement
1806 defaultMethods = providerConfig.methods || [];
1807 // This must be invoked after the publicService is initialized
1808 defaultOptions = invokeFactory(providerConfig.optionsFactory, {});
1810 // Copy over the simple custom methods
1811 angular.forEach(customMethods, function(fn, name) {
1812 publicService[name] = fn;
1815 angular.forEach(providerConfig.presets, function(definition, name) {
1816 var presetDefaults = invokeFactory(definition.optionsFactory, {});
1817 var presetMethods = (definition.methods || []).concat(defaultMethods);
1819 // Every interimElement built with a preset has a field called `$type`,
1820 // which matches the name of the preset.
1821 // Eg in preset 'confirm', options.$type === 'confirm'
1822 angular.extend(presetDefaults, { $type: name });
1824 // This creates a preset class which has setter methods for every
1825 // method given in the `.addPreset()` function, as well as every
1826 // method given in the `.setDefaults()` function.
1830 // methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'],
1831 // options: dialogDefaultOptions
1833 // .addPreset('alert', {
1834 // methods: ['title', 'ok'],
1835 // options: alertDialogOptions
1838 // Set values will be passed to the options when interimElemnt.show() is called.
1839 function Preset(opts) {
1840 this._options = angular.extend({}, presetDefaults, opts);
1842 angular.forEach(presetMethods, function(name) {
1843 Preset.prototype[name] = function(value) {
1844 this._options[name] = value;
1849 // Create shortcut method for one-linear methods
1850 if (definition.argOption) {
1851 var methodName = 'show' + name.charAt(0).toUpperCase() + name.slice(1);
1852 publicService[methodName] = function(arg) {
1853 var config = publicService[name](arg);
1854 return publicService.show(config);
1858 // eg $mdDialog.alert() will return a new alert preset
1859 publicService[name] = function(arg) {
1860 // If argOption is supplied, eg `argOption: 'content'`, then we assume
1861 // if the argument is not an options object then it is the `argOption` option.
1863 // @example `$mdToast.simple('hello')` // sets options.content to hello
1864 // // because argOption === 'content'
1865 if (arguments.length && definition.argOption && !angular.isObject(arg) &&
1866 !angular.isArray(arg)) {
1867 return (new Preset())[definition.argOption](arg);
1869 return new Preset(arg);
1875 return publicService;
1877 function showInterimElement(opts) {
1878 // opts is either a preset which stores its options on an _options field,
1879 // or just an object made up of options
1880 if (opts && opts._options) opts = opts._options;
1881 return interimElementService.show(
1882 angular.extend({}, defaultOptions, opts)
1887 * Helper to call $injector.invoke with a local of the factory name for
1889 * If an $mdDialog is providing options for a dialog and tries to inject
1890 * $mdDialog, a circular dependency error will happen.
1891 * We get around that by manually injecting $mdDialog as a local.
1893 function invokeFactory(factory, defaultVal) {
1895 locals[interimFactoryName] = publicService;
1896 return $injector.invoke(factory || function() { return defaultVal; }, {}, locals);
1904 function InterimElementFactory($document, $q, $rootScope, $timeout, $rootElement, $animate,
1905 $interpolate, $mdCompiler, $mdTheming ) {
1906 var startSymbol = $interpolate.startSymbol(),
1907 endSymbol = $interpolate.endSymbol(),
1908 usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}')),
1909 processTemplate = usesStandardSymbols ? angular.identity : replaceInterpolationSymbols;
1911 return function createInterimElementService() {
1914 * @name $$interimElement.$service
1917 * A service used to control inserting and removing an element into the DOM.
1930 * @name $$interimElement.$service#show
1934 * Adds the `$interimElement` to the DOM and returns a promise that will be resolved or rejected
1935 * with hide or cancel, respectively.
1937 * @param {*} options is hashMap of settings
1938 * @returns a Promise
1941 function show(options) {
1943 return service.cancel().then(function() {
1944 return show(options);
1947 var interimElement = new InterimElement(options);
1948 stack.push(interimElement);
1949 return interimElement.show().then(function() {
1950 return interimElement.deferred.promise;
1957 * @name $$interimElement.$service#hide
1961 * Removes the `$interimElement` from the DOM and resolves the promise returned from `show`
1963 * @param {*} resolveParam Data to resolve the promise with
1964 * @returns a Promise that will be resolved after the element has been removed.
1967 function hide(response) {
1968 var interimElement = stack.shift();
1969 return interimElement && interimElement.remove().then(function() {
1970 interimElement.deferred.resolve(response);
1976 * @name $$interimElement.$service#cancel
1980 * Removes the `$interimElement` from the DOM and rejects the promise returned from `show`
1982 * @param {*} reason Data to reject the promise with
1983 * @returns Promise that will be resolved after the element has been removed.
1986 function cancel(reason) {
1987 var interimElement = stack.shift();
1988 return $q.when(interimElement && interimElement.remove().then(function() {
1989 interimElement.deferred.reject(reason);
1995 * Internal Interim Element Object
1996 * Used internally to manage the DOM element and related data
1998 function InterimElement(options) {
2000 var hideTimeout, element, showDone, removeDone;
2002 options = options || {};
2003 options = angular.extend({
2004 preserveScope: false,
2005 scope: options.scope || $rootScope.$new(options.isolateScope),
2006 onShow: function(scope, element, options) {
2007 return $animate.enter(element, options.parent);
2009 onRemove: function(scope, element, options) {
2010 // Element could be undefined if a new element is shown before
2011 // the old one finishes compiling.
2012 return element && $animate.leave(element) || $q.when();
2016 if (options.template) {
2017 options.template = processTemplate(options.template);
2022 deferred: $q.defer(),
2025 if (options.skipCompile) {
2026 compilePromise = $q(function(resolve) {
2029 link: function() { return options.element; }
2033 compilePromise = $mdCompiler.compile(options);
2036 return showDone = compilePromise.then(function(compileData) {
2037 angular.extend(compileData.locals, self.options);
2039 element = compileData.link(options.scope);
2041 // Search for parent at insertion time, if not specified
2042 if (angular.isFunction(options.parent)) {
2043 options.parent = options.parent(options.scope, element, options);
2044 } else if (angular.isString(options.parent)) {
2045 options.parent = angular.element($document[0].querySelector(options.parent));
2048 // If parent querySelector/getter function fails, or it's just null,
2050 if (!(options.parent || {}).length) {
2052 if ($rootElement[0] && $rootElement[0].querySelector) {
2053 el = $rootElement[0].querySelector(':not(svg) > body');
2055 if (!el) el = $rootElement[0];
2056 if (el.nodeName == '#comment') {
2057 el = $document[0].body;
2059 options.parent = angular.element(el);
2062 if (options.themable) $mdTheming(element);
2063 var ret = options.onShow(options.scope, element, options);
2066 // Issue onComplete callback when the `show()` finishes
2067 (options.onComplete || angular.noop)(options.scope, element, options);
2071 function startHideTimeout() {
2072 if (options.hideDelay) {
2073 hideTimeout = $timeout(service.cancel, options.hideDelay) ;
2076 }, function(reason) { showDone = true; self.deferred.reject(reason); });
2078 cancelTimeout: function() {
2080 $timeout.cancel(hideTimeout);
2081 hideTimeout = undefined;
2084 remove: function() {
2085 self.cancelTimeout();
2086 return removeDone = $q.when(showDone).then(function() {
2087 var ret = element ? options.onRemove(options.scope, element, options) : true;
2088 return $q.when(ret).then(function() {
2089 if (!options.preserveScope) options.scope.$destroy();
2099 * Replace `{{` and `}}` in a string (usually a template) with the actual start-/endSymbols used
2100 * for interpolation. This allows pre-defined templates (for components such as dialog, toast etc)
2101 * to continue to work in apps that use custom interpolation start-/endSymbols.
2103 * @param {string} text The text in which to replace `{{` / `}}`
2104 * @returns {string} The modified string using the actual interpolation start-/endSymbols
2106 function replaceInterpolationSymbols(text) {
2107 if (!text || !angular.isString(text)) return text;
2108 return text.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
2116 * @name material.core.componentRegistry
2119 * A component instance registration service.
2120 * Note: currently this as a private service in the SideNav component.
2122 angular.module('material.core')
2123 .factory('$mdComponentRegistry', ComponentRegistry);
2128 * @name ComponentRegistry
2129 * @module material.core.componentRegistry
2132 function ComponentRegistry($log, $q) {
2135 var instances = [ ];
2140 * Used to print an error when an instance for a handle isn't found.
2142 notFoundError: function(handle) {
2143 $log.error('No instance found for handle', handle);
2146 * Return all registered instances as an array.
2148 getInstances: function() {
2153 * Get a registered instance.
2154 * @param handle the String handle to look up for a registered instance.
2156 get: function(handle) {
2157 if ( !isValidID(handle) ) return null;
2160 for(i = 0, j = instances.length; i < j; i++) {
2161 instance = instances[i];
2162 if(instance.$$mdHandle === handle) {
2170 * Register an instance.
2171 * @param instance the instance to register
2172 * @param handle the handle to identify the instance under.
2174 register: function(instance, handle) {
2175 if ( !handle ) return angular.noop;
2177 instance.$$mdHandle = handle;
2178 instances.push(instance);
2184 * Remove registration for an instance
2186 function deregister() {
2187 var index = instances.indexOf(instance);
2189 instances.splice(index, 1);
2194 * Resolve any pending promises for this instance
2196 function resolveWhen() {
2197 var dfd = pendings[handle];
2199 dfd.resolve( instance );
2200 delete pendings[handle];
2206 * Async accessor to registered component instance
2207 * If not available then a promise is created to notify
2208 * all listeners when the instance is registered.
2210 when : function(handle) {
2211 if ( isValidID(handle) ) {
2212 var deferred = $q.defer();
2213 var instance = self.get(handle);
2216 deferred.resolve( instance );
2218 pendings[handle] = deferred;
2221 return deferred.promise;
2223 return $q.reject("Invalid `md-component-id` value.");
2228 function isValidID(handle){
2229 return handle && (handle !== "");
2233 ComponentRegistry.$inject = ["$log", "$q"];
2240 * @name $mdButtonInkRipple
2241 * @module material.core
2244 * Provides ripple effects for md-button. See $mdInkRipple service for all possible configuration options.
2246 * @param {object=} scope Scope within the current context
2247 * @param {object=} element The element the ripple effect should be applied to
2248 * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
2251 angular.module('material.core')
2252 .factory('$mdButtonInkRipple', MdButtonInkRipple);
2254 function MdButtonInkRipple($mdInkRipple) {
2259 function attach(scope, element, options) {
2260 var elementOptions = optionsForElement(element);
2261 return $mdInkRipple.attach(scope, element, angular.extend(elementOptions, options));
2264 function optionsForElement(element) {
2265 if (element.hasClass('md-icon-button')) {
2267 isMenuItem: element.hasClass('md-menu-item'),
2273 isMenuItem: element.hasClass('md-menu-item'),
2279 MdButtonInkRipple.$inject = ["$mdInkRipple"];;
2287 * @name $mdCheckboxInkRipple
2288 * @module material.core
2291 * Provides ripple effects for md-checkbox. See $mdInkRipple service for all possible configuration options.
2293 * @param {object=} scope Scope within the current context
2294 * @param {object=} element The element the ripple effect should be applied to
2295 * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
2298 angular.module('material.core')
2299 .factory('$mdCheckboxInkRipple', MdCheckboxInkRipple);
2301 function MdCheckboxInkRipple($mdInkRipple) {
2306 function attach(scope, element, options) {
2307 return $mdInkRipple.attach(scope, element, angular.extend({
2309 dimBackground: false,
2314 MdCheckboxInkRipple.$inject = ["$mdInkRipple"];;
2322 * @name $mdListInkRipple
2323 * @module material.core
2326 * Provides ripple effects for md-list. See $mdInkRipple service for all possible configuration options.
2328 * @param {object=} scope Scope within the current context
2329 * @param {object=} element The element the ripple effect should be applied to
2330 * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
2333 angular.module('material.core')
2334 .factory('$mdListInkRipple', MdListInkRipple);
2336 function MdListInkRipple($mdInkRipple) {
2341 function attach(scope, element, options) {
2342 return $mdInkRipple.attach(scope, element, angular.extend({
2344 dimBackground: true,
2350 MdListInkRipple.$inject = ["$mdInkRipple"];;
2353 angular.module('material.core')
2354 .factory('$mdInkRipple', InkRippleService)
2355 .directive('mdInkRipple', InkRippleDirective)
2356 .directive('mdNoInk', attrNoDirective())
2357 .directive('mdNoBar', attrNoDirective())
2358 .directive('mdNoStretch', attrNoDirective());
2360 function InkRippleDirective($mdButtonInkRipple, $mdCheckboxInkRipple) {
2362 controller: angular.noop,
2363 link: function (scope, element, attr) {
2364 if (attr.hasOwnProperty('mdInkRippleCheckbox')) {
2365 $mdCheckboxInkRipple.attach(scope, element);
2367 $mdButtonInkRipple.attach(scope, element);
2372 InkRippleDirective.$inject = ["$mdButtonInkRipple", "$mdCheckboxInkRipple"];
2374 function InkRippleService($window, $timeout) {
2380 function attach(scope, element, options) {
2381 if (element.controller('mdNoInk')) return angular.noop;
2383 options = angular.extend({
2384 colorElement: element,
2389 mousedownPauseTime: 150,
2390 dimBackground: false,
2398 controller = element.controller('mdInkRipple') || {},
2402 isActiveExpr = element.attr('md-highlight'),
2406 rippleSizeSetting = element.attr('md-ripple-size'),
2407 color = parseColor(element.attr('md-ink-ripple')) || parseColor(options.colorElement.length && $window.getComputedStyle(options.colorElement[0]).color || 'rgb(0, 0, 0)');
2409 switch (rippleSizeSetting) {
2411 options.fullRipple = true;
2414 options.fullRipple = false;
2418 // expose onInput for ripple testing
2419 if (options.mousedown) {
2420 element.on('$md.pressdown', onPressDown)
2421 .on('$md.pressup', onPressUp);
2424 controller.createRipple = createRipple;
2427 scope.$watch(isActiveExpr, function watchActive(newValue) {
2428 isActive = newValue;
2429 if (isActive && !ripples.length) {
2430 $timeout(function () { createRipple(0, 0); }, 0, false);
2432 angular.forEach(ripples, updateElement);
2436 // Publish self-detach method if desired...
2437 return function detach() {
2438 element.off('$md.pressdown', onPressDown)
2439 .off('$md.pressup', onPressUp);
2440 getRippleContainer().remove();
2444 * Gets the current ripple container
2445 * If there is no ripple container, it creates one and returns it
2447 * @returns {angular.element} ripple container element
2449 function getRippleContainer() {
2450 var container = element.data('$mdRippleContainer');
2451 if (container) return container;
2452 container = angular.element('<div class="md-ripple-container">');
2453 element.append(container);
2454 element.data('$mdRippleContainer', container);
2458 function parseColor(color) {
2460 if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, '0.1)');
2461 if (color.indexOf('rgb') === 0) return rgbToRGBA(color);
2462 if (color.indexOf('#') === 0) return hexToRGBA(color);
2465 * Converts a hex value to an rgba string
2467 * @param {string} hex value (3 or 6 digits) to be converted
2469 * @returns {string} rgba color with 0.1 alpha
2471 function hexToRGBA(color) {
2472 var hex = color.charAt(0) === '#' ? color.substr(1) : color,
2473 dig = hex.length / 3,
2474 red = hex.substr(0, dig),
2475 grn = hex.substr(dig, dig),
2476 blu = hex.substr(dig * 2);
2482 return 'rgba(' + parseInt(red, 16) + ',' + parseInt(grn, 16) + ',' + parseInt(blu, 16) + ',0.1)';
2486 * Converts rgb value to rgba string
2488 * @param {string} rgb color string
2490 * @returns {string} rgba color with 0.1 alpha
2492 function rgbToRGBA(color) {
2493 return color.replace(')', ', 0.1)').replace('(', 'a(');
2498 function removeElement(elem, wait) {
2499 ripples.splice(ripples.indexOf(elem), 1);
2500 if (ripples.length === 0) {
2501 getRippleContainer().css({ backgroundColor: '' });
2503 $timeout(function () { elem.remove(); }, wait, false);
2506 function updateElement(elem) {
2507 var index = ripples.indexOf(elem),
2508 state = states[index] || {},
2509 elemIsActive = ripples.length > 1 ? false : isActive,
2510 elemIsHeld = ripples.length > 1 ? false : isHeld;
2511 if (elemIsActive || state.animating || elemIsHeld) {
2512 elem.addClass('md-ripple-visible');
2514 elem.removeClass('md-ripple-visible');
2515 if (options.outline) {
2517 width: rippleSize + 'px',
2518 height: rippleSize + 'px',
2519 marginLeft: (rippleSize * -1) + 'px',
2520 marginTop: (rippleSize * -1) + 'px'
2523 removeElement(elem, options.outline ? 450 : 650);
2528 * Creates a ripple at the provided coordinates
2530 * @param {number} left cursor position
2531 * @param {number} top cursor position
2533 * @returns {angular.element} the generated ripple element
2535 function createRipple(left, top) {
2537 color = parseColor(element.attr('md-ink-ripple')) || parseColor($window.getComputedStyle(options.colorElement[0]).color || 'rgb(0, 0, 0)');
2539 var container = getRippleContainer(),
2540 size = getRippleSize(left, top),
2541 css = getRippleCss(size, left, top),
2542 elem = getRippleElement(css),
2543 index = ripples.indexOf(elem),
2544 state = states[index] || {};
2548 state.animating = true;
2550 $timeout(function () {
2551 if (options.dimBackground) {
2552 container.css({ backgroundColor: color });
2554 elem.addClass('md-ripple-placed md-ripple-scaled');
2555 if (options.outline) {
2557 borderWidth: (size * 0.5) + 'px',
2558 marginLeft: (size * -0.5) + 'px',
2559 marginTop: (size * -0.5) + 'px'
2562 elem.css({ left: '50%', top: '50%' });
2564 updateElement(elem);
2565 $timeout(function () {
2566 state.animating = false;
2567 updateElement(elem);
2568 }, (options.outline ? 450 : 225), false);
2574 * Creates the ripple element with the provided css
2576 * @param {object} css properties to be applied
2578 * @returns {angular.element} the generated ripple element
2580 function getRippleElement(css) {
2581 var elem = angular.element('<div class="md-ripple" data-counter="' + counter++ + '">');
2582 ripples.unshift(elem);
2583 states.unshift({ animating: true });
2584 container.append(elem);
2585 css && elem.css(css);
2590 * Calculate the ripple size
2592 * @returns {number} calculated ripple diameter
2594 function getRippleSize(left, top) {
2595 var width = container.prop('offsetWidth'),
2596 height = container.prop('offsetHeight'),
2597 multiplier, size, rect;
2598 if (options.isMenuItem) {
2599 size = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
2600 } else if (options.outline) {
2601 rect = node.getBoundingClientRect();
2604 width = Math.max(left, width - left);
2605 height = Math.max(top, height - top);
2606 size = 2 * Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
2608 multiplier = options.fullRipple ? 1.1 : 0.8;
2609 size = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) * multiplier;
2610 if (options.fitRipple) {
2611 size = Math.min(height, width, size);
2618 * Generates the ripple css
2620 * @param {number} the diameter of the ripple
2621 * @param {number} the left cursor offset
2622 * @param {number} the top cursor offset
2624 * @returns {{backgroundColor: string, borderColor: string, width: string, height: string}}
2626 function getRippleCss(size, left, top) {
2627 var rect = node.getBoundingClientRect(),
2629 backgroundColor: rgbaToRGB(color),
2630 borderColor: rgbaToRGB(color),
2635 if (options.outline) {
2639 css.marginLeft = css.marginTop = (size * -0.5) + 'px';
2642 if (options.center) {
2643 css.left = css.top = '50%';
2645 css.left = Math.round((left - rect.left) / container.prop('offsetWidth') * 100) + '%';
2646 css.top = Math.round((top - rect.top) / container.prop('offsetHeight') * 100) + '%';
2652 * Converts rgba string to rgb, removing the alpha value
2654 * @param {string} rgba color
2656 * @returns {string} rgb color
2658 function rgbaToRGB(color) {
2659 return color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')');
2665 * Handles user input start and stop events
2668 function onPressDown(ev) {
2669 if (!isRippleAllowed()) return;
2671 createRipple(ev.pointer.x, ev.pointer.y);
2674 function onPressUp() {
2676 var ripple = ripples[ ripples.length - 1 ];
2677 $timeout(function () { updateElement(ripple); }, 0, false);
2681 * Determines if the ripple is allowed
2683 * @returns {boolean} true if the ripple is allowed, false if not
2685 function isRippleAllowed() {
2686 var parent = node.parentNode;
2687 var grandparent = parent && parent.parentNode;
2688 var ancestor = grandparent && grandparent.parentNode;
2689 return !isDisabled(node) && !isDisabled(parent) && !isDisabled(grandparent) && !isDisabled(ancestor);
2690 function isDisabled (elem) {
2691 return elem && elem.hasAttribute && elem.hasAttribute('disabled');
2697 InkRippleService.$inject = ["$window", "$timeout"];
2700 * noink/nobar/nostretch directive: make any element that has one of
2701 * these attributes be given a controller, so that other directives can
2702 * `require:` these and see if there is a `no<xxx>` parent attribute.
2705 * <hljs lang="html">
2706 * <parent md-no-ink>
2713 * myApp.directive('detectNo', function() {
2715 * require: ['^?mdNoInk', ^?mdNoBar'],
2716 * link: function(scope, element, attr, ctrls) {
2717 * var noinkCtrl = ctrls[0];
2718 * var nobarCtrl = ctrls[1];
2720 * alert("the md-no-ink flag has been specified on an ancestor!");
2723 * alert("the md-no-bar flag has been specified on an ancestor!");
2730 function attrNoDirective() {
2733 controller: angular.noop
2743 * @name $mdTabInkRipple
2744 * @module material.core
2747 * Provides ripple effects for md-tabs. See $mdInkRipple service for all possible configuration options.
2749 * @param {object=} scope Scope within the current context
2750 * @param {object=} element The element the ripple effect should be applied to
2751 * @param {object=} options (Optional) Configuration options to override the defaultripple configuration
2754 angular.module('material.core')
2755 .factory('$mdTabInkRipple', MdTabInkRipple);
2757 function MdTabInkRipple($mdInkRipple) {
2762 function attach(scope, element, options) {
2763 return $mdInkRipple.attach(scope, element, angular.extend({
2765 dimBackground: true,
2771 MdTabInkRipple.$inject = ["$mdInkRipple"];;
2774 angular.module('material.core.theming.palette', [])
2775 .constant('$mdColorPalette', {
2791 'contrastDefaultColor': 'light',
2792 'contrastDarkColors': '50 100 200 300 400 A100',
2793 'contrastStrongLightColors': '500 600 700 A200 A400 A700'
2810 'contrastDefaultColor': 'light',
2811 'contrastDarkColors': '50 100 200 300 400 A100',
2812 'contrastStrongLightColors': '500 600 A200 A400 A700'
2829 'contrastDefaultColor': 'light',
2830 'contrastDarkColors': '50 100 200 A100',
2831 'contrastStrongLightColors': '300 400 A200 A400 A700'
2848 'contrastDefaultColor': 'light',
2849 'contrastDarkColors': '50 100 200 A100',
2850 'contrastStrongLightColors': '300 400 A200'
2867 'contrastDefaultColor': 'light',
2868 'contrastDarkColors': '50 100 200 A100',
2869 'contrastStrongLightColors': '300 400 A200 A400'
2886 'contrastDefaultColor': 'light',
2887 'contrastDarkColors': '100 200 300 400 A100',
2888 'contrastStrongLightColors': '500 600 700 A200 A400 A700'
2905 'contrastDefaultColor': 'dark',
2906 'contrastLightColors': '500 600 700 800 900 A700',
2907 'contrastStrongLightColors': '500 600 700 800 A700'
2924 'contrastDefaultColor': 'dark',
2925 'contrastLightColors': '500 600 700 800 900',
2926 'contrastStrongLightColors': '500 600 700 800'
2943 'contrastDefaultColor': 'dark',
2944 'contrastLightColors': '500 600 700 800 900',
2945 'contrastStrongLightColors': '500 600 700'
2962 'contrastDefaultColor': 'dark',
2963 'contrastLightColors': '500 600 700 800 900',
2964 'contrastStrongLightColors': '500 600 700'
2981 'contrastDefaultColor': 'dark',
2982 'contrastLightColors': '800 900',
2983 'contrastStrongLightColors': '800 900'
3000 'contrastDefaultColor': 'dark',
3001 'contrastLightColors': '900',
3002 'contrastStrongLightColors': '900'
3019 'contrastDefaultColor': 'dark'
3036 'contrastDefaultColor': 'dark'
3053 'contrastDefaultColor': 'dark',
3054 'contrastLightColors': '800 900',
3055 'contrastStrongLightColors': '800 900'
3072 'contrastDefaultColor': 'light',
3073 'contrastDarkColors': '50 100 200 300 400 A100 A200',
3074 'contrastStrongLightColors': '500 600 700 800 900 A400 A700'
3091 'contrastDefaultColor': 'light',
3092 'contrastDarkColors': '50 100 200',
3093 'contrastStrongLightColors': '300 400'
3111 'contrastDefaultColor': 'dark',
3112 'contrastLightColors': '600 700 800 900'
3129 'contrastDefaultColor': 'light',
3130 'contrastDarkColors': '50 100 200 300',
3131 'contrastStrongLightColors': '400 500'
3135 angular.module('material.core.theming', ['material.core.theming.palette'])
3136 .directive('mdTheme', ThemingDirective)
3137 .directive('mdThemable', ThemableDirective)
3138 .provider('$mdTheming', ThemingProvider)
3139 .run(generateThemes);
3143 * @name $mdThemingProvider
3144 * @module material.core
3146 * @description Provider to configure the `$mdTheming` service.
3151 * @name $mdThemingProvider#setDefaultTheme
3152 * @param {string} themeName Default theme name to be applied to elements. Default value is `default`.
3157 * @name $mdThemingProvider#alwaysWatchTheme
3158 * @param {boolean} watch Whether or not to always watch themes for changes and re-apply
3159 * classes when they change. Default is `false`. Enabling can reduce performance.
3162 /* Some Example Valid Theming Expressions
3163 * =======================================
3165 * Intention group expansion: (valid for primary, accent, warn, background)
3167 * {{primary-100}} - grab shade 100 from the primary palette
3168 * {{primary-100-0.7}} - grab shade 100, apply opacity of 0.7
3169 * {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette
3170 * {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-1
3171 * {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue
3172 * {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules
3173 * {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue
3174 * {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules
3176 * Foreground expansion: Applies rgba to black/white foreground text
3178 * {{foreground-1}} - used for primary text
3179 * {{foreground-2}} - used for secondary text/divider
3180 * {{foreground-3}} - used for disabled text
3181 * {{foreground-4}} - used for dividers
3185 // In memory generated CSS rules; registered by theme.name
3186 var GENERATED = { };
3188 // In memory storage of defined themes and color palettes (both loaded by CSS, and user specified)
3192 var DARK_FOREGROUND = {
3194 '1': 'rgba(0,0,0,0.87)',
3195 '2': 'rgba(0,0,0,0.54)',
3196 '3': 'rgba(0,0,0,0.26)',
3197 '4': 'rgba(0,0,0,0.12)'
3199 var LIGHT_FOREGROUND = {
3201 '1': 'rgba(255,255,255,1.0)',
3202 '2': 'rgba(255,255,255,0.7)',
3203 '3': 'rgba(255,255,255,0.3)',
3204 '4': 'rgba(255,255,255,0.12)'
3207 var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)';
3208 var LIGHT_SHADOW = '';
3210 var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)');
3211 var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87');
3212 var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)');
3214 var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background'];
3215 var DEFAULT_COLOR_TYPE = 'primary';
3217 // A color in a theme will use these hues by default, if not specified by user.
3218 var LIGHT_DEFAULT_HUES = {
3233 var DARK_DEFAULT_HUES = {
3241 THEME_COLOR_TYPES.forEach(function(colorType) {
3242 // Color types with unspecified default hues will use these default hue values
3243 var defaultDefaultHues = {
3249 if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues;
3250 if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues;
3253 var VALID_HUE_VALUES = [
3254 '50', '100', '200', '300', '400', '500', '600',
3255 '700', '800', '900', 'A100', 'A200', 'A400', 'A700'
3258 function ThemingProvider($mdColorPalette) {
3262 var themingProvider;
3263 var defaultTheme = 'default';
3264 var alwaysWatchTheme = false;
3266 // Load JS Defined Palettes
3267 angular.extend(PALETTES, $mdColorPalette);
3269 // Default theme defined in core.js
3271 ThemingService.$inject = ["$rootScope", "$log"];
3272 return themingProvider = {
3273 definePalette: definePalette,
3274 extendPalette: extendPalette,
3275 theme: registerTheme,
3277 setDefaultTheme: function(theme) {
3278 defaultTheme = theme;
3280 alwaysWatchTheme: function(alwaysWatch) {
3281 alwaysWatchTheme = alwaysWatch;
3283 $get: ThemingService,
3284 _LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,
3285 _DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,
3286 _PALETTES: PALETTES,
3288 _parseRules: parseRules,
3292 // Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... });
3293 function definePalette(name, map) {
3295 PALETTES[name] = checkPaletteValid(name, map);
3296 return themingProvider;
3299 // Returns an new object which is a copy of a given palette `name` with variables from
3300 // `map` overwritten
3301 // Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' });
3302 function extendPalette(name, map) {
3303 return checkPaletteValid(name, angular.extend({}, PALETTES[name] || {}, map) );
3306 // Make sure that palette has all required hues
3307 function checkPaletteValid(name, map) {
3308 var missingColors = VALID_HUE_VALUES.filter(function(field) {
3311 if (missingColors.length) {
3312 throw new Error("Missing colors %1 in palette %2!"
3313 .replace('%1', missingColors.join(', '))
3314 .replace('%2', name));
3320 // Register a theme (which is a collection of color palettes to use with various states
3321 // ie. warn, accent, primary )
3322 // Optionally inherit from an existing theme
3323 // $mdThemingProvider.theme('custom-theme').primaryPalette('red');
3324 function registerTheme(name, inheritFrom) {
3325 if (THEMES[name]) return THEMES[name];
3327 inheritFrom = inheritFrom || 'default';
3329 var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom;
3330 var theme = new Theme(name);
3333 angular.forEach(parentTheme.colors, function(color, colorType) {
3334 theme.colors[colorType] = {
3336 // Make sure a COPY of the hues is given to the child color,
3337 // not the same reference.
3338 hues: angular.extend({}, color.hues)
3342 THEMES[name] = theme;
3347 function Theme(name) {
3352 self.dark = setDark;
3355 function setDark(isDark) {
3356 isDark = arguments.length === 0 ? true : !!isDark;
3358 // If no change, abort
3359 if (isDark === self.isDark) return;
3361 self.isDark = isDark;
3363 self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND;
3364 self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW;
3366 // Light and dark themes have different default hues.
3367 // Go through each existing color type for this theme, and for every
3368 // hue value that is still the default hue value from the previous light/dark setting,
3369 // set it to the default hue value from the new light/dark setting.
3370 var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES;
3371 var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES;
3372 angular.forEach(newDefaultHues, function(newDefaults, colorType) {
3373 var color = self.colors[colorType];
3374 var oldDefaults = oldDefaultHues[colorType];
3376 for (var hueName in color.hues) {
3377 if (color.hues[hueName] === oldDefaults[hueName]) {
3378 color.hues[hueName] = newDefaults[hueName];
3387 THEME_COLOR_TYPES.forEach(function(colorType) {
3388 var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType];
3389 self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) {
3390 var color = self.colors[colorType] = {
3392 hues: angular.extend({}, defaultHues, hues)
3395 Object.keys(color.hues).forEach(function(name) {
3396 if (!defaultHues[name]) {
3397 throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4"
3398 .replace('%1', name)
3399 .replace('%2', self.name)
3400 .replace('%3', paletteName)
3401 .replace('%4', Object.keys(defaultHues).join(', '))
3405 Object.keys(color.hues).map(function(key) {
3406 return color.hues[key];
3407 }).forEach(function(hueValue) {
3408 if (VALID_HUE_VALUES.indexOf(hueValue) == -1) {
3409 throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5"
3410 .replace('%1', hueValue)
3411 .replace('%2', self.name)
3412 .replace('%3', colorType)
3413 .replace('%4', paletteName)
3414 .replace('%5', VALID_HUE_VALUES.join(', '))
3421 self[colorType + 'Color'] = function() {
3422 var args = Array.prototype.slice.call(arguments);
3423 console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' +
3424 'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.');
3425 return self[colorType + 'Palette'].apply(self, args);
3436 * Service that makes an element apply theming related classes to itself.
3439 * app.directive('myFancyDirective', function($mdTheming) {
3442 * link: function(scope, el, attrs) {
3448 * @param {el=} element to apply theming to
3451 function ThemingService($rootScope, $log) {
3453 applyTheme.inherit = function(el, parent) {
3454 var ctrl = parent.controller('mdTheme');
3456 var attrThemeValue = el.attr('md-theme-watch');
3457 if ( (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false') {
3458 var deregisterWatch = $rootScope.$watch(function() {
3459 return ctrl && ctrl.$mdTheme || defaultTheme;
3461 el.on('$destroy', deregisterWatch);
3463 var theme = ctrl && ctrl.$mdTheme || defaultTheme;
3467 function changeTheme(theme) {
3468 if (!registered(theme)) {
3469 $log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' +
3470 'Register it with $mdThemingProvider.theme().');
3472 var oldTheme = el.data('$mdThemeName');
3473 if (oldTheme) el.removeClass('md-' + oldTheme +'-theme');
3474 el.addClass('md-' + theme + '-theme');
3475 el.data('$mdThemeName', theme);
3479 applyTheme.THEMES = angular.extend({}, THEMES);
3480 applyTheme.defaultTheme = function() { return defaultTheme; };
3481 applyTheme.registered = registered;
3485 function registered(themeName) {
3486 if (themeName === undefined || themeName === '') return true;
3487 return applyTheme.THEMES[themeName] !== undefined;
3490 function applyTheme(scope, el) {
3491 // Allow us to be invoked via a linking function signature.
3492 if (el === undefined) {
3496 if (scope === undefined) {
3499 applyTheme.inherit(el, el);
3503 ThemingProvider.$inject = ["$mdColorPalette"];
3505 function ThemingDirective($mdTheming, $interpolate, $log) {
3509 pre: function(scope, el, attrs) {
3511 $setTheme: function(theme) {
3512 if (!$mdTheming.registered(theme)) {
3513 $log.warn('attempted to use unregistered theme \'' + theme + '\'');
3515 ctrl.$mdTheme = theme;
3518 el.data('$mdThemeController', ctrl);
3519 ctrl.$setTheme($interpolate(attrs.mdTheme)(scope));
3520 attrs.$observe('mdTheme', ctrl.$setTheme);
3525 ThemingDirective.$inject = ["$mdTheming", "$interpolate", "$log"];
3527 function ThemableDirective($mdTheming) {
3530 ThemableDirective.$inject = ["$mdTheming"];
3532 function parseRules(theme, colorType, rules) {
3533 checkValidPalette(theme, colorType);
3535 rules = rules.replace(/THEME_NAME/g, theme.name);
3536 var generatedRules = [];
3537 var color = theme.colors[colorType];
3539 var themeNameRegex = new RegExp('.md-' + theme.name + '-theme', 'g');
3540 // Matches '{{ primary-color }}', etc
3541 var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g');
3542 var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow)-?(\d\.?\d*)?\s*\}\}'?"?/g;
3543 var palette = PALETTES[color.name];
3545 // find and replace simple variables where we use a specific hue, not an entire palette
3546 // eg. "{{primary-100}}"
3547 //\(' + THEME_COLOR_TYPES.join('\|') + '\)'
3548 rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity) {
3549 if (colorType === 'foreground') {
3550 if (hue == 'shadow') {
3551 return theme.foregroundShadow;
3553 return theme.foregroundPalette[hue] || theme.foregroundPalette['1'];
3556 if (hue.indexOf('hue') === 0) {
3557 hue = theme.colors[colorType].hues[hue];
3559 return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '').value, opacity );
3562 // For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3)
3563 angular.forEach(color.hues, function(hueValue, hueName) {
3565 .replace(hueRegex, function(match, _, colorType, hueType, opacity) {
3566 return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity);
3568 if (hueName !== 'default') {
3569 newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName);
3572 // Don't apply a selector rule to the default theme, making it easier to override
3573 // styles of the base-component
3574 if (theme.name == 'default') {
3575 newRule = newRule.replace(/\.md-default-theme/g, '');
3577 generatedRules.push(newRule);
3580 return generatedRules;
3583 // Generate our themes at run time given the state of THEMES and PALETTES
3584 function generateThemes($injector) {
3586 var head = document.getElementsByTagName('head')[0];
3587 var firstChild = head ? head.firstElementChild : null;
3588 var themeCss = $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : '';
3590 if ( !firstChild ) return;
3591 if (themeCss.length === 0) return; // no rules, so no point in running this expensive task
3593 // Expose contrast colors for palettes to ensure that text is always readable
3594 angular.forEach(PALETTES, sanitizePalette);
3596 // MD_THEME_CSS is a string generated by the build process that includes all the themable
3597 // components as templates
3599 // Break the CSS into individual rules
3600 var rulesByType = {};
3601 var rules = themeCss
3602 .split(/\}(?!(\}|'|"|;))/)
3603 .filter(function(rule) { return rule && rule.length; })
3604 .map(function(rule) { return rule.trim() + '}'; });
3607 var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g');
3609 THEME_COLOR_TYPES.forEach(function(type) {
3610 rulesByType[type] = '';
3614 // Sort the rules based on type, allowing us to do color substitution on a per-type basis
3615 rules.forEach(function(rule) {
3616 var match = rule.match(ruleMatchRegex);
3617 // First: test that if the rule has '.md-accent', it goes into the accent set of rules
3618 for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) {
3619 if (rule.indexOf('.md-' + type) > -1) {
3620 return rulesByType[type] += rule;
3624 // If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from
3626 for (i = 0; type = THEME_COLOR_TYPES[i]; i++) {
3627 if (rule.indexOf(type) > -1) {
3628 return rulesByType[type] += rule;
3632 // Default to the primary array
3633 return rulesByType[DEFAULT_COLOR_TYPE] += rule;
3636 // For each theme, use the color palettes specified for
3637 // `primary`, `warn` and `accent` to generate CSS rules.
3639 angular.forEach(THEMES, function(theme) {
3640 if ( !GENERATED[theme.name] ) {
3643 THEME_COLOR_TYPES.forEach(function(colorType) {
3644 var styleStrings = parseRules(theme, colorType, rulesByType[colorType]);
3645 while (styleStrings.length) {
3646 var style = document.createElement('style');
3647 style.setAttribute('type', 'text/css');
3648 style.appendChild(document.createTextNode(styleStrings.shift()));
3649 head.insertBefore(style, firstChild);
3654 if (theme.colors.primary.name == theme.colors.accent.name) {
3655 console.warn("$mdThemingProvider: Using the same palette for primary and" +
3656 " accent. This violates the material design spec.");
3659 GENERATED[theme.name] = true;
3664 // *************************
3665 // Internal functions
3666 // *************************
3668 // The user specifies a 'default' contrast color as either light or dark,
3669 // then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light)
3670 function sanitizePalette(palette) {
3671 var defaultContrast = palette.contrastDefaultColor;
3672 var lightColors = palette.contrastLightColors || [];
3673 var strongLightColors = palette.contrastStrongLightColors || [];
3674 var darkColors = palette.contrastDarkColors || [];
3676 // These colors are provided as space-separated lists
3677 if (typeof lightColors === 'string') lightColors = lightColors.split(' ');
3678 if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' ');
3679 if (typeof darkColors === 'string') darkColors = darkColors.split(' ');
3681 // Cleanup after ourselves
3682 delete palette.contrastDefaultColor;
3683 delete palette.contrastLightColors;
3684 delete palette.contrastStrongLightColors;
3685 delete palette.contrastDarkColors;
3687 // Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR }
3688 angular.forEach(palette, function(hueValue, hueName) {
3689 if (angular.isObject(hueValue)) return; // Already converted
3690 // Map everything to rgb colors
3691 var rgbValue = colorToRgbaArray(hueValue);
3693 throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected."
3694 .replace('%1', hueValue)
3695 .replace('%2', palette.name)
3696 .replace('%3', hueName));
3699 palette[hueName] = {
3701 contrast: getContrastColor()
3703 function getContrastColor() {
3704 if (defaultContrast === 'light') {
3705 if (darkColors.indexOf(hueName) > -1) {
3706 return DARK_CONTRAST_COLOR;
3708 return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
3709 : LIGHT_CONTRAST_COLOR;
3712 if (lightColors.indexOf(hueName) > -1) {
3713 return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
3714 : LIGHT_CONTRAST_COLOR;
3716 return DARK_CONTRAST_COLOR;
3725 generateThemes.$inject = ["$injector"];
3727 function checkValidPalette(theme, colorType) {
3728 // If theme attempts to use a palette that doesnt exist, throw error
3729 if (!PALETTES[ (theme.colors[colorType] || {}).name ]) {
3731 "You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3"
3732 .replace('%1', theme.name)
3733 .replace('%2', colorType)
3734 .replace('%3', Object.keys(PALETTES).join(', '))
3739 function colorToRgbaArray(clr) {
3740 if (angular.isArray(clr) && clr.length == 3) return clr;
3741 if (/^rgb/.test(clr)) {
3742 return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) {
3743 return i == 3 ? parseFloat(value, 10) : parseInt(value, 10);
3746 if (clr.charAt(0) == '#') clr = clr.substring(1);
3747 if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return;
3749 var dig = clr.length / 3;
3750 var red = clr.substr(0, dig);
3751 var grn = clr.substr(dig, dig);
3752 var blu = clr.substr(dig * 2);
3758 return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)];
3761 function rgba(rgbArray, opacity) {
3762 if ( !rgbArray ) return "rgb('0,0,0')";
3764 if (rgbArray.length == 4) {
3765 rgbArray = angular.copy(rgbArray);
3766 opacity ? rgbArray.pop() : opacity = rgbArray.pop();
3768 return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ?
3769 'rgba(' + rgbArray.join(',') + ',' + opacity + ')' :
3770 'rgb(' + rgbArray.join(',') + ')';
3775 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-chips.md-THEME_NAME-theme .md-chips { box-shadow: 0 1px '{{background-300}}'; } md-chips.md-THEME_NAME-theme .md-chips.md-focused { box-shadow: 0 2px '{{primary-color}}'; }md-chips.md-THEME_NAME-theme .md-chip { background: '{{background-300}}'; color: '{{background-800}}'; } md-chips.md-THEME_NAME-theme .md-chip.md-focused { background: '{{primary-color}}'; color: '{{primary-contrast}}'; } md-chips.md-THEME_NAME-theme .md-chip.md-focused md-icon { color: '{{primary-contrast}}'; }md-chips.md-THEME_NAME-theme md-chip-remove .md-button md-icon path { fill: '{{background-500}}'; }.md-contact-suggestion span.md-contact-email { color: '{{background-400}}'; }md-content.md-THEME_NAME-theme { background-color: '{{background-color}}'; }md-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-dialog.md-THEME_NAME-theme { border-radius: 4px; background-color: '{{background-color}}'; } md-dialog.md-THEME_NAME-theme.md-content-overflow .md-actions { border-top-color: '{{foreground-4}}'; }md-divider.md-THEME_NAME-theme { border-top-color: '{{foreground-4}}'; }md-icon.md-THEME_NAME-theme { color: '{{foreground-2}}'; } md-icon.md-THEME_NAME-theme.md-primary { color: '{{primary-color}}'; } md-icon.md-THEME_NAME-theme.md-accent { color: '{{accent-color}}'; } md-icon.md-THEME_NAME-theme.md-warn { color: '{{warn-color}}'; }md-input-container.md-THEME_NAME-theme .md-input { color: '{{foreground-1}}'; border-color: '{{foreground-4}}'; text-shadow: '{{foreground-shadow}}'; } md-input-container.md-THEME_NAME-theme .md-input::-webkit-input-placeholder, md-input-container.md-THEME_NAME-theme .md-input::-moz-placeholder, md-input-container.md-THEME_NAME-theme .md-input:-moz-placeholder, md-input-container.md-THEME_NAME-theme .md-input:-ms-input-placeholder { color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme > md-icon { color: '{{foreground-1}}'; }md-input-container.md-THEME_NAME-theme label, md-input-container.md-THEME_NAME-theme .md-placeholder { text-shadow: '{{foreground-shadow}}'; color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme ng-messages, md-input-container.md-THEME_NAME-theme [ng-message], md-input-container.md-THEME_NAME-theme [data-ng-message], md-input-container.md-THEME_NAME-theme [x-ng-message] { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-has-value label { color: '{{foreground-2}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused .md-input { border-color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused label { color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused md-icon { color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input { border-color: '{{accent-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent label { color: '{{accent-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input { border-color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn label { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input { border-color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid.md-input-focused label { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid data-ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid x-ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid [ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid [data-ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid [x-ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid .md-char-counter { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme .md-input[disabled], [disabled] md-input-container.md-THEME_NAME-theme .md-input { border-bottom-color: transparent; color: '{{foreground-3}}'; background-image: linear-gradient(to right, '{{foreground-3}}' 0%, '{{foreground-3}}' 33%, transparent 0%); background-image: -ms-linear-gradient(left, transparent 0%, '{{foreground-3}}' 100%); }md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h3, md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h4, md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h3, md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h4 { color: '{{foreground-1}}'; }md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text p, md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text p { color: '{{foreground-2}}'; }md-list.md-THEME_NAME-theme .md-proxy-focus.md-focused div.md-no-style { background-color: '{{background-100}}'; }md-list.md-THEME_NAME-theme md-list-item > md-icon { color: '{{foreground-2}}'; } md-list.md-THEME_NAME-theme md-list-item > md-icon.md-highlight { color: '{{primary-color}}'; } md-list.md-THEME_NAME-theme md-list-item > md-icon.md-highlight.md-accent { color: '{{accent-color}}'; }md-list.md-THEME_NAME-theme md-list-item button { background-color: '{{background-color}}'; } md-list.md-THEME_NAME-theme md-list-item button.md-button:not([disabled]):hover { background-color: '{{background-color}}'; }md-progress-circular.md-THEME_NAME-theme { background-color: transparent; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-gap { border-top-color: '{{primary-color}}'; border-bottom-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme .md-inner .md-right .md-half-circle { border-top-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-right .md-half-circle { border-right-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-left .md-half-circle { border-left-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-gap { border-top-color: '{{warn-color}}'; border-bottom-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-right .md-half-circle { border-top-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-right .md-half-circle { border-right-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-left .md-half-circle { border-left-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-gap { border-top-color: '{{accent-color}}'; border-bottom-color: '{{accent-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-right .md-half-circle { border-top-color: '{{accent-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-right .md-half-circle { border-right-color: '{{accent-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-left .md-half-circle { border-left-color: '{{accent-color}}'; }md-progress-linear.md-THEME_NAME-theme .md-container { background-color: '{{primary-100}}'; }md-progress-linear.md-THEME_NAME-theme .md-bar { background-color: '{{primary-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn .md-container { background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn .md-bar { background-color: '{{warn-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent .md-container { background-color: '{{accent-100}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent .md-bar { background-color: '{{accent-color}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-bar1 { background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-dashed:before { background: radial-gradient('{{warn-100}}' 0%, '{{warn-100}}' 16%, transparent 42%); }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-bar1 { background-color: '{{accent-100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-dashed:before { background: radial-gradient('{{accent-100}}' 0%, '{{accent-100}}' 16%, transparent 42%); }md-radio-button.md-THEME_NAME-theme .md-off { border-color: '{{foreground-2}}'; }md-radio-button.md-THEME_NAME-theme .md-on { background-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked .md-off { border-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked .md-ink-ripple { color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme .md-container .md-ripple { color: '{{accent-600}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-on { background-color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off { border-color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple { color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple { color: '{{primary-600}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-on, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-on { background-color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off { border-color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple { color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple { color: '{{warn-600}}'; }md-radio-group.md-THEME_NAME-theme[disabled], md-radio-button.md-THEME_NAME-theme[disabled] { color: '{{foreground-3}}'; } md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-off, md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-off { border-color: '{{foreground-3}}'; } md-radio-group.md-THEME_NAME-theme[disabled] .md-container .md-on, md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-on { border-color: '{{foreground-3}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked .md-container:before { background-color: '{{accent-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked:not([disabled]).md-primary .md-container:before { background-color: '{{primary-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-primary .md-container:before { background-color: '{{warn-color-0.26}}'; }md-sidenav.md-THEME_NAME-theme { background-color: '{{background-color}}'; }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-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}}'; }");
3779 })(window, window.angular);