Best JavaScript code snippet using best
core.js
Source:core.js
1/*!2 * Angular Material Design3 * https://github.com/angular/material4 * @license MIT5 * v1.1.16 */7goog.provide('ngmaterial.core');8/**9 * Initialization function that validates environment10 * requirements.11 */12DetectNgTouch.$inject = ["$log", "$injector"];13MdCoreConfigure.$inject = ["$provide", "$mdThemingProvider"];14rAFDecorator.$inject = ["$delegate"];15angular16 .module('material.core', [17 'ngAnimate',18 'material.core.animate',19 'material.core.layout',20 'material.core.gestures',21 'material.core.theming'22 ])23 .config(MdCoreConfigure)24 .run(DetectNgTouch);25/**26 * Detect if the ng-Touch module is also being used.27 * Warn if detected.28 * ngInject29 */30function DetectNgTouch($log, $injector) {31 if ( $injector.has('$swipe') ) {32 var msg = "" +33 "You are using the ngTouch module. \n" +34 "Angular Material already has mobile click, tap, and swipe support... \n" +35 "ngTouch is not supported with Angular Material!";36 $log.warn(msg);37 }38}39/**40 * ngInject41 */42function MdCoreConfigure($provide, $mdThemingProvider) {43 $provide.decorator('$$rAF', ["$delegate", rAFDecorator]);44 $mdThemingProvider.theme('default')45 .primaryPalette('indigo')46 .accentPalette('pink')47 .warnPalette('deep-orange')48 .backgroundPalette('grey');49}50/**51 * ngInject52 */53function rAFDecorator($delegate) {54 /**55 * Use this to throttle events that come in often.56 * The throttled function will always use the *last* invocation before the57 * coming frame.58 *59 * For example, window resize events that fire many times a second:60 * If we set to use an raf-throttled callback on window resize, then61 * our callback will only be fired once per frame, with the last resize62 * event that happened before that frame.63 *64 * @param {function} callback function to debounce65 */66 $delegate.throttle = function(cb) {67 var queuedArgs, alreadyQueued, queueCb, context;68 return function debounced() {69 queuedArgs = arguments;70 context = this;71 queueCb = cb;72 if (!alreadyQueued) {73 alreadyQueued = true;74 $delegate(function() {75 queueCb.apply(context, Array.prototype.slice.call(queuedArgs));76 alreadyQueued = false;77 });78 }79 };80 };81 return $delegate;82}83angular.module('material.core')84 .directive('mdAutofocus', MdAutofocusDirective)85 // Support the deprecated md-auto-focus and md-sidenav-focus as well86 .directive('mdAutoFocus', MdAutofocusDirective)87 .directive('mdSidenavFocus', MdAutofocusDirective);88/**89 * @ngdoc directive90 * @name mdAutofocus91 * @module material.core.util92 *93 * @description94 *95 * `[md-autofocus]` provides an optional way to identify the focused element when a `$mdDialog`,96 * `$mdBottomSheet`, `$mdMenu` or `$mdSidenav` opens or upon page load for input-like elements.97 *98 * When one of these opens, it will find the first nested element with the `[md-autofocus]`99 * attribute directive and optional expression. An expression may be specified as the directive100 * value to enable conditional activation of the autofocus.101 *102 * @usage103 *104 * ### Dialog105 * <hljs lang="html">106 * <md-dialog>107 * <form>108 * <md-input-container>109 * <label for="testInput">Label</label>110 * <input id="testInput" type="text" md-autofocus>111 * </md-input-container>112 * </form>113 * </md-dialog>114 * </hljs>115 *116 * ### Bottomsheet117 * <hljs lang="html">118 * <md-bottom-sheet class="md-list md-has-header">119 * <md-subheader>Comment Actions</md-subheader>120 * <md-list>121 * <md-list-item ng-repeat="item in items">122 *123 * <md-button md-autofocus="$index == 2">124 * <md-icon md-svg-src="{{item.icon}}"></md-icon>125 * <span class="md-inline-list-icon-label">{{ item.name }}</span>126 * </md-button>127 *128 * </md-list-item>129 * </md-list>130 * </md-bottom-sheet>131 * </hljs>132 *133 * ### Autocomplete134 * <hljs lang="html">135 * <md-autocomplete136 * md-autofocus137 * md-selected-item="selectedItem"138 * md-search-text="searchText"139 * md-items="item in getMatches(searchText)"140 * md-item-text="item.display">141 * <span md-highlight-text="searchText">{{item.display}}</span>142 * </md-autocomplete>143 * </hljs>144 *145 * ### Sidenav146 * <hljs lang="html">147 * <div layout="row" ng-controller="MyController">148 * <md-sidenav md-component-id="left" class="md-sidenav-left">149 * Left Nav!150 * </md-sidenav>151 *152 * <md-content>153 * Center Content154 * <md-button ng-click="openLeftMenu()">155 * Open Left Menu156 * </md-button>157 * </md-content>158 *159 * <md-sidenav md-component-id="right"160 * md-is-locked-open="$mdMedia('min-width: 333px')"161 * class="md-sidenav-right">162 * <form>163 * <md-input-container>164 * <label for="testInput">Test input</label>165 * <input id="testInput" type="text"166 * ng-model="data" md-autofocus>167 * </md-input-container>168 * </form>169 * </md-sidenav>170 * </div>171 * </hljs>172 **/173function MdAutofocusDirective() {174 return {175 restrict: 'A',176 link: postLink177 }178}179function postLink(scope, element, attrs) {180 var attr = attrs.mdAutoFocus || attrs.mdAutofocus || attrs.mdSidenavFocus;181 // Setup a watcher on the proper attribute to update a class we can check for in $mdUtil182 scope.$watch(attr, function(canAutofocus) {183 element.toggleClass('md-autofocus', canAutofocus);184 });185}186/**187 * @ngdoc module188 * @name material.core.colorUtil189 * @description190 * Color Util191 */192angular193 .module('material.core')194 .factory('$mdColorUtil', ColorUtilFactory);195function ColorUtilFactory() {196 /**197 * Converts hex value to RGBA string198 * @param color {string}199 * @returns {string}200 */201 function hexToRgba (color) {202 var hex = color[ 0 ] === '#' ? color.substr(1) : color,203 dig = hex.length / 3,204 red = hex.substr(0, dig),205 green = hex.substr(dig, dig),206 blue = hex.substr(dig * 2);207 if (dig === 1) {208 red += red;209 green += green;210 blue += blue;211 }212 return 'rgba(' + parseInt(red, 16) + ',' + parseInt(green, 16) + ',' + parseInt(blue, 16) + ',0.1)';213 }214 /**215 * Converts rgba value to hex string216 * @param color {string}217 * @returns {string}218 */219 function rgbaToHex(color) {220 color = color.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);221 var hex = (color && color.length === 4) ? "#" +222 ("0" + parseInt(color[1],10).toString(16)).slice(-2) +223 ("0" + parseInt(color[2],10).toString(16)).slice(-2) +224 ("0" + parseInt(color[3],10).toString(16)).slice(-2) : '';225 return hex.toUpperCase();226 }227 /**228 * Converts an RGB color to RGBA229 * @param color {string}230 * @returns {string}231 */232 function rgbToRgba (color) {233 return color.replace(')', ', 0.1)').replace('(', 'a(');234 }235 /**236 * Converts an RGBA color to RGB237 * @param color {string}238 * @returns {string}239 */240 function rgbaToRgb (color) {241 return color242 ? color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')')243 : 'rgb(0,0,0)';244 }245 return {246 rgbaToHex: rgbaToHex,247 hexToRgba: hexToRgba,248 rgbToRgba: rgbToRgba,249 rgbaToRgb: rgbaToRgb250 }251}252MdConstantFactory.$inject = ["$sniffer", "$window", "$document"];angular.module('material.core')253.factory('$mdConstant', MdConstantFactory);254/**255 * Factory function that creates the grab-bag $mdConstant service.256 * ngInject257 */258function MdConstantFactory($sniffer, $window, $document) {259 var vendorPrefix = $sniffer.vendorPrefix;260 var isWebkit = /webkit/i.test(vendorPrefix);261 var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g;262 var prefixTestEl = document.createElement('div');263 function vendorProperty(name) {264 // Add a dash between the prefix and name, to be able to transform the string into camelcase.265 var prefixedName = vendorPrefix + '-' + name;266 var ucPrefix = camelCase(prefixedName);267 var lcPrefix = ucPrefix.charAt(0).toLowerCase() + ucPrefix.substring(1);268 return hasStyleProperty(name) ? name : // The current browser supports the un-prefixed property269 hasStyleProperty(ucPrefix) ? ucPrefix : // The current browser only supports the prefixed property.270 hasStyleProperty(lcPrefix) ? lcPrefix : name; // Some browsers are only supporting the prefix in lowercase.271 }272 function hasStyleProperty(property) {273 return angular.isDefined(prefixTestEl.style[property]);274 }275 function camelCase(input) {276 return input.replace(SPECIAL_CHARS_REGEXP, function(matches, separator, letter, offset) {277 return offset ? letter.toUpperCase() : letter;278 });279 }280 var self = {281 isInputKey : function(e) { return (e.keyCode >= 31 && e.keyCode <= 90); },282 isNumPadKey : function (e){ return (3 === e.location && e.keyCode >= 97 && e.keyCode <= 105); },283 isNavigationKey : function(e) {284 var kc = self.KEY_CODE, NAVIGATION_KEYS = [kc.SPACE, kc.ENTER, kc.UP_ARROW, kc.DOWN_ARROW];285 return (NAVIGATION_KEYS.indexOf(e.keyCode) != -1); 286 },287 KEY_CODE: {288 COMMA: 188,289 SEMICOLON : 186,290 ENTER: 13,291 ESCAPE: 27,292 SPACE: 32,293 PAGE_UP: 33,294 PAGE_DOWN: 34,295 END: 35,296 HOME: 36,297 LEFT_ARROW : 37,298 UP_ARROW : 38,299 RIGHT_ARROW : 39,300 DOWN_ARROW : 40,301 TAB : 9,302 BACKSPACE: 8,303 DELETE: 46304 },305 CSS: {306 /* Constants */307 TRANSITIONEND: 'transitionend' + (isWebkit ? ' webkitTransitionEnd' : ''),308 ANIMATIONEND: 'animationend' + (isWebkit ? ' webkitAnimationEnd' : ''),309 TRANSFORM: vendorProperty('transform'),310 TRANSFORM_ORIGIN: vendorProperty('transformOrigin'),311 TRANSITION: vendorProperty('transition'),312 TRANSITION_DURATION: vendorProperty('transitionDuration'),313 ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'),314 ANIMATION_DURATION: vendorProperty('animationDuration'),315 ANIMATION_NAME: vendorProperty('animationName'),316 ANIMATION_TIMING: vendorProperty('animationTimingFunction'),317 ANIMATION_DIRECTION: vendorProperty('animationDirection')318 },319 /**320 * As defined in core/style/variables.scss321 *322 * $layout-breakpoint-xs: 600px !default;323 * $layout-breakpoint-sm: 960px !default;324 * $layout-breakpoint-md: 1280px !default;325 * $layout-breakpoint-lg: 1920px !default;326 *327 */328 MEDIA: {329 'xs' : '(max-width: 599px)' ,330 'gt-xs' : '(min-width: 600px)' ,331 'sm' : '(min-width: 600px) and (max-width: 959px)' ,332 'gt-sm' : '(min-width: 960px)' ,333 'md' : '(min-width: 960px) and (max-width: 1279px)' ,334 'gt-md' : '(min-width: 1280px)' ,335 'lg' : '(min-width: 1280px) and (max-width: 1919px)',336 'gt-lg' : '(min-width: 1920px)' ,337 'xl' : '(min-width: 1920px)' ,338 'landscape' : '(orientation: landscape)' ,339 'portrait' : '(orientation: portrait)' ,340 'print' : 'print'341 },342 MEDIA_PRIORITY: [343 'xl',344 'gt-lg',345 'lg',346 'gt-md',347 'md',348 'gt-sm',349 'sm',350 'gt-xs',351 'xs',352 'landscape',353 'portrait',354 'print'355 ]356 };357 return self;358}359 angular360 .module('material.core')361 .config( ["$provide", function($provide){362 $provide.decorator('$mdUtil', ['$delegate', function ($delegate){363 /**364 * Inject the iterator facade to easily support iteration and accessors365 * @see iterator below366 */367 $delegate.iterator = MdIterator;368 return $delegate;369 }370 ]);371 }]);372 /**373 * iterator is a list facade to easily support iteration and accessors374 *375 * @param items Array list which this iterator will enumerate376 * @param reloop Boolean enables iterator to consider the list as an endless reloop377 */378 function MdIterator(items, reloop) {379 var trueFn = function() { return true; };380 if (items && !angular.isArray(items)) {381 items = Array.prototype.slice.call(items);382 }383 reloop = !!reloop;384 var _items = items || [ ];385 // Published API386 return {387 items: getItems,388 count: count,389 inRange: inRange,390 contains: contains,391 indexOf: indexOf,392 itemAt: itemAt,393 findBy: findBy,394 add: add,395 remove: remove,396 first: first,397 last: last,398 next: angular.bind(null, findSubsequentItem, false),399 previous: angular.bind(null, findSubsequentItem, true),400 hasPrevious: hasPrevious,401 hasNext: hasNext402 };403 /**404 * Publish copy of the enumerable set405 * @returns {Array|*}406 */407 function getItems() {408 return [].concat(_items);409 }410 /**411 * Determine length of the list412 * @returns {Array.length|*|number}413 */414 function count() {415 return _items.length;416 }417 /**418 * Is the index specified valid419 * @param index420 * @returns {Array.length|*|number|boolean}421 */422 function inRange(index) {423 return _items.length && ( index > -1 ) && (index < _items.length );424 }425 /**426 * Can the iterator proceed to the next item in the list; relative to427 * the specified item.428 *429 * @param item430 * @returns {Array.length|*|number|boolean}431 */432 function hasNext(item) {433 return item ? inRange(indexOf(item) + 1) : false;434 }435 /**436 * Can the iterator proceed to the previous item in the list; relative to437 * the specified item.438 *439 * @param item440 * @returns {Array.length|*|number|boolean}441 */442 function hasPrevious(item) {443 return item ? inRange(indexOf(item) - 1) : false;444 }445 /**446 * Get item at specified index/position447 * @param index448 * @returns {*}449 */450 function itemAt(index) {451 return inRange(index) ? _items[index] : null;452 }453 /**454 * Find all elements matching the key/value pair455 * otherwise return null456 *457 * @param val458 * @param key459 *460 * @return array461 */462 function findBy(key, val) {463 return _items.filter(function(item) {464 return item[key] === val;465 });466 }467 /**468 * Add item to list469 * @param item470 * @param index471 * @returns {*}472 */473 function add(item, index) {474 if ( !item ) return -1;475 if (!angular.isNumber(index)) {476 index = _items.length;477 }478 _items.splice(index, 0, item);479 return indexOf(item);480 }481 /**482 * Remove item from list...483 * @param item484 */485 function remove(item) {486 if ( contains(item) ){487 _items.splice(indexOf(item), 1);488 }489 }490 /**491 * Get the zero-based index of the target item492 * @param item493 * @returns {*}494 */495 function indexOf(item) {496 return _items.indexOf(item);497 }498 /**499 * Boolean existence check500 * @param item501 * @returns {boolean}502 */503 function contains(item) {504 return item && (indexOf(item) > -1);505 }506 /**507 * Return first item in the list508 * @returns {*}509 */510 function first() {511 return _items.length ? _items[0] : null;512 }513 /**514 * Return last item in the list...515 * @returns {*}516 */517 function last() {518 return _items.length ? _items[_items.length - 1] : null;519 }520 /**521 * Find the next item. If reloop is true and at the end of the list, it will go back to the522 * first item. If given, the `validate` callback will be used to determine whether the next item523 * is valid. If not valid, it will try to find the next item again.524 *525 * @param {boolean} backwards Specifies the direction of searching (forwards/backwards)526 * @param {*} item The item whose subsequent item we are looking for527 * @param {Function=} validate The `validate` function528 * @param {integer=} limit The recursion limit529 *530 * @returns {*} The subsequent item or null531 */532 function findSubsequentItem(backwards, item, validate, limit) {533 validate = validate || trueFn;534 var curIndex = indexOf(item);535 while (true) {536 if (!inRange(curIndex)) return null;537 var nextIndex = curIndex + (backwards ? -1 : 1);538 var foundItem = null;539 if (inRange(nextIndex)) {540 foundItem = _items[nextIndex];541 } else if (reloop) {542 foundItem = backwards ? last() : first();543 nextIndex = indexOf(foundItem);544 }545 if ((foundItem === null) || (nextIndex === limit)) return null;546 if (validate(foundItem)) return foundItem;547 if (angular.isUndefined(limit)) limit = nextIndex;548 curIndex = nextIndex;549 }550 }551 }552mdMediaFactory.$inject = ["$mdConstant", "$rootScope", "$window"];angular.module('material.core')553.factory('$mdMedia', mdMediaFactory);554/**555 * @ngdoc service556 * @name $mdMedia557 * @module material.core558 *559 * @description560 * `$mdMedia` is used to evaluate whether a given media query is true or false given the561 * current device's screen / window size. The media query will be re-evaluated on resize, allowing562 * you to register a watch.563 *564 * `$mdMedia` also has pre-programmed support for media queries that match the layout breakpoints:565 *566 * <table class="md-api-table">567 * <thead>568 * <tr>569 * <th>Breakpoint</th>570 * <th>mediaQuery</th>571 * </tr>572 * </thead>573 * <tbody>574 * <tr>575 * <td>xs</td>576 * <td>(max-width: 599px)</td>577 * </tr>578 * <tr>579 * <td>gt-xs</td>580 * <td>(min-width: 600px)</td>581 * </tr>582 * <tr>583 * <td>sm</td>584 * <td>(min-width: 600px) and (max-width: 959px)</td>585 * </tr>586 * <tr>587 * <td>gt-sm</td>588 * <td>(min-width: 960px)</td>589 * </tr>590 * <tr>591 * <td>md</td>592 * <td>(min-width: 960px) and (max-width: 1279px)</td>593 * </tr>594 * <tr>595 * <td>gt-md</td>596 * <td>(min-width: 1280px)</td>597 * </tr>598 * <tr>599 * <td>lg</td>600 * <td>(min-width: 1280px) and (max-width: 1919px)</td>601 * </tr>602 * <tr>603 * <td>gt-lg</td>604 * <td>(min-width: 1920px)</td>605 * </tr>606 * <tr>607 * <td>xl</td>608 * <td>(min-width: 1920px)</td>609 * </tr>610 * <tr>611 * <td>landscape</td>612 * <td>landscape</td>613 * </tr>614 * <tr>615 * <td>portrait</td>616 * <td>portrait</td>617 * </tr>618 * <tr>619 * <td>print</td>620 * <td>print</td>621 * </tr>622 * </tbody>623 * </table>624 *625 * See Material Design's <a href="https://material.google.com/layout/responsive-ui.html">Layout - Adaptive UI</a> for more details.626 *627 * <a href="https://www.google.com/design/spec/layout/adaptive-ui.html">628 * <img src="https://material-design.storage.googleapis.com/publish/material_v_4/material_ext_publish/0B8olV15J7abPSGFxemFiQVRtb1k/layout_adaptive_breakpoints_01.png" width="100%" height="100%"></img>629 * </a>630 *631 * @returns {boolean} a boolean representing whether or not the given media query is true or false.632 *633 * @usage634 * <hljs lang="js">635 * app.controller('MyController', function($mdMedia, $scope) {636 * $scope.$watch(function() { return $mdMedia('lg'); }, function(big) {637 * $scope.bigScreen = big;638 * });639 *640 * $scope.screenIsSmall = $mdMedia('sm');641 * $scope.customQuery = $mdMedia('(min-width: 1234px)');642 * $scope.anotherCustom = $mdMedia('max-width: 300px');643 * });644 * </hljs>645 */646/* ngInject */647function mdMediaFactory($mdConstant, $rootScope, $window) {648 var queries = {};649 var mqls = {};650 var results = {};651 var normalizeCache = {};652 $mdMedia.getResponsiveAttribute = getResponsiveAttribute;653 $mdMedia.getQuery = getQuery;654 $mdMedia.watchResponsiveAttributes = watchResponsiveAttributes;655 return $mdMedia;656 function $mdMedia(query) {657 var validated = queries[query];658 if (angular.isUndefined(validated)) {659 validated = queries[query] = validate(query);660 }661 var result = results[validated];662 if (angular.isUndefined(result)) {663 result = add(validated);664 }665 return result;666 }667 function validate(query) {668 return $mdConstant.MEDIA[query] ||669 ((query.charAt(0) !== '(') ? ('(' + query + ')') : query);670 }671 function add(query) {672 var result = mqls[query];673 if ( !result ) {674 result = mqls[query] = $window.matchMedia(query);675 }676 result.addListener(onQueryChange);677 return (results[result.media] = !!result.matches);678 }679 function onQueryChange(query) {680 $rootScope.$evalAsync(function() {681 results[query.media] = !!query.matches;682 });683 }684 function getQuery(name) {685 return mqls[name];686 }687 function getResponsiveAttribute(attrs, attrName) {688 for (var i = 0; i < $mdConstant.MEDIA_PRIORITY.length; i++) {689 var mediaName = $mdConstant.MEDIA_PRIORITY[i];690 if (!mqls[queries[mediaName]].matches) {691 continue;692 }693 var normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);694 if (attrs[normalizedName]) {695 return attrs[normalizedName];696 }697 }698 // fallback on unprefixed699 return attrs[getNormalizedName(attrs, attrName)];700 }701 function watchResponsiveAttributes(attrNames, attrs, watchFn) {702 var unwatchFns = [];703 attrNames.forEach(function(attrName) {704 var normalizedName = getNormalizedName(attrs, attrName);705 if (angular.isDefined(attrs[normalizedName])) {706 unwatchFns.push(707 attrs.$observe(normalizedName, angular.bind(void 0, watchFn, null)));708 }709 for (var mediaName in $mdConstant.MEDIA) {710 normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);711 if (angular.isDefined(attrs[normalizedName])) {712 unwatchFns.push(713 attrs.$observe(normalizedName, angular.bind(void 0, watchFn, mediaName)));714 }715 }716 });717 return function unwatch() {718 unwatchFns.forEach(function(fn) { fn(); })719 };720 }721 // Improves performance dramatically722 function getNormalizedName(attrs, attrName) {723 return normalizeCache[attrName] ||724 (normalizeCache[attrName] = attrs.$normalize(attrName));725 }726}727angular728 .module('material.core')729 .config( ["$provide", function($provide) {730 $provide.decorator('$mdUtil', ['$delegate', function ($delegate) {731 // Inject the prefixer into our original $mdUtil service.732 $delegate.prefixer = MdPrefixer;733 return $delegate;734 }]);735 }]);736function MdPrefixer(initialAttributes, buildSelector) {737 var PREFIXES = ['data', 'x'];738 if (initialAttributes) {739 // The prefixer also accepts attributes as a parameter, and immediately builds a list or selector for740 // the specified attributes.741 return buildSelector ? _buildSelector(initialAttributes) : _buildList(initialAttributes);742 }743 return {744 buildList: _buildList,745 buildSelector: _buildSelector,746 hasAttribute: _hasAttribute,747 removeAttribute: _removeAttribute748 };749 function _buildList(attributes) {750 attributes = angular.isArray(attributes) ? attributes : [attributes];751 attributes.forEach(function(item) {752 PREFIXES.forEach(function(prefix) {753 attributes.push(prefix + '-' + item);754 });755 });756 return attributes;757 }758 function _buildSelector(attributes) {759 attributes = angular.isArray(attributes) ? attributes : [attributes];760 return _buildList(attributes)761 .map(function(item) {762 return '[' + item + ']'763 })764 .join(',');765 }766 function _hasAttribute(element, attribute) {767 element = _getNativeElement(element);768 if (!element) {769 return false;770 }771 var prefixedAttrs = _buildList(attribute);772 for (var i = 0; i < prefixedAttrs.length; i++) {773 if (element.hasAttribute(prefixedAttrs[i])) {774 return true;775 }776 }777 return false;778 }779 function _removeAttribute(element, attribute) {780 element = _getNativeElement(element);781 if (!element) {782 return;783 }784 _buildList(attribute).forEach(function(prefixedAttribute) {785 element.removeAttribute(prefixedAttribute);786 });787 }788 /**789 * Transforms a jqLite or DOM element into a HTML element.790 * This is useful when supporting jqLite elements and DOM elements at791 * same time.792 * @param element {JQLite|Element} Element to be parsed793 * @returns {HTMLElement} Parsed HTMLElement794 */795 function _getNativeElement(element) {796 element = element[0] || element;797 if (element.nodeType) {798 return element;799 }800 }801}802/*803 * This var has to be outside the angular factory, otherwise when804 * there are multiple material apps on the same page, each app805 * will create its own instance of this array and the app's IDs806 * will not be unique.807 */808UtilFactory.$inject = ["$document", "$timeout", "$compile", "$rootScope", "$$mdAnimate", "$interpolate", "$log", "$rootElement", "$window", "$$rAF"];809var nextUniqueId = 0;810/**811 * @ngdoc module812 * @name material.core.util813 * @description814 * Util815 */816angular817 .module('material.core')818 .factory('$mdUtil', UtilFactory);819/**820 * ngInject821 */822function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $interpolate, $log, $rootElement, $window, $$rAF) {823 // Setup some core variables for the processTemplate method824 var startSymbol = $interpolate.startSymbol(),825 endSymbol = $interpolate.endSymbol(),826 usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}'));827 /**828 * Checks if the target element has the requested style by key829 * @param {DOMElement|JQLite} target Target element830 * @param {string} key Style key831 * @param {string=} expectedVal Optional expected value832 * @returns {boolean} Whether the target element has the style or not833 */834 var hasComputedStyle = function (target, key, expectedVal) {835 var hasValue = false;836 if ( target && target.length ) {837 var computedStyles = $window.getComputedStyle(target[0]);838 hasValue = angular.isDefined(computedStyles[key]) && (expectedVal ? computedStyles[key] == expectedVal : true);839 }840 return hasValue;841 };842 function validateCssValue(value) {843 return !value ? '0' :844 hasPx(value) || hasPercent(value) ? value : value + 'px';845 }846 function hasPx(value) {847 return String(value).indexOf('px') > -1;848 }849 function hasPercent(value) {850 return String(value).indexOf('%') > -1;851 }852 var $mdUtil = {853 dom: {},854 now: window.performance ?855 angular.bind(window.performance, window.performance.now) : Date.now || function() {856 return new Date().getTime();857 },858 /**859 * Bi-directional accessor/mutator used to easily update an element's860 * property based on the current 'dir'ectional value.861 */862 bidi : function(element, property, lValue, rValue) {863 var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl');864 // If accessor865 if ( arguments.length == 0 ) return ltr ? 'ltr' : 'rtl';866 // If mutator867 var elem = angular.element(element);868 if ( ltr && angular.isDefined(lValue)) {869 elem.css(property, validateCssValue(lValue));870 }871 else if ( !ltr && angular.isDefined(rValue)) {872 elem.css(property, validateCssValue(rValue) );873 }874 },875 bidiProperty: function (element, lProperty, rProperty, value) {876 var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl');877 var elem = angular.element(element);878 if ( ltr && angular.isDefined(lProperty)) {879 elem.css(lProperty, validateCssValue(value));880 elem.css(rProperty, '');881 }882 else if ( !ltr && angular.isDefined(rProperty)) {883 elem.css(rProperty, validateCssValue(value) );884 elem.css(lProperty, '');885 }886 },887 clientRect: function(element, offsetParent, isOffsetRect) {888 var node = getNode(element);889 offsetParent = getNode(offsetParent || node.offsetParent || document.body);890 var nodeRect = node.getBoundingClientRect();891 // The user can ask for an offsetRect: a rect relative to the offsetParent,892 // or a clientRect: a rect relative to the page893 var offsetRect = isOffsetRect ?894 offsetParent.getBoundingClientRect() :895 {left: 0, top: 0, width: 0, height: 0};896 return {897 left: nodeRect.left - offsetRect.left,898 top: nodeRect.top - offsetRect.top,899 width: nodeRect.width,900 height: nodeRect.height901 };902 },903 offsetRect: function(element, offsetParent) {904 return $mdUtil.clientRect(element, offsetParent, true);905 },906 // Annoying method to copy nodes to an array, thanks to IE907 nodesToArray: function(nodes) {908 nodes = nodes || [];909 var results = [];910 for (var i = 0; i < nodes.length; ++i) {911 results.push(nodes.item(i));912 }913 return results;914 },915 /**916 * Calculate the positive scroll offset917 * TODO: Check with pinch-zoom in IE/Chrome;918 * https://code.google.com/p/chromium/issues/detail?id=496285919 */920 scrollTop: function(element) {921 element = angular.element(element || $document[0].body);922 var body = (element[0] == $document[0].body) ? $document[0].body : undefined;923 var scrollTop = body ? body.scrollTop + body.parentElement.scrollTop : 0;924 // Calculate the positive scroll offset925 return scrollTop || Math.abs(element[0].getBoundingClientRect().top);926 },927 /**928 * Finds the proper focus target by searching the DOM.929 *930 * @param containerEl931 * @param attributeVal932 * @returns {*}933 */934 findFocusTarget: function(containerEl, attributeVal) {935 var AUTO_FOCUS = this.prefixer('md-autofocus', true);936 var elToFocus;937 elToFocus = scanForFocusable(containerEl, attributeVal || AUTO_FOCUS);938 if ( !elToFocus && attributeVal != AUTO_FOCUS) {939 // Scan for deprecated attribute940 elToFocus = scanForFocusable(containerEl, this.prefixer('md-auto-focus', true));941 if ( !elToFocus ) {942 // Scan for fallback to 'universal' API943 elToFocus = scanForFocusable(containerEl, AUTO_FOCUS);944 }945 }946 return elToFocus;947 /**948 * Can target and nested children for specified Selector (attribute)949 * whose value may be an expression that evaluates to True/False.950 */951 function scanForFocusable(target, selector) {952 var elFound, items = target[0].querySelectorAll(selector);953 // Find the last child element with the focus attribute954 if ( items && items.length ){955 items.length && angular.forEach(items, function(it) {956 it = angular.element(it);957 // Check the element for the md-autofocus class to ensure any associated expression958 // evaluated to true.959 var isFocusable = it.hasClass('md-autofocus');960 if (isFocusable) elFound = it;961 });962 }963 return elFound;964 }965 },966 /**967 * Disables scroll around the passed parent element.968 * @param element Unused969 * @param {!Element|!angular.JQLite} parent Element to disable scrolling within.970 * Defaults to body if none supplied.971 * @param options Object of options to modify functionality972 * - disableScrollMask Boolean of whether or not to create a scroll mask element or973 * use the passed parent element.974 */975 disableScrollAround: function(element, parent, options) {976 $mdUtil.disableScrollAround._count = $mdUtil.disableScrollAround._count || 0;977 ++$mdUtil.disableScrollAround._count;978 if ($mdUtil.disableScrollAround._enableScrolling) return $mdUtil.disableScrollAround._enableScrolling;979 var body = $document[0].body,980 restoreBody = disableBodyScroll(),981 restoreElement = disableElementScroll(parent);982 return $mdUtil.disableScrollAround._enableScrolling = function() {983 if (!--$mdUtil.disableScrollAround._count) {984 restoreBody();985 restoreElement();986 delete $mdUtil.disableScrollAround._enableScrolling;987 }988 };989 // Creates a virtual scrolling mask to absorb touchmove, keyboard, scrollbar clicking, and wheel events990 function disableElementScroll(element) {991 element = angular.element(element || body);992 var scrollMask;993 if (options && options.disableScrollMask) {994 scrollMask = element;995 } else {996 element = element[0];997 scrollMask = angular.element(998 '<div class="md-scroll-mask">' +999 ' <div class="md-scroll-mask-bar"></div>' +1000 '</div>');1001 element.appendChild(scrollMask[0]);1002 }1003 scrollMask.on('wheel', preventDefault);1004 scrollMask.on('touchmove', preventDefault);1005 return function restoreScroll() {1006 scrollMask.off('wheel');1007 scrollMask.off('touchmove');1008 scrollMask[0].parentNode.removeChild(scrollMask[0]);1009 delete $mdUtil.disableScrollAround._enableScrolling;1010 };1011 function preventDefault(e) {1012 e.preventDefault();1013 }1014 }1015 // Converts the body to a position fixed block and translate it to the proper scroll position1016 function disableBodyScroll() {1017 var htmlNode = body.parentNode;1018 var restoreHtmlStyle = htmlNode.style.cssText || '';1019 var restoreBodyStyle = body.style.cssText || '';1020 var scrollOffset = $mdUtil.scrollTop(body);1021 var clientWidth = body.clientWidth;1022 if (body.scrollHeight > body.clientHeight + 1) {1023 applyStyles(body, {1024 position: 'fixed',1025 width: '100%',1026 top: -scrollOffset + 'px'1027 });1028 htmlNode.style.overflowY = 'scroll';1029 }1030 if (body.clientWidth < clientWidth) applyStyles(body, {overflow: 'hidden'});1031 return function restoreScroll() {1032 body.style.cssText = restoreBodyStyle;1033 htmlNode.style.cssText = restoreHtmlStyle;1034 body.scrollTop = scrollOffset;1035 htmlNode.scrollTop = scrollOffset;1036 };1037 }1038 function applyStyles(el, styles) {1039 for (var key in styles) {1040 el.style[key] = styles[key];1041 }1042 }1043 },1044 enableScrolling: function() {1045 var method = this.disableScrollAround._enableScrolling;1046 method && method();1047 },1048 floatingScrollbars: function() {1049 if (this.floatingScrollbars.cached === undefined) {1050 var tempNode = angular.element('<div><div></div></div>').css({1051 width: '100%',1052 'z-index': -1,1053 position: 'absolute',1054 height: '35px',1055 'overflow-y': 'scroll'1056 });1057 tempNode.children().css('height', '60px');1058 $document[0].body.appendChild(tempNode[0]);1059 this.floatingScrollbars.cached = (tempNode[0].offsetWidth == tempNode[0].childNodes[0].offsetWidth);1060 tempNode.remove();1061 }1062 return this.floatingScrollbars.cached;1063 },1064 // Mobile safari only allows you to set focus in click event listeners...1065 forceFocus: function(element) {1066 var node = element[0] || element;1067 document.addEventListener('click', function focusOnClick(ev) {1068 if (ev.target === node && ev.$focus) {1069 node.focus();1070 ev.stopImmediatePropagation();1071 ev.preventDefault();1072 node.removeEventListener('click', focusOnClick);1073 }1074 }, true);1075 var newEvent = document.createEvent('MouseEvents');1076 newEvent.initMouseEvent('click', false, true, window, {}, 0, 0, 0, 0,1077 false, false, false, false, 0, null);1078 newEvent.$material = true;1079 newEvent.$focus = true;1080 node.dispatchEvent(newEvent);1081 },1082 /**1083 * facade to build md-backdrop element with desired styles1084 * NOTE: Use $compile to trigger backdrop postLink function1085 */1086 createBackdrop: function(scope, addClass) {1087 return $compile($mdUtil.supplant('<md-backdrop class="{0}">', [addClass]))(scope);1088 },1089 /**1090 * supplant() method from Crockford's `Remedial Javascript`1091 * Equivalent to use of $interpolate; without dependency on1092 * interpolation symbols and scope. Note: the '{<token>}' can1093 * be property names, property chains, or array indices.1094 */1095 supplant: function(template, values, pattern) {1096 pattern = pattern || /\{([^\{\}]*)\}/g;1097 return template.replace(pattern, function(a, b) {1098 var p = b.split('.'),1099 r = values;1100 try {1101 for (var s in p) {1102 if (p.hasOwnProperty(s) ) {1103 r = r[p[s]];1104 }1105 }1106 } catch (e) {1107 r = a;1108 }1109 return (typeof r === 'string' || typeof r === 'number') ? r : a;1110 });1111 },1112 fakeNgModel: function() {1113 return {1114 $fake: true,1115 $setTouched: angular.noop,1116 $setViewValue: function(value) {1117 this.$viewValue = value;1118 this.$render(value);1119 this.$viewChangeListeners.forEach(function(cb) {1120 cb();1121 });1122 },1123 $isEmpty: function(value) {1124 return ('' + value).length === 0;1125 },1126 $parsers: [],1127 $formatters: [],1128 $viewChangeListeners: [],1129 $render: angular.noop1130 };1131 },1132 // Returns a function, that, as long as it continues to be invoked, will not1133 // be triggered. The function will be called after it stops being called for1134 // N milliseconds.1135 // @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs1136 // @param invokeApply should the $timeout trigger $digest() dirty checking1137 debounce: function(func, wait, scope, invokeApply) {1138 var timer;1139 return function debounced() {1140 var context = scope,1141 args = Array.prototype.slice.call(arguments);1142 $timeout.cancel(timer);1143 timer = $timeout(function() {1144 timer = undefined;1145 func.apply(context, args);1146 }, wait || 10, invokeApply);1147 };1148 },1149 // Returns a function that can only be triggered every `delay` milliseconds.1150 // In other words, the function will not be called unless it has been more1151 // than `delay` milliseconds since the last call.1152 throttle: function throttle(func, delay) {1153 var recent;1154 return function throttled() {1155 var context = this;1156 var args = arguments;1157 var now = $mdUtil.now();1158 if (!recent || (now - recent > delay)) {1159 func.apply(context, args);1160 recent = now;1161 }1162 };1163 },1164 /**1165 * Measures the number of milliseconds taken to run the provided callback1166 * function. Uses a high-precision timer if available.1167 */1168 time: function time(cb) {1169 var start = $mdUtil.now();1170 cb();1171 return $mdUtil.now() - start;1172 },1173 /**1174 * Create an implicit getter that caches its `getter()`1175 * lookup value1176 */1177 valueOnUse : function (scope, key, getter) {1178 var value = null, args = Array.prototype.slice.call(arguments);1179 var params = (args.length > 3) ? args.slice(3) : [ ];1180 Object.defineProperty(scope, key, {1181 get: function () {1182 if (value === null) value = getter.apply(scope, params);1183 return value;1184 }1185 });1186 },1187 /**1188 * Get a unique ID.1189 *1190 * @returns {string} an unique numeric string1191 */1192 nextUid: function() {1193 return '' + nextUniqueId++;1194 },1195 // Stop watchers and events from firing on a scope without destroying it,1196 // by disconnecting it from its parent and its siblings' linked lists.1197 disconnectScope: function disconnectScope(scope) {1198 if (!scope) return;1199 // we can't destroy the root scope or a scope that has been already destroyed1200 if (scope.$root === scope) return;1201 if (scope.$$destroyed) return;1202 var parent = scope.$parent;1203 scope.$$disconnected = true;1204 // See Scope.$destroy1205 if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling;1206 if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling;1207 if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;1208 if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;1209 scope.$$nextSibling = scope.$$prevSibling = null;1210 },1211 // Undo the effects of disconnectScope above.1212 reconnectScope: function reconnectScope(scope) {1213 if (!scope) return;1214 // we can't disconnect the root node or scope already disconnected1215 if (scope.$root === scope) return;1216 if (!scope.$$disconnected) return;1217 var child = scope;1218 var parent = child.$parent;1219 child.$$disconnected = false;1220 // See Scope.$new for this logic...1221 child.$$prevSibling = parent.$$childTail;1222 if (parent.$$childHead) {1223 parent.$$childTail.$$nextSibling = child;1224 parent.$$childTail = child;1225 } else {1226 parent.$$childHead = parent.$$childTail = child;1227 }1228 },1229 /*1230 * getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName1231 *1232 * @param el Element to start walking the DOM from1233 * @param check Either a string or a function. If a string is passed, it will be evaluated against1234 * each of the parent nodes' tag name. If a function is passed, the loop will call it with each of1235 * the parents and will use the return value to determine whether the node is a match.1236 * @param onlyParent Only start checking from the parent element, not `el`.1237 */1238 getClosest: function getClosest(el, validateWith, onlyParent) {1239 if ( angular.isString(validateWith) ) {1240 var tagName = validateWith.toUpperCase();1241 validateWith = function(el) {1242 return el.nodeName === tagName;1243 };1244 }1245 if (el instanceof angular.element) el = el[0];1246 if (onlyParent) el = el.parentNode;1247 if (!el) return null;1248 do {1249 if (validateWith(el)) {1250 return el;1251 }1252 } while (el = el.parentNode);1253 return null;1254 },1255 /**1256 * Build polyfill for the Node.contains feature (if needed)1257 */1258 elementContains: function(node, child) {1259 var hasContains = (window.Node && window.Node.prototype && Node.prototype.contains);1260 var findFn = hasContains ? angular.bind(node, node.contains) : angular.bind(node, function(arg) {1261 // compares the positions of two nodes and returns a bitmask1262 return (node === child) || !!(this.compareDocumentPosition(arg) & 16)1263 });1264 return findFn(child);1265 },1266 /**1267 * Functional equivalent for $element.filter(âmd-bottom-sheetâ)1268 * useful with interimElements where the element and its container are important...1269 *1270 * @param {[]} elements to scan1271 * @param {string} name of node to find (e.g. 'md-dialog')1272 * @param {boolean=} optional flag to allow deep scans; defaults to 'false'.1273 * @param {boolean=} optional flag to enable log warnings; defaults to false1274 */1275 extractElementByName: function(element, nodeName, scanDeep, warnNotFound) {1276 var found = scanTree(element);1277 if (!found && !!warnNotFound) {1278 $log.warn( $mdUtil.supplant("Unable to find node '{0}' in element '{1}'.",[nodeName, element[0].outerHTML]) );1279 }1280 return angular.element(found || element);1281 /**1282 * Breadth-First tree scan for element with matching `nodeName`1283 */1284 function scanTree(element) {1285 return scanLevel(element) || (!!scanDeep ? scanChildren(element) : null);1286 }1287 /**1288 * Case-insensitive scan of current elements only (do not descend).1289 */1290 function scanLevel(element) {1291 if ( element ) {1292 for (var i = 0, len = element.length; i < len; i++) {1293 if (element[i].nodeName.toLowerCase() === nodeName) {1294 return element[i];1295 }1296 }1297 }1298 return null;1299 }1300 /**1301 * Scan children of specified node1302 */1303 function scanChildren(element) {1304 var found;1305 if ( element ) {1306 for (var i = 0, len = element.length; i < len; i++) {1307 var target = element[i];1308 if ( !found ) {1309 for (var j = 0, numChild = target.childNodes.length; j < numChild; j++) {1310 found = found || scanTree([target.childNodes[j]]);1311 }1312 }1313 }1314 }1315 return found;1316 }1317 },1318 /**1319 * Give optional properties with no value a boolean true if attr provided or false otherwise1320 */1321 initOptionalProperties: function(scope, attr, defaults) {1322 defaults = defaults || {};1323 angular.forEach(scope.$$isolateBindings, function(binding, key) {1324 if (binding.optional && angular.isUndefined(scope[key])) {1325 var attrIsDefined = angular.isDefined(attr[binding.attrName]);1326 scope[key] = angular.isDefined(defaults[key]) ? defaults[key] : attrIsDefined;1327 }1328 });1329 },1330 /**1331 * Alternative to $timeout calls with 0 delay.1332 * nextTick() coalesces all calls within a single frame1333 * to minimize $digest thrashing1334 *1335 * @param callback1336 * @param digest1337 * @returns {*}1338 */1339 nextTick: function(callback, digest, scope) {1340 //-- grab function reference for storing state details1341 var nextTick = $mdUtil.nextTick;1342 var timeout = nextTick.timeout;1343 var queue = nextTick.queue || [];1344 //-- add callback to the queue1345 queue.push({scope: scope, callback: callback});1346 //-- set default value for digest1347 if (digest == null) digest = true;1348 //-- store updated digest/queue values1349 nextTick.digest = nextTick.digest || digest;1350 nextTick.queue = queue;1351 //-- either return existing timeout or create a new one1352 return timeout || (nextTick.timeout = $timeout(processQueue, 0, false));1353 /**1354 * Grab a copy of the current queue1355 * Clear the queue for future use1356 * Process the existing queue1357 * Trigger digest if necessary1358 */1359 function processQueue() {1360 var queue = nextTick.queue;1361 var digest = nextTick.digest;1362 nextTick.queue = [];1363 nextTick.timeout = null;1364 nextTick.digest = false;1365 queue.forEach(function(queueItem) {1366 var skip = queueItem.scope && queueItem.scope.$$destroyed;1367 if (!skip) {1368 queueItem.callback();1369 }1370 });1371 if (digest) $rootScope.$digest();1372 }1373 },1374 /**1375 * Processes a template and replaces the start/end symbols if the application has1376 * overriden them.1377 *1378 * @param template The template to process whose start/end tags may be replaced.1379 * @returns {*}1380 */1381 processTemplate: function(template) {1382 if (usesStandardSymbols) {1383 return template;1384 } else {1385 if (!template || !angular.isString(template)) return template;1386 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);1387 }1388 },1389 /**1390 * Scan up dom hierarchy for enabled parent;1391 */1392 getParentWithPointerEvents: function (element) {1393 var parent = element.parent();1394 // jqLite might return a non-null, but still empty, parent; so check for parent and length1395 while (hasComputedStyle(parent, 'pointer-events', 'none')) {1396 parent = parent.parent();1397 }1398 return parent;1399 },1400 getNearestContentElement: function (element) {1401 var current = element.parent()[0];1402 // Look for the nearest parent md-content, stopping at the rootElement.1403 while (current && current !== $rootElement[0] && current !== document.body && current.nodeName.toUpperCase() !== 'MD-CONTENT') {1404 current = current.parentNode;1405 }1406 return current;1407 },1408 /**1409 * Checks if the current browser is natively supporting the `sticky` position.1410 * @returns {string} supported sticky property name1411 */1412 checkStickySupport: function() {1413 var stickyProp;1414 var testEl = angular.element('<div>');1415 $document[0].body.appendChild(testEl[0]);1416 var stickyProps = ['sticky', '-webkit-sticky'];1417 for (var i = 0; i < stickyProps.length; ++i) {1418 testEl.css({1419 position: stickyProps[i],1420 top: 0,1421 'z-index': 21422 });1423 if (testEl.css('position') == stickyProps[i]) {1424 stickyProp = stickyProps[i];1425 break;1426 }1427 }1428 testEl.remove();1429 return stickyProp;1430 },1431 /**1432 * Parses an attribute value, mostly a string.1433 * By default checks for negated values and returns `false´ if present.1434 * Negated values are: (native falsy) and negative strings like:1435 * `false` or `0`.1436 * @param value Attribute value which should be parsed.1437 * @param negatedCheck When set to false, won't check for negated values.1438 * @returns {boolean}1439 */1440 parseAttributeBoolean: function(value, negatedCheck) {1441 return value === '' || !!value && (negatedCheck === false || value !== 'false' && value !== '0');1442 },1443 hasComputedStyle: hasComputedStyle,1444 /**1445 * Returns true if the parent form of the element has been submitted.1446 *1447 * @param element An Angular or HTML5 element.1448 *1449 * @returns {boolean}1450 */1451 isParentFormSubmitted: function(element) {1452 var parent = $mdUtil.getClosest(element, 'form');1453 var form = parent ? angular.element(parent).controller('form') : null;1454 return form ? form.$submitted : false;1455 },1456 /**1457 * Animate the requested element's scrollTop to the requested scrollPosition with basic easing.1458 *1459 * @param element The element to scroll.1460 * @param scrollEnd The new/final scroll position.1461 */1462 animateScrollTo: function(element, scrollEnd) {1463 var scrollStart = element.scrollTop;1464 var scrollChange = scrollEnd - scrollStart;1465 var scrollingDown = scrollStart < scrollEnd;1466 var startTime = $mdUtil.now();1467 $$rAF(scrollChunk);1468 function scrollChunk() {1469 var newPosition = calculateNewPosition();1470 1471 element.scrollTop = newPosition;1472 1473 if (scrollingDown ? newPosition < scrollEnd : newPosition > scrollEnd) {1474 $$rAF(scrollChunk);1475 }1476 }1477 1478 function calculateNewPosition() {1479 var duration = 1000;1480 var currentTime = $mdUtil.now() - startTime;1481 1482 return ease(currentTime, scrollStart, scrollChange, duration);1483 }1484 function ease(currentTime, start, change, duration) {1485 // If the duration has passed (which can occur if our app loses focus due to $$rAF), jump1486 // straight to the proper position1487 if (currentTime > duration) {1488 return start + change;1489 }1490 1491 var ts = (currentTime /= duration) * currentTime;1492 var tc = ts * currentTime;1493 return start + change * (-2 * tc + 3 * ts);1494 }1495 }1496 };1497// Instantiate other namespace utility methods1498 $mdUtil.dom.animator = $$mdAnimate($mdUtil);1499 return $mdUtil;1500 function getNode(el) {1501 return el[0] || el;1502 }1503}1504/*1505 * Since removing jQuery from the demos, some code that uses `element.focus()` is broken.1506 * We need to add `element.focus()`, because it's testable unlike `element[0].focus`.1507 */1508angular.element.prototype.focus = angular.element.prototype.focus || function() {1509 if (this.length) {1510 this[0].focus();1511 }1512 return this;1513 };1514angular.element.prototype.blur = angular.element.prototype.blur || function() {1515 if (this.length) {1516 this[0].blur();1517 }1518 return this;1519 };1520/**1521 * @ngdoc module1522 * @name material.core.aria1523 * @description1524 * Aria Expectations for ngMaterial components.1525 */1526MdAriaService.$inject = ["$$rAF", "$log", "$window", "$interpolate"];1527angular1528 .module('material.core')1529 .provider('$mdAria', MdAriaProvider);1530/**1531 * @ngdoc service1532 * @name $mdAriaProvider1533 * @module material.core.aria1534 *1535 * @description1536 *1537 * Modify options of the `$mdAria` service, which will be used by most of the Angular Material1538 * components.1539 *1540 * You are able to disable `$mdAria` warnings, by using the following markup.1541 *1542 * <hljs lang="js">1543 * app.config(function($mdAriaProvider) {1544 * // Globally disables all ARIA warnings.1545 * $mdAriaProvider.disableWarnings();1546 * });1547 * </hljs>1548 *1549 */1550function MdAriaProvider() {1551 var self = this;1552 /**1553 * Whether we should show ARIA warnings in the console, if labels are missing on the element1554 * By default the warnings are enabled1555 */1556 self.showWarnings = true;1557 return {1558 disableWarnings: disableWarnings,1559 $get: ["$$rAF", "$log", "$window", "$interpolate", function($$rAF, $log, $window, $interpolate) {1560 return MdAriaService.apply(self, arguments);1561 }]1562 };1563 /**1564 * @ngdoc method1565 * @name $mdAriaProvider#disableWarnings1566 * @description Disables all ARIA warnings generated by Angular Material.1567 */1568 function disableWarnings() {1569 self.showWarnings = false;1570 }1571}1572/*1573 * ngInject1574 */1575function MdAriaService($$rAF, $log, $window, $interpolate) {1576 // Load the showWarnings option from the current context and store it inside of a scope variable,1577 // because the context will be probably lost in some function calls.1578 var showWarnings = this.showWarnings;1579 return {1580 expect: expect,1581 expectAsync: expectAsync,1582 expectWithText: expectWithText,1583 expectWithoutText: expectWithoutText1584 };1585 /**1586 * Check if expected attribute has been specified on the target element or child1587 * @param element1588 * @param attrName1589 * @param {optional} defaultValue What to set the attr to if no value is found1590 */1591 function expect(element, attrName, defaultValue) {1592 var node = angular.element(element)[0] || element;1593 // if node exists and neither it nor its children have the attribute1594 if (node &&1595 ((!node.hasAttribute(attrName) ||1596 node.getAttribute(attrName).length === 0) &&1597 !childHasAttribute(node, attrName))) {1598 defaultValue = angular.isString(defaultValue) ? defaultValue.trim() : '';1599 if (defaultValue.length) {1600 element.attr(attrName, defaultValue);1601 } else if (showWarnings) {1602 $log.warn('ARIA: Attribute "', attrName, '", required for accessibility, is missing on node:', node);1603 }1604 }1605 }1606 function expectAsync(element, attrName, defaultValueGetter) {1607 // Problem: when retrieving the element's contents synchronously to find the label,1608 // the text may not be defined yet in the case of a binding.1609 // There is a higher chance that a binding will be defined if we wait one frame.1610 $$rAF(function() {1611 expect(element, attrName, defaultValueGetter());1612 });1613 }1614 function expectWithText(element, attrName) {1615 var content = getText(element) || "";1616 var hasBinding = content.indexOf($interpolate.startSymbol()) > -1;1617 if ( hasBinding ) {1618 expectAsync(element, attrName, function() {1619 return getText(element);1620 });1621 } else {1622 expect(element, attrName, content);1623 }1624 }1625 function expectWithoutText(element, attrName) {1626 var content = getText(element);1627 var hasBinding = content.indexOf($interpolate.startSymbol()) > -1;1628 if ( !hasBinding && !content) {1629 expect(element, attrName, content);1630 }1631 }1632 function getText(element) {1633 element = element[0] || element;1634 var walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);1635 var text = '';1636 var node;1637 while (node = walker.nextNode()) {1638 if (!isAriaHiddenNode(node)) {1639 text += node.textContent;1640 }1641 }1642 return text.trim() || '';1643 function isAriaHiddenNode(node) {1644 while (node.parentNode && (node = node.parentNode) !== element) {1645 if (node.getAttribute && node.getAttribute('aria-hidden') === 'true') {1646 return true;1647 }1648 }1649 }1650 }1651 function childHasAttribute(node, attrName) {1652 var hasChildren = node.hasChildNodes(),1653 hasAttr = false;1654 function isHidden(el) {1655 var style = el.currentStyle ? el.currentStyle : $window.getComputedStyle(el);1656 return (style.display === 'none');1657 }1658 if (hasChildren) {1659 var children = node.childNodes;1660 for (var i=0; i < children.length; i++) {1661 var child = children[i];1662 if (child.nodeType === 1 && child.hasAttribute(attrName)) {1663 if (!isHidden(child)) {1664 hasAttr = true;1665 }1666 }1667 }1668 }1669 return hasAttr;1670 }1671}1672mdCompilerService.$inject = ["$q", "$templateRequest", "$injector", "$compile", "$controller"];angular1673 .module('material.core')1674 .service('$mdCompiler', mdCompilerService);1675function mdCompilerService($q, $templateRequest, $injector, $compile, $controller) {1676 /* jshint validthis: true */1677 /*1678 * @ngdoc service1679 * @name $mdCompiler1680 * @module material.core1681 * @description1682 * The $mdCompiler service is an abstraction of angular's compiler, that allows the developer1683 * to easily compile an element with a templateUrl, controller, and locals.1684 *1685 * @usage1686 * <hljs lang="js">1687 * $mdCompiler.compile({1688 * templateUrl: 'modal.html',1689 * controller: 'ModalCtrl',1690 * locals: {1691 * modal: myModalInstance;1692 * }1693 * }).then(function(compileData) {1694 * compileData.element; // modal.html's template in an element1695 * compileData.link(myScope); //attach controller & scope to element1696 * });1697 * </hljs>1698 */1699 /*1700 * @ngdoc method1701 * @name $mdCompiler#compile1702 * @description A helper to compile an HTML template/templateUrl with a given controller,1703 * locals, and scope.1704 * @param {object} options An options object, with the following properties:1705 *1706 * - `controller` - `{(string=|function()=}` Controller fn that should be associated with1707 * newly created scope or the name of a registered controller if passed as a string.1708 * - `controllerAs` - `{string=}` A controller alias name. If present the controller will be1709 * published to scope under the `controllerAs` name.1710 * - `template` - `{string=}` An html template as a string.1711 * - `templateUrl` - `{string=}` A path to an html template.1712 * - `transformTemplate` - `{function(template)=}` A function which transforms the template after1713 * it is loaded. It will be given the template string as a parameter, and should1714 * return a a new string representing the transformed template.1715 * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should1716 * be injected into the controller. If any of these dependencies are promises, the compiler1717 * will wait for them all to be resolved, or if one is rejected before the controller is1718 * instantiated `compile()` will fail..1719 * * `key` - `{string}`: a name of a dependency to be injected into the controller.1720 * * `factory` - `{string|function}`: If `string` then it is an alias for a service.1721 * Otherwise if function, then it is injected and the return value is treated as the1722 * dependency. If the result is a promise, it is resolved before its value is1723 * injected into the controller.1724 *1725 * @returns {object=} promise A promise, which will be resolved with a `compileData` object.1726 * `compileData` has the following properties:1727 *1728 * - `element` - `{element}`: an uncompiled element matching the provided template.1729 * - `link` - `{function(scope)}`: A link function, which, when called, will compile1730 * the element and instantiate the provided controller (if given).1731 * - `locals` - `{object}`: The locals which will be passed into the controller once `link` is1732 * called. If `bindToController` is true, they will be coppied to the ctrl instead1733 * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.1734 */1735 this.compile = function(options) {1736 var templateUrl = options.templateUrl;1737 var template = options.template || '';1738 var controller = options.controller;1739 var controllerAs = options.controllerAs;1740 var resolve = angular.extend({}, options.resolve || {});1741 var locals = angular.extend({}, options.locals || {});1742 var transformTemplate = options.transformTemplate || angular.identity;1743 var bindToController = options.bindToController;1744 // Take resolve values and invoke them.1745 // Resolves can either be a string (value: 'MyRegisteredAngularConst'),1746 // or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {})1747 angular.forEach(resolve, function(value, key) {1748 if (angular.isString(value)) {1749 resolve[key] = $injector.get(value);1750 } else {1751 resolve[key] = $injector.invoke(value);1752 }1753 });1754 //Add the locals, which are just straight values to inject1755 //eg locals: { three: 3 }, will inject three into the controller1756 angular.extend(resolve, locals);1757 if (templateUrl) {1758 resolve.$template = $templateRequest(templateUrl)1759 .then(function(response) {1760 return response;1761 });1762 } else {1763 resolve.$template = $q.when(template);1764 }1765 // Wait for all the resolves to finish if they are promises1766 return $q.all(resolve).then(function(locals) {1767 var compiledData;1768 var template = transformTemplate(locals.$template, options);1769 var element = options.element || angular.element('<div>').html(template.trim()).contents();1770 var linkFn = $compile(element);1771 // Return a linking function that can be used later when the element is ready1772 return compiledData = {1773 locals: locals,1774 element: element,1775 link: function link(scope) {1776 locals.$scope = scope;1777 //Instantiate controller if it exists, because we have scope1778 if (controller) {1779 var invokeCtrl = $controller(controller, locals, true, controllerAs);1780 if (bindToController) {1781 angular.extend(invokeCtrl.instance, locals);1782 }1783 var ctrl = invokeCtrl();1784 //See angular-route source for this logic1785 element.data('$ngControllerController', ctrl);1786 element.children().data('$ngControllerController', ctrl);1787 // Publish reference to this controller1788 compiledData.controller = ctrl;1789 }1790 return linkFn(scope);1791 }1792 };1793 });1794 };1795}1796MdGesture.$inject = ["$$MdGestureHandler", "$$rAF", "$timeout"];1797attachToDocument.$inject = ["$mdGesture", "$$MdGestureHandler"];var HANDLERS = {};1798/* The state of the current 'pointer'1799 * The pointer represents the state of the current touch.1800 * It contains normalized x and y coordinates from DOM events,1801 * as well as other information abstracted from the DOM.1802 */1803var pointer, lastPointer, forceSkipClickHijack = false;1804/**1805 * The position of the most recent click if that click was on a label element.1806 * @type {{x: number, y: number}?}1807 */1808var lastLabelClickPos = null;1809// Used to attach event listeners once when multiple ng-apps are running.1810var isInitialized = false;1811angular1812 .module('material.core.gestures', [ ])1813 .provider('$mdGesture', MdGestureProvider)1814 .factory('$$MdGestureHandler', MdGestureHandler)1815 .run( attachToDocument );1816/**1817 * @ngdoc service1818 * @name $mdGestureProvider1819 * @module material.core.gestures1820 *1821 * @description1822 * In some scenarios on Mobile devices (without jQuery), the click events should NOT be hijacked.1823 * `$mdGestureProvider` is used to configure the Gesture module to ignore or skip click hijacking on mobile1824 * devices.1825 *1826 * <hljs lang="js">1827 * app.config(function($mdGestureProvider) {1828 *1829 * // For mobile devices without jQuery loaded, do not1830 * // intercept click events during the capture phase.1831 * $mdGestureProvider.skipClickHijack();1832 *1833 * });1834 * </hljs>1835 *1836 */1837function MdGestureProvider() { }1838MdGestureProvider.prototype = {1839 // Publish access to setter to configure a variable BEFORE the1840 // $mdGesture service is instantiated...1841 skipClickHijack: function() {1842 return forceSkipClickHijack = true;1843 },1844 /**1845 * $get is used to build an instance of $mdGesture1846 * ngInject1847 */1848 $get : ["$$MdGestureHandler", "$$rAF", "$timeout", function($$MdGestureHandler, $$rAF, $timeout) {1849 return new MdGesture($$MdGestureHandler, $$rAF, $timeout);1850 }]1851};1852/**1853 * MdGesture factory construction function1854 * ngInject1855 */1856function MdGesture($$MdGestureHandler, $$rAF, $timeout) {1857 var userAgent = navigator.userAgent || navigator.vendor || window.opera;1858 var isIos = userAgent.match(/ipad|iphone|ipod/i);1859 var isAndroid = userAgent.match(/android/i);1860 var touchActionProperty = getTouchAction();1861 var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);1862 var self = {1863 handler: addHandler,1864 register: register,1865 isIos: isIos,1866 isAndroid: isAndroid,1867 // On mobile w/out jQuery, we normally intercept clicks. Should we skip that?1868 isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack1869 };1870 if (self.isHijackingClicks) {1871 var maxClickDistance = 6;1872 self.handler('click', {1873 options: {1874 maxDistance: maxClickDistance1875 },1876 onEnd: checkDistanceAndEmit('click')1877 });1878 self.handler('focus', {1879 options: {1880 maxDistance: maxClickDistance1881 },1882 onEnd: function(ev, pointer) {1883 if (pointer.distance < this.state.options.maxDistance) {1884 if (canFocus(ev.target)) {1885 this.dispatchEvent(ev, 'focus', pointer);1886 ev.target.focus();1887 }1888 }1889 function canFocus(element) {1890 var focusableElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA', 'VIDEO', 'AUDIO'];1891 return (element.getAttribute('tabindex') != '-1') &&1892 !element.hasAttribute('DISABLED') &&1893 (element.hasAttribute('tabindex') || element.hasAttribute('href') || element.isContentEditable ||1894 (focusableElements.indexOf(element.nodeName) != -1));1895 }1896 }1897 });1898 self.handler('mouseup', {1899 options: {1900 maxDistance: maxClickDistance1901 },1902 onEnd: checkDistanceAndEmit('mouseup')1903 });1904 self.handler('mousedown', {1905 onStart: function(ev) {1906 this.dispatchEvent(ev, 'mousedown');1907 }1908 });1909 }1910 function checkDistanceAndEmit(eventName) {1911 return function(ev, pointer) {1912 if (pointer.distance < this.state.options.maxDistance) {1913 this.dispatchEvent(ev, eventName, pointer);1914 }1915 };1916 }1917 /*1918 * Register an element to listen for a handler.1919 * This allows an element to override the default options for a handler.1920 * Additionally, some handlers like drag and hold only dispatch events if1921 * the domEvent happens inside an element that's registered to listen for these events.1922 *1923 * @see GestureHandler for how overriding of default options works.1924 * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horziontal: false })1925 */1926 function register(element, handlerName, options) {1927 var handler = HANDLERS[handlerName.replace(/^\$md./, '')];1928 if (!handler) {1929 throw new Error('Failed to register element with handler ' + handlerName + '. ' +1930 'Available handlers: ' + Object.keys(HANDLERS).join(', '));1931 }1932 return handler.registerElement(element, options);1933 }1934 /*1935 * add a handler to $mdGesture. see below.1936 */1937 function addHandler(name, definition) {1938 var handler = new $$MdGestureHandler(name);1939 angular.extend(handler, definition);1940 HANDLERS[name] = handler;1941 return self;1942 }1943 /*1944 * Register handlers. These listen to touch/start/move events, interpret them,1945 * and dispatch gesture events depending on options & conditions. These are all1946 * instances of GestureHandler.1947 * @see GestureHandler1948 */1949 return self1950 /*1951 * The press handler dispatches an event on touchdown/touchend.1952 * It's a simple abstraction of touch/mouse/pointer start and end.1953 */1954 .handler('press', {1955 onStart: function (ev, pointer) {1956 this.dispatchEvent(ev, '$md.pressdown');1957 },1958 onEnd: function (ev, pointer) {1959 this.dispatchEvent(ev, '$md.pressup');1960 }1961 })1962 /*1963 * The hold handler dispatches an event if the user keeps their finger within1964 * the same <maxDistance> area for <delay> ms.1965 * The hold handler will only run if a parent of the touch target is registered1966 * to listen for hold events through $mdGesture.register()1967 */1968 .handler('hold', {1969 options: {1970 maxDistance: 6,1971 delay: 5001972 },1973 onCancel: function () {1974 $timeout.cancel(this.state.timeout);1975 },1976 onStart: function (ev, pointer) {1977 // For hold, require a parent to be registered with $mdGesture.register()1978 // Because we prevent scroll events, this is necessary.1979 if (!this.state.registeredParent) return this.cancel();1980 this.state.pos = {x: pointer.x, y: pointer.y};1981 this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() {1982 this.dispatchEvent(ev, '$md.hold');1983 this.cancel(); //we're done!1984 }), this.state.options.delay, false);1985 },1986 onMove: function (ev, pointer) {1987 // Don't scroll while waiting for hold.1988 // If we don't preventDefault touchmove events here, Android will assume we don't1989 // want to listen to anymore touch events. It will start scrolling and stop sending1990 // touchmove events.1991 if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();1992 // If the user moves greater than <maxDistance> pixels, stop the hold timer1993 // set in onStart1994 var dx = this.state.pos.x - pointer.x;1995 var dy = this.state.pos.y - pointer.y;1996 if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) {1997 this.cancel();1998 }1999 },2000 onEnd: function () {2001 this.onCancel();2002 }2003 })2004 /*2005 * The drag handler dispatches a drag event if the user holds and moves his finger greater than2006 * <minDistance> px in the x or y direction, depending on options.horizontal.2007 * The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in2008 * the perpendicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>2009 * pixels vertically, this handler won't consider the move part of a drag.2010 */2011 .handler('drag', {2012 options: {2013 minDistance: 6,2014 horizontal: true,2015 cancelMultiplier: 1.52016 },2017 onSetup: function(element, options) {2018 if (touchActionProperty) {2019 // We check for horizontal to be false, because otherwise we would overwrite the default opts.2020 this.oldTouchAction = element[0].style[touchActionProperty];2021 element[0].style[touchActionProperty] = options.horizontal === false ? 'pan-y' : 'pan-x';2022 }2023 },2024 onCleanup: function(element) {2025 if (this.oldTouchAction) {2026 element[0].style[touchActionProperty] = this.oldTouchAction;2027 }2028 },2029 onStart: function (ev) {2030 // For drag, require a parent to be registered with $mdGesture.register()2031 if (!this.state.registeredParent) this.cancel();2032 },2033 onMove: function (ev, pointer) {2034 var shouldStartDrag, shouldCancel;2035 // Don't scroll while deciding if this touchmove qualifies as a drag event.2036 // If we don't preventDefault touchmove events here, Android will assume we don't2037 // want to listen to anymore touch events. It will start scrolling and stop sending2038 // touchmove events.2039 if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();2040 if (!this.state.dragPointer) {2041 if (this.state.options.horizontal) {2042 shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance;2043 shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier;2044 } else {2045 shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance;2046 shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier;2047 }2048 if (shouldStartDrag) {2049 // Create a new pointer representing this drag, starting at this point where the drag started.2050 this.state.dragPointer = makeStartPointer(ev);2051 updatePointerState(ev, this.state.dragPointer);2052 this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer);2053 } else if (shouldCancel) {2054 this.cancel();2055 }2056 } else {2057 this.dispatchDragMove(ev);2058 }2059 },2060 // Only dispatch dragmove events every frame; any more is unnecessary2061 dispatchDragMove: $$rAF.throttle(function (ev) {2062 // Make sure the drag didn't stop while waiting for the next frame2063 if (this.state.isRunning) {2064 updatePointerState(ev, this.state.dragPointer);2065 this.dispatchEvent(ev, '$md.drag', this.state.dragPointer);2066 }2067 }),2068 onEnd: function (ev, pointer) {2069 if (this.state.dragPointer) {2070 updatePointerState(ev, this.state.dragPointer);2071 this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer);2072 }2073 }2074 })2075 /*2076 * The swipe handler will dispatch a swipe event if, on the end of a touch,2077 * the velocity and distance were high enough.2078 */2079 .handler('swipe', {2080 options: {2081 minVelocity: 0.65,2082 minDistance: 102083 },2084 onEnd: function (ev, pointer) {2085 var eventType;2086 if (Math.abs(pointer.velocityX) > this.state.options.minVelocity &&2087 Math.abs(pointer.distanceX) > this.state.options.minDistance) {2088 eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight';2089 this.dispatchEvent(ev, eventType);2090 }2091 else if (Math.abs(pointer.velocityY) > this.state.options.minVelocity &&2092 Math.abs(pointer.distanceY) > this.state.options.minDistance) {2093 eventType = pointer.directionY == 'up' ? '$md.swipeup' : '$md.swipedown';2094 this.dispatchEvent(ev, eventType);2095 }2096 }2097 });2098 function getTouchAction() {2099 var testEl = document.createElement('div');2100 var vendorPrefixes = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];2101 for (var i = 0; i < vendorPrefixes.length; i++) {2102 var prefix = vendorPrefixes[i];2103 var property = prefix ? prefix + 'TouchAction' : 'touchAction';2104 if (angular.isDefined(testEl.style[property])) {2105 return property;2106 }2107 }2108 }2109}2110/**2111 * MdGestureHandler2112 * A GestureHandler is an object which is able to dispatch custom dom events2113 * based on native dom {touch,pointer,mouse}{start,move,end} events.2114 *2115 * A gesture will manage its lifecycle through the start,move,end, and cancel2116 * functions, which are called by native dom events.2117 *2118 * A gesture has the concept of 'options' (eg a swipe's required velocity), which can be2119 * overridden by elements registering through $mdGesture.register()2120 */2121function GestureHandler (name) {2122 this.name = name;2123 this.state = {};2124}2125function MdGestureHandler() {2126 var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);2127 GestureHandler.prototype = {2128 options: {},2129 // jQuery listeners don't work with custom DOMEvents, so we have to dispatch events2130 // differently when jQuery is loaded2131 dispatchEvent: hasJQuery ? jQueryDispatchEvent : nativeDispatchEvent,2132 // These are overridden by the registered handler2133 onSetup: angular.noop,2134 onCleanup: angular.noop,2135 onStart: angular.noop,2136 onMove: angular.noop,2137 onEnd: angular.noop,2138 onCancel: angular.noop,2139 // onStart sets up a new state for the handler, which includes options from the2140 // nearest registered parent element of ev.target.2141 start: function (ev, pointer) {2142 if (this.state.isRunning) return;2143 var parentTarget = this.getNearestParent(ev.target);2144 // Get the options from the nearest registered parent2145 var parentTargetOptions = parentTarget && parentTarget.$mdGesture[this.name] || {};2146 this.state = {2147 isRunning: true,2148 // Override the default options with the nearest registered parent's options2149 options: angular.extend({}, this.options, parentTargetOptions),2150 // Pass in the registered parent node to the state so the onStart listener can use2151 registeredParent: parentTarget2152 };2153 this.onStart(ev, pointer);2154 },2155 move: function (ev, pointer) {2156 if (!this.state.isRunning) return;2157 this.onMove(ev, pointer);2158 },2159 end: function (ev, pointer) {2160 if (!this.state.isRunning) return;2161 this.onEnd(ev, pointer);2162 this.state.isRunning = false;2163 },2164 cancel: function (ev, pointer) {2165 this.onCancel(ev, pointer);2166 this.state = {};2167 },2168 // Find and return the nearest parent element that has been registered to2169 // listen for this handler via $mdGesture.register(element, 'handlerName').2170 getNearestParent: function (node) {2171 var current = node;2172 while (current) {2173 if ((current.$mdGesture || {})[this.name]) {2174 return current;2175 }2176 current = current.parentNode;2177 }2178 return null;2179 },2180 // Called from $mdGesture.register when an element registers itself with a handler.2181 // Store the options the user gave on the DOMElement itself. These options will2182 // be retrieved with getNearestParent when the handler starts.2183 registerElement: function (element, options) {2184 var self = this;2185 element[0].$mdGesture = element[0].$mdGesture || {};2186 element[0].$mdGesture[this.name] = options || {};2187 element.on('$destroy', onDestroy);2188 self.onSetup(element, options || {});2189 return onDestroy;2190 function onDestroy() {2191 delete element[0].$mdGesture[self.name];2192 element.off('$destroy', onDestroy);2193 self.onCleanup(element, options || {});2194 }2195 }2196 };2197 return GestureHandler;2198 /*2199 * Dispatch an event with jQuery2200 * TODO: Make sure this sends bubbling events2201 *2202 * @param srcEvent the original DOM touch event that started this.2203 * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')2204 * @param eventPointer the pointer object that matches this event.2205 */2206 function jQueryDispatchEvent(srcEvent, eventType, eventPointer) {2207 eventPointer = eventPointer || pointer;2208 var eventObj = new angular.element.Event(eventType);2209 eventObj.$material = true;2210 eventObj.pointer = eventPointer;2211 eventObj.srcEvent = srcEvent;2212 angular.extend(eventObj, {2213 clientX: eventPointer.x,2214 clientY: eventPointer.y,2215 screenX: eventPointer.x,2216 screenY: eventPointer.y,2217 pageX: eventPointer.x,2218 pageY: eventPointer.y,2219 ctrlKey: srcEvent.ctrlKey,2220 altKey: srcEvent.altKey,2221 shiftKey: srcEvent.shiftKey,2222 metaKey: srcEvent.metaKey2223 });2224 angular.element(eventPointer.target).trigger(eventObj);2225 }2226 /*2227 * NOTE: nativeDispatchEvent is very performance sensitive.2228 * @param srcEvent the original DOM touch event that started this.2229 * @param eventType the name of the custom event to send (eg 'click' or '$md.drag')2230 * @param eventPointer the pointer object that matches this event.2231 */2232 function nativeDispatchEvent(srcEvent, eventType, eventPointer) {2233 eventPointer = eventPointer || pointer;2234 var eventObj;2235 if (eventType === 'click' || eventType == 'mouseup' || eventType == 'mousedown' ) {2236 eventObj = document.createEvent('MouseEvents');2237 eventObj.initMouseEvent(2238 eventType, true, true, window, srcEvent.detail,2239 eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y,2240 srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey,2241 srcEvent.button, srcEvent.relatedTarget || null2242 );2243 } else {2244 eventObj = document.createEvent('CustomEvent');2245 eventObj.initCustomEvent(eventType, true, true, {});2246 }2247 eventObj.$material = true;2248 eventObj.pointer = eventPointer;2249 eventObj.srcEvent = srcEvent;2250 eventPointer.target.dispatchEvent(eventObj);2251 }2252}2253/**2254 * Attach Gestures: hook document and check shouldHijack clicks2255 * ngInject2256 */2257function attachToDocument( $mdGesture, $$MdGestureHandler ) {2258 // Polyfill document.contains for IE11.2259 // TODO: move to util2260 document.contains || (document.contains = function (node) {2261 return document.body.contains(node);2262 });2263 if (!isInitialized && $mdGesture.isHijackingClicks ) {2264 /*2265 * If hijack clicks is true, we preventDefault any click that wasn't2266 * sent by ngMaterial. This is because on older Android & iOS, a false, or 'ghost',2267 * click event will be sent ~400ms after a touchend event happens.2268 * The only way to know if this click is real is to prevent any normal2269 * click events, and add a flag to events sent by material so we know not to prevent those.2270 *2271 * Two exceptions to click events that should be prevented are:2272 * - click events sent by the keyboard (eg form submit)2273 * - events that originate from an Ionic app2274 */2275 document.addEventListener('click' , clickHijacker , true);2276 document.addEventListener('mouseup' , mouseInputHijacker, true);2277 document.addEventListener('mousedown', mouseInputHijacker, true);2278 document.addEventListener('focus' , mouseInputHijacker, true);2279 isInitialized = true;2280 }2281 function mouseInputHijacker(ev) {2282 var isKeyClick = !ev.clientX && !ev.clientY;2283 if (!isKeyClick && !ev.$material && !ev.isIonicTap2284 && !isInputEventFromLabelClick(ev)) {2285 ev.preventDefault();2286 ev.stopPropagation();2287 }2288 }2289 function clickHijacker(ev) {2290 var isKeyClick = ev.clientX === 0 && ev.clientY === 0;2291 if (!isKeyClick && !ev.$material && !ev.isIonicTap2292 && !isInputEventFromLabelClick(ev)) {2293 ev.preventDefault();2294 ev.stopPropagation();2295 lastLabelClickPos = null;2296 } else {2297 lastLabelClickPos = null;2298 if (ev.target.tagName.toLowerCase() == 'label') {2299 lastLabelClickPos = {x: ev.x, y: ev.y};2300 }2301 }2302 }2303 // Listen to all events to cover all platforms.2304 var START_EVENTS = 'mousedown touchstart pointerdown';2305 var MOVE_EVENTS = 'mousemove touchmove pointermove';2306 var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel';2307 angular.element(document)2308 .on(START_EVENTS, gestureStart)2309 .on(MOVE_EVENTS, gestureMove)2310 .on(END_EVENTS, gestureEnd)2311 // For testing2312 .on('$$mdGestureReset', function gestureClearCache () {2313 lastPointer = pointer = null;2314 });2315 /*2316 * When a DOM event happens, run all registered gesture handlers' lifecycle2317 * methods which match the DOM event.2318 * Eg when a 'touchstart' event happens, runHandlers('start') will call and2319 * run `handler.cancel()` and `handler.start()` on all registered handlers.2320 */2321 function runHandlers(handlerEvent, event) {2322 var handler;2323 for (var name in HANDLERS) {2324 handler = HANDLERS[name];2325 if( handler instanceof $$MdGestureHandler ) {2326 if (handlerEvent === 'start') {2327 // Run cancel to reset any handlers' state2328 handler.cancel();2329 }2330 handler[handlerEvent](event, pointer);2331 }2332 }2333 }2334 /*2335 * gestureStart vets if a start event is legitimate (and not part of a 'ghost click' from iOS/Android)2336 * If it is legitimate, we initiate the pointer state and mark the current pointer's type2337 * For example, for a touchstart event, mark the current pointer as a 'touch' pointer, so mouse events2338 * won't effect it.2339 */2340 function gestureStart(ev) {2341 // If we're already touched down, abort2342 if (pointer) return;2343 var now = +Date.now();2344 // iOS & old android bug: after a touch event, a click event is sent 350 ms later.2345 // If <400ms have passed, don't allow an event of a different type than the previous event2346 if (lastPointer && !typesMatch(ev, lastPointer) && (now - lastPointer.endTime < 1500)) {2347 return;2348 }2349 pointer = makeStartPointer(ev);2350 runHandlers('start', ev);2351 }2352 /*2353 * If a move event happens of the right type, update the pointer and run all the move handlers.2354 * "of the right type": if a mousemove happens but our pointer started with a touch event, do nothing.2355 */2356 function gestureMove(ev) {2357 if (!pointer || !typesMatch(ev, pointer)) return;2358 updatePointerState(ev, pointer);2359 runHandlers('move', ev);2360 }2361 /*2362 * If an end event happens of the right type, update the pointer, run endHandlers, and save the pointer as 'lastPointer'2363 */2364 function gestureEnd(ev) {2365 if (!pointer || !typesMatch(ev, pointer)) return;2366 updatePointerState(ev, pointer);2367 pointer.endTime = +Date.now();2368 runHandlers('end', ev);2369 lastPointer = pointer;2370 pointer = null;2371 }2372}2373// ********************2374// Module Functions2375// ********************2376/*2377 * Initiate the pointer. x, y, and the pointer's type.2378 */2379function makeStartPointer(ev) {2380 var point = getEventPoint(ev);2381 var startPointer = {2382 startTime: +Date.now(),2383 target: ev.target,2384 // 'p' for pointer events, 'm' for mouse, 't' for touch2385 type: ev.type.charAt(0)2386 };2387 startPointer.startX = startPointer.x = point.pageX;2388 startPointer.startY = startPointer.y = point.pageY;2389 return startPointer;2390}2391/*2392 * return whether the pointer's type matches the event's type.2393 * Eg if a touch event happens but the pointer has a mouse type, return false.2394 */2395function typesMatch(ev, pointer) {2396 return ev && pointer && ev.type.charAt(0) === pointer.type;2397}2398/**2399 * Gets whether the given event is an input event that was caused by clicking on an2400 * associated label element.2401 *2402 * This is necessary because the browser will, upon clicking on a label element, fire an2403 * *extra* click event on its associated input (if any). mdGesture is able to flag the label2404 * click as with `$material` correctly, but not the second input click.2405 *2406 * In order to determine whether an input event is from a label click, we compare the (x, y) for2407 * the event to the (x, y) for the most recent label click (which is cleared whenever a non-label2408 * click occurs). Unfortunately, there are no event properties that tie the input and the label2409 * together (such as relatedTarget).2410 *2411 * @param {MouseEvent} event2412 * @returns {boolean}2413 */2414function isInputEventFromLabelClick(event) {2415 return lastLabelClickPos2416 && lastLabelClickPos.x == event.x2417 && lastLabelClickPos.y == event.y;2418}2419/*2420 * Update the given pointer based upon the given DOMEvent.2421 * Distance, velocity, direction, duration, etc2422 */2423function updatePointerState(ev, pointer) {2424 var point = getEventPoint(ev);2425 var x = pointer.x = point.pageX;2426 var y = pointer.y = point.pageY;2427 pointer.distanceX = x - pointer.startX;2428 pointer.distanceY = y - pointer.startY;2429 pointer.distance = Math.sqrt(2430 pointer.distanceX * pointer.distanceX + pointer.distanceY * pointer.distanceY2431 );2432 pointer.directionX = pointer.distanceX > 0 ? 'right' : pointer.distanceX < 0 ? 'left' : '';2433 pointer.directionY = pointer.distanceY > 0 ? 'down' : pointer.distanceY < 0 ? 'up' : '';2434 pointer.duration = +Date.now() - pointer.startTime;2435 pointer.velocityX = pointer.distanceX / pointer.duration;2436 pointer.velocityY = pointer.distanceY / pointer.duration;2437}2438/*2439 * Normalize the point where the DOM event happened whether it's touch or mouse.2440 * @returns point event obj with pageX and pageY on it.2441 */2442function getEventPoint(ev) {2443 ev = ev.originalEvent || ev; // support jQuery events2444 return (ev.touches && ev.touches[0]) ||2445 (ev.changedTouches && ev.changedTouches[0]) ||2446 ev;2447}2448angular.module('material.core')2449 .provider('$$interimElement', InterimElementProvider);2450/*2451 * @ngdoc service2452 * @name $$interimElement2453 * @module material.core2454 *2455 * @description2456 *2457 * Factory that contructs `$$interimElement.$service` services.2458 * Used internally in material design for elements that appear on screen temporarily.2459 * The service provides a promise-like API for interacting with the temporary2460 * elements.2461 *2462 * ```js2463 * app.service('$mdToast', function($$interimElement) {2464 * var $mdToast = $$interimElement(toastDefaultOptions);2465 * return $mdToast;2466 * });2467 * ```2468 * @param {object=} defaultOptions Options used by default for the `show` method on the service.2469 *2470 * @returns {$$interimElement.$service}2471 *2472 */2473function InterimElementProvider() {2474 InterimElementFactory.$inject = ["$document", "$q", "$$q", "$rootScope", "$timeout", "$rootElement", "$animate", "$mdUtil", "$mdCompiler", "$mdTheming", "$injector"];2475 createInterimElementProvider.$get = InterimElementFactory;2476 return createInterimElementProvider;2477 /**2478 * Returns a new provider which allows configuration of a new interimElement2479 * service. Allows configuration of default options & methods for options,2480 * as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method)2481 */2482 function createInterimElementProvider(interimFactoryName) {2483 factory.$inject = ["$$interimElement", "$injector"];2484 var EXPOSED_METHODS = ['onHide', 'onShow', 'onRemove'];2485 var customMethods = {};2486 var providerConfig = {2487 presets: {}2488 };2489 var provider = {2490 setDefaults: setDefaults,2491 addPreset: addPreset,2492 addMethod: addMethod,2493 $get: factory2494 };2495 /**2496 * all interim elements will come with the 'build' preset2497 */2498 provider.addPreset('build', {2499 methods: ['controller', 'controllerAs', 'resolve',2500 'template', 'templateUrl', 'themable', 'transformTemplate', 'parent']2501 });2502 return provider;2503 /**2504 * Save the configured defaults to be used when the factory is instantiated2505 */2506 function setDefaults(definition) {2507 providerConfig.optionsFactory = definition.options;2508 providerConfig.methods = (definition.methods || []).concat(EXPOSED_METHODS);2509 return provider;2510 }2511 /**2512 * Add a method to the factory that isn't specific to any interim element operations2513 */2514 function addMethod(name, fn) {2515 customMethods[name] = fn;2516 return provider;2517 }2518 /**2519 * Save the configured preset to be used when the factory is instantiated2520 */2521 function addPreset(name, definition) {2522 definition = definition || {};2523 definition.methods = definition.methods || [];2524 definition.options = definition.options || function() { return {}; };2525 if (/^cancel|hide|show$/.test(name)) {2526 throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!");2527 }2528 if (definition.methods.indexOf('_options') > -1) {2529 throw new Error("Method '_options' in " + interimFactoryName + " is reserved!");2530 }2531 providerConfig.presets[name] = {2532 methods: definition.methods.concat(EXPOSED_METHODS),2533 optionsFactory: definition.options,2534 argOption: definition.argOption2535 };2536 return provider;2537 }2538 function addPresetMethod(presetName, methodName, method) {2539 providerConfig.presets[presetName][methodName] = method;2540 }2541 /**2542 * Create a factory that has the given methods & defaults implementing interimElement2543 */2544 /* ngInject */2545 function factory($$interimElement, $injector) {2546 var defaultMethods;2547 var defaultOptions;2548 var interimElementService = $$interimElement();2549 /*2550 * publicService is what the developer will be using.2551 * It has methods hide(), cancel(), show(), build(), and any other2552 * presets which were set during the config phase.2553 */2554 var publicService = {2555 hide: interimElementService.hide,2556 cancel: interimElementService.cancel,2557 show: showInterimElement,2558 // Special internal method to destroy an interim element without animations2559 // used when navigation changes causes a $scope.$destroy() action2560 destroy : destroyInterimElement2561 };2562 defaultMethods = providerConfig.methods || [];2563 // This must be invoked after the publicService is initialized2564 defaultOptions = invokeFactory(providerConfig.optionsFactory, {});2565 // Copy over the simple custom methods2566 angular.forEach(customMethods, function(fn, name) {2567 publicService[name] = fn;2568 });2569 angular.forEach(providerConfig.presets, function(definition, name) {2570 var presetDefaults = invokeFactory(definition.optionsFactory, {});2571 var presetMethods = (definition.methods || []).concat(defaultMethods);2572 // Every interimElement built with a preset has a field called `$type`,2573 // which matches the name of the preset.2574 // Eg in preset 'confirm', options.$type === 'confirm'2575 angular.extend(presetDefaults, { $type: name });2576 // This creates a preset class which has setter methods for every2577 // method given in the `.addPreset()` function, as well as every2578 // method given in the `.setDefaults()` function.2579 //2580 // @example2581 // .setDefaults({2582 // methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'],2583 // options: dialogDefaultOptions2584 // })2585 // .addPreset('alert', {2586 // methods: ['title', 'ok'],2587 // options: alertDialogOptions2588 // })2589 //2590 // Set values will be passed to the options when interimElement.show() is called.2591 function Preset(opts) {2592 this._options = angular.extend({}, presetDefaults, opts);2593 }2594 angular.forEach(presetMethods, function(name) {2595 Preset.prototype[name] = function(value) {2596 this._options[name] = value;2597 return this;2598 };2599 });2600 // Create shortcut method for one-linear methods2601 if (definition.argOption) {2602 var methodName = 'show' + name.charAt(0).toUpperCase() + name.slice(1);2603 publicService[methodName] = function(arg) {2604 var config = publicService[name](arg);2605 return publicService.show(config);2606 };2607 }2608 // eg $mdDialog.alert() will return a new alert preset2609 publicService[name] = function(arg) {2610 // If argOption is supplied, eg `argOption: 'content'`, then we assume2611 // if the argument is not an options object then it is the `argOption` option.2612 //2613 // @example `$mdToast.simple('hello')` // sets options.content to hello2614 // // because argOption === 'content'2615 if (arguments.length && definition.argOption &&2616 !angular.isObject(arg) && !angular.isArray(arg)) {2617 return (new Preset())[definition.argOption](arg);2618 } else {2619 return new Preset(arg);2620 }2621 };2622 });2623 return publicService;2624 /**2625 *2626 */2627 function showInterimElement(opts) {2628 // opts is either a preset which stores its options on an _options field,2629 // or just an object made up of options2630 opts = opts || { };2631 if (opts._options) opts = opts._options;2632 return interimElementService.show(2633 angular.extend({}, defaultOptions, opts)2634 );2635 }2636 /**2637 * Special method to hide and destroy an interimElement WITHOUT2638 * any 'leave` or hide animations ( an immediate force hide/remove )2639 *2640 * NOTE: This calls the onRemove() subclass method for each component...2641 * which must have code to respond to `options.$destroy == true`2642 */2643 function destroyInterimElement(opts) {2644 return interimElementService.destroy(opts);2645 }2646 /**2647 * Helper to call $injector.invoke with a local of the factory name for2648 * this provider.2649 * If an $mdDialog is providing options for a dialog and tries to inject2650 * $mdDialog, a circular dependency error will happen.2651 * We get around that by manually injecting $mdDialog as a local.2652 */2653 function invokeFactory(factory, defaultVal) {2654 var locals = {};2655 locals[interimFactoryName] = publicService;2656 return $injector.invoke(factory || function() { return defaultVal; }, {}, locals);2657 }2658 }2659 }2660 /* ngInject */2661 function InterimElementFactory($document, $q, $$q, $rootScope, $timeout, $rootElement, $animate,2662 $mdUtil, $mdCompiler, $mdTheming, $injector ) {2663 return function createInterimElementService() {2664 var SHOW_CANCELLED = false;2665 /*2666 * @ngdoc service2667 * @name $$interimElement.$service2668 *2669 * @description2670 * A service used to control inserting and removing an element into the DOM.2671 *2672 */2673 var service, stack = [];2674 // Publish instance $$interimElement service;2675 // ... used as $mdDialog, $mdToast, $mdMenu, and $mdSelect2676 return service = {2677 show: show,2678 hide: hide,2679 cancel: cancel,2680 destroy : destroy,2681 $injector_: $injector2682 };2683 /*2684 * @ngdoc method2685 * @name $$interimElement.$service#show2686 * @kind function2687 *2688 * @description2689 * Adds the `$interimElement` to the DOM and returns a special promise that will be resolved or rejected2690 * with hide or cancel, respectively. To external cancel/hide, developers should use the2691 *2692 * @param {*} options is hashMap of settings2693 * @returns a Promise2694 *2695 */2696 function show(options) {2697 options = options || {};2698 var interimElement = new InterimElement(options || {});2699 // When an interim element is currently showing, we have to cancel it.2700 // Just hiding it, will resolve the InterimElement's promise, the promise should be2701 // rejected instead.2702 var hideExisting = !options.skipHide && stack.length ? service.cancel() : $q.when(true);2703 // This hide()s only the current interim element before showing the next, new one2704 // NOTE: this is not reversible (e.g. interim elements are not stackable)2705 hideExisting.finally(function() {2706 stack.push(interimElement);2707 interimElement2708 .show()2709 .catch(function( reason ) {2710 //$log.error("InterimElement.show() error: " + reason );2711 return reason;2712 });2713 });2714 // Return a promise that will be resolved when the interim2715 // element is hidden or cancelled...2716 return interimElement.deferred.promise;2717 }2718 /*2719 * @ngdoc method2720 * @name $$interimElement.$service#hide2721 * @kind function2722 *2723 * @description2724 * Removes the `$interimElement` from the DOM and resolves the promise returned from `show`2725 *2726 * @param {*} resolveParam Data to resolve the promise with2727 * @returns a Promise that will be resolved after the element has been removed.2728 *2729 */2730 function hide(reason, options) {2731 if ( !stack.length ) return $q.when(reason);2732 options = options || {};2733 if (options.closeAll) {2734 var promise = $q.all(stack.reverse().map(closeElement));2735 stack = [];2736 return promise;2737 } else if (options.closeTo !== undefined) {2738 return $q.all(stack.splice(options.closeTo).map(closeElement));2739 } else {2740 var interim = stack.pop();2741 return closeElement(interim);2742 }2743 function closeElement(interim) {2744 interim2745 .remove(reason, false, options || { })2746 .catch(function( reason ) {2747 //$log.error("InterimElement.hide() error: " + reason );2748 return reason;2749 });2750 return interim.deferred.promise;2751 }2752 }2753 /*2754 * @ngdoc method2755 * @name $$interimElement.$service#cancel2756 * @kind function2757 *2758 * @description2759 * Removes the `$interimElement` from the DOM and rejects the promise returned from `show`2760 *2761 * @param {*} reason Data to reject the promise with2762 * @returns Promise that will be resolved after the element has been removed.2763 *2764 */2765 function cancel(reason, options) {2766 var interim = stack.pop();2767 if ( !interim ) return $q.when(reason);2768 interim2769 .remove(reason, true, options || { })2770 .catch(function( reason ) {2771 //$log.error("InterimElement.cancel() error: " + reason );2772 return reason;2773 });2774 // Since Angular 1.6.7, promises will be logged to $exceptionHandler when the promise2775 // is not handling the rejection. We create a pseudo catch handler, which will prevent the2776 // promise from being logged to the $exceptionHandler.2777 return interim.deferred.promise.catch(angular.noop);2778 }2779 /*2780 * Special method to quick-remove the interim element without animations2781 * Note: interim elements are in "interim containers"2782 */2783 function destroy(target) {2784 var interim = !target ? stack.shift() : null;2785 var cntr = angular.element(target).length ? angular.element(target)[0].parentNode : null;2786 if (cntr) {2787 // Try to find the interim element in the stack which corresponds to the supplied DOM element.2788 var filtered = stack.filter(function(entry) {2789 var currNode = entry.options.element[0];2790 return (currNode === cntr);2791 });2792 // Note: this function might be called when the element already has been removed, in which2793 // case we won't find any matches. That's ok.2794 if (filtered.length > 0) {2795 interim = filtered[0];2796 stack.splice(stack.indexOf(interim), 1);2797 }2798 }2799 return interim ? interim.remove(SHOW_CANCELLED, false, {'$destroy':true}) : $q.when(SHOW_CANCELLED);2800 }2801 /*2802 * Internal Interim Element Object2803 * Used internally to manage the DOM element and related data2804 */2805 function InterimElement(options) {2806 var self, element, showAction = $q.when(true);2807 options = configureScopeAndTransitions(options);2808 return self = {2809 options : options,2810 deferred: $q.defer(),2811 show : createAndTransitionIn,2812 remove : transitionOutAndRemove2813 };2814 /**2815 * Compile, link, and show this interim element2816 * Use optional autoHided and transition-in effects2817 */2818 function createAndTransitionIn() {2819 return $q(function(resolve, reject) {2820 // Trigger onCompiling callback before the compilation starts.2821 // This is useful, when modifying options, which can be influenced by developers.2822 options.onCompiling && options.onCompiling(options);2823 compileElement(options)2824 .then(function( compiledData ) {2825 element = linkElement( compiledData, options );2826 showAction = showElement(element, options, compiledData.controller)2827 .then(resolve, rejectAll);2828 }, rejectAll);2829 function rejectAll(fault) {2830 // Force the '$md<xxx>.show()' promise to reject2831 self.deferred.reject(fault);2832 // Continue rejection propagation2833 reject(fault);2834 }2835 });2836 }2837 /**2838 * After the show process has finished/rejected:2839 * - announce 'removing',2840 * - perform the transition-out, and2841 * - perform optional clean up scope.2842 */2843 function transitionOutAndRemove(response, isCancelled, opts) {2844 // abort if the show() and compile failed2845 if ( !element ) return $q.when(false);2846 options = angular.extend(options || {}, opts || {});2847 options.cancelAutoHide && options.cancelAutoHide();2848 options.element.triggerHandler('$mdInterimElementRemove');2849 if ( options.$destroy === true ) {2850 return hideElement(options.element, options).then(function(){2851 (isCancelled && rejectAll(response)) || resolveAll(response);2852 });2853 } else {2854 $q.when(showAction)2855 .finally(function() {2856 hideElement(options.element, options).then(function() {2857 (isCancelled && rejectAll(response)) || resolveAll(response);2858 }, rejectAll);2859 });2860 return self.deferred.promise;2861 }2862 /**2863 * The `show()` returns a promise that will be resolved when the interim2864 * element is hidden or cancelled...2865 */2866 function resolveAll(response) {2867 self.deferred.resolve(response);2868 }2869 /**2870 * Force the '$md<xxx>.show()' promise to reject2871 */2872 function rejectAll(fault) {2873 self.deferred.reject(fault);2874 }2875 }2876 /**2877 * Prepare optional isolated scope and prepare $animate with default enter and leave2878 * transitions for the new element instance.2879 */2880 function configureScopeAndTransitions(options) {2881 options = options || { };2882 if ( options.template ) {2883 options.template = $mdUtil.processTemplate(options.template);2884 }2885 return angular.extend({2886 preserveScope: false,2887 cancelAutoHide : angular.noop,2888 scope: options.scope || $rootScope.$new(options.isolateScope),2889 /**2890 * Default usage to enable $animate to transition-in; can be easily overridden via 'options'2891 */2892 onShow: function transitionIn(scope, element, options) {2893 return $animate.enter(element, options.parent);2894 },2895 /**2896 * Default usage to enable $animate to transition-out; can be easily overridden via 'options'2897 */2898 onRemove: function transitionOut(scope, element) {2899 // Element could be undefined if a new element is shown before2900 // the old one finishes compiling.2901 return element && $animate.leave(element) || $q.when();2902 }2903 }, options );2904 }2905 /**2906 * Compile an element with a templateUrl, controller, and locals2907 */2908 function compileElement(options) {2909 var compiled = !options.skipCompile ? $mdCompiler.compile(options) : null;2910 return compiled || $q(function (resolve) {2911 resolve({2912 locals: {},2913 link: function () {2914 return options.element;2915 }2916 });2917 });2918 }2919 /**2920 * Link an element with compiled configuration2921 */2922 function linkElement(compileData, options){2923 angular.extend(compileData.locals, options);2924 var element = compileData.link(options.scope);2925 // Search for parent at insertion time, if not specified2926 options.element = element;2927 options.parent = findParent(element, options);2928 if (options.themable) $mdTheming(element);2929 return element;2930 }2931 /**2932 * Search for parent at insertion time, if not specified2933 */2934 function findParent(element, options) {2935 var parent = options.parent;2936 // Search for parent at insertion time, if not specified2937 if (angular.isFunction(parent)) {2938 parent = parent(options.scope, element, options);2939 } else if (angular.isString(parent)) {2940 parent = angular.element($document[0].querySelector(parent));2941 } else {2942 parent = angular.element(parent);2943 }2944 // If parent querySelector/getter function fails, or it's just null,2945 // find a default.2946 if (!(parent || {}).length) {2947 var el;2948 if ($rootElement[0] && $rootElement[0].querySelector) {2949 el = $rootElement[0].querySelector(':not(svg) > body');2950 }2951 if (!el) el = $rootElement[0];2952 if (el.nodeName == '#comment') {2953 el = $document[0].body;2954 }2955 return angular.element(el);2956 }2957 return parent;2958 }2959 /**2960 * If auto-hide is enabled, start timer and prepare cancel function2961 */2962 function startAutoHide() {2963 var autoHideTimer, cancelAutoHide = angular.noop;2964 if (options.hideDelay) {2965 autoHideTimer = $timeout(service.hide, options.hideDelay) ;2966 cancelAutoHide = function() {2967 $timeout.cancel(autoHideTimer);2968 }2969 }2970 // Cache for subsequent use2971 options.cancelAutoHide = function() {2972 cancelAutoHide();2973 options.cancelAutoHide = undefined;2974 }2975 }2976 /**2977 * Show the element ( with transitions), notify complete and start2978 * optional auto-Hide2979 */2980 function showElement(element, options, controller) {2981 // Trigger onShowing callback before the `show()` starts2982 var notifyShowing = options.onShowing || angular.noop;2983 // Trigger onComplete callback when the `show()` finishes2984 var notifyComplete = options.onComplete || angular.noop;2985 notifyShowing(options.scope, element, options, controller);2986 return $q(function (resolve, reject) {2987 try {2988 // Start transitionIn2989 $q.when(options.onShow(options.scope, element, options, controller))2990 .then(function () {2991 notifyComplete(options.scope, element, options);2992 startAutoHide();2993 resolve(element);2994 }, reject );2995 } catch(e) {2996 reject(e.message);2997 }2998 });2999 }3000 function hideElement(element, options) {3001 var announceRemoving = options.onRemoving || angular.noop;3002 return $$q(function (resolve, reject) {3003 try {3004 // Start transitionIn3005 var action = $$q.when( options.onRemove(options.scope, element, options) || true );3006 // Trigger callback *before* the remove operation starts3007 announceRemoving(element, action);3008 if ( options.$destroy == true ) {3009 // For $destroy, onRemove should be synchronous3010 resolve(element);3011 } else {3012 // Wait until transition-out is done3013 action.then(function () {3014 if (!options.preserveScope && options.scope ) {3015 options.scope.$destroy();3016 }3017 resolve(element);3018 }, reject );3019 }3020 } catch(e) {3021 reject(e);3022 }3023 });3024 }3025 }3026 };3027 }3028}3029(function() {3030 'use strict';3031 var $mdUtil, $interpolate, $log;3032 var SUFFIXES = /(-gt)?-(sm|md|lg|print)/g;3033 var WHITESPACE = /\s+/g;3034 var FLEX_OPTIONS = ['grow', 'initial', 'auto', 'none', 'noshrink', 'nogrow' ];3035 var LAYOUT_OPTIONS = ['row', 'column'];3036 var ALIGNMENT_MAIN_AXIS= [ "", "start", "center", "end", "stretch", "space-around", "space-between" ];3037 var ALIGNMENT_CROSS_AXIS= [ "", "start", "center", "end", "stretch" ];3038 var config = {3039 /**3040 * Enable directive attribute-to-class conversions3041 * Developers can use `<body md-layout-css />` to quickly3042 * disable the Layout directives and prohibit the injection of Layout classNames3043 */3044 enabled: true,3045 /**3046 * List of mediaQuery breakpoints and associated suffixes3047 *3048 * [3049 * { suffix: "sm", mediaQuery: "screen and (max-width: 599px)" },3050 * { suffix: "md", mediaQuery: "screen and (min-width: 600px) and (max-width: 959px)" }3051 * ]3052 */3053 breakpoints: []3054 };3055 registerLayoutAPI( angular.module('material.core.layout', ['ng']) );3056 /**3057 * registerLayoutAPI()3058 *3059 * The original ngMaterial Layout solution used attribute selectors and CSS.3060 *3061 * ```html3062 * <div layout="column"> My Content </div>3063 * ```3064 *3065 * ```css3066 * [layout] {3067 * box-sizing: border-box;3068 * display:flex;3069 * }3070 * [layout=column] {3071 * flex-direction : column3072 * }3073 * ```3074 *3075 * Use of attribute selectors creates significant performance impacts in some3076 * browsers... mainly IE.3077 *3078 * This module registers directives that allow the same layout attributes to be3079 * interpreted and converted to class selectors. The directive will add equivalent classes to each element that3080 * contains a Layout directive.3081 *3082 * ```html3083 * <div layout="column" class="layout layout-column"> My Content </div>3084 *```3085 *3086 * ```css3087 * .layout {3088 * box-sizing: border-box;3089 * display:flex;3090 * }3091 * .layout-column {3092 * flex-direction : column3093 * }3094 * ```3095 */3096 function registerLayoutAPI(module){3097 var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;3098 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;3099 // NOTE: these are also defined in constants::MEDIA_PRIORITY and constants::MEDIA3100 var BREAKPOINTS = [ "", "xs", "gt-xs", "sm", "gt-sm", "md", "gt-md", "lg", "gt-lg", "xl", "print" ];3101 var API_WITH_VALUES = [ "layout", "flex", "flex-order", "flex-offset", "layout-align" ];3102 var API_NO_VALUES = [ "show", "hide", "layout-padding", "layout-margin" ];3103 // Build directive registration functions for the standard Layout API... for all breakpoints.3104 angular.forEach(BREAKPOINTS, function(mqb) {3105 // Attribute directives with expected, observable value(s)3106 angular.forEach( API_WITH_VALUES, function(name){3107 var fullName = mqb ? name + "-" + mqb : name;3108 module.directive( directiveNormalize(fullName), attributeWithObserve(fullName));3109 });3110 // Attribute directives with no expected value(s)3111 angular.forEach( API_NO_VALUES, function(name){3112 var fullName = mqb ? name + "-" + mqb : name;3113 module.directive( directiveNormalize(fullName), attributeWithoutValue(fullName));3114 });3115 });3116 // Register other, special directive functions for the Layout features:3117 module3118 .provider('$$mdLayout' , function() {3119 // Publish internal service for Layouts3120 return {3121 $get : angular.noop,3122 validateAttributeValue : validateAttributeValue,3123 validateAttributeUsage : validateAttributeUsage,3124 /**3125 * Easy way to disable/enable the Layout API.3126 * When disabled, this stops all attribute-to-classname generations3127 */3128 disableLayouts : function(isDisabled) {3129 config.enabled = (isDisabled !== true);3130 }3131 };3132 })3133 .directive('mdLayoutCss' , disableLayoutDirective )3134 .directive('ngCloak' , buildCloakInterceptor('ng-cloak'))3135 .directive('layoutWrap' , attributeWithoutValue('layout-wrap'))3136 .directive('layoutNowrap' , attributeWithoutValue('layout-nowrap'))3137 .directive('layoutNoWrap' , attributeWithoutValue('layout-no-wrap'))3138 .directive('layoutFill' , attributeWithoutValue('layout-fill'))3139 // !! Deprecated attributes: use the `-lt` (aka less-than) notations3140 .directive('layoutLtMd' , warnAttrNotSupported('layout-lt-md', true))3141 .directive('layoutLtLg' , warnAttrNotSupported('layout-lt-lg', true))3142 .directive('flexLtMd' , warnAttrNotSupported('flex-lt-md', true))3143 .directive('flexLtLg' , warnAttrNotSupported('flex-lt-lg', true))3144 .directive('layoutAlignLtMd', warnAttrNotSupported('layout-align-lt-md'))3145 .directive('layoutAlignLtLg', warnAttrNotSupported('layout-align-lt-lg'))3146 .directive('flexOrderLtMd' , warnAttrNotSupported('flex-order-lt-md'))3147 .directive('flexOrderLtLg' , warnAttrNotSupported('flex-order-lt-lg'))3148 .directive('offsetLtMd' , warnAttrNotSupported('flex-offset-lt-md'))3149 .directive('offsetLtLg' , warnAttrNotSupported('flex-offset-lt-lg'))3150 .directive('hideLtMd' , warnAttrNotSupported('hide-lt-md'))3151 .directive('hideLtLg' , warnAttrNotSupported('hide-lt-lg'))3152 .directive('showLtMd' , warnAttrNotSupported('show-lt-md'))3153 .directive('showLtLg' , warnAttrNotSupported('show-lt-lg'))3154 // Determine if3155 .config( detectDisabledLayouts );3156 /**3157 * Converts snake_case to camelCase.3158 * Also there is special case for Moz prefix starting with upper case letter.3159 * @param name Name to normalize3160 */3161 function directiveNormalize(name) {3162 return name3163 .replace(PREFIX_REGEXP, '')3164 .replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {3165 return offset ? letter.toUpperCase() : letter;3166 });3167 }3168 }3169 /**3170 * Detect if any of the HTML tags has a [md-layouts-disabled] attribute;3171 * If yes, then immediately disable all layout API features3172 *3173 * Note: this attribute should be specified on either the HTML or BODY tags3174 */3175 /**3176 * ngInject3177 */3178 function detectDisabledLayouts() {3179 var isDisabled = !!document.querySelector('[md-layouts-disabled]');3180 config.enabled = !isDisabled;3181 }3182 /**3183 * Special directive that will disable ALL Layout conversions of layout3184 * attribute(s) to classname(s).3185 *3186 * <link rel="stylesheet" href="angular-material.min.css">3187 * <link rel="stylesheet" href="angular-material.layout.css">3188 *3189 * <body md-layout-css>3190 * ...3191 * </body>3192 *3193 * Note: Using md-layout-css directive requires the developer to load the Material3194 * Layout Attribute stylesheet (which only uses attribute selectors):3195 *3196 * `angular-material.layout.css`3197 *3198 * Another option is to use the LayoutProvider to configure and disable the attribute3199 * conversions; this would obviate the use of the `md-layout-css` directive3200 *3201 */3202 function disableLayoutDirective() {3203 // Return a 1x-only, first-match attribute directive3204 config.enabled = false;3205 return {3206 restrict : 'A',3207 priority : '900'3208 };3209 }3210 /**3211 * Tail-hook ngCloak to delay the uncloaking while Layout transformers3212 * finish processing. Eliminates flicker with Material.Layouts3213 */3214 function buildCloakInterceptor(className) {3215 return [ '$timeout', function($timeout){3216 return {3217 restrict : 'A',3218 priority : -10, // run after normal ng-cloak3219 compile : function( element ) {3220 if (!config.enabled) return angular.noop;3221 // Re-add the cloak3222 element.addClass(className);3223 return function( scope, element ) {3224 // Wait while layout injectors configure, then uncloak3225 // NOTE: $rAF does not delay enough... and this is a 1x-only event,3226 // $timeout is acceptable.3227 $timeout( function(){3228 element.removeClass(className);3229 }, 10, false);3230 };3231 }3232 };3233 }];3234 }3235 // *********************************************************************************3236 //3237 // These functions create registration functions for ngMaterial Layout attribute directives3238 // This provides easy translation to switch ngMaterial attribute selectors to3239 // CLASS selectors and directives; which has huge performance implications3240 // for IE Browsers3241 //3242 // *********************************************************************************3243 /**3244 * Creates a directive registration function where a possible dynamic attribute3245 * value will be observed/watched.3246 * @param {string} className attribute name; eg `layout-gt-md` with value ="row"3247 */3248 function attributeWithObserve(className) {3249 return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) {3250 $mdUtil = _$mdUtil_;3251 $interpolate = _$interpolate_;3252 $log = _$log_;3253 return {3254 restrict: 'A',3255 compile: function(element, attr) {3256 var linkFn;3257 if (config.enabled) {3258 // immediately replace static (non-interpolated) invalid values...3259 validateAttributeUsage(className, attr, element, $log);3260 validateAttributeValue( className,3261 getNormalizedAttrValue(className, attr, ""),3262 buildUpdateFn(element, className, attr)3263 );3264 linkFn = translateWithValueToCssClass;3265 }3266 // Use for postLink to account for transforms after ng-transclude.3267 return linkFn || angular.noop;3268 }3269 };3270 }];3271 /**3272 * Add as transformed class selector(s), then3273 * remove the deprecated attribute selector3274 */3275 function translateWithValueToCssClass(scope, element, attrs) {3276 var updateFn = updateClassWithValue(element, className, attrs);3277 var unwatch = attrs.$observe(attrs.$normalize(className), updateFn);3278 updateFn(getNormalizedAttrValue(className, attrs, ""));3279 scope.$on("$destroy", function() { unwatch(); });3280 }3281 }3282 /**3283 * Creates a registration function for ngMaterial Layout attribute directive.3284 * This is a `simple` transpose of attribute usage to class usage; where we ignore3285 * any attribute value3286 */3287 function attributeWithoutValue(className) {3288 return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) {3289 $mdUtil = _$mdUtil_;3290 $interpolate = _$interpolate_;3291 $log = _$log_;3292 return {3293 restrict: 'A',3294 compile: function(element, attr) {3295 var linkFn;3296 if (config.enabled) {3297 // immediately replace static (non-interpolated) invalid values...3298 validateAttributeValue( className,3299 getNormalizedAttrValue(className, attr, ""),3300 buildUpdateFn(element, className, attr)3301 );3302 translateToCssClass(null, element);3303 // Use for postLink to account for transforms after ng-transclude.3304 linkFn = translateToCssClass;3305 }3306 return linkFn || angular.noop;3307 }3308 };3309 }];3310 /**3311 * Add as transformed class selector, then3312 * remove the deprecated attribute selector3313 */3314 function translateToCssClass(scope, element) {3315 element.addClass(className);3316 }3317 }3318 /**3319 * After link-phase, do NOT remove deprecated layout attribute selector.3320 * Instead watch the attribute so interpolated data-bindings to layout3321 * selectors will continue to be supported.3322 *3323 * $observe() the className and update with new class (after removing the last one)3324 *3325 * e.g. `layout="{{layoutDemo.direction}}"` will update...3326 *3327 * NOTE: The value must match one of the specified styles in the CSS.3328 * For example `flex-gt-md="{{size}}` where `scope.size == 47` will NOT work since3329 * only breakpoints for 0, 5, 10, 15... 100, 33, 34, 66, 67 are defined.3330 *3331 */3332 function updateClassWithValue(element, className) {3333 var lastClass;3334 return function updateClassFn(newValue) {3335 var value = validateAttributeValue(className, newValue || "");3336 if ( angular.isDefined(value) ) {3337 if (lastClass) element.removeClass(lastClass);3338 lastClass = !value ? className : className + "-" + value.replace(WHITESPACE, "-");3339 element.addClass(lastClass);3340 }3341 };3342 }3343 /**3344 * Provide console warning that this layout attribute has been deprecated3345 *3346 */3347 function warnAttrNotSupported(className) {3348 var parts = className.split("-");3349 return ["$log", function($log) {3350 $log.warn(className + "has been deprecated. Please use a `" + parts[0] + "-gt-<xxx>` variant.");3351 return angular.noop;3352 }];3353 }3354 /**3355 * Centralize warnings for known flexbox issues (especially IE-related issues)3356 */3357 function validateAttributeUsage(className, attr, element, $log){3358 var message, usage, url;3359 var nodeName = element[0].nodeName.toLowerCase();3360 switch(className.replace(SUFFIXES,"")) {3361 case "flex":3362 if ((nodeName == "md-button") || (nodeName == "fieldset")){3363 // @see https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers3364 // Use <div flex> wrapper inside (preferred) or outside3365 usage = "<" + nodeName + " " + className + "></" + nodeName + ">";3366 url = "https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers";3367 message = "Markup '{0}' may not work as expected in IE Browsers. Consult '{1}' for details.";3368 $log.warn( $mdUtil.supplant(message, [usage, url]) );3369 }3370 }3371 }3372 /**3373 * For the Layout attribute value, validate or replace with default3374 * fallback value3375 */3376 function validateAttributeValue(className, value, updateFn) {3377 var origValue = value;3378 if (!needsInterpolation(value)) {3379 switch (className.replace(SUFFIXES,"")) {3380 case 'layout' :3381 if ( !findIn(value, LAYOUT_OPTIONS) ) {3382 value = LAYOUT_OPTIONS[0]; // 'row';3383 }3384 break;3385 case 'flex' :3386 if (!findIn(value, FLEX_OPTIONS)) {3387 if (isNaN(value)) {3388 value = '';3389 }3390 }3391 break;3392 case 'flex-offset' :3393 case 'flex-order' :3394 if (!value || isNaN(+value)) {3395 value = '0';3396 }3397 break;3398 case 'layout-align' :3399 var axis = extractAlignAxis(value);3400 value = $mdUtil.supplant("{main}-{cross}",axis);3401 break;3402 case 'layout-padding' :3403 case 'layout-margin' :3404 case 'layout-fill' :3405 case 'layout-wrap' :3406 case 'layout-nowrap' :3407 case 'layout-nowrap' :3408 value = '';3409 break;3410 }3411 if (value != origValue) {3412 (updateFn || angular.noop)(value);3413 }3414 }3415 return value;3416 }3417 /**3418 * Replace current attribute value with fallback value3419 */3420 function buildUpdateFn(element, className, attrs) {3421 return function updateAttrValue(fallback) {3422 if (!needsInterpolation(fallback)) {3423 // Do not modify the element's attribute value; so3424 // uses '<ui-layout layout="/api/sidebar.html" />' will not3425 // be affected. Just update the attrs value.3426 attrs[attrs.$normalize(className)] = fallback;3427 }3428 };3429 }3430 /**3431 * See if the original value has interpolation symbols:3432 * e.g. flex-gt-md="{{triggerPoint}}"3433 */3434 function needsInterpolation(value) {3435 return (value || "").indexOf($interpolate.startSymbol()) > -1;3436 }3437 function getNormalizedAttrValue(className, attrs, defaultVal) {3438 var normalizedAttr = attrs.$normalize(className);3439 return attrs[normalizedAttr] ? attrs[normalizedAttr].replace(WHITESPACE, "-") : defaultVal || null;3440 }3441 function findIn(item, list, replaceWith) {3442 item = replaceWith && item ? item.replace(WHITESPACE, replaceWith) : item;3443 var found = false;3444 if (item) {3445 list.forEach(function(it) {3446 it = replaceWith ? it.replace(WHITESPACE, replaceWith) : it;3447 found = found || (it === item);3448 });3449 }3450 return found;3451 }3452 function extractAlignAxis(attrValue) {3453 var axis = {3454 main : "start",3455 cross: "stretch"3456 }, values;3457 attrValue = (attrValue || "");3458 if ( attrValue.indexOf("-") === 0 || attrValue.indexOf(" ") === 0) {3459 // For missing main-axis values3460 attrValue = "none" + attrValue;3461 }3462 values = attrValue.toLowerCase().trim().replace(WHITESPACE, "-").split("-");3463 if ( values.length && (values[0] === "space") ) {3464 // for main-axis values of "space-around" or "space-between"3465 values = [ values[0]+"-"+values[1],values[2] ];3466 }3467 if ( values.length > 0 ) axis.main = values[0] || axis.main;3468 if ( values.length > 1 ) axis.cross = values[1] || axis.cross;3469 if ( ALIGNMENT_MAIN_AXIS.indexOf(axis.main) < 0 ) axis.main = "start";3470 if ( ALIGNMENT_CROSS_AXIS.indexOf(axis.cross) < 0 ) axis.cross = "stretch";3471 return axis;3472 }3473})();3474/**3475 * @ngdoc service3476 * @name $$mdMeta3477 * @module material.core.meta3478 *3479 * @description3480 *3481 * A provider and a service that simplifies meta tags access3482 *3483 * Note: This is intended only for use with dynamic meta tags such as browser color and title.3484 * Tags that are only processed when the page is rendered (such as `charset`, and `http-equiv`)3485 * will not work since `$$mdMeta` adds the tags after the page has already been loaded.3486 *3487 * ```js3488 * app.config(function($$mdMetaProvider) {3489 * var removeMeta = $$mdMetaProvider.setMeta('meta-name', 'content');3490 * var metaValue = $$mdMetaProvider.getMeta('meta-name'); // -> 'content'3491 *3492 * removeMeta();3493 * });3494 *3495 * app.controller('myController', function($$mdMeta) {3496 * var removeMeta = $$mdMeta.setMeta('meta-name', 'content');3497 * var metaValue = $$mdMeta.getMeta('meta-name'); // -> 'content'3498 *3499 * removeMeta();3500 * });3501 * ```3502 *3503 * @returns {$$mdMeta.$service}3504 *3505 */3506angular.module('material.core.meta', [])3507 .provider('$$mdMeta', function () {3508 var head = angular.element(document.head);3509 var metaElements = {};3510 /**3511 * Checks if the requested element was written manually and maps it3512 *3513 * @param {string} name meta tag 'name' attribute value3514 * @returns {boolean} returns true if there is an element with the requested name3515 */3516 function mapExistingElement(name) {3517 if (metaElements[name]) {3518 return true;3519 }3520 var element = document.getElementsByName(name)[0];3521 if (!element) {3522 return false;3523 }3524 metaElements[name] = angular.element(element);3525 return true;3526 }3527 /**3528 * @ngdoc method3529 * @name $$mdMeta#setMeta3530 *3531 * @description3532 * Creates meta element with the 'name' and 'content' attributes,3533 * if the meta tag is already created than we replace the 'content' value3534 *3535 * @param {string} name meta tag 'name' attribute value3536 * @param {string} content meta tag 'content' attribute value3537 * @returns {function} remove function3538 *3539 */3540 function setMeta(name, content) {3541 mapExistingElement(name);3542 if (!metaElements[name]) {3543 var newMeta = angular.element('<meta name="' + name + '" content="' + content + '"/>');3544 head.append(newMeta);3545 metaElements[name] = newMeta;3546 }3547 else {3548 metaElements[name].attr('content', content);3549 }3550 return function () {3551 metaElements[name].attr('content', '');3552 metaElements[name].remove();3553 delete metaElements[name];3554 };3555 }3556 /**3557 * @ngdoc method3558 * @name $$mdMeta#getMeta3559 *3560 * @description3561 * Gets the 'content' attribute value of the wanted meta element3562 *3563 * @param {string} name meta tag 'name' attribute value3564 * @returns {string} content attribute value3565 */3566 function getMeta(name) {3567 if (!mapExistingElement(name)) {3568 throw Error('$$mdMeta: could not find a meta tag with the name \'' + name + '\'');3569 }3570 return metaElements[name].attr('content');3571 }3572 var module = {3573 setMeta: setMeta,3574 getMeta: getMeta3575 };3576 return angular.extend({}, module, {3577 $get: function () {3578 return module;3579 }3580 });3581 });3582 /**3583 * @ngdoc module3584 * @name material.core.componentRegistry3585 *3586 * @description3587 * A component instance registration service.3588 * Note: currently this as a private service in the SideNav component.3589 */3590 ComponentRegistry.$inject = ["$log", "$q"];3591 angular.module('material.core')3592 .factory('$mdComponentRegistry', ComponentRegistry);3593 /*3594 * @private3595 * @ngdoc factory3596 * @name ComponentRegistry3597 * @module material.core.componentRegistry3598 *3599 */3600 function ComponentRegistry($log, $q) {3601 var self;3602 var instances = [ ];3603 var pendings = { };3604 return self = {3605 /**3606 * Used to print an error when an instance for a handle isn't found.3607 */3608 notFoundError: function(handle, msgContext) {3609 $log.error( (msgContext || "") + 'No instance found for handle', handle);3610 },3611 /**3612 * Return all registered instances as an array.3613 */3614 getInstances: function() {3615 return instances;3616 },3617 /**3618 * Get a registered instance.3619 * @param handle the String handle to look up for a registered instance.3620 */3621 get: function(handle) {3622 if ( !isValidID(handle) ) return null;3623 var i, j, instance;3624 for(i = 0, j = instances.length; i < j; i++) {3625 instance = instances[i];3626 if(instance.$$mdHandle === handle) {3627 return instance;3628 }3629 }3630 return null;3631 },3632 /**3633 * Register an instance.3634 * @param instance the instance to register3635 * @param handle the handle to identify the instance under.3636 */3637 register: function(instance, handle) {3638 if ( !handle ) return angular.noop;3639 instance.$$mdHandle = handle;3640 instances.push(instance);3641 resolveWhen();3642 return deregister;3643 /**3644 * Remove registration for an instance3645 */3646 function deregister() {3647 var index = instances.indexOf(instance);3648 if (index !== -1) {3649 instances.splice(index, 1);3650 }3651 }3652 /**3653 * Resolve any pending promises for this instance3654 */3655 function resolveWhen() {3656 var dfd = pendings[handle];3657 if ( dfd ) {3658 dfd.forEach(function (promise) {3659 promise.resolve(instance);3660 });3661 delete pendings[handle];3662 }3663 }3664 },3665 /**3666 * Async accessor to registered component instance3667 * If not available then a promise is created to notify3668 * all listeners when the instance is registered.3669 */3670 when : function(handle) {3671 if ( isValidID(handle) ) {3672 var deferred = $q.defer();3673 var instance = self.get(handle);3674 if ( instance ) {3675 deferred.resolve( instance );3676 } else {3677 if (pendings[handle] === undefined) {3678 pendings[handle] = [];3679 }3680 pendings[handle].push(deferred);3681 }3682 return deferred.promise;3683 }3684 return $q.reject("Invalid `md-component-id` value.");3685 }3686 };3687 function isValidID(handle){3688 return handle && (handle !== "");3689 }3690 }3691(function() {3692 'use strict';3693 /**3694 * @ngdoc service3695 * @name $mdButtonInkRipple3696 * @module material.core3697 *3698 * @description3699 * Provides ripple effects for md-button. See $mdInkRipple service for all possible configuration options.3700 *3701 * @param {object=} scope Scope within the current context3702 * @param {object=} element The element the ripple effect should be applied to3703 * @param {object=} options (Optional) Configuration options to override the default ripple configuration3704 */3705 MdButtonInkRipple.$inject = ["$mdInkRipple"];3706 angular.module('material.core')3707 .factory('$mdButtonInkRipple', MdButtonInkRipple);3708 function MdButtonInkRipple($mdInkRipple) {3709 return {3710 attach: function attachRipple(scope, element, options) {3711 options = angular.extend(optionsForElement(element), options);3712 return $mdInkRipple.attach(scope, element, options);3713 }3714 };3715 function optionsForElement(element) {3716 if (element.hasClass('md-icon-button')) {3717 return {3718 isMenuItem: element.hasClass('md-menu-item'),3719 fitRipple: true,3720 center: true3721 };3722 } else {3723 return {3724 isMenuItem: element.hasClass('md-menu-item'),3725 dimBackground: true3726 }3727 }3728 };3729 };3730})();3731(function() {3732 'use strict';3733 /**3734 * @ngdoc service3735 * @name $mdCheckboxInkRipple3736 * @module material.core3737 *3738 * @description3739 * Provides ripple effects for md-checkbox. See $mdInkRipple service for all possible configuration options.3740 *3741 * @param {object=} scope Scope within the current context3742 * @param {object=} element The element the ripple effect should be applied to3743 * @param {object=} options (Optional) Configuration options to override the defaultripple configuration3744 */3745 MdCheckboxInkRipple.$inject = ["$mdInkRipple"];3746 angular.module('material.core')3747 .factory('$mdCheckboxInkRipple', MdCheckboxInkRipple);3748 function MdCheckboxInkRipple($mdInkRipple) {3749 return {3750 attach: attach3751 };3752 function attach(scope, element, options) {3753 return $mdInkRipple.attach(scope, element, angular.extend({3754 center: true,3755 dimBackground: false,3756 fitRipple: true3757 }, options));3758 };3759 };3760})();3761(function() {3762 'use strict';3763 /**3764 * @ngdoc service3765 * @name $mdListInkRipple3766 * @module material.core3767 *3768 * @description3769 * Provides ripple effects for md-list. See $mdInkRipple service for all possible configuration options.3770 *3771 * @param {object=} scope Scope within the current context3772 * @param {object=} element The element the ripple effect should be applied to3773 * @param {object=} options (Optional) Configuration options to override the defaultripple configuration3774 */3775 MdListInkRipple.$inject = ["$mdInkRipple"];3776 angular.module('material.core')3777 .factory('$mdListInkRipple', MdListInkRipple);3778 function MdListInkRipple($mdInkRipple) {3779 return {3780 attach: attach3781 };3782 function attach(scope, element, options) {3783 return $mdInkRipple.attach(scope, element, angular.extend({3784 center: false,3785 dimBackground: true,3786 outline: false,3787 rippleSize: 'full'3788 }, options));3789 };3790 };3791})();3792/**3793 * @ngdoc module3794 * @name material.core.ripple3795 * @description3796 * Ripple3797 */3798InkRippleCtrl.$inject = ["$scope", "$element", "rippleOptions", "$window", "$timeout", "$mdUtil", "$mdColorUtil"];3799InkRippleDirective.$inject = ["$mdButtonInkRipple", "$mdCheckboxInkRipple"];3800angular.module('material.core')3801 .provider('$mdInkRipple', InkRippleProvider)3802 .directive('mdInkRipple', InkRippleDirective)3803 .directive('mdNoInk', attrNoDirective)3804 .directive('mdNoBar', attrNoDirective)3805 .directive('mdNoStretch', attrNoDirective);3806var DURATION = 450;3807/**3808 * @ngdoc directive3809 * @name mdInkRipple3810 * @module material.core.ripple3811 *3812 * @description3813 * The `md-ink-ripple` directive allows you to specify the ripple color or id a ripple is allowed.3814 *3815 * @param {string|boolean} md-ink-ripple A color string `#FF0000` or boolean (`false` or `0`) for preventing ripple3816 *3817 * @usage3818 * ### String values3819 * <hljs lang="html">3820 * <ANY md-ink-ripple="#FF0000">3821 * Ripples in red3822 * </ANY>3823 *3824 * <ANY md-ink-ripple="false">3825 * Not rippling3826 * </ANY>3827 * </hljs>3828 *3829 * ### Interpolated values3830 * <hljs lang="html">3831 * <ANY md-ink-ripple="{{ randomColor() }}">3832 * Ripples with the return value of 'randomColor' function3833 * </ANY>3834 *3835 * <ANY md-ink-ripple="{{ canRipple() }}">3836 * Ripples if 'canRipple' function return value is not 'false' or '0'3837 * </ANY>3838 * </hljs>3839 */3840function InkRippleDirective ($mdButtonInkRipple, $mdCheckboxInkRipple) {3841 return {3842 controller: angular.noop,3843 link: function (scope, element, attr) {3844 attr.hasOwnProperty('mdInkRippleCheckbox')3845 ? $mdCheckboxInkRipple.attach(scope, element)3846 : $mdButtonInkRipple.attach(scope, element);3847 }3848 };3849}3850/**3851 * @ngdoc service3852 * @name $mdInkRipple3853 * @module material.core.ripple3854 *3855 * @description3856 * `$mdInkRipple` is a service for adding ripples to any element3857 *3858 * @usage3859 * <hljs lang="js">3860 * app.factory('$myElementInkRipple', function($mdInkRipple) {3861 * return {3862 * attach: function (scope, element, options) {3863 * return $mdInkRipple.attach(scope, element, angular.extend({3864 * center: false,3865 * dimBackground: true3866 * }, options));3867 * }3868 * };3869 * });3870 *3871 * app.controller('myController', function ($scope, $element, $myElementInkRipple) {3872 * $scope.onClick = function (ev) {3873 * $myElementInkRipple.attach($scope, angular.element(ev.target), { center: true });3874 * }3875 * });3876 * </hljs>3877 *3878 * ### Disabling ripples globally3879 * If you want to disable ink ripples globally, for all components, you can call the3880 * `disableInkRipple` method in your app's config.3881 *3882 * <hljs lang="js">3883 * app.config(function ($mdInkRippleProvider) {3884 * $mdInkRippleProvider.disableInkRipple();3885 * });3886 */3887function InkRippleProvider () {3888 var isDisabledGlobally = false;3889 return {3890 disableInkRipple: disableInkRipple,3891 $get: ["$injector", function($injector) {3892 return { attach: attach };3893 /**3894 * @ngdoc method3895 * @name $mdInkRipple#attach3896 *3897 * @description3898 * Attaching given scope, element and options to inkRipple controller3899 *3900 * @param {object=} scope Scope within the current context3901 * @param {object=} element The element the ripple effect should be applied to3902 * @param {object=} options (Optional) Configuration options to override the defaultRipple configuration3903 * * `center` - Whether the ripple should start from the center of the container element3904 * * `dimBackground` - Whether the background should be dimmed with the ripple color3905 * * `colorElement` - The element the ripple should take its color from, defined by css property `color`3906 * * `fitRipple` - Whether the ripple should fill the element3907 */3908 function attach (scope, element, options) {3909 if (isDisabledGlobally || element.controller('mdNoInk')) return angular.noop;3910 return $injector.instantiate(InkRippleCtrl, {3911 $scope: scope,3912 $element: element,3913 rippleOptions: options3914 });3915 }3916 }]3917 };3918 /**3919 * @ngdoc method3920 * @name $mdInkRipple#disableInkRipple3921 *3922 * @description3923 * A config-time method that, when called, disables ripples globally.3924 */3925 function disableInkRipple () {3926 isDisabledGlobally = true;3927 }3928}3929/**3930 * Controller used by the ripple service in order to apply ripples3931 * ngInject3932 */3933function InkRippleCtrl ($scope, $element, rippleOptions, $window, $timeout, $mdUtil, $mdColorUtil) {3934 this.$window = $window;3935 this.$timeout = $timeout;3936 this.$mdUtil = $mdUtil;3937 this.$mdColorUtil = $mdColorUtil;3938 this.$scope = $scope;3939 this.$element = $element;3940 this.options = rippleOptions;3941 this.mousedown = false;3942 this.ripples = [];3943 this.timeout = null; // Stores a reference to the most-recent ripple timeout3944 this.lastRipple = null;3945 $mdUtil.valueOnUse(this, 'container', this.createContainer);3946 this.$element.addClass('md-ink-ripple');3947 // attach method for unit tests3948 ($element.controller('mdInkRipple') || {}).createRipple = angular.bind(this, this.createRipple);3949 ($element.controller('mdInkRipple') || {}).setColor = angular.bind(this, this.color);3950 this.bindEvents();3951}3952/**3953 * Either remove or unlock any remaining ripples when the user mouses off of the element (either by3954 * mouseup or mouseleave event)3955 */3956function autoCleanup (self, cleanupFn) {3957 if ( self.mousedown || self.lastRipple ) {3958 self.mousedown = false;3959 self.$mdUtil.nextTick( angular.bind(self, cleanupFn), false);3960 }3961}3962/**3963 * Returns the color that the ripple should be (either based on CSS or hard-coded)3964 * @returns {string}3965 */3966InkRippleCtrl.prototype.color = function (value) {3967 var self = this;3968 // If assigning a color value, apply it to background and the ripple color3969 if (angular.isDefined(value)) {3970 self._color = self._parseColor(value);3971 }3972 // If color lookup, use assigned, defined, or inherited3973 return self._color || self._parseColor( self.inkRipple() ) || self._parseColor( getElementColor() );3974 /**3975 * Finds the color element and returns its text color for use as default ripple color3976 * @returns {string}3977 */3978 function getElementColor () {3979 var items = self.options && self.options.colorElement ? self.options.colorElement : [];3980 var elem = items.length ? items[ 0 ] : self.$element[ 0 ];3981 return elem ? self.$window.getComputedStyle(elem).color : 'rgb(0,0,0)';3982 }3983};3984/**3985 * Updating the ripple colors based on the current inkRipple value3986 * or the element's computed style color3987 */3988InkRippleCtrl.prototype.calculateColor = function () {3989 return this.color();3990};3991/**3992 * Takes a string color and converts it to RGBA format3993 * @param color {string}3994 * @param [multiplier] {int}3995 * @returns {string}3996 */3997InkRippleCtrl.prototype._parseColor = function parseColor (color, multiplier) {3998 multiplier = multiplier || 1;3999 var colorUtil = this.$mdColorUtil;4000 if (!color) return;4001 if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, (0.1 * multiplier).toString() + ')');4002 if (color.indexOf('rgb') === 0) return colorUtil.rgbToRgba(color);4003 if (color.indexOf('#') === 0) return colorUtil.hexToRgba(color);4004};4005/**4006 * Binds events to the root element for4007 */4008InkRippleCtrl.prototype.bindEvents = function () {4009 this.$element.on('mousedown', angular.bind(this, this.handleMousedown));4010 this.$element.on('mouseup touchend', angular.bind(this, this.handleMouseup));4011 this.$element.on('mouseleave', angular.bind(this, this.handleMouseup));4012 this.$element.on('touchmove', angular.bind(this, this.handleTouchmove));4013};4014/**4015 * Create a new ripple on every mousedown event from the root element4016 * @param event {MouseEvent}4017 */4018InkRippleCtrl.prototype.handleMousedown = function (event) {4019 if ( this.mousedown ) return;4020 // When jQuery is loaded, we have to get the original event4021 if (event.hasOwnProperty('originalEvent')) event = event.originalEvent;4022 this.mousedown = true;4023 if (this.options.center) {4024 this.createRipple(this.container.prop('clientWidth') / 2, this.container.prop('clientWidth') / 2);4025 } else {4026 // We need to calculate the relative coordinates if the target is a sublayer of the ripple element4027 if (event.srcElement !== this.$element[0]) {4028 var layerRect = this.$element[0].getBoundingClientRect();4029 var layerX = event.clientX - layerRect.left;4030 var layerY = event.clientY - layerRect.top;4031 this.createRipple(layerX, layerY);4032 } else {4033 this.createRipple(event.offsetX, event.offsetY);4034 }4035 }4036};4037/**4038 * Either remove or unlock any remaining ripples when the user mouses off of the element (either by4039 * mouseup, touchend or mouseleave event)4040 */4041InkRippleCtrl.prototype.handleMouseup = function () {4042 autoCleanup(this, this.clearRipples);4043};4044/**4045 * Either remove or unlock any remaining ripples when the user mouses off of the element (by4046 * touchmove)4047 */4048InkRippleCtrl.prototype.handleTouchmove = function () {4049 autoCleanup(this, this.deleteRipples);4050};4051/**4052 * Cycles through all ripples and attempts to remove them.4053 */4054InkRippleCtrl.prototype.deleteRipples = function () {4055 for (var i = 0; i < this.ripples.length; i++) {4056 this.ripples[ i ].remove();4057 }4058};4059/**4060 * Cycles through all ripples and attempts to remove them with fade.4061 * Depending on logic within `fadeInComplete`, some removals will be postponed.4062 */4063InkRippleCtrl.prototype.clearRipples = function () {4064 for (var i = 0; i < this.ripples.length; i++) {4065 this.fadeInComplete(this.ripples[ i ]);4066 }4067};4068/**4069 * Creates the ripple container element4070 * @returns {*}4071 */4072InkRippleCtrl.prototype.createContainer = function () {4073 var container = angular.element('<div class="md-ripple-container"></div>');4074 this.$element.append(container);4075 return container;4076};4077InkRippleCtrl.prototype.clearTimeout = function () {4078 if (this.timeout) {4079 this.$timeout.cancel(this.timeout);4080 this.timeout = null;4081 }4082};4083InkRippleCtrl.prototype.isRippleAllowed = function () {4084 var element = this.$element[0];4085 do {4086 if (!element.tagName || element.tagName === 'BODY') break;4087 if (element && angular.isFunction(element.hasAttribute)) {4088 if (element.hasAttribute('disabled')) return false;4089 if (this.inkRipple() === 'false' || this.inkRipple() === '0') return false;4090 }4091 } while (element = element.parentNode);4092 return true;4093};4094/**4095 * The attribute `md-ink-ripple` may be a static or interpolated4096 * color value OR a boolean indicator (used to disable ripples)4097 */4098InkRippleCtrl.prototype.inkRipple = function () {4099 return this.$element.attr('md-ink-ripple');4100};4101/**4102 * Creates a new ripple and adds it to the container. Also tracks ripple in `this.ripples`.4103 * @param left4104 * @param top4105 */4106InkRippleCtrl.prototype.createRipple = function (left, top) {4107 if (!this.isRippleAllowed()) return;4108 var ctrl = this;4109 var colorUtil = ctrl.$mdColorUtil;4110 var ripple = angular.element('<div class="md-ripple"></div>');4111 var width = this.$element.prop('clientWidth');4112 var height = this.$element.prop('clientHeight');4113 var x = Math.max(Math.abs(width - left), left) * 2;4114 var y = Math.max(Math.abs(height - top), top) * 2;4115 var size = getSize(this.options.fitRipple, x, y);4116 var color = this.calculateColor();4117 ripple.css({4118 left: left + 'px',4119 top: top + 'px',4120 background: 'black',4121 width: size + 'px',4122 height: size + 'px',4123 backgroundColor: colorUtil.rgbaToRgb(color),4124 borderColor: colorUtil.rgbaToRgb(color)4125 });4126 this.lastRipple = ripple;4127 // we only want one timeout to be running at a time4128 this.clearTimeout();4129 this.timeout = this.$timeout(function () {4130 ctrl.clearTimeout();4131 if (!ctrl.mousedown) ctrl.fadeInComplete(ripple);4132 }, DURATION * 0.35, false);4133 if (this.options.dimBackground) this.container.css({ backgroundColor: color });4134 this.container.append(ripple);4135 this.ripples.push(ripple);4136 ripple.addClass('md-ripple-placed');4137 this.$mdUtil.nextTick(function () {4138 ripple.addClass('md-ripple-scaled md-ripple-active');4139 ctrl.$timeout(function () {4140 ctrl.clearRipples();4141 }, DURATION, false);4142 }, false);4143 function getSize (fit, x, y) {4144 return fit4145 ? Math.max(x, y)4146 : Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));4147 }4148};4149/**4150 * After fadeIn finishes, either kicks off the fade-out animation or queues the element for removal on mouseup4151 * @param ripple4152 */4153InkRippleCtrl.prototype.fadeInComplete = function (ripple) {4154 if (this.lastRipple === ripple) {4155 if (!this.timeout && !this.mousedown) {4156 this.removeRipple(ripple);4157 }4158 } else {4159 this.removeRipple(ripple);4160 }4161};4162/**4163 * Kicks off the animation for removing a ripple4164 * @param ripple {Element}4165 */4166InkRippleCtrl.prototype.removeRipple = function (ripple) {4167 var ctrl = this;4168 var index = this.ripples.indexOf(ripple);4169 if (index < 0) return;4170 this.ripples.splice(this.ripples.indexOf(ripple), 1);4171 ripple.removeClass('md-ripple-active');4172 ripple.addClass('md-ripple-remove');4173 if (this.ripples.length === 0) this.container.css({ backgroundColor: '' });4174 // use a 2-second timeout in order to allow for the animation to finish4175 // we don't actually care how long the animation takes4176 this.$timeout(function () {4177 ctrl.fadeOutComplete(ripple);4178 }, DURATION, false);4179};4180/**4181 * Removes the provided ripple from the DOM4182 * @param ripple4183 */4184InkRippleCtrl.prototype.fadeOutComplete = function (ripple) {4185 ripple.remove();4186 this.lastRipple = null;4187};4188/**4189 * Used to create an empty directive. This is used to track flag-directives whose children may have4190 * functionality based on them.4191 *4192 * Example: `md-no-ink` will potentially be used by all child directives.4193 */4194function attrNoDirective () {4195 return { controller: angular.noop };4196}4197(function() {4198 'use strict';4199 /**4200 * @ngdoc service4201 * @name $mdTabInkRipple4202 * @module material.core4203 *4204 * @description4205 * Provides ripple effects for md-tabs. See $mdInkRipple service for all possible configuration options.4206 *4207 * @param {object=} scope Scope within the current context4208 * @param {object=} element The element the ripple effect should be applied to4209 * @param {object=} options (Optional) Configuration options to override the defaultripple configuration4210 */4211 MdTabInkRipple.$inject = ["$mdInkRipple"];4212 angular.module('material.core')4213 .factory('$mdTabInkRipple', MdTabInkRipple);4214 function MdTabInkRipple($mdInkRipple) {4215 return {4216 attach: attach4217 };4218 function attach(scope, element, options) {4219 return $mdInkRipple.attach(scope, element, angular.extend({4220 center: false,4221 dimBackground: true,4222 outline: false,4223 rippleSize: 'full'4224 }, options));4225 };4226 };4227})();4228angular.module('material.core.theming.palette', [])4229.constant('$mdColorPalette', {4230 'red': {4231 '50': '#ffebee',4232 '100': '#ffcdd2',4233 '200': '#ef9a9a',4234 '300': '#e57373',4235 '400': '#ef5350',4236 '500': '#f44336',4237 '600': '#e53935',4238 '700': '#d32f2f',4239 '800': '#c62828',4240 '900': '#b71c1c',4241 'A100': '#ff8a80',4242 'A200': '#ff5252',4243 'A400': '#ff1744',4244 'A700': '#d50000',4245 'contrastDefaultColor': 'light',4246 'contrastDarkColors': '50 100 200 300 A100',4247 'contrastStrongLightColors': '400 500 600 700 A200 A400 A700'4248 },4249 'pink': {4250 '50': '#fce4ec',4251 '100': '#f8bbd0',4252 '200': '#f48fb1',4253 '300': '#f06292',4254 '400': '#ec407a',4255 '500': '#e91e63',4256 '600': '#d81b60',4257 '700': '#c2185b',4258 '800': '#ad1457',4259 '900': '#880e4f',4260 'A100': '#ff80ab',4261 'A200': '#ff4081',4262 'A400': '#f50057',4263 'A700': '#c51162',4264 'contrastDefaultColor': 'light',4265 'contrastDarkColors': '50 100 200 A100',4266 'contrastStrongLightColors': '500 600 A200 A400 A700'4267 },4268 'purple': {4269 '50': '#f3e5f5',4270 '100': '#e1bee7',4271 '200': '#ce93d8',4272 '300': '#ba68c8',4273 '400': '#ab47bc',4274 '500': '#9c27b0',4275 '600': '#8e24aa',4276 '700': '#7b1fa2',4277 '800': '#6a1b9a',4278 '900': '#4a148c',4279 'A100': '#ea80fc',4280 'A200': '#e040fb',4281 'A400': '#d500f9',4282 'A700': '#aa00ff',4283 'contrastDefaultColor': 'light',4284 'contrastDarkColors': '50 100 200 A100',4285 'contrastStrongLightColors': '300 400 A200 A400 A700'4286 },4287 'deep-purple': {4288 '50': '#ede7f6',4289 '100': '#d1c4e9',4290 '200': '#b39ddb',4291 '300': '#9575cd',4292 '400': '#7e57c2',4293 '500': '#673ab7',4294 '600': '#5e35b1',4295 '700': '#512da8',4296 '800': '#4527a0',4297 '900': '#311b92',4298 'A100': '#b388ff',4299 'A200': '#7c4dff',4300 'A400': '#651fff',4301 'A700': '#6200ea',4302 'contrastDefaultColor': 'light',4303 'contrastDarkColors': '50 100 200 A100',4304 'contrastStrongLightColors': '300 400 A200'4305 },4306 'indigo': {4307 '50': '#e8eaf6',4308 '100': '#c5cae9',4309 '200': '#9fa8da',4310 '300': '#7986cb',4311 '400': '#5c6bc0',4312 '500': '#3f51b5',4313 '600': '#3949ab',4314 '700': '#303f9f',4315 '800': '#283593',4316 '900': '#1a237e',4317 'A100': '#8c9eff',4318 'A200': '#536dfe',4319 'A400': '#3d5afe',4320 'A700': '#304ffe',4321 'contrastDefaultColor': 'light',4322 'contrastDarkColors': '50 100 200 A100',4323 'contrastStrongLightColors': '300 400 A200 A400'4324 },4325 'blue': {4326 '50': '#e3f2fd',4327 '100': '#bbdefb',4328 '200': '#90caf9',4329 '300': '#64b5f6',4330 '400': '#42a5f5',4331 '500': '#2196f3',4332 '600': '#1e88e5',4333 '700': '#1976d2',4334 '800': '#1565c0',4335 '900': '#0d47a1',4336 'A100': '#82b1ff',4337 'A200': '#448aff',4338 'A400': '#2979ff',4339 'A700': '#2962ff',4340 'contrastDefaultColor': 'light',4341 'contrastDarkColors': '50 100 200 300 400 A100',4342 'contrastStrongLightColors': '500 600 700 A200 A400 A700'4343 },4344 'light-blue': {4345 '50': '#e1f5fe',4346 '100': '#b3e5fc',4347 '200': '#81d4fa',4348 '300': '#4fc3f7',4349 '400': '#29b6f6',4350 '500': '#03a9f4',4351 '600': '#039be5',4352 '700': '#0288d1',4353 '800': '#0277bd',4354 '900': '#01579b',4355 'A100': '#80d8ff',4356 'A200': '#40c4ff',4357 'A400': '#00b0ff',4358 'A700': '#0091ea',4359 'contrastDefaultColor': 'dark',4360 'contrastLightColors': '600 700 800 900 A700',4361 'contrastStrongLightColors': '600 700 800 A700'4362 },4363 'cyan': {4364 '50': '#e0f7fa',4365 '100': '#b2ebf2',4366 '200': '#80deea',4367 '300': '#4dd0e1',4368 '400': '#26c6da',4369 '500': '#00bcd4',4370 '600': '#00acc1',4371 '700': '#0097a7',4372 '800': '#00838f',4373 '900': '#006064',4374 'A100': '#84ffff',4375 'A200': '#18ffff',4376 'A400': '#00e5ff',4377 'A700': '#00b8d4',4378 'contrastDefaultColor': 'dark',4379 'contrastLightColors': '700 800 900',4380 'contrastStrongLightColors': '700 800 900'4381 },4382 'teal': {4383 '50': '#e0f2f1',4384 '100': '#b2dfdb',4385 '200': '#80cbc4',4386 '300': '#4db6ac',4387 '400': '#26a69a',4388 '500': '#009688',4389 '600': '#00897b',4390 '700': '#00796b',4391 '800': '#00695c',4392 '900': '#004d40',4393 'A100': '#a7ffeb',4394 'A200': '#64ffda',4395 'A400': '#1de9b6',4396 'A700': '#00bfa5',4397 'contrastDefaultColor': 'dark',4398 'contrastLightColors': '500 600 700 800 900',4399 'contrastStrongLightColors': '500 600 700'4400 },4401 'green': {4402 '50': '#e8f5e9',4403 '100': '#c8e6c9',4404 '200': '#a5d6a7',4405 '300': '#81c784',4406 '400': '#66bb6a',4407 '500': '#4caf50',4408 '600': '#43a047',4409 '700': '#388e3c',4410 '800': '#2e7d32',4411 '900': '#1b5e20',4412 'A100': '#b9f6ca',4413 'A200': '#69f0ae',4414 'A400': '#00e676',4415 'A700': '#00c853',4416 'contrastDefaultColor': 'dark',4417 'contrastLightColors': '500 600 700 800 900',4418 'contrastStrongLightColors': '500 600 700'4419 },4420 'light-green': {4421 '50': '#f1f8e9',4422 '100': '#dcedc8',4423 '200': '#c5e1a5',4424 '300': '#aed581',4425 '400': '#9ccc65',4426 '500': '#8bc34a',4427 '600': '#7cb342',4428 '700': '#689f38',4429 '800': '#558b2f',4430 '900': '#33691e',4431 'A100': '#ccff90',4432 'A200': '#b2ff59',4433 'A400': '#76ff03',4434 'A700': '#64dd17',4435 'contrastDefaultColor': 'dark',4436 'contrastLightColors': '700 800 900',4437 'contrastStrongLightColors': '700 800 900'4438 },4439 'lime': {4440 '50': '#f9fbe7',4441 '100': '#f0f4c3',4442 '200': '#e6ee9c',4443 '300': '#dce775',4444 '400': '#d4e157',4445 '500': '#cddc39',4446 '600': '#c0ca33',4447 '700': '#afb42b',4448 '800': '#9e9d24',4449 '900': '#827717',4450 'A100': '#f4ff81',4451 'A200': '#eeff41',4452 'A400': '#c6ff00',4453 'A700': '#aeea00',4454 'contrastDefaultColor': 'dark',4455 'contrastLightColors': '900',4456 'contrastStrongLightColors': '900'4457 },4458 'yellow': {4459 '50': '#fffde7',4460 '100': '#fff9c4',4461 '200': '#fff59d',4462 '300': '#fff176',4463 '400': '#ffee58',4464 '500': '#ffeb3b',4465 '600': '#fdd835',4466 '700': '#fbc02d',4467 '800': '#f9a825',4468 '900': '#f57f17',4469 'A100': '#ffff8d',4470 'A200': '#ffff00',4471 'A400': '#ffea00',4472 'A700': '#ffd600',4473 'contrastDefaultColor': 'dark'4474 },4475 'amber': {4476 '50': '#fff8e1',4477 '100': '#ffecb3',4478 '200': '#ffe082',4479 '300': '#ffd54f',4480 '400': '#ffca28',4481 '500': '#ffc107',4482 '600': '#ffb300',4483 '700': '#ffa000',4484 '800': '#ff8f00',4485 '900': '#ff6f00',4486 'A100': '#ffe57f',4487 'A200': '#ffd740',4488 'A400': '#ffc400',4489 'A700': '#ffab00',4490 'contrastDefaultColor': 'dark'4491 },4492 'orange': {4493 '50': '#fff3e0',4494 '100': '#ffe0b2',4495 '200': '#ffcc80',4496 '300': '#ffb74d',4497 '400': '#ffa726',4498 '500': '#ff9800',4499 '600': '#fb8c00',4500 '700': '#f57c00',4501 '800': '#ef6c00',4502 '900': '#e65100',4503 'A100': '#ffd180',4504 'A200': '#ffab40',4505 'A400': '#ff9100',4506 'A700': '#ff6d00',4507 'contrastDefaultColor': 'dark',4508 'contrastLightColors': '800 900',4509 'contrastStrongLightColors': '800 900'4510 },4511 'deep-orange': {4512 '50': '#fbe9e7',4513 '100': '#ffccbc',4514 '200': '#ffab91',4515 '300': '#ff8a65',4516 '400': '#ff7043',4517 '500': '#ff5722',4518 '600': '#f4511e',4519 '700': '#e64a19',4520 '800': '#d84315',4521 '900': '#bf360c',4522 'A100': '#ff9e80',4523 'A200': '#ff6e40',4524 'A400': '#ff3d00',4525 'A700': '#dd2c00',4526 'contrastDefaultColor': 'light',4527 'contrastDarkColors': '50 100 200 300 400 A100 A200',4528 'contrastStrongLightColors': '500 600 700 800 900 A400 A700'4529 },4530 'brown': {4531 '50': '#efebe9',4532 '100': '#d7ccc8',4533 '200': '#bcaaa4',4534 '300': '#a1887f',4535 '400': '#8d6e63',4536 '500': '#795548',4537 '600': '#6d4c41',4538 '700': '#5d4037',4539 '800': '#4e342e',4540 '900': '#3e2723',4541 'A100': '#d7ccc8',4542 'A200': '#bcaaa4',4543 'A400': '#8d6e63',4544 'A700': '#5d4037',4545 'contrastDefaultColor': 'light',4546 'contrastDarkColors': '50 100 200 A100 A200',4547 'contrastStrongLightColors': '300 400'4548 },4549 'grey': {4550 '50': '#fafafa',4551 '100': '#f5f5f5',4552 '200': '#eeeeee',4553 '300': '#e0e0e0',4554 '400': '#bdbdbd',4555 '500': '#9e9e9e',4556 '600': '#757575',4557 '700': '#616161',4558 '800': '#424242',4559 '900': '#212121',4560 'A100': '#ffffff',4561 'A200': '#000000',4562 'A400': '#303030',4563 'A700': '#616161',4564 'contrastDefaultColor': 'dark',4565 'contrastLightColors': '600 700 800 900 A200 A400 A700'4566 },4567 'blue-grey': {4568 '50': '#eceff1',4569 '100': '#cfd8dc',4570 '200': '#b0bec5',4571 '300': '#90a4ae',4572 '400': '#78909c',4573 '500': '#607d8b',4574 '600': '#546e7a',4575 '700': '#455a64',4576 '800': '#37474f',4577 '900': '#263238',4578 'A100': '#cfd8dc',4579 'A200': '#b0bec5',4580 'A400': '#78909c',4581 'A700': '#455a64',4582 'contrastDefaultColor': 'light',4583 'contrastDarkColors': '50 100 200 300 A100 A200',4584 'contrastStrongLightColors': '400 500 700'4585 }4586});4587(function(angular) {4588 'use strict';4589/**4590 * @ngdoc module4591 * @name material.core.theming4592 * @description4593 * Theming4594 */4595detectDisabledThemes.$inject = ["$mdThemingProvider"];4596ThemingDirective.$inject = ["$mdTheming", "$interpolate", "$log"];4597ThemableDirective.$inject = ["$mdTheming"];4598ThemingProvider.$inject = ["$mdColorPalette", "$$mdMetaProvider"];4599generateAllThemes.$inject = ["$injector", "$mdTheming"];4600angular.module('material.core.theming', ['material.core.theming.palette', 'material.core.meta'])4601 .directive('mdTheme', ThemingDirective)4602 .directive('mdThemable', ThemableDirective)4603 .directive('mdThemesDisabled', disableThemesDirective )4604 .provider('$mdTheming', ThemingProvider)4605 .config( detectDisabledThemes )4606 .run(generateAllThemes);4607/**4608 * Detect if the HTML or the BODY tags has a [md-themes-disabled] attribute4609 * If yes, then immediately disable all theme stylesheet generation and DOM injection4610 */4611/**4612 * ngInject4613 */4614function detectDisabledThemes($mdThemingProvider) {4615 var isDisabled = !!document.querySelector('[md-themes-disabled]');4616 $mdThemingProvider.disableTheming(isDisabled);4617}4618/**4619 * @ngdoc service4620 * @name $mdThemingProvider4621 * @module material.core.theming4622 *4623 * @description Provider to configure the `$mdTheming` service.4624 *4625 * ### Default Theme4626 * The `$mdThemingProvider` uses by default the following theme configuration:4627 *4628 * - Primary Palette: `Primary`4629 * - Accent Palette: `Pink`4630 * - Warn Palette: `Deep-Orange`4631 * - Background Palette: `Grey`4632 *4633 * If you don't want to use the `md-theme` directive on the elements itself, you may want to overwrite4634 * the default theme.<br/>4635 * This can be done by using the following markup.4636 *4637 * <hljs lang="js">4638 * myAppModule.config(function($mdThemingProvider) {4639 * $mdThemingProvider4640 * .theme('default')4641 * .primaryPalette('blue')4642 * .accentPalette('teal')4643 * .warnPalette('red')4644 * .backgroundPalette('grey');4645 * });4646 * </hljs>4647 *4648 * ### Dynamic Themes4649 *4650 * By default, if you change a theme at runtime, the `$mdTheming` service will not detect those changes.<br/>4651 * If you have an application, which changes its theme on runtime, you have to enable theme watching.4652 *4653 * <hljs lang="js">4654 * myAppModule.config(function($mdThemingProvider) {4655 * // Enable theme watching.4656 * $mdThemingProvider.alwaysWatchTheme(true);4657 * });4658 * </hljs>4659 *4660 * ### Custom Theme Styles4661 *4662 * Sometimes you may want to use your own theme styles for some custom components.<br/>4663 * You are able to register your own styles by using the following markup.4664 *4665 * <hljs lang="js">4666 * myAppModule.config(function($mdThemingProvider) {4667 * // Register our custom stylesheet into the theming provider.4668 * $mdThemingProvider.registerStyles(STYLESHEET);4669 * });4670 * </hljs>4671 *4672 * The `registerStyles` method only accepts strings as value, so you're actually not able to load an external4673 * stylesheet file into the `$mdThemingProvider`.4674 *4675 * If it's necessary to load an external stylesheet, we suggest using a bundler, which supports including raw content,4676 * like [raw-loader](https://github.com/webpack/raw-loader) for `webpack`.4677 *4678 * <hljs lang="js">4679 * myAppModule.config(function($mdThemingProvider) {4680 * // Register your custom stylesheet into the theming provider.4681 * $mdThemingProvider.registerStyles(require('../styles/my-component.theme.css'));4682 * });4683 * </hljs>4684 *4685 * ### Browser color4686 *4687 * Enables browser header coloring4688 * for more info please visit:4689 * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color4690 *4691 * Options parameter: <br/>4692 * `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>4693 * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',4694 * 'accent', 'background' and 'warn'. Default is `primary`. <br/>4695 * `hue` - The hue from the selected palette. Default is `800`<br/>4696 *4697 * <hljs lang="js">4698 * myAppModule.config(function($mdThemingProvider) {4699 * // Enable browser color4700 * $mdThemingProvider.enableBrowserColor({4701 * theme: 'myTheme', // Default is 'default'4702 * palette: 'accent', // Default is 'primary', any basic material palette and extended palettes are available4703 * hue: '200' // Default is '800'4704 * });4705 * });4706 * </hljs>4707 */4708/**4709 * @ngdoc method4710 * @name $mdThemingProvider#registerStyles4711 * @param {string} styles The styles to be appended to Angular Material's built in theme css.4712 */4713/**4714 * @ngdoc method4715 * @name $mdThemingProvider#setNonce4716 * @param {string} nonceValue The nonce to be added as an attribute to the theme style tags.4717 * Setting a value allows the use of CSP policy without using the unsafe-inline directive.4718 */4719/**4720 * @ngdoc method4721 * @name $mdThemingProvider#setDefaultTheme4722 * @param {string} themeName Default theme name to be applied to elements. Default value is `default`.4723 */4724/**4725 * @ngdoc method4726 * @name $mdThemingProvider#alwaysWatchTheme4727 * @param {boolean} watch Whether or not to always watch themes for changes and re-apply4728 * classes when they change. Default is `false`. Enabling can reduce performance.4729 */4730/**4731 * @ngdoc method4732 * @name $mdThemingProvider#enableBrowserColor4733 * @param {Object=} options Options object for the browser color<br/>4734 * `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>4735 * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',4736 * 'accent', 'background' and 'warn'. Default is `primary`. <br/>4737 * `hue` - The hue from the selected palette. Default is `800`<br/>4738 * @returns {Function} remove function of the browser color4739 */4740/* Some Example Valid Theming Expressions4741 * =======================================4742 *4743 * Intention group expansion: (valid for primary, accent, warn, background)4744 *4745 * {{primary-100}} - grab shade 100 from the primary palette4746 * {{primary-100-0.7}} - grab shade 100, apply opacity of 0.74747 * {{primary-100-contrast}} - grab shade 100's contrast color4748 * {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette4749 * {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-14750 * {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue4751 * {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules4752 * {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue4753 * {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules4754 *4755 * Foreground expansion: Applies rgba to black/white foreground text4756 *4757 * {{foreground-1}} - used for primary text4758 * {{foreground-2}} - used for secondary text/divider4759 * {{foreground-3}} - used for disabled text4760 * {{foreground-4}} - used for dividers4761 *4762 */4763// In memory generated CSS rules; registered by theme.name4764var GENERATED = { };4765// In memory storage of defined themes and color palettes (both loaded by CSS, and user specified)4766var PALETTES;4767// Text Colors on light and dark backgrounds4768// @see https://www.google.com/design/spec/style/color.html#color-text-background-colors4769var DARK_FOREGROUND = {4770 name: 'dark',4771 '1': 'rgba(0,0,0,0.87)',4772 '2': 'rgba(0,0,0,0.54)',4773 '3': 'rgba(0,0,0,0.38)',4774 '4': 'rgba(0,0,0,0.12)'4775};4776var LIGHT_FOREGROUND = {4777 name: 'light',4778 '1': 'rgba(255,255,255,1.0)',4779 '2': 'rgba(255,255,255,0.7)',4780 '3': 'rgba(255,255,255,0.5)',4781 '4': 'rgba(255,255,255,0.12)'4782};4783var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)';4784var LIGHT_SHADOW = '';4785var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)');4786var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87)');4787var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)');4788var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background'];4789var DEFAULT_COLOR_TYPE = 'primary';4790// A color in a theme will use these hues by default, if not specified by user.4791var LIGHT_DEFAULT_HUES = {4792 'accent': {4793 'default': 'A200',4794 'hue-1': 'A100',4795 'hue-2': 'A400',4796 'hue-3': 'A700'4797 },4798 'background': {4799 'default': '50',4800 'hue-1': 'A100',4801 'hue-2': '100',4802 'hue-3': '300'4803 }4804};4805var DARK_DEFAULT_HUES = {4806 'background': {4807 'default': 'A400',4808 'hue-1': '800',4809 'hue-2': '900',4810 'hue-3': 'A200'4811 }4812};4813THEME_COLOR_TYPES.forEach(function(colorType) {4814 // Color types with unspecified default hues will use these default hue values4815 var defaultDefaultHues = {4816 'default': '500',4817 'hue-1': '300',4818 'hue-2': '800',4819 'hue-3': 'A100'4820 };4821 if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues;4822 if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues;4823});4824var VALID_HUE_VALUES = [4825 '50', '100', '200', '300', '400', '500', '600',4826 '700', '800', '900', 'A100', 'A200', 'A400', 'A700'4827];4828var themeConfig = {4829 disableTheming : false, // Generate our themes at run time; also disable stylesheet DOM injection4830 generateOnDemand : false, // Whether or not themes are to be generated on-demand (vs. eagerly).4831 registeredStyles : [], // Custom styles registered to be used in the theming of custom components.4832 nonce : null // Nonce to be added as an attribute to the generated themes style tags.4833};4834/**4835 *4836 */4837function ThemingProvider($mdColorPalette, $$mdMetaProvider) {4838 ThemingService.$inject = ["$rootScope", "$log"];4839 PALETTES = { };4840 var THEMES = { };4841 var themingProvider;4842 var alwaysWatchTheme = false;4843 var defaultTheme = 'default';4844 // Load JS Defined Palettes4845 angular.extend(PALETTES, $mdColorPalette);4846 // Default theme defined in core.js4847 /**4848 * Adds `theme-color` and `msapplication-navbutton-color` meta tags with the color parameter4849 * @param {string} color Hex value of the wanted browser color4850 * @returns {Function} Remove function of the meta tags4851 */4852 var setBrowserColor = function (color) {4853 // Chrome, Firefox OS and Opera4854 var removeChrome = $$mdMetaProvider.setMeta('theme-color', color);4855 // Windows Phone4856 var removeWindows = $$mdMetaProvider.setMeta('msapplication-navbutton-color', color);4857 return function () {4858 removeChrome();4859 removeWindows();4860 }4861 };4862 /**4863 * Enables browser header coloring4864 * for more info please visit:4865 * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color4866 *4867 * The default color is `800` from `primary` palette of the `default` theme4868 *4869 * options are:4870 * `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme4871 * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',4872 * 'accent', 'background' and 'warn'. Default is `primary`4873 * `hue` - The hue from the selected palette. Default is `800`4874 *4875 * @param {Object=} options Options object for the browser color4876 * @returns {Function} remove function of the browser color4877 */4878 var enableBrowserColor = function (options) {4879 options = angular.isObject(options) ? options : {};4880 var theme = options.theme || 'default';4881 var hue = options.hue || '800';4882 var palette = PALETTES[options.palette] ||4883 PALETTES[THEMES[theme].colors[options.palette || 'primary'].name];4884 var color = angular.isObject(palette[hue]) ? palette[hue].hex : palette[hue];4885 return setBrowserColor(color);4886 };4887 return themingProvider = {4888 definePalette: definePalette,4889 extendPalette: extendPalette,4890 theme: registerTheme,4891 /**4892 * return a read-only clone of the current theme configuration4893 */4894 configuration : function() {4895 return angular.extend( { }, themeConfig, {4896 defaultTheme : defaultTheme,4897 alwaysWatchTheme : alwaysWatchTheme,4898 registeredStyles : [].concat(themeConfig.registeredStyles)4899 });4900 },4901 /**4902 * Easy way to disable theming without having to use4903 * `.constant("$MD_THEME_CSS","");` This disables4904 * all dynamic theme style sheet generations and injections...4905 */4906 disableTheming: function(isDisabled) {4907 themeConfig.disableTheming = angular.isUndefined(isDisabled) || !!isDisabled;4908 },4909 registerStyles: function(styles) {4910 themeConfig.registeredStyles.push(styles);4911 },4912 setNonce: function(nonceValue) {4913 themeConfig.nonce = nonceValue;4914 },4915 generateThemesOnDemand: function(onDemand) {4916 themeConfig.generateOnDemand = onDemand;4917 },4918 setDefaultTheme: function(theme) {4919 defaultTheme = theme;4920 },4921 alwaysWatchTheme: function(alwaysWatch) {4922 alwaysWatchTheme = alwaysWatch;4923 },4924 enableBrowserColor: enableBrowserColor,4925 $get: ThemingService,4926 _LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,4927 _DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,4928 _PALETTES: PALETTES,4929 _THEMES: THEMES,4930 _parseRules: parseRules,4931 _rgba: rgba4932 };4933 // Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... });4934 function definePalette(name, map) {4935 map = map || {};4936 PALETTES[name] = checkPaletteValid(name, map);4937 return themingProvider;4938 }4939 // Returns an new object which is a copy of a given palette `name` with variables from4940 // `map` overwritten4941 // Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' });4942 function extendPalette(name, map) {4943 return checkPaletteValid(name, angular.extend({}, PALETTES[name] || {}, map) );4944 }4945 // Make sure that palette has all required hues4946 function checkPaletteValid(name, map) {4947 var missingColors = VALID_HUE_VALUES.filter(function(field) {4948 return !map[field];4949 });4950 if (missingColors.length) {4951 throw new Error("Missing colors %1 in palette %2!"4952 .replace('%1', missingColors.join(', '))4953 .replace('%2', name));4954 }4955 return map;4956 }4957 // Register a theme (which is a collection of color palettes to use with various states4958 // ie. warn, accent, primary )4959 // Optionally inherit from an existing theme4960 // $mdThemingProvider.theme('custom-theme').primaryPalette('red');4961 function registerTheme(name, inheritFrom) {4962 if (THEMES[name]) return THEMES[name];4963 inheritFrom = inheritFrom || 'default';4964 var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom;4965 var theme = new Theme(name);4966 if (parentTheme) {4967 angular.forEach(parentTheme.colors, function(color, colorType) {4968 theme.colors[colorType] = {4969 name: color.name,4970 // Make sure a COPY of the hues is given to the child color,4971 // not the same reference.4972 hues: angular.extend({}, color.hues)4973 };4974 });4975 }4976 THEMES[name] = theme;4977 return theme;4978 }4979 function Theme(name) {4980 var self = this;4981 self.name = name;4982 self.colors = {};4983 self.dark = setDark;4984 setDark(false);4985 function setDark(isDark) {4986 isDark = arguments.length === 0 ? true : !!isDark;4987 // If no change, abort4988 if (isDark === self.isDark) return;4989 self.isDark = isDark;4990 self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND;4991 self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW;4992 // Light and dark themes have different default hues.4993 // Go through each existing color type for this theme, and for every4994 // hue value that is still the default hue value from the previous light/dark setting,4995 // set it to the default hue value from the new light/dark setting.4996 var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES;4997 var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES;4998 angular.forEach(newDefaultHues, function(newDefaults, colorType) {4999 var color = self.colors[colorType];5000 var oldDefaults = oldDefaultHues[colorType];5001 if (color) {5002 for (var hueName in color.hues) {5003 if (color.hues[hueName] === oldDefaults[hueName]) {5004 color.hues[hueName] = newDefaults[hueName];5005 }5006 }5007 }5008 });5009 return self;5010 }5011 THEME_COLOR_TYPES.forEach(function(colorType) {5012 var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType];5013 self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) {5014 var color = self.colors[colorType] = {5015 name: paletteName,5016 hues: angular.extend({}, defaultHues, hues)5017 };5018 Object.keys(color.hues).forEach(function(name) {5019 if (!defaultHues[name]) {5020 throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4"5021 .replace('%1', name)5022 .replace('%2', self.name)5023 .replace('%3', paletteName)5024 .replace('%4', Object.keys(defaultHues).join(', '))5025 );5026 }5027 });5028 Object.keys(color.hues).map(function(key) {5029 return color.hues[key];5030 }).forEach(function(hueValue) {5031 if (VALID_HUE_VALUES.indexOf(hueValue) == -1) {5032 throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5"5033 .replace('%1', hueValue)5034 .replace('%2', self.name)5035 .replace('%3', colorType)5036 .replace('%4', paletteName)5037 .replace('%5', VALID_HUE_VALUES.join(', '))5038 );5039 }5040 });5041 return self;5042 };5043 self[colorType + 'Color'] = function() {5044 var args = Array.prototype.slice.call(arguments);5045 console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' +5046 'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.');5047 return self[colorType + 'Palette'].apply(self, args);5048 };5049 });5050 }5051 /**5052 * @ngdoc service5053 * @name $mdTheming5054 *5055 * @description5056 *5057 * Service that makes an element apply theming related classes to itself.5058 *5059 * ```js5060 * app.directive('myFancyDirective', function($mdTheming) {5061 * return {5062 * restrict: 'e',5063 * link: function(scope, el, attrs) {5064 * $mdTheming(el);5065 * }5066 * };5067 * });5068 * ```5069 * @param {el=} element to apply theming to5070 */5071 /* ngInject */5072 function ThemingService($rootScope, $log) {5073 // Allow us to be invoked via a linking function signature.5074 var applyTheme = function (scope, el) {5075 if (el === undefined) { el = scope; scope = undefined; }5076 if (scope === undefined) { scope = $rootScope; }5077 applyTheme.inherit(el, el);5078 };5079 applyTheme.THEMES = angular.extend({}, THEMES);5080 applyTheme.PALETTES = angular.extend({}, PALETTES);5081 applyTheme.inherit = inheritTheme;5082 applyTheme.registered = registered;5083 applyTheme.defaultTheme = function() { return defaultTheme; };5084 applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };5085 applyTheme.setBrowserColor = enableBrowserColor;5086 return applyTheme;5087 /**5088 * Determine is specified theme name is a valid, registered theme5089 */5090 function registered(themeName) {5091 if (themeName === undefined || themeName === '') return true;5092 return applyTheme.THEMES[themeName] !== undefined;5093 }5094 /**5095 * Get theme name for the element, then update with Theme CSS class5096 */5097 function inheritTheme (el, parent) {5098 var ctrl = parent.controller('mdTheme');5099 var attrThemeValue = el.attr('md-theme-watch');5100 var watchTheme = (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false';5101 updateThemeClass(lookupThemeName());5102 if ((alwaysWatchTheme && !registerChangeCallback()) || (!alwaysWatchTheme && watchTheme)) {5103 el.on('$destroy', $rootScope.$watch(lookupThemeName, updateThemeClass) );5104 }5105 /**5106 * Find the theme name from the parent controller or element data5107 */5108 function lookupThemeName() {5109 // As a few components (dialog) add their controllers later, we should also watch for a controller init.5110 ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');5111 return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme);5112 }5113 /**5114 * Remove old theme class and apply a new one5115 * NOTE: if not a valid theme name, then the current name is not changed5116 */5117 function updateThemeClass(theme) {5118 if (!theme) return;5119 if (!registered(theme)) {5120 $log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' +5121 'Register it with $mdThemingProvider.theme().');5122 }5123 var oldTheme = el.data('$mdThemeName');5124 if (oldTheme) el.removeClass('md-' + oldTheme +'-theme');5125 el.addClass('md-' + theme + '-theme');5126 el.data('$mdThemeName', theme);5127 if (ctrl) {5128 el.data('$mdThemeController', ctrl);5129 }5130 }5131 /**5132 * Register change callback with parent mdTheme controller5133 */5134 function registerChangeCallback() {5135 var parentController = parent.controller('mdTheme');5136 if (!parentController) return false;5137 el.on('$destroy', parentController.registerChanges( function() {5138 updateThemeClass(lookupThemeName());5139 }));5140 return true;5141 }5142 }5143 }5144}5145function ThemingDirective($mdTheming, $interpolate, $log) {5146 return {5147 priority: 100,5148 link: {5149 pre: function(scope, el, attrs) {5150 var registeredCallbacks = [];5151 var ctrl = {5152 registerChanges: function (cb, context) {5153 if (context) {5154 cb = angular.bind(context, cb);5155 }5156 registeredCallbacks.push(cb);5157 return function () {5158 var index = registeredCallbacks.indexOf(cb);5159 if (index > -1) {5160 registeredCallbacks.splice(index, 1);5161 }5162 };5163 },5164 $setTheme: function (theme) {5165 if (!$mdTheming.registered(theme)) {5166 $log.warn('attempted to use unregistered theme \'' + theme + '\'');5167 }5168 ctrl.$mdTheme = theme;5169 registeredCallbacks.forEach(function (cb) {5170 cb();5171 });5172 }5173 };5174 el.data('$mdThemeController', ctrl);5175 ctrl.$setTheme($interpolate(attrs.mdTheme)(scope));5176 attrs.$observe('mdTheme', ctrl.$setTheme);5177 }5178 }5179 };5180}5181/**5182 * Special directive that will disable ALL runtime Theme style generation and DOM injection5183 *5184 * <link rel="stylesheet" href="angular-material.min.css">5185 * <link rel="stylesheet" href="angular-material.themes.css">5186 *5187 * <body md-themes-disabled>5188 * ...5189 * </body>5190 *5191 * Note: Using md-themes-css directive requires the developer to load external5192 * theme stylesheets; e.g. custom themes from Material-Tools:5193 *5194 * `angular-material.themes.css`5195 *5196 * Another option is to use the ThemingProvider to configure and disable the attribute5197 * conversions; this would obviate the use of the `md-themes-css` directive5198 *5199 */5200function disableThemesDirective() {5201 themeConfig.disableTheming = true;5202 // Return a 1x-only, first-match attribute directive5203 return {5204 restrict : 'A',5205 priority : '900'5206 };5207}5208function ThemableDirective($mdTheming) {5209 return $mdTheming;5210}5211function parseRules(theme, colorType, rules) {5212 checkValidPalette(theme, colorType);5213 rules = rules.replace(/THEME_NAME/g, theme.name);5214 var generatedRules = [];5215 var color = theme.colors[colorType];5216 var themeNameRegex = new RegExp('\\.md-' + theme.name + '-theme', 'g');5217 // Matches '{{ primary-color }}', etc5218 var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g');5219 var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow|default)-?(\d\.?\d*)?(contrast)?\s*\}\}'?"?/g;5220 var palette = PALETTES[color.name];5221 // find and replace simple variables where we use a specific hue, not an entire palette5222 // eg. "{{primary-100}}"5223 //\(' + THEME_COLOR_TYPES.join('\|') + '\)'5224 rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity, contrast) {5225 if (colorType === 'foreground') {5226 if (hue == 'shadow') {5227 return theme.foregroundShadow;5228 } else {5229 return theme.foregroundPalette[hue] || theme.foregroundPalette['1'];5230 }5231 }5232 // `default` is also accepted as a hue-value, because the background palettes are5233 // using it as a name for the default hue.5234 if (hue.indexOf('hue') === 0 || hue === 'default') {5235 hue = theme.colors[colorType].hues[hue];5236 }5237 return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '')[contrast ? 'contrast' : 'value'], opacity );5238 });5239 // For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3)5240 angular.forEach(color.hues, function(hueValue, hueName) {5241 var newRule = rules5242 .replace(hueRegex, function(match, _, colorType, hueType, opacity) {5243 return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity);5244 });5245 if (hueName !== 'default') {5246 newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName);5247 }5248 // Don't apply a selector rule to the default theme, making it easier to override5249 // styles of the base-component5250 if (theme.name == 'default') {5251 var themeRuleRegex = /((?:(?:(?: |>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)+) )?)((?:(?:\w|\.|-)+)?)\.md-default-theme((?: |>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)/g;5252 newRule = newRule.replace(themeRuleRegex, function(match, prefix, target, suffix) {5253 return match + ', ' + prefix + target + suffix;5254 });5255 }5256 generatedRules.push(newRule);5257 });5258 return generatedRules;5259}5260var rulesByType = {};5261// Generate our themes at run time given the state of THEMES and PALETTES5262function generateAllThemes($injector, $mdTheming) {5263 var head = document.head;5264 var firstChild = head ? head.firstElementChild : null;5265 var themeCss = !themeConfig.disableTheming && $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : '';5266 // Append our custom registered styles to the theme stylesheet.5267 themeCss += themeConfig.registeredStyles.join('');5268 if ( !firstChild ) return;5269 if (themeCss.length === 0) return; // no rules, so no point in running this expensive task5270 // Expose contrast colors for palettes to ensure that text is always readable5271 angular.forEach(PALETTES, sanitizePalette);5272 // MD_THEME_CSS is a string generated by the build process that includes all the themable5273 // components as templates5274 // Break the CSS into individual rules5275 var rules = themeCss5276 .split(/\}(?!(\}|'|"|;))/)5277 .filter(function(rule) { return rule && rule.trim().length; })5278 .map(function(rule) { return rule.trim() + '}'; });5279 var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g');5280 THEME_COLOR_TYPES.forEach(function(type) {5281 rulesByType[type] = '';5282 });5283 // Sort the rules based on type, allowing us to do color substitution on a per-type basis5284 rules.forEach(function(rule) {5285 var match = rule.match(ruleMatchRegex);5286 // First: test that if the rule has '.md-accent', it goes into the accent set of rules5287 for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) {5288 if (rule.indexOf('.md-' + type) > -1) {5289 return rulesByType[type] += rule;5290 }5291 }5292 // If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from5293 // there5294 for (i = 0; type = THEME_COLOR_TYPES[i]; i++) {5295 if (rule.indexOf(type) > -1) {5296 return rulesByType[type] += rule;5297 }5298 }5299 // Default to the primary array5300 return rulesByType[DEFAULT_COLOR_TYPE] += rule;5301 });5302 // If themes are being generated on-demand, quit here. The user will later manually5303 // call generateTheme to do this on a theme-by-theme basis.5304 if (themeConfig.generateOnDemand) return;5305 angular.forEach($mdTheming.THEMES, function(theme) {5306 if (!GENERATED[theme.name] && !($mdTheming.defaultTheme() !== 'default' && theme.name === 'default')) {5307 generateTheme(theme, theme.name, themeConfig.nonce);5308 }5309 });5310 // *************************5311 // Internal functions5312 // *************************5313 // The user specifies a 'default' contrast color as either light or dark,5314 // then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light)5315 function sanitizePalette(palette, name) {5316 var defaultContrast = palette.contrastDefaultColor;5317 var lightColors = palette.contrastLightColors || [];5318 var strongLightColors = palette.contrastStrongLightColors || [];5319 var darkColors = palette.contrastDarkColors || [];5320 // These colors are provided as space-separated lists5321 if (typeof lightColors === 'string') lightColors = lightColors.split(' ');5322 if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' ');5323 if (typeof darkColors === 'string') darkColors = darkColors.split(' ');5324 // Cleanup after ourselves5325 delete palette.contrastDefaultColor;5326 delete palette.contrastLightColors;5327 delete palette.contrastStrongLightColors;5328 delete palette.contrastDarkColors;5329 // Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR }5330 angular.forEach(palette, function(hueValue, hueName) {5331 if (angular.isObject(hueValue)) return; // Already converted5332 // Map everything to rgb colors5333 var rgbValue = colorToRgbaArray(hueValue);5334 if (!rgbValue) {5335 throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected."5336 .replace('%1', hueValue)5337 .replace('%2', palette.name)5338 .replace('%3', hueName));5339 }5340 palette[hueName] = {5341 hex: palette[hueName],5342 value: rgbValue,5343 contrast: getContrastColor()5344 };5345 function getContrastColor() {5346 if (defaultContrast === 'light') {5347 if (darkColors.indexOf(hueName) > -1) {5348 return DARK_CONTRAST_COLOR;5349 } else {5350 return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR5351 : LIGHT_CONTRAST_COLOR;5352 }5353 } else {5354 if (lightColors.indexOf(hueName) > -1) {5355 return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR5356 : LIGHT_CONTRAST_COLOR;5357 } else {5358 return DARK_CONTRAST_COLOR;5359 }5360 }5361 }5362 });5363 }5364}5365function generateTheme(theme, name, nonce) {5366 var head = document.head;5367 var firstChild = head ? head.firstElementChild : null;5368 if (!GENERATED[name]) {5369 // For each theme, use the color palettes specified for5370 // `primary`, `warn` and `accent` to generate CSS rules.5371 THEME_COLOR_TYPES.forEach(function(colorType) {5372 var styleStrings = parseRules(theme, colorType, rulesByType[colorType]);5373 while (styleStrings.length) {5374 var styleContent = styleStrings.shift();5375 if (styleContent) {5376 var style = document.createElement('style');5377 style.setAttribute('md-theme-style', '');5378 if (nonce) {5379 style.setAttribute('nonce', nonce);5380 }5381 style.appendChild(document.createTextNode(styleContent));5382 head.insertBefore(style, firstChild);5383 }5384 }5385 });5386 GENERATED[theme.name] = true;5387 }5388}5389function checkValidPalette(theme, colorType) {5390 // If theme attempts to use a palette that doesnt exist, throw error5391 if (!PALETTES[ (theme.colors[colorType] || {}).name ]) {5392 throw new Error(5393 "You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3"5394 .replace('%1', theme.name)5395 .replace('%2', colorType)5396 .replace('%3', Object.keys(PALETTES).join(', '))5397 );5398 }5399}5400function colorToRgbaArray(clr) {5401 if (angular.isArray(clr) && clr.length == 3) return clr;5402 if (/^rgb/.test(clr)) {5403 return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) {5404 return i == 3 ? parseFloat(value, 10) : parseInt(value, 10);5405 });5406 }5407 if (clr.charAt(0) == '#') clr = clr.substring(1);5408 if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return;5409 var dig = clr.length / 3;5410 var red = clr.substr(0, dig);5411 var grn = clr.substr(dig, dig);5412 var blu = clr.substr(dig * 2);5413 if (dig === 1) {5414 red += red;5415 grn += grn;5416 blu += blu;5417 }5418 return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)];5419}5420function rgba(rgbArray, opacity) {5421 if ( !rgbArray ) return "rgb('0,0,0')";5422 if (rgbArray.length == 4) {5423 rgbArray = angular.copy(rgbArray);5424 opacity ? rgbArray.pop() : opacity = rgbArray.pop();5425 }5426 return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ?5427 'rgba(' + rgbArray.join(',') + ',' + opacity + ')' :5428 'rgb(' + rgbArray.join(',') + ')';5429}5430})(window.angular);5431// Polyfill angular < 1.4 (provide $animateCss)5432angular5433 .module('material.core')5434 .factory('$$mdAnimate', ["$q", "$timeout", "$mdConstant", "$animateCss", function($q, $timeout, $mdConstant, $animateCss){5435 // Since $$mdAnimate is injected into $mdUtil... use a wrapper function5436 // to subsequently inject $mdUtil as an argument to the AnimateDomUtils5437 return function($mdUtil) {5438 return AnimateDomUtils( $mdUtil, $q, $timeout, $mdConstant, $animateCss);5439 };5440 }]);5441/**5442 * Factory function that requires special injections5443 */5444function AnimateDomUtils($mdUtil, $q, $timeout, $mdConstant, $animateCss) {5445 var self;5446 return self = {5447 /**5448 *5449 */5450 translate3d : function( target, from, to, options ) {5451 return $animateCss(target,{5452 from:from,5453 to:to,5454 addClass:options.transitionInClass,5455 removeClass:options.transitionOutClass5456 })5457 .start()5458 .then(function(){5459 // Resolve with reverser function...5460 return reverseTranslate;5461 });5462 /**5463 * Specific reversal of the request translate animation above...5464 */5465 function reverseTranslate (newFrom) {5466 return $animateCss(target, {5467 to: newFrom || from,5468 addClass: options.transitionOutClass,5469 removeClass: options.transitionInClass5470 }).start();5471 }5472 },5473 /**5474 * Listen for transitionEnd event (with optional timeout)5475 * Announce completion or failure via promise handlers5476 */5477 waitTransitionEnd: function (element, opts) {5478 var TIMEOUT = 3000; // fallback is 3 secs5479 return $q(function(resolve, reject){5480 opts = opts || { };5481 // If there is no transition is found, resolve immediately5482 //5483 // NOTE: using $mdUtil.nextTick() causes delays/issues5484 if (noTransitionFound(opts.cachedTransitionStyles)) {5485 TIMEOUT = 0;5486 }5487 var timer = $timeout(finished, opts.timeout || TIMEOUT);5488 element.on($mdConstant.CSS.TRANSITIONEND, finished);5489 /**5490 * Upon timeout or transitionEnd, reject or resolve (respectively) this promise.5491 * NOTE: Make sure this transitionEnd didn't bubble up from a child5492 */5493 function finished(ev) {5494 if ( ev && ev.target !== element[0]) return;5495 if ( ev ) $timeout.cancel(timer);5496 element.off($mdConstant.CSS.TRANSITIONEND, finished);5497 // Never reject since ngAnimate may cause timeouts due missed transitionEnd events5498 resolve();5499 }5500 /**5501 * Checks whether or not there is a transition.5502 *5503 * @param styles The cached styles to use for the calculation. If null, getComputedStyle()5504 * will be used.5505 *5506 * @returns {boolean} True if there is no transition/duration; false otherwise.5507 */5508 function noTransitionFound(styles) {5509 styles = styles || window.getComputedStyle(element[0]);5510 return styles.transitionDuration == '0s' || (!styles.transition && !styles.transitionProperty);5511 }5512 });5513 },5514 calculateTransformValues: function (element, originator) {5515 var origin = originator.element;5516 var bounds = originator.bounds;5517 if (origin || bounds) {5518 var originBnds = origin ? self.clientRect(origin) || currentBounds() : self.copyRect(bounds);5519 var dialogRect = self.copyRect(element[0].getBoundingClientRect());5520 var dialogCenterPt = self.centerPointFor(dialogRect);5521 var originCenterPt = self.centerPointFor(originBnds);5522 return {5523 centerX: originCenterPt.x - dialogCenterPt.x,5524 centerY: originCenterPt.y - dialogCenterPt.y,5525 scaleX: Math.round(100 * Math.min(0.5, originBnds.width / dialogRect.width)) / 100,5526 scaleY: Math.round(100 * Math.min(0.5, originBnds.height / dialogRect.height)) / 1005527 };5528 }5529 return {centerX: 0, centerY: 0, scaleX: 0.5, scaleY: 0.5};5530 /**5531 * This is a fallback if the origin information is no longer valid, then the5532 * origin bounds simply becomes the current bounds for the dialogContainer's parent5533 */5534 function currentBounds() {5535 var cntr = element ? element.parent() : null;5536 var parent = cntr ? cntr.parent() : null;5537 return parent ? self.clientRect(parent) : null;5538 }5539 },5540 /**5541 * Calculate the zoom transform from dialog to origin.5542 *5543 * We use this to set the dialog position immediately;5544 * then the md-transition-in actually translates back to5545 * `translate3d(0,0,0) scale(1.0)`...5546 *5547 * NOTE: all values are rounded to the nearest integer5548 */5549 calculateZoomToOrigin: function (element, originator) {5550 var zoomTemplate = "translate3d( {centerX}px, {centerY}px, 0 ) scale( {scaleX}, {scaleY} )";5551 var buildZoom = angular.bind(null, $mdUtil.supplant, zoomTemplate);5552 return buildZoom(self.calculateTransformValues(element, originator));5553 },5554 /**5555 * Calculate the slide transform from panel to origin.5556 * NOTE: all values are rounded to the nearest integer5557 */5558 calculateSlideToOrigin: function (element, originator) {5559 var slideTemplate = "translate3d( {centerX}px, {centerY}px, 0 )";5560 var buildSlide = angular.bind(null, $mdUtil.supplant, slideTemplate);5561 return buildSlide(self.calculateTransformValues(element, originator));5562 },5563 /**5564 * Enhance raw values to represent valid css stylings...5565 */5566 toCss : function( raw ) {5567 var css = { };5568 var lookups = 'left top right bottom width height x y min-width min-height max-width max-height';5569 angular.forEach(raw, function(value,key) {5570 if ( angular.isUndefined(value) ) return;5571 if ( lookups.indexOf(key) >= 0 ) {5572 css[key] = value + 'px';5573 } else {5574 switch (key) {5575 case 'transition':5576 convertToVendor(key, $mdConstant.CSS.TRANSITION, value);5577 break;5578 case 'transform':5579 convertToVendor(key, $mdConstant.CSS.TRANSFORM, value);5580 break;5581 case 'transformOrigin':5582 convertToVendor(key, $mdConstant.CSS.TRANSFORM_ORIGIN, value);5583 break;5584 case 'font-size':5585 css['font-size'] = value; // font sizes aren't always in px5586 break;5587 }5588 }5589 });5590 return css;5591 function convertToVendor(key, vendor, value) {5592 angular.forEach(vendor.split(' '), function (key) {5593 css[key] = value;5594 });5595 }5596 },5597 /**5598 * Convert the translate CSS value to key/value pair(s).5599 */5600 toTransformCss: function (transform, addTransition, transition) {5601 var css = {};5602 angular.forEach($mdConstant.CSS.TRANSFORM.split(' '), function (key) {5603 css[key] = transform;5604 });5605 if (addTransition) {5606 transition = transition || "all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) !important";5607 css.transition = transition;5608 }5609 return css;5610 },5611 /**5612 * Clone the Rect and calculate the height/width if needed5613 */5614 copyRect: function (source, destination) {5615 if (!source) return null;5616 destination = destination || {};5617 angular.forEach('left top right bottom width height'.split(' '), function (key) {5618 destination[key] = Math.round(source[key]);5619 });5620 destination.width = destination.width || (destination.right - destination.left);5621 destination.height = destination.height || (destination.bottom - destination.top);5622 return destination;5623 },5624 /**5625 * Calculate ClientRect of element; return null if hidden or zero size5626 */5627 clientRect: function (element) {5628 var bounds = angular.element(element)[0].getBoundingClientRect();5629 var isPositiveSizeClientRect = function (rect) {5630 return rect && (rect.width > 0) && (rect.height > 0);5631 };5632 // If the event origin element has zero size, it has probably been hidden.5633 return isPositiveSizeClientRect(bounds) ? self.copyRect(bounds) : null;5634 },5635 /**5636 * Calculate 'rounded' center point of Rect5637 */5638 centerPointFor: function (targetRect) {5639 return targetRect ? {5640 x: Math.round(targetRect.left + (targetRect.width / 2)),5641 y: Math.round(targetRect.top + (targetRect.height / 2))5642 } : { x : 0, y : 0 };5643 }5644 };5645}5646"use strict";5647if (angular.version.minor >= 4) {5648 angular.module('material.core.animate', []);5649} else {5650(function() {5651 var forEach = angular.forEach;5652 var WEBKIT = angular.isDefined(document.documentElement.style.WebkitAppearance);5653 var TRANSITION_PROP = WEBKIT ? 'WebkitTransition' : 'transition';5654 var ANIMATION_PROP = WEBKIT ? 'WebkitAnimation' : 'animation';5655 var PREFIX = WEBKIT ? '-webkit-' : '';5656 var TRANSITION_EVENTS = (WEBKIT ? 'webkitTransitionEnd ' : '') + 'transitionend';5657 var ANIMATION_EVENTS = (WEBKIT ? 'webkitAnimationEnd ' : '') + 'animationend';5658 var $$ForceReflowFactory = ['$document', function($document) {5659 return function() {5660 return $document[0].body.clientWidth + 1;5661 }5662 }];5663 var $$rAFMutexFactory = ['$$rAF', function($$rAF) {5664 return function() {5665 var passed = false;5666 $$rAF(function() {5667 passed = true;5668 });5669 return function(fn) {5670 passed ? fn() : $$rAF(fn);5671 };5672 };5673 }];5674 var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {5675 var INITIAL_STATE = 0;5676 var DONE_PENDING_STATE = 1;5677 var DONE_COMPLETE_STATE = 2;5678 function AnimateRunner(host) {5679 this.setHost(host);5680 this._doneCallbacks = [];5681 this._runInAnimationFrame = $$rAFMutex();5682 this._state = 0;5683 }5684 AnimateRunner.prototype = {5685 setHost: function(host) {5686 this.host = host || {};5687 },5688 done: function(fn) {5689 if (this._state === DONE_COMPLETE_STATE) {5690 fn();5691 } else {5692 this._doneCallbacks.push(fn);5693 }5694 },5695 progress: angular.noop,5696 getPromise: function() {5697 if (!this.promise) {5698 var self = this;5699 this.promise = $q(function(resolve, reject) {5700 self.done(function(status) {5701 status === false ? reject() : resolve();5702 });5703 });5704 }5705 return this.promise;5706 },5707 then: function(resolveHandler, rejectHandler) {5708 return this.getPromise().then(resolveHandler, rejectHandler);5709 },5710 'catch': function(handler) {5711 return this.getPromise()['catch'](handler);5712 },5713 'finally': function(handler) {5714 return this.getPromise()['finally'](handler);5715 },5716 pause: function() {5717 if (this.host.pause) {5718 this.host.pause();5719 }5720 },5721 resume: function() {5722 if (this.host.resume) {5723 this.host.resume();5724 }5725 },5726 end: function() {5727 if (this.host.end) {5728 this.host.end();5729 }5730 this._resolve(true);5731 },5732 cancel: function() {5733 if (this.host.cancel) {5734 this.host.cancel();5735 }5736 this._resolve(false);5737 },5738 complete: function(response) {5739 var self = this;5740 if (self._state === INITIAL_STATE) {5741 self._state = DONE_PENDING_STATE;5742 self._runInAnimationFrame(function() {5743 self._resolve(response);5744 });5745 }5746 },5747 _resolve: function(response) {5748 if (this._state !== DONE_COMPLETE_STATE) {5749 forEach(this._doneCallbacks, function(fn) {5750 fn(response);5751 });5752 this._doneCallbacks.length = 0;5753 this._state = DONE_COMPLETE_STATE;5754 }5755 }5756 };5757 // Polyfill AnimateRunner.all which is used by input animations5758 AnimateRunner.all = function(runners, callback) {5759 var count = 0;5760 var status = true;5761 forEach(runners, function(runner) {5762 runner.done(onProgress);5763 });5764 function onProgress(response) {5765 status = status && response;5766 if (++count === runners.length) {5767 callback(status);5768 }5769 }5770 };5771 return AnimateRunner;5772 }];5773 angular5774 .module('material.core.animate', [])5775 .factory('$$forceReflow', $$ForceReflowFactory)5776 .factory('$$AnimateRunner', $$AnimateRunnerFactory)5777 .factory('$$rAFMutex', $$rAFMutexFactory)5778 .factory('$animateCss', ['$window', '$$rAF', '$$AnimateRunner', '$$forceReflow', '$$jqLite', '$timeout', '$animate',5779 function($window, $$rAF, $$AnimateRunner, $$forceReflow, $$jqLite, $timeout, $animate) {5780 function init(element, options) {5781 var temporaryStyles = [];5782 var node = getDomNode(element);5783 var areAnimationsAllowed = node && $animate.enabled();5784 var hasCompleteStyles = false;5785 var hasCompleteClasses = false;5786 if (areAnimationsAllowed) {5787 if (options.transitionStyle) {5788 temporaryStyles.push([PREFIX + 'transition', options.transitionStyle]);5789 }5790 if (options.keyframeStyle) {5791 temporaryStyles.push([PREFIX + 'animation', options.keyframeStyle]);5792 }5793 if (options.delay) {5794 temporaryStyles.push([PREFIX + 'transition-delay', options.delay + 's']);5795 }5796 if (options.duration) {5797 temporaryStyles.push([PREFIX + 'transition-duration', options.duration + 's']);5798 }5799 hasCompleteStyles = options.keyframeStyle ||5800 (options.to && (options.duration > 0 || options.transitionStyle));5801 hasCompleteClasses = !!options.addClass || !!options.removeClass;5802 blockTransition(element, true);5803 }5804 var hasCompleteAnimation = areAnimationsAllowed && (hasCompleteStyles || hasCompleteClasses);5805 applyAnimationFromStyles(element, options);5806 var animationClosed = false;5807 var events, eventFn;5808 return {5809 close: $window.close,5810 start: function() {5811 var runner = new $$AnimateRunner();5812 waitUntilQuiet(function() {5813 blockTransition(element, false);5814 if (!hasCompleteAnimation) {5815 return close();5816 }5817 forEach(temporaryStyles, function(entry) {5818 var key = entry[0];5819 var value = entry[1];5820 node.style[camelCase(key)] = value;5821 });5822 applyClasses(element, options);5823 var timings = computeTimings(element);5824 if (timings.duration === 0) {5825 return close();5826 }5827 var moreStyles = [];5828 if (options.easing) {5829 if (timings.transitionDuration) {5830 moreStyles.push([PREFIX + 'transition-timing-function', options.easing]);5831 }5832 if (timings.animationDuration) {5833 moreStyles.push([PREFIX + 'animation-timing-function', options.easing]);5834 }5835 }5836 if (options.delay && timings.animationDelay) {5837 moreStyles.push([PREFIX + 'animation-delay', options.delay + 's']);5838 }5839 if (options.duration && timings.animationDuration) {5840 moreStyles.push([PREFIX + 'animation-duration', options.duration + 's']);5841 }5842 forEach(moreStyles, function(entry) {5843 var key = entry[0];5844 var value = entry[1];5845 node.style[camelCase(key)] = value;5846 temporaryStyles.push(entry);5847 });5848 var maxDelay = timings.delay;5849 var maxDelayTime = maxDelay * 1000;5850 var maxDuration = timings.duration;5851 var maxDurationTime = maxDuration * 1000;5852 var startTime = Date.now();5853 events = [];5854 if (timings.transitionDuration) {5855 events.push(TRANSITION_EVENTS);5856 }5857 if (timings.animationDuration) {5858 events.push(ANIMATION_EVENTS);5859 }5860 events = events.join(' ');5861 eventFn = function(event) {5862 event.stopPropagation();5863 var ev = event.originalEvent || event;5864 var timeStamp = ev.timeStamp || Date.now();5865 var elapsedTime = parseFloat(ev.elapsedTime.toFixed(3));5866 if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {5867 close();5868 }5869 };5870 element.on(events, eventFn);5871 applyAnimationToStyles(element, options);5872 $timeout(close, maxDelayTime + maxDurationTime * 1.5, false);5873 });5874 return runner;5875 function close() {5876 if (animationClosed) return;5877 animationClosed = true;5878 if (events && eventFn) {5879 element.off(events, eventFn);5880 }5881 applyClasses(element, options);5882 applyAnimationStyles(element, options);5883 forEach(temporaryStyles, function(entry) {5884 node.style[camelCase(entry[0])] = '';5885 });5886 runner.complete(true);5887 return runner;5888 }5889 }5890 }5891 }5892 function applyClasses(element, options) {5893 if (options.addClass) {5894 $$jqLite.addClass(element, options.addClass);5895 options.addClass = null;5896 }5897 if (options.removeClass) {5898 $$jqLite.removeClass(element, options.removeClass);5899 options.removeClass = null;5900 }5901 }5902 function computeTimings(element) {5903 var node = getDomNode(element);5904 var cs = $window.getComputedStyle(node)5905 var tdr = parseMaxTime(cs[prop('transitionDuration')]);5906 var adr = parseMaxTime(cs[prop('animationDuration')]);5907 var tdy = parseMaxTime(cs[prop('transitionDelay')]);5908 var ady = parseMaxTime(cs[prop('animationDelay')]);5909 adr *= (parseInt(cs[prop('animationIterationCount')], 10) || 1);5910 var duration = Math.max(adr, tdr);5911 var delay = Math.max(ady, tdy);5912 return {5913 duration: duration,5914 delay: delay,5915 animationDuration: adr,5916 transitionDuration: tdr,5917 animationDelay: ady,5918 transitionDelay: tdy5919 };5920 function prop(key) {5921 return WEBKIT ? 'Webkit' + key.charAt(0).toUpperCase() + key.substr(1)5922 : key;5923 }5924 }5925 function parseMaxTime(str) {5926 var maxValue = 0;5927 var values = (str || "").split(/\s*,\s*/);5928 forEach(values, function(value) {5929 // it's always safe to consider only second values and omit `ms` values since5930 // getComputedStyle will always handle the conversion for us5931 if (value.charAt(value.length - 1) == 's') {5932 value = value.substring(0, value.length - 1);5933 }5934 value = parseFloat(value) || 0;5935 maxValue = maxValue ? Math.max(value, maxValue) : value;5936 });5937 return maxValue;5938 }5939 var cancelLastRAFRequest;5940 var rafWaitQueue = [];5941 function waitUntilQuiet(callback) {5942 if (cancelLastRAFRequest) {5943 cancelLastRAFRequest(); //cancels the request5944 }5945 rafWaitQueue.push(callback);5946 cancelLastRAFRequest = $$rAF(function() {5947 cancelLastRAFRequest = null;5948 // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.5949 // PLEASE EXAMINE THE `$$forceReflow` service to understand why.5950 var pageWidth = $$forceReflow();5951 // we use a for loop to ensure that if the queue is changed5952 // during this looping then it will consider new requests5953 for (var i = 0; i < rafWaitQueue.length; i++) {5954 rafWaitQueue[i](pageWidth);5955 }5956 rafWaitQueue.length = 0;5957 });5958 }5959 function applyAnimationStyles(element, options) {5960 applyAnimationFromStyles(element, options);5961 applyAnimationToStyles(element, options);5962 }5963 function applyAnimationFromStyles(element, options) {5964 if (options.from) {5965 element.css(options.from);5966 options.from = null;5967 }5968 }5969 function applyAnimationToStyles(element, options) {5970 if (options.to) {5971 element.css(options.to);5972 options.to = null;5973 }5974 }5975 function getDomNode(element) {5976 for (var i = 0; i < element.length; i++) {5977 if (element[i].nodeType === 1) return element[i];5978 }5979 }5980 function blockTransition(element, bool) {5981 var node = getDomNode(element);5982 var key = camelCase(PREFIX + 'transition-delay');5983 node.style[key] = bool ? '-9999s' : '';5984 }5985 return init;5986 }]);5987 /**5988 * Older browsers [FF31] expect camelCase5989 * property keys.5990 * e.g.5991 * animation-duration --> animationDuration5992 */5993 function camelCase(str) {5994 return str.replace(/-[a-z]/g, function(str) {5995 return str.charAt(1).toUpperCase();5996 });5997 }5998})();5999}6000(function(){ 6001angular.module("material.core").constant("$MD_THEME_CSS", "md-autocomplete.md-THEME_NAME-theme { background: '{{background-A100}}'; } md-autocomplete.md-THEME_NAME-theme[disabled]:not([md-floating-label]) { 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-container.md-THEME_NAME-theme { background: '{{background-A100}}'; } .md-autocomplete-suggestions-container.md-THEME_NAME-theme li { color: '{{background-900}}'; } .md-autocomplete-suggestions-container.md-THEME_NAME-theme li .highlight { color: '{{background-600}}'; } .md-autocomplete-suggestions-container.md-THEME_NAME-theme li:hover, .md-autocomplete-suggestions-container.md-THEME_NAME-theme li.selected { background: '{{background-200}}'; }md-backdrop { background-color: '{{background-900-0.0}}'; } md-backdrop.md-opaque.md-THEME_NAME-theme { background-color: '{{background-900-1.0}}'; }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}}'; }.md-button.md-THEME_NAME-theme:not([disabled]):hover { background-color: '{{background-500-0.2}}'; }.md-button.md-THEME_NAME-theme:not([disabled]).md-focused { background-color: '{{background-500-0.2}}'; }.md-button.md-THEME_NAME-theme:not([disabled]).md-icon-button:hover { background-color: transparent; }.md-button.md-THEME_NAME-theme.md-fab { background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-fab md-icon { color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover { background-color: '{{accent-A700}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused { background-color: '{{accent-A700}}'; }.md-button.md-THEME_NAME-theme.md-primary { color: '{{primary-color}}'; } .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}}'; } .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}}'; } .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-600}}'; } .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}}'; } .md-button.md-THEME_NAME-theme.md-primary:not([disabled]) md-icon { color: '{{primary-color}}'; }.md-button.md-THEME_NAME-theme.md-fab { background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]) .md-icon { color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover { background-color: '{{accent-A700}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused { background-color: '{{accent-A700}}'; }.md-button.md-THEME_NAME-theme.md-raised { color: '{{background-900}}'; background-color: '{{background-50}}'; } .md-button.md-THEME_NAME-theme.md-raised:not([disabled]) md-icon { color: '{{background-900}}'; } .md-button.md-THEME_NAME-theme.md-raised:not([disabled]):hover { background-color: '{{background-50}}'; } .md-button.md-THEME_NAME-theme.md-raised:not([disabled]).md-focused { background-color: '{{background-200}}'; }.md-button.md-THEME_NAME-theme.md-warn { color: '{{warn-color}}'; } .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}}'; } .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}}'; } .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-600}}'; } .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-600}}'; } .md-button.md-THEME_NAME-theme.md-warn:not([disabled]) md-icon { color: '{{warn-color}}'; }.md-button.md-THEME_NAME-theme.md-accent { color: '{{accent-color}}'; } .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}}'; } .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}}'; } .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-A700}}'; } .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-A700}}'; } .md-button.md-THEME_NAME-theme.md-accent:not([disabled]) md-icon { color: '{{accent-color}}'; }.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: default; } .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}}'; }.md-button.md-THEME_NAME-theme.md-raised[disabled], .md-button.md-THEME_NAME-theme.md-fab[disabled] { background-color: '{{foreground-4}}'; }.md-button.md-THEME_NAME-theme[disabled] { background-color: transparent; }._md a.md-THEME_NAME-theme:not(.md-button).md-primary { color: '{{primary-color}}'; } ._md a.md-THEME_NAME-theme:not(.md-button).md-primary:hover { color: '{{primary-700}}'; }._md a.md-THEME_NAME-theme:not(.md-button).md-accent { color: '{{accent-color}}'; } ._md a.md-THEME_NAME-theme:not(.md-button).md-accent:hover { color: '{{accent-700}}'; }._md a.md-THEME_NAME-theme:not(.md-button).md-accent { color: '{{accent-color}}'; } ._md a.md-THEME_NAME-theme:not(.md-button).md-accent:hover { color: '{{accent-A700}}'; }._md a.md-THEME_NAME-theme:not(.md-button).md-warn { color: '{{warn-color}}'; } ._md a.md-THEME_NAME-theme:not(.md-button).md-warn:hover { color: '{{warn-700}}'; }md-card.md-THEME_NAME-theme { color: '{{foreground-1}}'; background-color: '{{background-hue-1}}'; border-radius: 2px; } md-card.md-THEME_NAME-theme .md-card-image { border-radius: 2px 2px 0 0; } md-card.md-THEME_NAME-theme md-card-header md-card-avatar md-icon { color: '{{background-color}}'; background-color: '{{foreground-3}}'; } md-card.md-THEME_NAME-theme md-card-header md-card-header-text .md-subhead { color: '{{foreground-2}}'; } md-card.md-THEME_NAME-theme md-card-title md-card-title-text:not(:only-child) .md-subhead { color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme .md-ripple { color: '{{accent-A700}}'; }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-ink-ripple { color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme.md-checked .md-ink-ripple { color: '{{accent-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not(.md-checked) .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: '{{accent-contrast-0.87}}'; }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-ink-ripple { color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple { color: '{{primary-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary:not(.md-checked) .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: '{{primary-contrast-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-indeterminate[disabled] .md-container { color: '{{foreground-3}}'; }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-ink-ripple { color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple { color: '{{warn-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn:not(.md-checked) .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]:not(.md-checked) .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-checked .md-icon:after { border-color: '{{background-200}}'; }md-checkbox.md-THEME_NAME-theme[disabled] .md-icon:after { border-color: '{{foreground-3}}'; }md-checkbox.md-THEME_NAME-theme[disabled] .md-label { color: '{{foreground-3}}'; }md-chips.md-THEME_NAME-theme .md-chips { box-shadow: 0 1px '{{foreground-4}}'; } md-chips.md-THEME_NAME-theme .md-chips.md-focused { box-shadow: 0 2px '{{primary-color}}'; } md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input { color: '{{foreground-1}}'; } md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input::-webkit-input-placeholder { color: '{{foreground-3}}'; } md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input:-moz-placeholder { color: '{{foreground-3}}'; } md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input::-moz-placeholder { color: '{{foreground-3}}'; } md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input:-ms-input-placeholder { color: '{{foreground-3}}'; } md-chips.md-THEME_NAME-theme .md-chips .md-chip-input-container input::-webkit-input-placeholder { color: '{{foreground-3}}'; }md-chips.md-THEME_NAME-theme md-chip { background: '{{background-300}}'; color: '{{background-800}}'; } md-chips.md-THEME_NAME-theme md-chip md-icon { color: '{{background-700}}'; } 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._md-chip-editing { background: transparent; color: '{{background-800}}'; }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 { color: '{{foreground-1}}'; background-color: '{{background-default}}'; }/** Theme styles for mdCalendar. */.md-calendar.md-THEME_NAME-theme { background: '{{background-A100}}'; color: '{{background-A200-0.87}}'; } .md-calendar.md-THEME_NAME-theme tr:last-child td { border-bottom-color: '{{background-200}}'; }.md-THEME_NAME-theme .md-calendar-day-header { background: '{{background-300}}'; color: '{{background-A200-0.87}}'; }.md-THEME_NAME-theme .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator { border: 1px solid '{{primary-500}}'; }.md-THEME_NAME-theme .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled { color: '{{primary-500-0.6}}'; }.md-calendar-date.md-focus .md-THEME_NAME-theme .md-calendar-date-selection-indicator, .md-THEME_NAME-theme .md-calendar-date-selection-indicator:hover { background: '{{background-300}}'; }.md-THEME_NAME-theme .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-THEME_NAME-theme .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator { background: '{{primary-500}}'; color: '{{primary-500-contrast}}'; border-color: transparent; }.md-THEME_NAME-theme .md-calendar-date-disabled,.md-THEME_NAME-theme .md-calendar-month-label-disabled { color: '{{background-A200-0.435}}'; }/** Theme styles for mdDatepicker. */.md-THEME_NAME-theme .md-datepicker-input { color: '{{foreground-1}}'; } .md-THEME_NAME-theme .md-datepicker-input::-webkit-input-placeholder { color: '{{foreground-3}}'; } .md-THEME_NAME-theme .md-datepicker-input:-moz-placeholder { color: '{{foreground-3}}'; } .md-THEME_NAME-theme .md-datepicker-input::-moz-placeholder { color: '{{foreground-3}}'; } .md-THEME_NAME-theme .md-datepicker-input:-ms-input-placeholder { color: '{{foreground-3}}'; } .md-THEME_NAME-theme .md-datepicker-input::-webkit-input-placeholder { color: '{{foreground-3}}'; }.md-THEME_NAME-theme .md-datepicker-input-container { border-bottom-color: '{{foreground-4}}'; } .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused { border-bottom-color: '{{primary-color}}'; } .md-accent .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused { border-bottom-color: '{{accent-color}}'; } .md-warn .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused { border-bottom-color: '{{warn-A700}}'; } .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-invalid { border-bottom-color: '{{warn-A700}}'; }.md-THEME_NAME-theme .md-datepicker-calendar-pane { border-color: '{{background-hue-1}}'; }.md-THEME_NAME-theme .md-datepicker-triangle-button .md-datepicker-expand-triangle { border-top-color: '{{foreground-3}}'; }.md-THEME_NAME-theme .md-datepicker-triangle-button:hover .md-datepicker-expand-triangle { border-top-color: '{{foreground-2}}'; }.md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon { color: '{{primary-color}}'; }.md-THEME_NAME-theme .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-accent .md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon { color: '{{accent-color}}'; }.md-THEME_NAME-theme .md-datepicker-open.md-warn .md-datepicker-calendar-icon, .md-warn .md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon { color: '{{warn-A700}}'; }.md-THEME_NAME-theme .md-datepicker-calendar { background: '{{background-A100}}'; }.md-THEME_NAME-theme .md-datepicker-input-mask-opaque { box-shadow: 0 0 0 9999px \"{{background-hue-1}}\"; }.md-THEME_NAME-theme .md-datepicker-open .md-datepicker-input-container { background: \"{{background-hue-1}}\"; }md-dialog.md-THEME_NAME-theme { border-radius: 4px; background-color: '{{background-hue-1}}'; color: '{{foreground-1}}'; } md-dialog.md-THEME_NAME-theme.md-content-overflow .md-actions, md-dialog.md-THEME_NAME-theme.md-content-overflow md-dialog-actions { border-top-color: '{{foreground-4}}'; }md-divider.md-THEME_NAME-theme { border-top-color: '{{foreground-4}}'; }.layout-row > md-divider.md-THEME_NAME-theme,.layout-xs-row > md-divider.md-THEME_NAME-theme, .layout-gt-xs-row > md-divider.md-THEME_NAME-theme,.layout-sm-row > md-divider.md-THEME_NAME-theme, .layout-gt-sm-row > md-divider.md-THEME_NAME-theme,.layout-md-row > md-divider.md-THEME_NAME-theme, .layout-gt-md-row > md-divider.md-THEME_NAME-theme,.layout-lg-row > md-divider.md-THEME_NAME-theme, .layout-gt-lg-row > md-divider.md-THEME_NAME-theme,.layout-xl-row > md-divider.md-THEME_NAME-theme { border-right-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}}'; } md-input-container.md-THEME_NAME-theme .md-input::-webkit-input-placeholder { color: '{{foreground-3}}'; } md-input-container.md-THEME_NAME-theme .md-input:-moz-placeholder { color: '{{foreground-3}}'; } md-input-container.md-THEME_NAME-theme .md-input::-moz-placeholder { color: '{{foreground-3}}'; } md-input-container.md-THEME_NAME-theme .md-input:-ms-input-placeholder { color: '{{foreground-3}}'; } md-input-container.md-THEME_NAME-theme .md-input::-webkit-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 { color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme label.md-required:after { color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-focused):not(.md-input-invalid) label.md-required:after { color: '{{foreground-2}}'; }md-input-container.md-THEME_NAME-theme .md-input-messages-animation, md-input-container.md-THEME_NAME-theme .md-input-message-animation { color: '{{warn-A700}}'; } md-input-container.md-THEME_NAME-theme .md-input-messages-animation .md-char-counter, md-input-container.md-THEME_NAME-theme .md-input-message-animation .md-char-counter { color: '{{foreground-1}}'; }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, md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-resized .md-input { border-color: '{{primary-color}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused label,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused md-icon { color: '{{primary-color}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input { border-color: '{{accent-color}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent md-icon { color: '{{accent-color}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input { border-color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn md-icon { color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input { border-color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid label,md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input-message-animation,md-input-container.md-THEME_NAME-theme.md-input-invalid .md-char-counter { color: '{{warn-A700}}'; }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-avatar-icon { background-color: '{{foreground-3}}'; color: '{{background-color}}'; }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-menu-content.md-THEME_NAME-theme { background-color: '{{background-A100}}'; } md-menu-content.md-THEME_NAME-theme md-menu-item { color: '{{background-A200-0.87}}'; } md-menu-content.md-THEME_NAME-theme md-menu-item md-icon { color: '{{background-A200-0.54}}'; } md-menu-content.md-THEME_NAME-theme md-menu-item .md-button[disabled] { color: '{{background-A200-0.25}}'; } md-menu-content.md-THEME_NAME-theme md-menu-item .md-button[disabled] md-icon { color: '{{background-A200-0.25}}'; } md-menu-content.md-THEME_NAME-theme md-menu-divider { background-color: '{{background-A200-0.11}}'; }md-menu-bar.md-THEME_NAME-theme > button.md-button { color: '{{foreground-2}}'; border-radius: 2px; }md-menu-bar.md-THEME_NAME-theme md-menu.md-open > button, md-menu-bar.md-THEME_NAME-theme md-menu > button:focus { outline: none; background: '{{background-200}}'; }md-menu-bar.md-THEME_NAME-theme.md-open:not(.md-keyboard-mode) md-menu:hover > button { background-color: '{{ background-500-0.2}}'; }md-menu-bar.md-THEME_NAME-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:hover,md-menu-bar.md-THEME_NAME-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:focus { background: transparent; }md-menu-content.md-THEME_NAME-theme .md-menu > .md-button:after { color: '{{background-A200-0.54}}'; }md-menu-content.md-THEME_NAME-theme .md-menu.md-open > .md-button { background-color: '{{ background-500-0.2}}'; }md-toolbar.md-THEME_NAME-theme.md-menu-toolbar { background-color: '{{background-A100}}'; color: '{{background-A200}}'; } md-toolbar.md-THEME_NAME-theme.md-menu-toolbar md-toolbar-filler { background-color: '{{primary-color}}'; color: '{{background-A100-0.87}}'; } md-toolbar.md-THEME_NAME-theme.md-menu-toolbar md-toolbar-filler md-icon { color: '{{background-A100-0.87}}'; }md-nav-bar.md-THEME_NAME-theme .md-nav-bar { background-color: transparent; border-color: '{{foreground-4}}'; }md-nav-bar.md-THEME_NAME-theme .md-button._md-nav-button.md-unselected { color: '{{foreground-2}}'; }md-nav-bar.md-THEME_NAME-theme md-nav-ink-bar { color: '{{accent-color}}'; background: '{{accent-color}}'; }.md-panel { background-color: '{{background-900-0.0}}'; } .md-panel._md-panel-backdrop.md-THEME_NAME-theme { background-color: '{{background-900-1.0}}'; }md-progress-circular.md-THEME_NAME-theme path { stroke: '{{primary-color}}'; }md-progress-circular.md-THEME_NAME-theme.md-warn path { stroke: '{{warn-color}}'; }md-progress-circular.md-THEME_NAME-theme.md-accent path { stroke: '{{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-A700}}'; }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-checked .md-ink-ripple { color: '{{accent-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-THEME_NAME-theme .md-checked:not([disabled]).md-primary .md-ink-ripple { color: '{{primary-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme .md-checked.md-primary .md-ink-ripple { color: '{{warn-color-0.26}}'; }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-primary .md-checked .md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-primary .md-container:before { background-color: '{{primary-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty).md-warn .md-checked .md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-warn .md-container:before { background-color: '{{warn-color-0.26}}'; }md-input-container md-select.md-THEME_NAME-theme .md-select-value span:first-child:after { color: '{{warn-A700}}'; }md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-THEME_NAME-theme .md-select-value span:first-child:after { color: '{{foreground-3}}'; }md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-THEME_NAME-theme .md-select-value { color: '{{primary-color}}'; } md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-THEME_NAME-theme .md-select-value.md-select-placeholder { color: '{{primary-color}}'; }md-input-container.md-input-invalid md-select.md-THEME_NAME-theme .md-select-value { color: '{{warn-A700}}' !important; border-bottom-color: '{{warn-A700}}' !important; }md-input-container.md-input-invalid md-select.md-THEME_NAME-theme.md-no-underline .md-select-value { border-bottom-color: transparent !important; }md-select.md-THEME_NAME-theme[disabled] .md-select-value { border-bottom-color: transparent; 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-select.md-THEME_NAME-theme .md-select-value { border-bottom-color: '{{foreground-4}}'; } md-select.md-THEME_NAME-theme .md-select-value.md-select-placeholder { color: '{{foreground-3}}'; } md-select.md-THEME_NAME-theme .md-select-value span:first-child:after { color: '{{warn-A700}}'; }md-select.md-THEME_NAME-theme.md-no-underline .md-select-value { border-bottom-color: transparent !important; }md-select.md-THEME_NAME-theme.ng-invalid.ng-touched .md-select-value { color: '{{warn-A700}}' !important; border-bottom-color: '{{warn-A700}}' !important; }md-select.md-THEME_NAME-theme.ng-invalid.ng-touched.md-no-underline .md-select-value { border-bottom-color: transparent !important; }md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-value { border-bottom-color: '{{primary-color}}'; color: '{{ foreground-1 }}'; } md-select.md-THEME_NAME-theme:not([disabled]):focus .md-select-value.md-select-placeholder { color: '{{ foreground-1 }}'; }md-select.md-THEME_NAME-theme:not([disabled]):focus.md-no-underline .md-select-value { border-bottom-color: transparent !important; }md-select.md-THEME_NAME-theme:not([disabled]):focus.md-accent .md-select-value { border-bottom-color: '{{accent-color}}'; }md-select.md-THEME_NAME-theme:not([disabled]):focus.md-warn .md-select-value { border-bottom-color: '{{warn-color}}'; }md-select.md-THEME_NAME-theme[disabled] .md-select-value { color: '{{foreground-3}}'; } md-select.md-THEME_NAME-theme[disabled] .md-select-value.md-select-placeholder { color: '{{foreground-3}}'; }md-select-menu.md-THEME_NAME-theme md-content { background: '{{background-A100}}'; } md-select-menu.md-THEME_NAME-theme md-content md-optgroup { color: '{{background-600-0.87}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option { color: '{{background-900-0.87}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[disabled] .md-text { color: '{{background-400-0.87}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option:not([disabled]):focus, md-select-menu.md-THEME_NAME-theme md-content md-option:not([disabled]):hover { background: '{{background-200}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[selected] { color: '{{primary-500}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[selected]:focus { color: '{{primary-600}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[selected].md-accent { color: '{{accent-color}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[selected].md-accent:focus { color: '{{accent-A700}}'; }.md-checkbox-enabled.md-THEME_NAME-theme .md-ripple { color: '{{primary-600}}'; }.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-ripple { color: '{{background-600}}'; }.md-checkbox-enabled.md-THEME_NAME-theme .md-ink-ripple { color: '{{foreground-2}}'; }.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-ink-ripple { color: '{{primary-color-0.87}}'; }.md-checkbox-enabled.md-THEME_NAME-theme:not(.md-checked) .md-icon { border-color: '{{foreground-2}}'; }.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-icon { background-color: '{{primary-color-0.87}}'; }.md-checkbox-enabled.md-THEME_NAME-theme[selected].md-focused .md-container:before { background-color: '{{primary-color-0.26}}'; }.md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-icon:after { border-color: '{{primary-contrast-0.87}}'; }.md-checkbox-enabled.md-THEME_NAME-theme .md-indeterminate[disabled] .md-container { color: '{{foreground-3}}'; }.md-checkbox-enabled.md-THEME_NAME-theme md-option .md-text { color: '{{background-900-0.87}}'; }md-sidenav.md-THEME_NAME-theme, md-sidenav.md-THEME_NAME-theme md-content { background-color: '{{background-hue-1}}'; }md-slider.md-THEME_NAME-theme .md-track { background-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme .md-track-ticks { color: '{{background-contrast}}'; }md-slider.md-THEME_NAME-theme .md-focus-ring { background-color: '{{accent-A200-0.2}}'; }md-slider.md-THEME_NAME-theme .md-disabled-thumb { border-color: '{{background-color}}'; background-color: '{{background-color}}'; }md-slider.md-THEME_NAME-theme.md-min .md-thumb:after { background-color: '{{background-color}}'; border-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme.md-min .md-focus-ring { background-color: '{{foreground-3-0.38}}'; }md-slider.md-THEME_NAME-theme.md-min[md-discrete] .md-thumb:after { background-color: '{{background-contrast}}'; border-color: transparent; }md-slider.md-THEME_NAME-theme.md-min[md-discrete] .md-sign { background-color: '{{background-400}}'; } md-slider.md-THEME_NAME-theme.md-min[md-discrete] .md-sign:after { border-top-color: '{{background-400}}'; }md-slider.md-THEME_NAME-theme.md-min[md-discrete][md-vertical] .md-sign:after { border-top-color: transparent; border-left-color: '{{background-400}}'; }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-vertical] .md-sign:after { border-top-color: transparent; border-left-color: '{{accent-color}}'; }md-slider.md-THEME_NAME-theme .md-thumb-text { color: '{{accent-contrast}}'; }md-slider.md-THEME_NAME-theme.md-warn .md-focus-ring { background-color: '{{warn-200-0.38}}'; }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-vertical] .md-sign:after { border-top-color: transparent; border-left-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-focus-ring { background-color: '{{primary-200-0.38}}'; }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-vertical] .md-sign:after { border-top-color: transparent; border-left-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: transparent; }md-slider.md-THEME_NAME-theme[disabled]:not(.md-min) .md-thumb:after, md-slider.md-THEME_NAME-theme[disabled][md-discrete] .md-thumb:after { background-color: '{{foreground-3}}'; border-color: transparent; }md-slider.md-THEME_NAME-theme[disabled][readonly] .md-sign { background-color: '{{background-400}}'; } md-slider.md-THEME_NAME-theme[disabled][readonly] .md-sign:after { border-top-color: '{{background-400}}'; }md-slider.md-THEME_NAME-theme[disabled][readonly][md-vertical] .md-sign:after { border-top-color: transparent; border-left-color: '{{background-400}}'; }md-slider.md-THEME_NAME-theme[disabled][readonly] .md-disabled-thumb { border-color: transparent; background-color: transparent; }md-slider-container[disabled] > *:first-child:not(md-slider),md-slider-container[disabled] > *:last-child:not(md-slider) { color: '{{foreground-3}}'; }.md-subheader.md-THEME_NAME-theme { color: '{{ foreground-2-0.23 }}'; background-color: '{{background-default}}'; } .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-ink-ripple { color: '{{background-500}}'; }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-ink-ripple { color: '{{accent-color}}'; }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-ink-ripple { color: '{{primary-color}}'; }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-ink-ripple { color: '{{warn-color}}'; }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], md-tabs.md-THEME_NAME-theme .md-tab[disabled] md-icon { color: '{{foreground-3}}'; } md-tabs.md-THEME_NAME-theme .md-tab.md-active, md-tabs.md-THEME_NAME-theme .md-tab.md-active md-icon, md-tabs.md-THEME_NAME-theme .md-tab.md-focused, md-tabs.md-THEME_NAME-theme .md-tab.md-focused md-icon { 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-A100}}'; }md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper { background-color: '{{accent-color}}'; } md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{accent-A100}}'; } md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{accent-contrast}}'; } md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused { background: '{{accent-contrast-0.1}}'; } md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > 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-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{primary-100}}'; } md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{primary-contrast}}'; } md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > 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-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{warn-100}}'; } md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{warn-contrast}}'; } md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > 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-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{primary-100}}'; } md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{primary-contrast}}'; } md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > 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-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{accent-A100}}'; } md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{accent-contrast}}'; } md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused { background: '{{accent-contrast-0.1}}'; } md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > 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-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{warn-100}}'; } md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{warn-contrast}}'; } md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused { background: '{{warn-contrast-0.1}}'; }md-toast.md-THEME_NAME-theme .md-toast-content { background-color: #323232; color: '{{background-50}}'; } md-toast.md-THEME_NAME-theme .md-toast-content .md-button { color: '{{background-50}}'; } md-toast.md-THEME_NAME-theme .md-toast-content .md-button.md-highlight { color: '{{accent-color}}'; } md-toast.md-THEME_NAME-theme .md-toast-content .md-button.md-highlight.md-primary { color: '{{primary-color}}'; } md-toast.md-THEME_NAME-theme .md-toast-content .md-button.md-highlight.md-warn { color: '{{warn-color}}'; }md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar) { background-color: '{{primary-color}}'; color: '{{primary-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar) md-icon { color: '{{primary-contrast}}'; fill: '{{primary-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar) .md-button[disabled] md-icon { color: '{{primary-contrast-0.26}}'; fill: '{{primary-contrast-0.26}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent { background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent .md-ink-ripple { color: '{{accent-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent md-icon { color: '{{accent-contrast}}'; fill: '{{accent-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon { color: '{{accent-contrast-0.26}}'; fill: '{{accent-contrast-0.26}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-warn { background-color: '{{warn-color}}'; color: '{{warn-contrast}}'; }md-tooltip.md-THEME_NAME-theme { color: '{{background-700-contrast}}'; } md-tooltip.md-THEME_NAME-theme .md-content { background-color: '{{background-700}}'; }/* Only used with Theme processes */html.md-THEME_NAME-theme, body.md-THEME_NAME-theme { color: '{{foreground-1}}'; background-color: '{{background-color}}'; }"); 6002})();...
chips.js
Source:chips.js
1/*!2 * Angular Material Design3 * https://github.com/angular/material4 * @license MIT5 * v1.1.16 */7goog.provide('ngmaterial.components.chips');8goog.require('ngmaterial.components.autocomplete');9goog.require('ngmaterial.core');10/**11 * @ngdoc module12 * @name material.components.chips13 */14/*15 * @see js folder for chips implementation16 */17angular.module('material.components.chips', [18 'material.core',19 'material.components.autocomplete'20]);21MdChipCtrl.$inject = ["$scope", "$element", "$mdConstant", "$timeout", "$mdUtil"];angular22 .module('material.components.chips')23 .controller('MdChipCtrl', MdChipCtrl);24/**25 * Controller for the MdChip component. Responsible for handling keyboard26 * events and editting the chip if needed.27 *28 * @param $scope29 * @param $element30 * @param $mdConstant31 * @param $timeout32 * @param $mdUtil33 * @constructor34 */35function MdChipCtrl ($scope, $element, $mdConstant, $timeout, $mdUtil) {36 /**37 * @type {$scope}38 */39 this.$scope = $scope;40 /**41 * @type {$element}42 */43 this.$element = $element;44 /**45 * @type {$mdConstant}46 */47 this.$mdConstant = $mdConstant;48 /**49 * @type {$timeout}50 */51 this.$timeout = $timeout;52 /**53 * @type {$mdUtil}54 */55 this.$mdUtil = $mdUtil;56 /**57 * @type {boolean}58 */59 this.isEditting = false;60 /**61 * @type {MdChipsCtrl}62 */63 this.parentController = undefined;64 /**65 * @type {boolean}66 */67 this.enableChipEdit = false;68}69/**70 * @param {MdChipsCtrl} controller71 */72MdChipCtrl.prototype.init = function(controller) {73 this.parentController = controller;74 this.enableChipEdit = this.parentController.enableChipEdit;75 if (this.enableChipEdit) {76 this.$element.on('keydown', this.chipKeyDown.bind(this));77 this.$element.on('mousedown', this.chipMouseDown.bind(this));78 this.getChipContent().addClass('_md-chip-content-edit-is-enabled');79 }80};81/**82 * @return {Object}83 */84MdChipCtrl.prototype.getChipContent = function() {85 var chipContents = this.$element[0].getElementsByClassName('md-chip-content');86 return angular.element(chipContents[0]);87};88/**89 * @return {Object}90 */91MdChipCtrl.prototype.getContentElement = function() {92 return angular.element(this.getChipContent().children()[0]);93};94/**95 * @return {number}96 */97MdChipCtrl.prototype.getChipIndex = function() {98 return parseInt(this.$element.attr('index'));99};100/**101 * Presents an input element to edit the contents of the chip.102 */103MdChipCtrl.prototype.goOutOfEditMode = function() {104 if (!this.isEditting) return;105 this.isEditting = false;106 this.$element.removeClass('_md-chip-editing');107 this.getChipContent()[0].contentEditable = 'false';108 var chipIndex = this.getChipIndex();109 var content = this.getContentElement().text();110 if (content) {111 this.parentController.updateChipContents(112 chipIndex,113 this.getContentElement().text()114 );115 this.$mdUtil.nextTick(function() {116 if (this.parentController.selectedChip === chipIndex) {117 this.parentController.focusChip(chipIndex);118 }119 }.bind(this));120 } else {121 this.parentController.removeChipAndFocusInput(chipIndex);122 }123};124/**125 * Given an HTML element. Selects contents of it.126 * @param node127 */128MdChipCtrl.prototype.selectNodeContents = function(node) {129 var range, selection;130 if (document.body.createTextRange) {131 range = document.body.createTextRange();132 range.moveToElementText(node);133 range.select();134 } else if (window.getSelection) {135 selection = window.getSelection();136 range = document.createRange();137 range.selectNodeContents(node);138 selection.removeAllRanges();139 selection.addRange(range);140 }141};142/**143 * Presents an input element to edit the contents of the chip.144 */145MdChipCtrl.prototype.goInEditMode = function() {146 this.isEditting = true;147 this.$element.addClass('_md-chip-editing');148 this.getChipContent()[0].contentEditable = 'true';149 this.getChipContent().on('blur', function() {150 this.goOutOfEditMode();151 }.bind(this));152 this.selectNodeContents(this.getChipContent()[0]);153};154/**155 * Handles the keydown event on the chip element. If enable-chip-edit attribute is156 * set to true, space or enter keys can trigger going into edit mode. Enter can also157 * trigger submitting if the chip is already being edited.158 * @param event159 */160MdChipCtrl.prototype.chipKeyDown = function(event) {161 if (!this.isEditting &&162 (event.keyCode === this.$mdConstant.KEY_CODE.ENTER ||163 event.keyCode === this.$mdConstant.KEY_CODE.SPACE)) {164 event.preventDefault();165 this.goInEditMode();166 } else if (this.isEditting &&167 event.keyCode === this.$mdConstant.KEY_CODE.ENTER) {168 event.preventDefault();169 this.goOutOfEditMode();170 }171};172/**173 * Handles the double click event174 */175MdChipCtrl.prototype.chipMouseDown = function() {176 if(this.getChipIndex() == this.parentController.selectedChip &&177 this.enableChipEdit &&178 !this.isEditting) {179 this.goInEditMode();180 }181};182MdChip.$inject = ["$mdTheming", "$mdUtil"];angular183 .module('material.components.chips')184 .directive('mdChip', MdChip);185/**186 * @ngdoc directive187 * @name mdChip188 * @module material.components.chips189 *190 * @description191 * `<md-chip>` is a component used within `<md-chips>` and is responsible for rendering individual192 * chips.193 *194 *195 * @usage196 * <hljs lang="html">197 * <md-chip>{{$chip}}</md-chip>198 * </hljs>199 *200 */201// This hint text is hidden within a chip but used by screen readers to202// inform the user how they can interact with a chip.203var DELETE_HINT_TEMPLATE = '\204 <span ng-if="!$mdChipsCtrl.readonly" class="md-visually-hidden">\205 {{$mdChipsCtrl.deleteHint}}\206 </span>';207/**208 * MDChip Directive Definition209 *210 * @param $mdTheming211 * @param $mdUtil212 * ngInject213 */214function MdChip($mdTheming, $mdUtil) {215 var hintTemplate = $mdUtil.processTemplate(DELETE_HINT_TEMPLATE);216 return {217 restrict: 'E',218 require: ['^?mdChips', 'mdChip'],219 compile: compile,220 controller: 'MdChipCtrl'221 };222 function compile(element, attr) {223 // Append the delete template224 element.append($mdUtil.processTemplate(hintTemplate));225 return function postLink(scope, element, attr, ctrls) {226 var chipsController = ctrls.shift();227 var chipController = ctrls.shift();228 $mdTheming(element);229 if (chipsController) {230 chipController.init(chipsController);231 angular232 .element(element[0]233 .querySelector('.md-chip-content'))234 .on('blur', function () {235 chipsController.resetSelectedChip();236 chipsController.$scope.$applyAsync();237 });238 }239 };240 }241}242MdChipRemove.$inject = ["$timeout"];angular243 .module('material.components.chips')244 .directive('mdChipRemove', MdChipRemove);245/**246 * @ngdoc directive247 * @name mdChipRemove248 * @restrict A249 * @module material.components.chips250 *251 * @description252 * Designates an element to be used as the delete button for a chip. <br/>253 * This element is passed as a child of the `md-chips` element.254 *255 * The designated button will be just appended to the chip and removes the given chip on click.<br/>256 * By default the button is not being styled by the `md-chips` component.257 *258 * @usage259 * <hljs lang="html">260 * <md-chips>261 * <button md-chip-remove="">262 * <md-icon md-svg-icon="md-close"></md-icon>263 * </button>264 * </md-chips>265 * </hljs>266 */267/**268 * MdChipRemove Directive Definition.269 * 270 * @param $timeout271 * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}272 * @constructor273 */274function MdChipRemove ($timeout) {275 return {276 restrict: 'A',277 require: '^mdChips',278 scope: false,279 link: postLink280 };281 function postLink(scope, element, attr, ctrl) {282 element.on('click', function(event) {283 scope.$apply(function() {284 ctrl.removeChip(scope.$$replacedScope.$index);285 });286 });287 // Child elements aren't available until after a $timeout tick as they are hidden by an288 // `ng-if`. see http://goo.gl/zIWfuw289 $timeout(function() {290 element.attr({ tabindex: -1, 'aria-hidden': true });291 element.find('button').attr('tabindex', '-1');292 });293 }294}295MdChipTransclude.$inject = ["$compile"];angular296 .module('material.components.chips')297 .directive('mdChipTransclude', MdChipTransclude);298function MdChipTransclude ($compile) {299 return {300 restrict: 'EA',301 terminal: true,302 link: link,303 scope: false304 };305 function link (scope, element, attr) {306 var ctrl = scope.$parent.$mdChipsCtrl,307 newScope = ctrl.parent.$new(false, ctrl.parent);308 newScope.$$replacedScope = scope;309 newScope.$chip = scope.$chip;310 newScope.$index = scope.$index;311 newScope.$mdChipsCtrl = ctrl;312 var newHtml = ctrl.$scope.$eval(attr.mdChipTransclude);313 element.html(newHtml);314 $compile(element.contents())(newScope);315 }316}317MdChipsCtrl.$inject = ["$scope", "$attrs", "$mdConstant", "$log", "$element", "$timeout", "$mdUtil"];angular318 .module('material.components.chips')319 .controller('MdChipsCtrl', MdChipsCtrl);320/**321 * Controller for the MdChips component. Responsible for adding to and322 * removing from the list of chips, marking chips as selected, and binding to323 * the models of various input components.324 *325 * @param $scope326 * @param $attrs327 * @param $mdConstant328 * @param $log329 * @param $element330 * @param $timeout331 * @param $mdUtil332 * @constructor333 */334function MdChipsCtrl ($scope, $attrs, $mdConstant, $log, $element, $timeout, $mdUtil) {335 /** @type {$timeout} **/336 this.$timeout = $timeout;337 /** @type {Object} */338 this.$mdConstant = $mdConstant;339 /** @type {angular.$scope} */340 this.$scope = $scope;341 /** @type {angular.$scope} */342 this.parent = $scope.$parent;343 /** @type {$log} */344 this.$log = $log;345 /** @type {$element} */346 this.$element = $element;347 /** @type {angular.NgModelController} */348 this.ngModelCtrl = null;349 /** @type {angular.NgModelController} */350 this.userInputNgModelCtrl = null;351 /** @type {MdAutocompleteCtrl} */352 this.autocompleteCtrl = null;353 /** @type {Element} */354 this.userInputElement = null;355 /** @type {Array.<Object>} */356 this.items = [];357 /** @type {number} */358 this.selectedChip = -1;359 /** @type {string} */360 this.enableChipEdit = $mdUtil.parseAttributeBoolean($attrs.mdEnableChipEdit);361 /** @type {string} */362 this.addOnBlur = $mdUtil.parseAttributeBoolean($attrs.mdAddOnBlur);363 /**364 * Hidden hint text for how to delete a chip. Used to give context to screen readers.365 * @type {string}366 */367 this.deleteHint = 'Press delete to remove this chip.';368 /**369 * Hidden label for the delete button. Used to give context to screen readers.370 * @type {string}371 */372 this.deleteButtonLabel = 'Remove';373 /**374 * Model used by the input element.375 * @type {string}376 */377 this.chipBuffer = '';378 /**379 * Whether to use the transformChip expression to transform the chip buffer380 * before appending it to the list.381 * @type {boolean}382 */383 this.useTransformChip = false;384 /**385 * Whether to use the onAdd expression to notify of chip additions.386 * @type {boolean}387 */388 this.useOnAdd = false;389 /**390 * Whether to use the onRemove expression to notify of chip removals.391 * @type {boolean}392 */393 this.useOnRemove = false;394 /**395 * Whether to use the onSelect expression to notify the component's user396 * after selecting a chip from the list.397 * @type {boolean}398 */399}400/**401 * Handles the keydown event on the input element: by default <enter> appends402 * the buffer to the chip list, while backspace removes the last chip in the403 * list if the current buffer is empty.404 * @param event405 */406MdChipsCtrl.prototype.inputKeydown = function(event) {407 var chipBuffer = this.getChipBuffer();408 // If we have an autocomplete, and it handled the event, we have nothing to do409 if (this.autocompleteCtrl && event.isDefaultPrevented && event.isDefaultPrevented()) {410 return;411 }412 if (event.keyCode === this.$mdConstant.KEY_CODE.BACKSPACE) {413 // Only select and focus the previous chip, if the current caret position of the414 // input element is at the beginning.415 if (this.getCursorPosition(event.target) !== 0) {416 return;417 }418 event.preventDefault();419 event.stopPropagation();420 if (this.items.length) {421 this.selectAndFocusChipSafe(this.items.length - 1);422 }423 return;424 }425 // By default <enter> appends the buffer to the chip list.426 if (!this.separatorKeys || this.separatorKeys.length < 1) {427 this.separatorKeys = [this.$mdConstant.KEY_CODE.ENTER];428 }429 // Support additional separator key codes in an array of `md-separator-keys`.430 if (this.separatorKeys.indexOf(event.keyCode) !== -1) {431 if ((this.autocompleteCtrl && this.requireMatch) || !chipBuffer) return;432 event.preventDefault();433 // Only append the chip and reset the chip buffer if the max chips limit isn't reached.434 if (this.hasMaxChipsReached()) return;435 this.appendChip(chipBuffer.trim());436 this.resetChipBuffer();437 }438};439/**440 * Returns the cursor position of the specified input element.441 * @param element HTMLInputElement442 * @returns {Number} Cursor Position of the input.443 */444MdChipsCtrl.prototype.getCursorPosition = function(element) {445 /*446 * Figure out whether the current input for the chips buffer is valid for using447 * the selectionStart / end property to retrieve the cursor position.448 * Some browsers do not allow the use of those attributes, on different input types.449 */450 try {451 if (element.selectionStart === element.selectionEnd) {452 return element.selectionStart;453 }454 } catch (e) {455 if (!element.value) {456 return 0;457 }458 }459};460/**461 * Updates the content of the chip at given index462 * @param chipIndex463 * @param chipContents464 */465MdChipsCtrl.prototype.updateChipContents = function(chipIndex, chipContents){466 if(chipIndex >= 0 && chipIndex < this.items.length) {467 this.items[chipIndex] = chipContents;468 this.ngModelCtrl.$setDirty();469 }470};471/**472 * Returns true if a chip is currently being edited. False otherwise.473 * @return {boolean}474 */475MdChipsCtrl.prototype.isEditingChip = function() {476 return !!this.$element[0].getElementsByClassName('_md-chip-editing').length;477};478MdChipsCtrl.prototype.isRemovable = function() {479 // Return false if we have static chips480 if (!this.ngModelCtrl) {481 return false;482 }483 return this.readonly ? this.removable :484 angular.isDefined(this.removable) ? this.removable : true;485};486/**487 * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow488 * keys switch which chips is active489 * @param event490 */491MdChipsCtrl.prototype.chipKeydown = function (event) {492 if (this.getChipBuffer()) return;493 if (this.isEditingChip()) return;494 495 switch (event.keyCode) {496 case this.$mdConstant.KEY_CODE.BACKSPACE:497 case this.$mdConstant.KEY_CODE.DELETE:498 if (this.selectedChip < 0) return;499 event.preventDefault();500 // Cancel the delete action only after the event cancel. Otherwise the page will go back.501 if (!this.isRemovable()) return;502 this.removeAndSelectAdjacentChip(this.selectedChip);503 break;504 case this.$mdConstant.KEY_CODE.LEFT_ARROW:505 event.preventDefault();506 if (this.selectedChip < 0) this.selectedChip = this.items.length;507 if (this.items.length) this.selectAndFocusChipSafe(this.selectedChip - 1);508 break;509 case this.$mdConstant.KEY_CODE.RIGHT_ARROW:510 event.preventDefault();511 this.selectAndFocusChipSafe(this.selectedChip + 1);512 break;513 case this.$mdConstant.KEY_CODE.ESCAPE:514 case this.$mdConstant.KEY_CODE.TAB:515 if (this.selectedChip < 0) return;516 event.preventDefault();517 this.onFocus();518 break;519 }520};521/**522 * Get the input's placeholder - uses `placeholder` when list is empty and `secondary-placeholder`523 * when the list is non-empty. If `secondary-placeholder` is not provided, `placeholder` is used524 * always.525 */526MdChipsCtrl.prototype.getPlaceholder = function() {527 // Allow `secondary-placeholder` to be blank.528 var useSecondary = (this.items && this.items.length &&529 (this.secondaryPlaceholder == '' || this.secondaryPlaceholder));530 return useSecondary ? this.secondaryPlaceholder : this.placeholder;531};532/**533 * Removes chip at {@code index} and selects the adjacent chip.534 * @param index535 */536MdChipsCtrl.prototype.removeAndSelectAdjacentChip = function(index) {537 var selIndex = this.getAdjacentChipIndex(index);538 this.removeChip(index);539 this.$timeout(angular.bind(this, function () {540 this.selectAndFocusChipSafe(selIndex);541 }));542};543/**544 * Sets the selected chip index to -1.545 */546MdChipsCtrl.prototype.resetSelectedChip = function() {547 this.selectedChip = -1;548};549/**550 * Gets the index of an adjacent chip to select after deletion. Adjacency is551 * determined as the next chip in the list, unless the target chip is the552 * last in the list, then it is the chip immediately preceding the target. If553 * there is only one item in the list, -1 is returned (select none).554 * The number returned is the index to select AFTER the target has been555 * removed.556 * If the current chip is not selected, then -1 is returned to select none.557 */558MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {559 var len = this.items.length - 1;560 return (len == 0) ? -1 :561 (index == len) ? index -1 : index;562};563/**564 * Append the contents of the buffer to the chip list. This method will first565 * call out to the md-transform-chip method, if provided.566 *567 * @param newChip568 */569MdChipsCtrl.prototype.appendChip = function(newChip) {570 if (this.useTransformChip && this.transformChip) {571 var transformedChip = this.transformChip({'$chip': newChip});572 // Check to make sure the chip is defined before assigning it, otherwise, we'll just assume573 // they want the string version.574 if (angular.isDefined(transformedChip)) {575 newChip = transformedChip;576 }577 }578 // If items contains an identical object to newChip, do not append579 if (angular.isObject(newChip)){580 var identical = this.items.some(function(item){581 return angular.equals(newChip, item);582 });583 if (identical) return;584 }585 // Check for a null (but not undefined), or existing chip and cancel appending586 if (newChip == null || this.items.indexOf(newChip) + 1) return;587 // Append the new chip onto our list588 var index = this.items.push(newChip);589 // Update model validation590 this.ngModelCtrl.$setDirty();591 this.validateModel();592 // If they provide the md-on-add attribute, notify them of the chip addition593 if (this.useOnAdd && this.onAdd) {594 this.onAdd({ '$chip': newChip, '$index': index });595 }596};597/**598 * Sets whether to use the md-transform-chip expression. This expression is599 * bound to scope and controller in {@code MdChipsDirective} as600 * {@code transformChip}. Due to the nature of directive scope bindings, the601 * controller cannot know on its own/from the scope whether an expression was602 * actually provided.603 */604MdChipsCtrl.prototype.useTransformChipExpression = function() {605 this.useTransformChip = true;606};607/**608 * Sets whether to use the md-on-add expression. This expression is609 * bound to scope and controller in {@code MdChipsDirective} as610 * {@code onAdd}. Due to the nature of directive scope bindings, the611 * controller cannot know on its own/from the scope whether an expression was612 * actually provided.613 */614MdChipsCtrl.prototype.useOnAddExpression = function() {615 this.useOnAdd = true;616};617/**618 * Sets whether to use the md-on-remove expression. This expression is619 * bound to scope and controller in {@code MdChipsDirective} as620 * {@code onRemove}. Due to the nature of directive scope bindings, the621 * controller cannot know on its own/from the scope whether an expression was622 * actually provided.623 */624MdChipsCtrl.prototype.useOnRemoveExpression = function() {625 this.useOnRemove = true;626};627/*628 * Sets whether to use the md-on-select expression. This expression is629 * bound to scope and controller in {@code MdChipsDirective} as630 * {@code onSelect}. Due to the nature of directive scope bindings, the631 * controller cannot know on its own/from the scope whether an expression was632 * actually provided.633 */634MdChipsCtrl.prototype.useOnSelectExpression = function() {635 this.useOnSelect = true;636};637/**638 * Gets the input buffer. The input buffer can be the model bound to the639 * default input item {@code this.chipBuffer}, the {@code selectedItem}640 * model of an {@code md-autocomplete}, or, through some magic, the model641 * bound to any inpput or text area element found within a642 * {@code md-input-container} element.643 * @return {Object|string}644 */645MdChipsCtrl.prototype.getChipBuffer = function() {646 return !this.userInputElement ? this.chipBuffer :647 this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :648 this.userInputElement[0].value;649};650/**651 * Resets the input buffer for either the internal input or user provided input element.652 */653MdChipsCtrl.prototype.resetChipBuffer = function() {654 if (this.userInputElement) {655 if (this.userInputNgModelCtrl) {656 this.userInputNgModelCtrl.$setViewValue('');657 this.userInputNgModelCtrl.$render();658 } else {659 this.userInputElement[0].value = '';660 }661 } else {662 this.chipBuffer = '';663 }664};665MdChipsCtrl.prototype.hasMaxChipsReached = function() {666 if (angular.isString(this.maxChips)) this.maxChips = parseInt(this.maxChips, 10) || 0;667 return this.maxChips > 0 && this.items.length >= this.maxChips;668};669/**670 * Updates the validity properties for the ngModel.671 */672MdChipsCtrl.prototype.validateModel = function() {673 this.ngModelCtrl.$setValidity('md-max-chips', !this.hasMaxChipsReached());674};675/**676 * Removes the chip at the given index.677 * @param index678 */679MdChipsCtrl.prototype.removeChip = function(index) {680 var removed = this.items.splice(index, 1);681 // Update model validation682 this.ngModelCtrl.$setDirty();683 this.validateModel();684 if (removed && removed.length && this.useOnRemove && this.onRemove) {685 this.onRemove({ '$chip': removed[0], '$index': index });686 }687};688MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {689 this.removeChip(index);690 if (this.autocompleteCtrl) {691 // Always hide the autocomplete dropdown before focusing the autocomplete input.692 // Wait for the input to move horizontally, because the chip was removed.693 // This can lead to an incorrect dropdown position.694 this.autocompleteCtrl.hidden = true;695 this.$mdUtil.nextTick(this.onFocus.bind(this));696 } else {697 this.onFocus();698 }699};700/**701 * Selects the chip at `index`,702 * @param index703 */704MdChipsCtrl.prototype.selectAndFocusChipSafe = function(index) {705 if (!this.items.length) {706 this.selectChip(-1);707 this.onFocus();708 return;709 }710 if (index === this.items.length) return this.onFocus();711 index = Math.max(index, 0);712 index = Math.min(index, this.items.length - 1);713 this.selectChip(index);714 this.focusChip(index);715};716/**717 * Marks the chip at the given index as selected.718 * @param index719 */720MdChipsCtrl.prototype.selectChip = function(index) {721 if (index >= -1 && index <= this.items.length) {722 this.selectedChip = index;723 // Fire the onSelect if provided724 if (this.useOnSelect && this.onSelect) {725 this.onSelect({'$chip': this.items[this.selectedChip] });726 }727 } else {728 this.$log.warn('Selected Chip index out of bounds; ignoring.');729 }730};731/**732 * Selects the chip at `index` and gives it focus.733 * @param index734 */735MdChipsCtrl.prototype.selectAndFocusChip = function(index) {736 this.selectChip(index);737 if (index != -1) {738 this.focusChip(index);739 }740};741/**742 * Call `focus()` on the chip at `index`743 */744MdChipsCtrl.prototype.focusChip = function(index) {745 this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content').focus();746};747/**748 * Configures the required interactions with the ngModel Controller.749 * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.750 * @param ngModelCtrl751 */752MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {753 this.ngModelCtrl = ngModelCtrl;754 var self = this;755 ngModelCtrl.$render = function() {756 // model is updated. do something.757 self.items = self.ngModelCtrl.$viewValue;758 };759};760MdChipsCtrl.prototype.onFocus = function () {761 var input = this.$element[0].querySelector('input');762 input && input.focus();763 this.resetSelectedChip();764};765MdChipsCtrl.prototype.onInputFocus = function () {766 this.inputHasFocus = true;767 this.resetSelectedChip();768};769MdChipsCtrl.prototype.onInputBlur = function () {770 this.inputHasFocus = false;771 var chipBuffer = this.getChipBuffer().trim();772 // Update the custom chip validators.773 this.validateModel();774 var isModelValid = this.ngModelCtrl.$valid;775 if (this.userInputNgModelCtrl) {776 isModelValid &= this.userInputNgModelCtrl.$valid;777 }778 // Only append the chip and reset the chip buffer if the chips and input ngModel is valid.779 if (this.addOnBlur && chipBuffer && isModelValid) {780 this.appendChip(chipBuffer);781 this.resetChipBuffer();782 }783};784/**785 * Configure event bindings on a user-provided input element.786 * @param inputElement787 */788MdChipsCtrl.prototype.configureUserInput = function(inputElement) {789 this.userInputElement = inputElement;790 // Find the NgModelCtrl for the input element791 var ngModelCtrl = inputElement.controller('ngModel');792 // `.controller` will look in the parent as well.793 if (ngModelCtrl != this.ngModelCtrl) {794 this.userInputNgModelCtrl = ngModelCtrl;795 }796 var scope = this.$scope;797 var ctrl = this;798 // Run all of the events using evalAsync because a focus may fire a blur in the same digest loop799 var scopeApplyFn = function(event, fn) {800 scope.$evalAsync(angular.bind(ctrl, fn, event));801 };802 // Bind to keydown and focus events of input803 inputElement804 .attr({ tabindex: 0 })805 .on('keydown', function(event) { scopeApplyFn(event, ctrl.inputKeydown) })806 .on('focus', function(event) { scopeApplyFn(event, ctrl.onInputFocus) })807 .on('blur', function(event) { scopeApplyFn(event, ctrl.onInputBlur) })808};809MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {810 if (ctrl) {811 this.autocompleteCtrl = ctrl;812 ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {813 if (item) {814 // Only append the chip and reset the chip buffer if the max chips limit isn't reached.815 if (this.hasMaxChipsReached()) return;816 this.appendChip(item);817 this.resetChipBuffer();818 }819 }));820 this.$element.find('input')821 .on('focus',angular.bind(this, this.onInputFocus) )822 .on('blur', angular.bind(this, this.onInputBlur) );823 }824};825MdChipsCtrl.prototype.hasFocus = function () {826 return this.inputHasFocus || this.selectedChip >= 0;827};828 829 MdChips.$inject = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout", "$$mdSvgRegistry"];angular830 .module('material.components.chips')831 .directive('mdChips', MdChips);832 /**833 * @ngdoc directive834 * @name mdChips835 * @module material.components.chips836 *837 * @description838 * `<md-chips>` is an input component for building lists of strings or objects. The list items are839 * displayed as 'chips'. This component can make use of an `<input>` element or an 840 * `<md-autocomplete>` element.841 *842 * ### Custom templates843 * A custom template may be provided to render the content of each chip. This is achieved by844 * specifying an `<md-chip-template>` element containing the custom content as a child of845 * `<md-chips>`.846 *847 * Note: Any attributes on848 * `<md-chip-template>` will be dropped as only the innerHTML is used for the chip template. The849 * variables `$chip` and `$index` are available in the scope of `<md-chip-template>`, representing850 * the chip object and its index in the list of chips, respectively.851 * To override the chip delete control, include an element (ideally a button) with the attribute852 * `md-chip-remove`. A click listener to remove the chip will be added automatically. The element853 * is also placed as a sibling to the chip content (on which there are also click listeners) to854 * avoid a nested ng-click situation.855 *856 * <h3> Pending Features </h3>857 * <ul style="padding-left:20px;">858 *859 * <ul>Style860 * <li>Colours for hover, press states (ripple?).</li>861 * </ul>862 *863 * <ul>Validation864 * <li>allow a validation callback</li>865 * <li>hilighting style for invalid chips</li>866 * </ul>867 *868 * <ul>Item mutation869 * <li>Support `870 * <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double871 * click?872 * </li>873 * </ul>874 *875 * <ul>Truncation and Disambiguation (?)876 * <li>Truncate chip text where possible, but do not truncate entries such that two are877 * indistinguishable.</li>878 * </ul>879 *880 * <ul>Drag and Drop881 * <li>Drag and drop chips between related `<md-chips>` elements.882 * </li>883 * </ul>884 * </ul>885 *886 * <span style="font-size:.8em;text-align:center">887 * Warning: This component is a WORK IN PROGRESS. If you use it now,888 * it will probably break on you in the future.889 * </span>890 *891 * Sometimes developers want to limit the amount of possible chips.<br/>892 * You can specify the maximum amount of chips by using the following markup.893 *894 * <hljs lang="html">895 * <md-chips896 * ng-model="myItems"897 * placeholder="Add an item"898 * md-max-chips="5">899 * </md-chips>900 * </hljs>901 *902 * In some cases, you have an autocomplete inside of the `md-chips`.<br/>903 * When the maximum amount of chips has been reached, you can also disable the autocomplete selection.<br/>904 * Here is an example markup.905 *906 * <hljs lang="html">907 * <md-chips ng-model="myItems" md-max-chips="5">908 * <md-autocomplete ng-hide="myItems.length > 5" ...></md-autocomplete>909 * </md-chips>910 * </hljs>911 *912 * @param {string=|object=} ng-model A model to bind the list of items to913 * @param {string=} placeholder Placeholder text that will be forwarded to the input.914 * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,915 * displayed when there is at least one item in the list916 * @param {boolean=} md-removable Enables or disables the deletion of chips through the917 * removal icon or the Delete/Backspace key. Defaults to true.918 * @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding919 * the input and delete buttons. If no `ng-model` is provided, the chips will automatically be920 * marked as readonly.<br/><br/>921 * When `md-removable` is not defined, the `md-remove` behavior will be overwritten and disabled.922 * @param {string=} md-enable-chip-edit Set this to "true" to enable editing of chip contents. The user can 923 * go into edit mode with pressing "space", "enter", or double clicking on the chip. Chip edit is only924 * supported for chips with basic template.925 * @param {number=} md-max-chips The maximum number of chips allowed to add through user input.926 * <br/><br/>The validation property `md-max-chips` can be used when the max chips927 * amount is reached.928 * @param {boolean=} md-add-on-blur When set to true, remaining text inside of the input will929 * be converted into a new chip on blur.930 * @param {expression} md-transform-chip An expression of form `myFunction($chip)` that when called931 * expects one of the following return values:932 * - an object representing the `$chip` input string933 * - `undefined` to simply add the `$chip` input string, or934 * - `null` to prevent the chip from being appended935 * @param {expression=} md-on-add An expression which will be called when a chip has been936 * added.937 * @param {expression=} md-on-remove An expression which will be called when a chip has been938 * removed.939 * @param {expression=} md-on-select An expression which will be called when a chip is selected.940 * @param {boolean} md-require-match If true, and the chips template contains an autocomplete,941 * only allow selection of pre-defined chips (i.e. you cannot add new ones).942 * @param {string=} delete-hint A string read by screen readers instructing users that pressing943 * the delete key will remove the chip.944 * @param {string=} delete-button-label A label for the delete button. Also hidden and read by945 * screen readers.946 * @param {expression=} md-separator-keys An array of key codes used to separate chips.947 *948 * @usage949 * <hljs lang="html">950 * <md-chips951 * ng-model="myItems"952 * placeholder="Add an item"953 * readonly="isReadOnly">954 * </md-chips>955 * </hljs>956 *957 * <h3>Validation</h3>958 * When using [ngMessages](https://docs.angularjs.org/api/ngMessages), you can show errors based959 * on our custom validators.960 * <hljs lang="html">961 * <form name="userForm">962 * <md-chips963 * name="fruits"964 * ng-model="myItems"965 * placeholder="Add an item"966 * md-max-chips="5">967 * </md-chips>968 * <div ng-messages="userForm.fruits.$error" ng-if="userForm.$dirty">969 * <div ng-message="md-max-chips">You reached the maximum amount of chips</div>970 * </div>971 * </form>972 * </hljs>973 *974 */975 var MD_CHIPS_TEMPLATE = '\976 <md-chips-wrap\977 ng-keydown="$mdChipsCtrl.chipKeydown($event)"\978 ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus(), \979 \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly,\980 \'md-removable\': $mdChipsCtrl.isRemovable() }"\981 class="md-chips">\982 <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\983 index="{{$index}}"\984 ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index, \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly}">\985 <div class="md-chip-content"\986 tabindex="-1"\987 aria-hidden="true"\988 ng-click="!$mdChipsCtrl.readonly && $mdChipsCtrl.focusChip($index)"\989 ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)"\990 md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div>\991 <div ng-if="$mdChipsCtrl.isRemovable()"\992 class="md-chip-remove-container"\993 md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\994 </md-chip>\995 <div class="md-chip-input-container" ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl">\996 <div md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\997 </div>\998 </md-chips-wrap>';999 var CHIP_INPUT_TEMPLATE = '\1000 <input\1001 class="md-input"\1002 tabindex="0"\1003 placeholder="{{$mdChipsCtrl.getPlaceholder()}}"\1004 aria-label="{{$mdChipsCtrl.getPlaceholder()}}"\1005 ng-model="$mdChipsCtrl.chipBuffer"\1006 ng-focus="$mdChipsCtrl.onInputFocus()"\1007 ng-blur="$mdChipsCtrl.onInputBlur()"\1008 ng-keydown="$mdChipsCtrl.inputKeydown($event)">';1009 var CHIP_DEFAULT_TEMPLATE = '\1010 <span>{{$chip}}</span>';1011 var CHIP_REMOVE_TEMPLATE = '\1012 <button\1013 class="md-chip-remove"\1014 ng-if="$mdChipsCtrl.isRemovable()"\1015 ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\1016 type="button"\1017 aria-hidden="true"\1018 tabindex="-1">\1019 <md-icon md-svg-src="{{ $mdChipsCtrl.mdCloseIcon }}"></md-icon>\1020 <span class="md-visually-hidden">\1021 {{$mdChipsCtrl.deleteButtonLabel}}\1022 </span>\1023 </button>';1024 /**1025 * MDChips Directive Definition1026 */1027 function MdChips ($mdTheming, $mdUtil, $compile, $log, $timeout, $$mdSvgRegistry) {1028 // Run our templates through $mdUtil.processTemplate() to allow custom start/end symbols1029 var templates = getTemplates();1030 return {1031 template: function(element, attrs) {1032 // Clone the element into an attribute. By prepending the attribute1033 // name with '$', Angular won't write it into the DOM. The cloned1034 // element propagates to the link function via the attrs argument,1035 // where various contained-elements can be consumed.1036 attrs['$mdUserTemplate'] = element.clone();1037 return templates.chips;1038 },1039 require: ['mdChips'],1040 restrict: 'E',1041 controller: 'MdChipsCtrl',1042 controllerAs: '$mdChipsCtrl',1043 bindToController: true,1044 compile: compile,1045 scope: {1046 readonly: '=readonly',1047 removable: '=mdRemovable',1048 placeholder: '@',1049 secondaryPlaceholder: '@',1050 maxChips: '@mdMaxChips',1051 transformChip: '&mdTransformChip',1052 onAppend: '&mdOnAppend',1053 onAdd: '&mdOnAdd',1054 onRemove: '&mdOnRemove',1055 onSelect: '&mdOnSelect',1056 deleteHint: '@',1057 deleteButtonLabel: '@',1058 separatorKeys: '=?mdSeparatorKeys',1059 requireMatch: '=?mdRequireMatch'1060 }1061 };1062 /**1063 * Builds the final template for `md-chips` and returns the postLink function.1064 *1065 * Building the template involves 3 key components:1066 * static chips1067 * chip template1068 * input control1069 *1070 * If no `ng-model` is provided, only the static chip work needs to be done.1071 *1072 * If no user-passed `md-chip-template` exists, the default template is used. This resulting1073 * template is appended to the chip content element.1074 *1075 * The remove button may be overridden by passing an element with an md-chip-remove attribute.1076 *1077 * If an `input` or `md-autocomplete` element is provided by the caller, it is set aside for1078 * transclusion later. The transclusion happens in `postLink` as the parent scope is required.1079 * If no user input is provided, a default one is appended to the input container node in the1080 * template.1081 *1082 * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for1083 * transclusion in the `postLink` function.1084 *1085 *1086 * @param element1087 * @param attr1088 * @returns {Function}1089 */1090 function compile(element, attr) {1091 // Grab the user template from attr and reset the attribute to null.1092 var userTemplate = attr['$mdUserTemplate'];1093 attr['$mdUserTemplate'] = null;1094 var chipTemplate = getTemplateByQuery('md-chips>md-chip-template');1095 var chipRemoveSelector = $mdUtil1096 .prefixer()1097 .buildList('md-chip-remove')1098 .map(function(attr) {1099 return 'md-chips>*[' + attr + ']';1100 })1101 .join(',');1102 // Set the chip remove, chip contents and chip input templates. The link function will put1103 // them on the scope for transclusion later.1104 var chipRemoveTemplate = getTemplateByQuery(chipRemoveSelector) || templates.remove,1105 chipContentsTemplate = chipTemplate || templates.default,1106 chipInputTemplate = getTemplateByQuery('md-chips>md-autocomplete')1107 || getTemplateByQuery('md-chips>input')1108 || templates.input,1109 staticChips = userTemplate.find('md-chip');1110 // Warn of malformed template. See #25451111 if (userTemplate[0].querySelector('md-chip-template>*[md-chip-remove]')) {1112 $log.warn('invalid placement of md-chip-remove within md-chip-template.');1113 }1114 function getTemplateByQuery (query) {1115 if (!attr.ngModel) return;1116 var element = userTemplate[0].querySelector(query);1117 return element && element.outerHTML;1118 }1119 /**1120 * Configures controller and transcludes.1121 */1122 return function postLink(scope, element, attrs, controllers) {1123 $mdUtil.initOptionalProperties(scope, attr);1124 $mdTheming(element);1125 var mdChipsCtrl = controllers[0];1126 if(chipTemplate) {1127 // Chip editing functionality assumes we are using the default chip template.1128 mdChipsCtrl.enableChipEdit = false;1129 }1130 mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;1131 mdChipsCtrl.chipRemoveTemplate = chipRemoveTemplate;1132 mdChipsCtrl.chipInputTemplate = chipInputTemplate;1133 mdChipsCtrl.mdCloseIcon = $$mdSvgRegistry.mdClose;1134 element1135 .attr({ 'aria-hidden': true, tabindex: -1 })1136 .on('focus', function () { mdChipsCtrl.onFocus(); });1137 if (attr.ngModel) {1138 mdChipsCtrl.configureNgModel(element.controller('ngModel'));1139 // If an `md-transform-chip` attribute was set, tell the controller to use the expression1140 // before appending chips.1141 if (attrs.mdTransformChip) mdChipsCtrl.useTransformChipExpression();1142 // If an `md-on-append` attribute was set, tell the controller to use the expression1143 // when appending chips.1144 //1145 // DEPRECATED: Will remove in official 1.0 release1146 if (attrs.mdOnAppend) mdChipsCtrl.useOnAppendExpression();1147 // If an `md-on-add` attribute was set, tell the controller to use the expression1148 // when adding chips.1149 if (attrs.mdOnAdd) mdChipsCtrl.useOnAddExpression();1150 // If an `md-on-remove` attribute was set, tell the controller to use the expression1151 // when removing chips.1152 if (attrs.mdOnRemove) mdChipsCtrl.useOnRemoveExpression();1153 // If an `md-on-select` attribute was set, tell the controller to use the expression1154 // when selecting chips.1155 if (attrs.mdOnSelect) mdChipsCtrl.useOnSelectExpression();1156 // The md-autocomplete and input elements won't be compiled until after this directive1157 // is complete (due to their nested nature). Wait a tick before looking for them to1158 // configure the controller.1159 if (chipInputTemplate != templates.input) {1160 // The autocomplete will not appear until the readonly attribute is not true (i.e.1161 // false or undefined), so we have to watch the readonly and then on the next tick1162 // after the chip transclusion has run, we can configure the autocomplete and user1163 // input.1164 scope.$watch('$mdChipsCtrl.readonly', function(readonly) {1165 if (!readonly) {1166 $mdUtil.nextTick(function(){1167 if (chipInputTemplate.indexOf('<md-autocomplete') === 0) {1168 var autocompleteEl = element.find('md-autocomplete');1169 mdChipsCtrl.configureAutocomplete(autocompleteEl.controller('mdAutocomplete'));1170 }1171 mdChipsCtrl.configureUserInput(element.find('input'));1172 });1173 }1174 });1175 }1176 // At the next tick, if we find an input, make sure it has the md-input class1177 $mdUtil.nextTick(function() {1178 var input = element.find('input');1179 input && input.toggleClass('md-input', true);1180 });1181 }1182 // Compile with the parent's scope and prepend any static chips to the wrapper.1183 if (staticChips.length > 0) {1184 var compiledStaticChips = $compile(staticChips.clone())(scope.$parent);1185 $timeout(function() { element.find('md-chips-wrap').prepend(compiledStaticChips); });1186 }1187 };1188 }1189 function getTemplates() {1190 return {1191 chips: $mdUtil.processTemplate(MD_CHIPS_TEMPLATE),1192 input: $mdUtil.processTemplate(CHIP_INPUT_TEMPLATE),1193 default: $mdUtil.processTemplate(CHIP_DEFAULT_TEMPLATE),1194 remove: $mdUtil.processTemplate(CHIP_REMOVE_TEMPLATE)1195 };1196 }1197 }1198angular1199 .module('material.components.chips')1200 .controller('MdContactChipsCtrl', MdContactChipsCtrl);1201/**1202 * Controller for the MdContactChips component1203 * @constructor1204 */1205function MdContactChipsCtrl () {1206 /** @type {Object} */1207 this.selectedItem = null;1208 /** @type {string} */1209 this.searchText = '';1210}1211MdContactChipsCtrl.prototype.queryContact = function(searchText) {1212 var results = this.contactQuery({'$query': searchText});1213 return this.filterSelected ?1214 results.filter(angular.bind(this, this.filterSelectedContacts)) : results;1215};1216MdContactChipsCtrl.prototype.itemName = function(item) {1217 return item[this.contactName];1218};1219MdContactChipsCtrl.prototype.filterSelectedContacts = function(contact) {1220 return this.contacts.indexOf(contact) == -1;1221};1222MdContactChips.$inject = ["$mdTheming", "$mdUtil"];angular1223 .module('material.components.chips')1224 .directive('mdContactChips', MdContactChips);1225/**1226 * @ngdoc directive1227 * @name mdContactChips1228 * @module material.components.chips1229 *1230 * @description1231 * `<md-contact-chips>` is an input component based on `md-chips` and makes use of an1232 * `md-autocomplete` element. The component allows the caller to supply a query expression which1233 * returns a list of possible contacts. The user can select one of these and add it to the list of1234 * chips.1235 *1236 * You may also use the `md-highlight-text` directive along with its parameters to control the1237 * appearance of the matched text inside of the contacts' autocomplete popup.1238 *1239 * @param {string=|object=} ng-model A model to bind the list of items to1240 * @param {string=} placeholder Placeholder text that will be forwarded to the input.1241 * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,1242 * displayed when there is at least on item in the list1243 * @param {expression} md-contacts An expression expected to return contacts matching the search1244 * test, `$query`. If this expression involves a promise, a loading bar is displayed while1245 * waiting for it to resolve.1246 * @param {string} md-contact-name The field name of the contact object representing the1247 * contact's name.1248 * @param {string} md-contact-email The field name of the contact object representing the1249 * contact's email address.1250 * @param {string} md-contact-image The field name of the contact object representing the1251 * contact's image.1252 *1253 *1254 * @param {expression=} filter-selected Whether to filter selected contacts from the list of1255 * suggestions shown in the autocomplete. This attribute has been removed but may come back.1256 *1257 *1258 *1259 * @usage1260 * <hljs lang="html">1261 * <md-contact-chips1262 * ng-model="ctrl.contacts"1263 * md-contacts="ctrl.querySearch($query)"1264 * md-contact-name="name"1265 * md-contact-image="image"1266 * md-contact-email="email"1267 * placeholder="To">1268 * </md-contact-chips>1269 * </hljs>1270 *1271 */1272var MD_CONTACT_CHIPS_TEMPLATE = '\1273 <md-chips class="md-contact-chips"\1274 ng-model="$mdContactChipsCtrl.contacts"\1275 md-require-match="$mdContactChipsCtrl.requireMatch"\1276 md-autocomplete-snap>\1277 <md-autocomplete\1278 md-menu-class="md-contact-chips-suggestions"\1279 md-selected-item="$mdContactChipsCtrl.selectedItem"\1280 md-search-text="$mdContactChipsCtrl.searchText"\1281 md-items="item in $mdContactChipsCtrl.queryContact($mdContactChipsCtrl.searchText)"\1282 md-item-text="$mdContactChipsCtrl.itemName(item)"\1283 md-no-cache="true"\1284 md-autoselect\1285 placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\1286 $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\1287 <div class="md-contact-suggestion">\1288 <img \1289 ng-src="{{item[$mdContactChipsCtrl.contactImage]}}"\1290 alt="{{item[$mdContactChipsCtrl.contactName]}}"\1291 ng-if="item[$mdContactChipsCtrl.contactImage]" />\1292 <span class="md-contact-name" md-highlight-text="$mdContactChipsCtrl.searchText"\1293 md-highlight-flags="{{$mdContactChipsCtrl.highlightFlags}}">\1294 {{item[$mdContactChipsCtrl.contactName]}}\1295 </span>\1296 <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\1297 </div>\1298 </md-autocomplete>\1299 <md-chip-template>\1300 <div class="md-contact-avatar">\1301 <img \1302 ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\1303 alt="{{$chip[$mdContactChipsCtrl.contactName]}}"\1304 ng-if="$chip[$mdContactChipsCtrl.contactImage]" />\1305 </div>\1306 <div class="md-contact-name">\1307 {{$chip[$mdContactChipsCtrl.contactName]}}\1308 </div>\1309 </md-chip-template>\1310 </md-chips>';1311/**1312 * MDContactChips Directive Definition1313 *1314 * @param $mdTheming1315 * @returns {*}1316 * ngInject1317 */1318function MdContactChips($mdTheming, $mdUtil) {1319 return {1320 template: function(element, attrs) {1321 return MD_CONTACT_CHIPS_TEMPLATE;1322 },1323 restrict: 'E',1324 controller: 'MdContactChipsCtrl',1325 controllerAs: '$mdContactChipsCtrl',1326 bindToController: true,1327 compile: compile,1328 scope: {1329 contactQuery: '&mdContacts',1330 placeholder: '@',1331 secondaryPlaceholder: '@',1332 contactName: '@mdContactName',1333 contactImage: '@mdContactImage',1334 contactEmail: '@mdContactEmail',1335 contacts: '=ngModel',1336 requireMatch: '=?mdRequireMatch',1337 highlightFlags: '@?mdHighlightFlags'1338 }1339 };1340 function compile(element, attr) {1341 return function postLink(scope, element, attrs, controllers) {1342 $mdUtil.initOptionalProperties(scope, attr);1343 $mdTheming(element);1344 element.attr('tabindex', '-1');1345 };1346 }1347}...
list.js
Source:list.js
1/*!2 * Angular Material Design3 * https://github.com/angular/material4 * @license MIT5 * v1.1.16 */7(function( window, angular, undefined ){8"use strict";9/**10 * @ngdoc module11 * @name material.components.list12 * @description13 * List module14 */15MdListController.$inject = ["$scope", "$element", "$mdListInkRipple"];16mdListDirective.$inject = ["$mdTheming"];17mdListItemDirective.$inject = ["$mdAria", "$mdConstant", "$mdUtil", "$timeout"];18angular.module('material.components.list', [19 'material.core'20])21 .controller('MdListController', MdListController)22 .directive('mdList', mdListDirective)23 .directive('mdListItem', mdListItemDirective);24/**25 * @ngdoc directive26 * @name mdList27 * @module material.components.list28 *29 * @restrict E30 *31 * @description32 * The `<md-list>` directive is a list container for 1..n `<md-list-item>` tags.33 *34 * @usage35 * <hljs lang="html">36 * <md-list>37 * <md-list-item class="md-2-line" ng-repeat="item in todos">38 * <md-checkbox ng-model="item.done"></md-checkbox>39 * <div class="md-list-item-text">40 * <h3>{{item.title}}</h3>41 * <p>{{item.description}}</p>42 * </div>43 * </md-list-item>44 * </md-list>45 * </hljs>46 */47function mdListDirective($mdTheming) {48 return {49 restrict: 'E',50 compile: function(tEl) {51 tEl[0].setAttribute('role', 'list');52 return $mdTheming;53 }54 };55}56/**57 * @ngdoc directive58 * @name mdListItem59 * @module material.components.list60 *61 * @restrict E62 *63 * @description64 * A `md-list-item` element can be used to represent some information in a row.<br/>65 *66 * @usage67 * ### Single Row Item68 * <hljs lang="html">69 * <md-list-item>70 * <span>Single Row Item</span>71 * </md-list-item>72 * </hljs>73 *74 * ### Multiple Lines75 * By using the following markup, you will be able to have two lines inside of one `md-list-item`.76 *77 * <hljs lang="html">78 * <md-list-item class="md-2-line">79 * <div class="md-list-item-text" layout="column">80 * <p>First Line</p>81 * <p>Second Line</p>82 * </div>83 * </md-list-item>84 * </hljs>85 *86 * It is also possible to have three lines inside of one list item.87 *88 * <hljs lang="html">89 * <md-list-item class="md-3-line">90 * <div class="md-list-item-text" layout="column">91 * <p>First Line</p>92 * <p>Second Line</p>93 * <p>Third Line</p>94 * </div>95 * </md-list-item>96 * </hljs>97 *98 * ### Secondary Items99 * Secondary items are elements which will be aligned at the end of the `md-list-item`.100 *101 * <hljs lang="html">102 * <md-list-item>103 * <span>Single Row Item</span>104 * <md-button class="md-secondary">105 * Secondary Button106 * </md-button>107 * </md-list-item>108 * </hljs>109 *110 * It also possible to have multiple secondary items inside of one `md-list-item`.111 *112 * <hljs lang="html">113 * <md-list-item>114 * <span>Single Row Item</span>115 * <md-button class="md-secondary">First Button</md-button>116 * <md-button class="md-secondary">Second Button</md-button>117 * </md-list-item>118 * </hljs>119 *120 * ### Proxy Item121 * Proxies are elements, which will execute their specific action on click<br/>122 * Currently supported proxy items are123 * - `md-checkbox` (Toggle)124 * - `md-switch` (Toggle)125 * - `md-menu` (Open)126 *127 * This means, when using a supported proxy item inside of `md-list-item`, the list item will128 * become clickable and executes the associated action of the proxy element on click.129 *130 * <hljs lang="html">131 * <md-list-item>132 * <span>First Line</span>133 * <md-checkbox class="md-secondary"></md-checkbox>134 * </md-list-item>135 * </hljs>136 *137 * The `md-checkbox` element will be automatically detected as a proxy element and will toggle on click.138 *139 * <hljs lang="html">140 * <md-list-item>141 * <span>First Line</span>142 * <md-switch class="md-secondary"></md-switch>143 * </md-list-item>144 * </hljs>145 *146 * The recognized `md-switch` will toggle its state, when the user clicks on the `md-list-item`.147 *148 * It is also possible to have a `md-menu` inside of a `md-list-item`.149 * <hljs lang="html">150 * <md-list-item>151 * <p>Click anywhere to fire the secondary action</p>152 * <md-menu class="md-secondary">153 * <md-button class="md-icon-button">154 * <md-icon md-svg-icon="communication:message"></md-icon>155 * </md-button>156 * <md-menu-content width="4">157 * <md-menu-item>158 * <md-button>159 * Redial160 * </md-button>161 * </md-menu-item>162 * <md-menu-item>163 * <md-button>164 * Check voicemail165 * </md-button>166 * </md-menu-item>167 * <md-menu-divider></md-menu-divider>168 * <md-menu-item>169 * <md-button>170 * Notifications171 * </md-button>172 * </md-menu-item>173 * </md-menu-content>174 * </md-menu>175 * </md-list-item>176 * </hljs>177 *178 * The menu will automatically open, when the users clicks on the `md-list-item`.<br/>179 *180 * If the developer didn't specify any position mode on the menu, the `md-list-item` will automatically detect the181 * position mode and applies it to the `md-menu`.182 *183 * ### Avatars184 * Sometimes you may want to have some avatars inside of the `md-list-item `.<br/>185 * You are able to create a optimized icon for the list item, by applying the `.md-avatar` class on the `<img>` element.186 *187 * <hljs lang="html">188 * <md-list-item>189 * <img src="my-avatar.png" class="md-avatar">190 * <span>Alan Turing</span>191 * </hljs>192 *193 * When using `<md-icon>` for an avater, you have to use the `.md-avatar-icon` class.194 * <hljs lang="html">195 * <md-list-item>196 * <md-icon class="md-avatar-icon" md-svg-icon="avatars:timothy"></md-icon>197 * <span>Timothy Kopra</span>198 * </md-list-item>199 * </hljs>200 *201 * In cases, you have a `md-list-item`, which doesn't have any avatar,202 * but you want to align it with the other avatar items, you have to use the `.md-offset` class.203 *204 * <hljs lang="html">205 * <md-list-item class="md-offset">206 * <span>Jon Doe</span>207 * </md-list-item>208 * </hljs>209 *210 * ### DOM modification211 * The `md-list-item` component automatically detects if the list item should be clickable.212 *213 * ---214 * If the `md-list-item` is clickable, we wrap all content inside of a `<div>` and create215 * an overlaying button, which will will execute the given actions (like `ng-href`, `ng-click`)216 *217 * We create an overlaying button, instead of wrapping all content inside of the button,218 * because otherwise some elements may not be clickable inside of the button.219 *220 * ---221 * When using a secondary item inside of your list item, the `md-list-item` component will automatically create222 * a secondary container at the end of the `md-list-item`, which contains all secondary items.223 *224 * The secondary item container is not static, because otherwise the overflow will not work properly on the225 * list item.226 *227 */228function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {229 var proxiedTypes = ['md-checkbox', 'md-switch', 'md-menu'];230 return {231 restrict: 'E',232 controller: 'MdListController',233 compile: function(tEl, tAttrs) {234 // Check for proxy controls (no ng-click on parent, and a control inside)235 var secondaryItems = tEl[0].querySelectorAll('.md-secondary');236 var hasProxiedElement;237 var proxyElement;238 var itemContainer = tEl;239 tEl[0].setAttribute('role', 'listitem');240 if (tAttrs.ngClick || tAttrs.ngDblclick || tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref) {241 wrapIn('button');242 } else {243 for (var i = 0, type; type = proxiedTypes[i]; ++i) {244 if (proxyElement = tEl[0].querySelector(type)) {245 hasProxiedElement = true;246 break;247 }248 }249 if (hasProxiedElement) {250 wrapIn('div');251 } else if (!tEl[0].querySelector('md-button:not(.md-secondary):not(.md-exclude)')) {252 tEl.addClass('md-no-proxy');253 }254 }255 wrapSecondaryItems();256 setupToggleAria();257 if (hasProxiedElement && proxyElement.nodeName === "MD-MENU") {258 setupProxiedMenu();259 }260 function setupToggleAria() {261 var toggleTypes = ['md-switch', 'md-checkbox'];262 var toggle;263 for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) {264 if (toggle = tEl.find(toggleType)[0]) {265 if (!toggle.hasAttribute('aria-label')) {266 var p = tEl.find('p')[0];267 if (!p) return;268 toggle.setAttribute('aria-label', 'Toggle ' + p.textContent);269 }270 }271 }272 }273 function setupProxiedMenu() {274 var menuEl = angular.element(proxyElement);275 var isEndAligned = menuEl.parent().hasClass('md-secondary-container') ||276 proxyElement.parentNode.firstElementChild !== proxyElement;277 var xAxisPosition = 'left';278 if (isEndAligned) {279 // When the proxy item is aligned at the end of the list, we have to set the origin to the end.280 xAxisPosition = 'right';281 }282 283 // Set the position mode / origin of the proxied menu.284 if (!menuEl.attr('md-position-mode')) {285 menuEl.attr('md-position-mode', xAxisPosition + ' target');286 }287 // Apply menu open binding to menu button288 var menuOpenButton = menuEl.children().eq(0);289 if (!hasClickEvent(menuOpenButton[0])) {290 menuOpenButton.attr('ng-click', '$mdOpenMenu($event)');291 }292 if (!menuOpenButton.attr('aria-label')) {293 menuOpenButton.attr('aria-label', 'Open List Menu');294 }295 }296 function wrapIn(type) {297 if (type == 'div') {298 itemContainer = angular.element('<div class="md-no-style md-list-item-inner">');299 itemContainer.append(tEl.contents());300 tEl.addClass('md-proxy-focus');301 } else {302 // Element which holds the default list-item content.303 itemContainer = angular.element(304 '<div class="md-button md-no-style">'+305 ' <div class="md-list-item-inner"></div>'+306 '</div>'307 );308 // Button which shows ripple and executes primary action.309 var buttonWrap = angular.element(310 '<md-button class="md-no-style"></md-button>'311 );312 buttonWrap[0].setAttribute('aria-label', tEl[0].textContent);313 copyAttributes(tEl[0], buttonWrap[0]);314 // We allow developers to specify the `md-no-focus` class, to disable the focus style315 // on the button executor. Once more classes should be forwarded, we should probably make the316 // class forward more generic.317 if (tEl.hasClass('md-no-focus')) {318 buttonWrap.addClass('md-no-focus');319 }320 // Append the button wrap before our list-item content, because it will overlay in relative.321 itemContainer.prepend(buttonWrap);322 itemContainer.children().eq(1).append(tEl.contents());323 tEl.addClass('_md-button-wrap');324 }325 tEl[0].setAttribute('tabindex', '-1');326 tEl.append(itemContainer);327 }328 function wrapSecondaryItems() {329 var secondaryItemsWrapper = angular.element('<div class="md-secondary-container">');330 angular.forEach(secondaryItems, function(secondaryItem) {331 wrapSecondaryItem(secondaryItem, secondaryItemsWrapper);332 });333 itemContainer.append(secondaryItemsWrapper);334 }335 function wrapSecondaryItem(secondaryItem, container) {336 // If the current secondary item is not a button, but contains a ng-click attribute,337 // the secondary item will be automatically wrapped inside of a button.338 if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) {339 $mdAria.expect(secondaryItem, 'aria-label');340 var buttonWrapper = angular.element('<md-button class="md-secondary md-icon-button">');341 // Copy the attributes from the secondary item to the generated button.342 // We also support some additional attributes from the secondary item,343 // because some developers may use a ngIf, ngHide, ngShow on their item.344 copyAttributes(secondaryItem, buttonWrapper[0], ['ng-if', 'ng-hide', 'ng-show']);345 secondaryItem.setAttribute('tabindex', '-1');346 buttonWrapper.append(secondaryItem);347 secondaryItem = buttonWrapper[0];348 }349 if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) {350 // In this case we remove the secondary class, so we can identify it later, when we searching for the351 // proxy items.352 angular.element(secondaryItem).removeClass('md-secondary');353 }354 tEl.addClass('md-with-secondary');355 container.append(secondaryItem);356 }357 /**358 * Copies attributes from a source element to the destination element359 * By default the function will copy the most necessary attributes, supported360 * by the button executor for clickable list items.361 * @param source Element with the specified attributes362 * @param destination Element which will retrieve the attributes363 * @param extraAttrs Additional attributes, which will be copied over.364 */365 function copyAttributes(source, destination, extraAttrs) {366 var copiedAttrs = $mdUtil.prefixer([367 'ng-if', 'ng-click', 'ng-dblclick', 'aria-label', 'ng-disabled', 'ui-sref',368 'href', 'ng-href', 'target', 'ng-attr-ui-sref', 'ui-sref-opts'369 ]);370 if (extraAttrs) {371 copiedAttrs = copiedAttrs.concat($mdUtil.prefixer(extraAttrs));372 }373 angular.forEach(copiedAttrs, function(attr) {374 if (source.hasAttribute(attr)) {375 destination.setAttribute(attr, source.getAttribute(attr));376 source.removeAttribute(attr);377 }378 });379 }380 function isProxiedElement(el) {381 return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1;382 }383 function isButton(el) {384 var nodeName = el.nodeName.toUpperCase();385 return nodeName == "MD-BUTTON" || nodeName == "BUTTON";386 }387 function hasClickEvent (element) {388 var attr = element.attributes;389 for (var i = 0; i < attr.length; i++) {390 if (tAttrs.$normalize(attr[i].name) === 'ngClick') return true;391 }392 return false;393 }394 return postLink;395 function postLink($scope, $element, $attr, ctrl) {396 $element.addClass('_md'); // private md component indicator for styling397 var proxies = [],398 firstElement = $element[0].firstElementChild,399 isButtonWrap = $element.hasClass('_md-button-wrap'),400 clickChild = isButtonWrap ? firstElement.firstElementChild : firstElement,401 hasClick = clickChild && hasClickEvent(clickChild);402 computeProxies();403 computeClickable();404 if ($element.hasClass('md-proxy-focus') && proxies.length) {405 angular.forEach(proxies, function(proxy) {406 proxy = angular.element(proxy);407 $scope.mouseActive = false;408 proxy.on('mousedown', function() {409 $scope.mouseActive = true;410 $timeout(function(){411 $scope.mouseActive = false;412 }, 100);413 })414 .on('focus', function() {415 if ($scope.mouseActive === false) { $element.addClass('md-focused'); }416 proxy.on('blur', function proxyOnBlur() {417 $element.removeClass('md-focused');418 proxy.off('blur', proxyOnBlur);419 });420 });421 });422 }423 function computeProxies() {424 if (firstElement && firstElement.children && !hasClick) {425 angular.forEach(proxiedTypes, function(type) {426 // All elements which are not capable for being used a proxy have the .md-secondary class427 // applied. These items had been sorted out in the secondary wrap function.428 angular.forEach(firstElement.querySelectorAll(type + ':not(.md-secondary)'), function(child) {429 proxies.push(child);430 });431 });432 }433 }434 function computeClickable() {435 if (proxies.length == 1 || hasClick) {436 $element.addClass('md-clickable');437 if (!hasClick) {438 ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style')));439 }440 }441 }442 function isEventFromControl(event) {443 var forbiddenControls = ['md-slider'];444 // If there is no path property in the event, then we can assume that the event was not bubbled.445 if (!event.path) {446 return forbiddenControls.indexOf(event.target.tagName.toLowerCase()) !== -1;447 }448 // We iterate the event path up and check for a possible component.449 // Our maximum index to search, is the list item root.450 var maxPath = event.path.indexOf($element.children()[0]);451 for (var i = 0; i < maxPath; i++) {452 if (forbiddenControls.indexOf(event.path[i].tagName.toLowerCase()) !== -1) {453 return true;454 }455 }456 }457 var clickChildKeypressListener = function(e) {458 if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) {459 var keyCode = e.which || e.keyCode;460 if (keyCode == $mdConstant.KEY_CODE.SPACE) {461 if (clickChild) {462 clickChild.click();463 e.preventDefault();464 e.stopPropagation();465 }466 }467 }468 };469 if (!hasClick && !proxies.length) {470 clickChild && clickChild.addEventListener('keypress', clickChildKeypressListener);471 }472 $element.off('click');473 $element.off('keypress');474 if (proxies.length == 1 && clickChild) {475 $element.children().eq(0).on('click', function(e) {476 // When the event is coming from an control and it should not trigger the proxied element477 // then we are skipping.478 if (isEventFromControl(e)) return;479 var parentButton = $mdUtil.getClosest(e.target, 'BUTTON');480 if (!parentButton && clickChild.contains(e.target)) {481 angular.forEach(proxies, function(proxy) {482 if (e.target !== proxy && !proxy.contains(e.target)) {483 if (proxy.nodeName === 'MD-MENU') {484 proxy = proxy.children[0];485 }486 angular.element(proxy).triggerHandler('click');487 }488 });489 }490 });491 }492 $scope.$on('$destroy', function () {493 clickChild && clickChild.removeEventListener('keypress', clickChildKeypressListener);494 });495 }496 }497 };498}499/*500 * @private501 * @ngdoc controller502 * @name MdListController503 * @module material.components.list504 *505 */506function MdListController($scope, $element, $mdListInkRipple) {507 var ctrl = this;508 ctrl.attachRipple = attachRipple;509 function attachRipple (scope, element) {510 var options = {};511 $mdListInkRipple.attach(scope, element, options);512 }513}...
menuBar.js
Source:menuBar.js
1/*!2 * Angular Material Design3 * https://github.com/angular/material4 * @license MIT5 * v1.1.16 */7(function( window, angular, undefined ){8"use strict";9/**10 * @ngdoc module11 * @name material.components.menu-bar12 */13angular.module('material.components.menuBar', [14 'material.core',15 'material.components.icon',16 'material.components.menu'17]);18MenuBarController.$inject = ["$scope", "$rootScope", "$element", "$attrs", "$mdConstant", "$document", "$mdUtil", "$timeout"];19angular20 .module('material.components.menuBar')21 .controller('MenuBarController', MenuBarController);22var BOUND_MENU_METHODS = ['handleKeyDown', 'handleMenuHover', 'scheduleOpenHoveredMenu', 'cancelScheduledOpen'];23/**24 * ngInject25 */26function MenuBarController($scope, $rootScope, $element, $attrs, $mdConstant, $document, $mdUtil, $timeout) {27 this.$element = $element;28 this.$attrs = $attrs;29 this.$mdConstant = $mdConstant;30 this.$mdUtil = $mdUtil;31 this.$document = $document;32 this.$scope = $scope;33 this.$rootScope = $rootScope;34 this.$timeout = $timeout;35 var self = this;36 angular.forEach(BOUND_MENU_METHODS, function(methodName) {37 self[methodName] = angular.bind(self, self[methodName]);38 });39}40MenuBarController.prototype.init = function() {41 var $element = this.$element;42 var $mdUtil = this.$mdUtil;43 var $scope = this.$scope;44 var self = this;45 var deregisterFns = [];46 $element.on('keydown', this.handleKeyDown);47 this.parentToolbar = $mdUtil.getClosest($element, 'MD-TOOLBAR');48 deregisterFns.push(this.$rootScope.$on('$mdMenuOpen', function(event, el) {49 if (self.getMenus().indexOf(el[0]) != -1) {50 $element[0].classList.add('md-open');51 el[0].classList.add('md-open');52 self.currentlyOpenMenu = el.controller('mdMenu');53 self.currentlyOpenMenu.registerContainerProxy(self.handleKeyDown);54 self.enableOpenOnHover();55 }56 }));57 deregisterFns.push(this.$rootScope.$on('$mdMenuClose', function(event, el, opts) {58 var rootMenus = self.getMenus();59 if (rootMenus.indexOf(el[0]) != -1) {60 $element[0].classList.remove('md-open');61 el[0].classList.remove('md-open');62 }63 if ($element[0].contains(el[0])) {64 var parentMenu = el[0];65 while (parentMenu && rootMenus.indexOf(parentMenu) == -1) {66 parentMenu = $mdUtil.getClosest(parentMenu, 'MD-MENU', true);67 }68 if (parentMenu) {69 if (!opts.skipFocus) parentMenu.querySelector('button:not([disabled])').focus();70 self.currentlyOpenMenu = undefined;71 self.disableOpenOnHover();72 self.setKeyboardMode(true);73 }74 }75 }));76 $scope.$on('$destroy', function() {77 self.disableOpenOnHover();78 while (deregisterFns.length) {79 deregisterFns.shift()();80 }81 });82 this.setKeyboardMode(true);83};84MenuBarController.prototype.setKeyboardMode = function(enabled) {85 if (enabled) this.$element[0].classList.add('md-keyboard-mode');86 else this.$element[0].classList.remove('md-keyboard-mode');87};88MenuBarController.prototype.enableOpenOnHover = function() {89 if (this.openOnHoverEnabled) return;90 var self = this;91 self.openOnHoverEnabled = true;92 if (self.parentToolbar) {93 self.parentToolbar.classList.add('md-has-open-menu');94 // Needs to be on the next tick so it doesn't close immediately.95 self.$mdUtil.nextTick(function() {96 angular.element(self.parentToolbar).on('click', self.handleParentClick);97 }, false);98 }99 angular100 .element(self.getMenus())101 .on('mouseenter', self.handleMenuHover);102};103MenuBarController.prototype.handleMenuHover = function(e) {104 this.setKeyboardMode(false);105 if (this.openOnHoverEnabled) {106 this.scheduleOpenHoveredMenu(e);107 }108};109MenuBarController.prototype.disableOpenOnHover = function() {110 if (!this.openOnHoverEnabled) return;111 this.openOnHoverEnabled = false;112 if (this.parentToolbar) {113 this.parentToolbar.classList.remove('md-has-open-menu');114 angular.element(this.parentToolbar).off('click', this.handleParentClick);115 }116 angular117 .element(this.getMenus())118 .off('mouseenter', this.handleMenuHover);119};120MenuBarController.prototype.scheduleOpenHoveredMenu = function(e) {121 var menuEl = angular.element(e.currentTarget);122 var menuCtrl = menuEl.controller('mdMenu');123 this.setKeyboardMode(false);124 this.scheduleOpenMenu(menuCtrl);125};126MenuBarController.prototype.scheduleOpenMenu = function(menuCtrl) {127 var self = this;128 var $timeout = this.$timeout;129 if (menuCtrl != self.currentlyOpenMenu) {130 $timeout.cancel(self.pendingMenuOpen);131 self.pendingMenuOpen = $timeout(function() {132 self.pendingMenuOpen = undefined;133 if (self.currentlyOpenMenu) {134 self.currentlyOpenMenu.close(true, { closeAll: true });135 }136 menuCtrl.open();137 }, 200, false);138 }139};140MenuBarController.prototype.handleKeyDown = function(e) {141 var keyCodes = this.$mdConstant.KEY_CODE;142 var currentMenu = this.currentlyOpenMenu;143 var wasOpen = currentMenu && currentMenu.isOpen;144 this.setKeyboardMode(true);145 var handled, newMenu, newMenuCtrl;146 switch (e.keyCode) {147 case keyCodes.DOWN_ARROW:148 if (currentMenu) {149 currentMenu.focusMenuContainer();150 } else {151 this.openFocusedMenu();152 }153 handled = true;154 break;155 case keyCodes.UP_ARROW:156 currentMenu && currentMenu.close();157 handled = true;158 break;159 case keyCodes.LEFT_ARROW:160 newMenu = this.focusMenu(-1);161 if (wasOpen) {162 newMenuCtrl = angular.element(newMenu).controller('mdMenu');163 this.scheduleOpenMenu(newMenuCtrl);164 }165 handled = true;166 break;167 case keyCodes.RIGHT_ARROW:168 newMenu = this.focusMenu(+1);169 if (wasOpen) {170 newMenuCtrl = angular.element(newMenu).controller('mdMenu');171 this.scheduleOpenMenu(newMenuCtrl);172 }173 handled = true;174 break;175 }176 if (handled) {177 e && e.preventDefault && e.preventDefault();178 e && e.stopImmediatePropagation && e.stopImmediatePropagation();179 }180};181MenuBarController.prototype.focusMenu = function(direction) {182 var menus = this.getMenus();183 var focusedIndex = this.getFocusedMenuIndex();184 if (focusedIndex == -1) { focusedIndex = this.getOpenMenuIndex(); }185 var changed = false;186 if (focusedIndex == -1) { focusedIndex = 0; changed = true; }187 else if (188 direction < 0 && focusedIndex > 0 ||189 direction > 0 && focusedIndex < menus.length - direction190 ) {191 focusedIndex += direction;192 changed = true;193 }194 if (changed) {195 menus[focusedIndex].querySelector('button').focus();196 return menus[focusedIndex];197 }198};199MenuBarController.prototype.openFocusedMenu = function() {200 var menu = this.getFocusedMenu();201 menu && angular.element(menu).controller('mdMenu').open();202};203MenuBarController.prototype.getMenus = function() {204 var $element = this.$element;205 return this.$mdUtil.nodesToArray($element[0].children)206 .filter(function(el) { return el.nodeName == 'MD-MENU'; });207};208MenuBarController.prototype.getFocusedMenu = function() {209 return this.getMenus()[this.getFocusedMenuIndex()];210};211MenuBarController.prototype.getFocusedMenuIndex = function() {212 var $mdUtil = this.$mdUtil;213 var focusedEl = $mdUtil.getClosest(214 this.$document[0].activeElement,215 'MD-MENU'216 );217 if (!focusedEl) return -1;218 var focusedIndex = this.getMenus().indexOf(focusedEl);219 return focusedIndex;220};221MenuBarController.prototype.getOpenMenuIndex = function() {222 var menus = this.getMenus();223 for (var i = 0; i < menus.length; ++i) {224 if (menus[i].classList.contains('md-open')) return i;225 }226 return -1;227};228MenuBarController.prototype.handleParentClick = function(event) {229 var openMenu = this.querySelector('md-menu.md-open');230 if (openMenu && !openMenu.contains(event.target)) {231 angular.element(openMenu).controller('mdMenu').close();232 }233};234/**235 * @ngdoc directive236 * @name mdMenuBar237 * @module material.components.menu-bar238 * @restrict E239 * @description240 *241 * Menu bars are containers that hold multiple menus. They change the behavior and appearence242 * of the `md-menu` directive to behave similar to an operating system provided menu.243 *244 * @usage245 * <hljs lang="html">246 * <md-menu-bar>247 * <md-menu>248 * <button ng-click="$mdOpenMenu()">249 * File250 * </button>251 * <md-menu-content>252 * <md-menu-item>253 * <md-button ng-click="ctrl.sampleAction('share', $event)">254 * Share...255 * </md-button>256 * </md-menu-item>257 * <md-menu-divider></md-menu-divider>258 * <md-menu-item>259 * <md-menu-item>260 * <md-menu>261 * <md-button ng-click="$mdOpenMenu()">New</md-button>262 * <md-menu-content>263 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Document', $event)">Document</md-button></md-menu-item>264 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Spreadsheet', $event)">Spreadsheet</md-button></md-menu-item>265 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Presentation', $event)">Presentation</md-button></md-menu-item>266 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Form', $event)">Form</md-button></md-menu-item>267 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Drawing', $event)">Drawing</md-button></md-menu-item>268 * </md-menu-content>269 * </md-menu>270 * </md-menu-item>271 * </md-menu-content>272 * </md-menu>273 * </md-menu-bar>274 * </hljs>275 *276 * ## Menu Bar Controls277 *278 * You may place `md-menu-items` that function as controls within menu bars.279 * There are two modes that are exposed via the `type` attribute of the `md-menu-item`.280 * `type="checkbox"` will function as a boolean control for the `ng-model` attribute of the281 * `md-menu-item`. `type="radio"` will function like a radio button, setting the `ngModel`282 * to the `string` value of the `value` attribute. If you need non-string values, you can use283 * `ng-value` to provide an expression (this is similar to how angular's native `input[type=radio]` works.284 *285 * <hljs lang="html">286 * <md-menu-bar>287 * <md-menu>288 * <button ng-click="$mdOpenMenu()">289 * Sample Menu290 * </button>291 * <md-menu-content>292 * <md-menu-item type="checkbox" ng-model="settings.allowChanges">Allow changes</md-menu-item>293 * <md-menu-divider></md-menu-divider>294 * <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 1</md-menu-item>295 * <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 2</md-menu-item>296 * <md-menu-item type="radio" ng-model="settings.mode" ng-value="1">Mode 3</md-menu-item>297 * </md-menu-content>298 * </md-menu>299 * </md-menu-bar>300 * </hljs>301 *302 *303 * ### Nesting Menus304 *305 * Menus may be nested within menu bars. This is commonly called cascading menus.306 * To nest a menu place the nested menu inside the content of the `md-menu-item`.307 * <hljs lang="html">308 * <md-menu-item>309 * <md-menu>310 * <button ng-click="$mdOpenMenu()">New</md-button>311 * <md-menu-content>312 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Document', $event)">Document</md-button></md-menu-item>313 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Spreadsheet', $event)">Spreadsheet</md-button></md-menu-item>314 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Presentation', $event)">Presentation</md-button></md-menu-item>315 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Form', $event)">Form</md-button></md-menu-item>316 * <md-menu-item><md-button ng-click="ctrl.sampleAction('New Drawing', $event)">Drawing</md-button></md-menu-item>317 * </md-menu-content>318 * </md-menu>319 * </md-menu-item>320 * </hljs>321 *322 */323MenuBarDirective.$inject = ["$mdUtil", "$mdTheming"];324angular325 .module('material.components.menuBar')326 .directive('mdMenuBar', MenuBarDirective);327/* ngInject */328function MenuBarDirective($mdUtil, $mdTheming) {329 return {330 restrict: 'E',331 require: 'mdMenuBar',332 controller: 'MenuBarController',333 compile: function compile(templateEl, templateAttrs) {334 if (!templateAttrs.ariaRole) {335 templateEl[0].setAttribute('role', 'menubar');336 }337 angular.forEach(templateEl[0].children, function(menuEl) {338 if (menuEl.nodeName == 'MD-MENU') {339 if (!menuEl.hasAttribute('md-position-mode')) {340 menuEl.setAttribute('md-position-mode', 'left bottom');341 // Since we're in the compile function and actual `md-buttons` are not compiled yet,342 // we need to query for possible `md-buttons` as well.343 menuEl.querySelector('button, a, md-button').setAttribute('role', 'menuitem');344 }345 var contentEls = $mdUtil.nodesToArray(menuEl.querySelectorAll('md-menu-content'));346 angular.forEach(contentEls, function(contentEl) {347 contentEl.classList.add('md-menu-bar-menu');348 contentEl.classList.add('md-dense');349 if (!contentEl.hasAttribute('width')) {350 contentEl.setAttribute('width', 5);351 }352 });353 }354 });355 // Mark the child menu items that they're inside a menu bar. This is necessary,356 // because mnMenuItem has special behaviour during compilation, depending on357 // whether it is inside a mdMenuBar. We can usually figure this out via the DOM,358 // however if a directive that uses documentFragment is applied to the child (e.g. ngRepeat),359 // the element won't have a parent and won't compile properly.360 templateEl.find('md-menu-item').addClass('md-in-menu-bar');361 return function postLink(scope, el, attr, ctrl) {362 el.addClass('_md'); // private md component indicator for styling363 $mdTheming(scope, el);364 ctrl.init();365 };366 }367 };368}369angular370 .module('material.components.menuBar')371 .directive('mdMenuDivider', MenuDividerDirective);372function MenuDividerDirective() {373 return {374 restrict: 'E',375 compile: function(templateEl, templateAttrs) {376 if (!templateAttrs.role) {377 templateEl[0].setAttribute('role', 'separator');378 }379 }380 };381}382MenuItemController.$inject = ["$scope", "$element", "$attrs"];383angular384 .module('material.components.menuBar')385 .controller('MenuItemController', MenuItemController);386/**387 * ngInject388 */389function MenuItemController($scope, $element, $attrs) {390 this.$element = $element;391 this.$attrs = $attrs;392 this.$scope = $scope;393}394MenuItemController.prototype.init = function(ngModel) {395 var $element = this.$element;396 var $attrs = this.$attrs;397 this.ngModel = ngModel;398 if ($attrs.type == 'checkbox' || $attrs.type == 'radio') {399 this.mode = $attrs.type;400 this.iconEl = $element[0].children[0];401 this.buttonEl = $element[0].children[1];402 if (ngModel) {403 // Clear ngAria set attributes404 this.initClickListeners();405 }406 }407};408// ngAria auto sets attributes on a menu-item with a ngModel.409// We don't want this because our content (buttons) get the focus410// and set their own aria attributes appropritately. Having both411// breaks NVDA / JAWS. This undeoes ngAria's attrs.412MenuItemController.prototype.clearNgAria = function() {413 var el = this.$element[0];414 var clearAttrs = ['role', 'tabindex', 'aria-invalid', 'aria-checked'];415 angular.forEach(clearAttrs, function(attr) {416 el.removeAttribute(attr);417 });418};419MenuItemController.prototype.initClickListeners = function() {420 var self = this;421 var ngModel = this.ngModel;422 var $scope = this.$scope;423 var $attrs = this.$attrs;424 var $element = this.$element;425 var mode = this.mode;426 this.handleClick = angular.bind(this, this.handleClick);427 var icon = this.iconEl;428 var button = angular.element(this.buttonEl);429 var handleClick = this.handleClick;430 $attrs.$observe('disabled', setDisabled);431 setDisabled($attrs.disabled);432 ngModel.$render = function render() {433 self.clearNgAria();434 if (isSelected()) {435 icon.style.display = '';436 button.attr('aria-checked', 'true');437 } else {438 icon.style.display = 'none';439 button.attr('aria-checked', 'false');440 }441 };442 $scope.$$postDigest(ngModel.$render);443 function isSelected() {444 if (mode == 'radio') {445 var val = $attrs.ngValue ? $scope.$eval($attrs.ngValue) : $attrs.value;446 return ngModel.$modelValue == val;447 } else {448 return ngModel.$modelValue;449 }450 }451 function setDisabled(disabled) {452 if (disabled) {453 button.off('click', handleClick);454 } else {455 button.on('click', handleClick);456 }457 }458};459MenuItemController.prototype.handleClick = function(e) {460 var mode = this.mode;461 var ngModel = this.ngModel;462 var $attrs = this.$attrs;463 var newVal;464 if (mode == 'checkbox') {465 newVal = !ngModel.$modelValue;466 } else if (mode == 'radio') {467 newVal = $attrs.ngValue ? this.$scope.$eval($attrs.ngValue) : $attrs.value;468 }469 ngModel.$setViewValue(newVal);470 ngModel.$render();471};472MenuItemDirective.$inject = ["$mdUtil", "$$mdSvgRegistry"];473angular474 .module('material.components.menuBar')475 .directive('mdMenuItem', MenuItemDirective);476 /* ngInject */477function MenuItemDirective($mdUtil, $$mdSvgRegistry) {478 return {479 controller: 'MenuItemController',480 require: ['mdMenuItem', '?ngModel'],481 priority: 210, // ensure that our post link runs after ngAria482 compile: function(templateEl, templateAttrs) {483 var type = templateAttrs.type;484 var inMenuBarClass = 'md-in-menu-bar';485 // Note: This allows us to show the `check` icon for the md-menu-bar items.486 // The `md-in-menu-bar` class is set by the mdMenuBar directive.487 if ((type == 'checkbox' || type == 'radio') && templateEl.hasClass(inMenuBarClass)) {488 var text = templateEl[0].textContent;489 var buttonEl = angular.element('<md-button type="button"></md-button>');490 var iconTemplate = '<md-icon md-svg-src="' + $$mdSvgRegistry.mdChecked + '"></md-icon>';491 buttonEl.html(text);492 buttonEl.attr('tabindex', '0');493 templateEl.html('');494 templateEl.append(angular.element(iconTemplate));495 templateEl.append(buttonEl);496 templateEl.addClass('md-indent').removeClass(inMenuBarClass);497 setDefault('role', type == 'checkbox' ? 'menuitemcheckbox' : 'menuitemradio', buttonEl);498 moveAttrToButton('ng-disabled');499 } else {500 setDefault('role', 'menuitem', templateEl[0].querySelector('md-button, button, a'));501 }502 return function(scope, el, attrs, ctrls) {503 var ctrl = ctrls[0];504 var ngModel = ctrls[1];505 ctrl.init(ngModel);506 };507 function setDefault(attr, val, el) {508 el = el || templateEl;509 if (el instanceof angular.element) {510 el = el[0];511 }512 if (!el.hasAttribute(attr)) {513 el.setAttribute(attr, val);514 }515 }516 function moveAttrToButton(attribute) {517 var attributes = $mdUtil.prefixer(attribute);518 angular.forEach(attributes, function(attr) {519 if (templateEl[0].hasAttribute(attr)) {520 var val = templateEl[0].getAttribute(attr);521 buttonEl[0].setAttribute(attr, val);522 templateEl[0].removeAttribute(attr);523 }524 });525 }526 }527 };528}...
navBar.js
Source:navBar.js
1/*!2 * Angular Material Design3 * https://github.com/angular/material4 * @license MIT5 * v1.1.16 */7(function( window, angular, undefined ){8"use strict";9/**10 * @ngdoc module11 * @name material.components.navBar12 */13MdNavBarController.$inject = ["$element", "$scope", "$timeout", "$mdConstant"];14MdNavItem.$inject = ["$$rAF"];15MdNavItemController.$inject = ["$element"];16MdNavBar.$inject = ["$mdAria", "$mdTheming"];17angular.module('material.components.navBar', ['material.core'])18 .controller('MdNavBarController', MdNavBarController)19 .directive('mdNavBar', MdNavBar)20 .controller('MdNavItemController', MdNavItemController)21 .directive('mdNavItem', MdNavItem);22/*****************************************************************************23 * PUBLIC DOCUMENTATION *24 *****************************************************************************/25/**26 * @ngdoc directive27 * @name mdNavBar28 * @module material.components.navBar29 *30 * @restrict E31 *32 * @description33 * The `<md-nav-bar>` directive renders a list of material tabs that can be used34 * for top-level page navigation. Unlike `<md-tabs>`, it has no concept of a tab35 * body and no bar pagination.36 *37 * Because it deals with page navigation, certain routing concepts are built-in.38 * Route changes via via ng-href, ui-sref, or ng-click events are supported.39 * Alternatively, the user could simply watch currentNavItem for changes.40 *41 * Accessibility functionality is implemented as a site navigator with a42 * listbox, according to43 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style44 *45 * @param {string=} mdSelectedNavItem The name of the current tab; this must46 * match the name attribute of `<md-nav-item>`47 * @param {string=} navBarAriaLabel An aria-label for the nav-bar48 *49 * @usage50 * <hljs lang="html">51 * <md-nav-bar md-selected-nav-item="currentNavItem">52 * <md-nav-item md-nav-click="goto('page1')" name="page1">Page One</md-nav-item>53 * <md-nav-item md-nav-sref="app.page2" name="page2">Page Two</md-nav-item>54 * <md-nav-item md-nav-href="#page3" name="page3">Page Three</md-nav-item>55 * </md-nav-bar>56 *</hljs>57 * <hljs lang="js">58 * (function() {59 * âuse strictâ;60 *61 * $rootScope.$on('$routeChangeSuccess', function(event, current) {62 * $scope.currentLink = getCurrentLinkFromRoute(current);63 * });64 * });65 * </hljs>66 */67/*****************************************************************************68 * mdNavItem69 *****************************************************************************/70/**71 * @ngdoc directive72 * @name mdNavItem73 * @module material.components.navBar74 *75 * @restrict E76 *77 * @description78 * `<md-nav-item>` describes a page navigation link within the `<md-nav-bar>`79 * component. It renders an md-button as the actual link.80 *81 * Exactly one of the mdNavClick, mdNavHref, mdNavSref attributes are required to be82 * specified.83 *84 * @param {Function=} mdNavClick Function which will be called when the85 * link is clicked to change the page. Renders as an `ng-click`.86 * @param {string=} mdNavHref url to transition to when this link is clicked.87 * Renders as an `ng-href`.88 * @param {string=} mdNavSref Ui-router state to transition to when this link is89 * clicked. Renders as a `ui-sref`.90 * @param {string=} name The name of this link. Used by the nav bar to know91 * which link is currently selected.92 *93 * @usage94 * See `<md-nav-bar>` for usage.95 */96/*****************************************************************************97 * IMPLEMENTATION *98 *****************************************************************************/99function MdNavBar($mdAria, $mdTheming) {100 return {101 restrict: 'E',102 transclude: true,103 controller: MdNavBarController,104 controllerAs: 'ctrl',105 bindToController: true,106 scope: {107 'mdSelectedNavItem': '=?',108 'navBarAriaLabel': '@?',109 },110 template:111 '<div class="md-nav-bar">' +112 '<nav role="navigation">' +113 '<ul class="_md-nav-bar-list" ng-transclude role="listbox"' +114 'tabindex="0"' +115 'ng-focus="ctrl.onFocus()"' +116 'ng-blur="ctrl.onBlur()"' +117 'ng-keydown="ctrl.onKeydown($event)"' +118 'aria-label="{{ctrl.navBarAriaLabel}}">' +119 '</ul>' +120 '</nav>' +121 '<md-nav-ink-bar></md-nav-ink-bar>' +122 '</div>',123 link: function(scope, element, attrs, ctrl) {124 $mdTheming(element);125 if (!ctrl.navBarAriaLabel) {126 $mdAria.expectAsync(element, 'aria-label', angular.noop);127 }128 },129 };130}131/**132 * Controller for the nav-bar component.133 *134 * Accessibility functionality is implemented as a site navigator with a135 * listbox, according to136 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style137 * @param {!angular.JQLite} $element138 * @param {!angular.Scope} $scope139 * @param {!angular.Timeout} $timeout140 * @param {!Object} $mdConstant141 * @constructor142 * @final143 * ngInject144 */145function MdNavBarController($element, $scope, $timeout, $mdConstant) {146 // Injected variables147 /** @private @const {!angular.Timeout} */148 this._$timeout = $timeout;149 /** @private @const {!angular.Scope} */150 this._$scope = $scope;151 /** @private @const {!Object} */152 this._$mdConstant = $mdConstant;153 // Data-bound variables.154 /** @type {string} */155 this.mdSelectedNavItem;156 /** @type {string} */157 this.navBarAriaLabel;158 // State variables.159 /** @type {?angular.JQLite} */160 this._navBarEl = $element[0];161 /** @type {?angular.JQLite} */162 this._inkbar;163 var self = this;164 // need to wait for transcluded content to be available165 var deregisterTabWatch = this._$scope.$watch(function() {166 return self._navBarEl.querySelectorAll('._md-nav-button').length;167 },168 function(newLength) {169 if (newLength > 0) {170 self._initTabs();171 deregisterTabWatch();172 }173 });174}175/**176 * Initializes the tab components once they exist.177 * @private178 */179MdNavBarController.prototype._initTabs = function() {180 this._inkbar = angular.element(this._navBarEl.getElementsByTagName('md-nav-ink-bar')[0]);181 var self = this;182 this._$timeout(function() {183 self._updateTabs(self.mdSelectedNavItem, undefined);184 });185 this._$scope.$watch('ctrl.mdSelectedNavItem', function(newValue, oldValue) {186 // Wait a digest before update tabs for products doing187 // anything dynamic in the template.188 self._$timeout(function() {189 self._updateTabs(newValue, oldValue);190 });191 });192};193/**194 * Set the current tab to be selected.195 * @param {string|undefined} newValue New current tab name.196 * @param {string|undefined} oldValue Previous tab name.197 * @private198 */199MdNavBarController.prototype._updateTabs = function(newValue, oldValue) {200 var self = this;201 var tabs = this._getTabs();202 var oldIndex = -1;203 var newIndex = -1;204 var newTab = this._getTabByName(newValue);205 var oldTab = this._getTabByName(oldValue);206 if (oldTab) {207 oldTab.setSelected(false);208 oldIndex = tabs.indexOf(oldTab);209 }210 if (newTab) {211 newTab.setSelected(true);212 newIndex = tabs.indexOf(newTab);213 }214 this._$timeout(function() {215 self._updateInkBarStyles(newTab, newIndex, oldIndex);216 });217};218/**219 * Repositions the ink bar to the selected tab.220 * @private221 */222MdNavBarController.prototype._updateInkBarStyles = function(tab, newIndex, oldIndex) {223 this._inkbar.toggleClass('_md-left', newIndex < oldIndex)224 .toggleClass('_md-right', newIndex > oldIndex);225 this._inkbar.css({display: newIndex < 0 ? 'none' : ''});226 if(tab){227 var tabEl = tab.getButtonEl();228 var left = tabEl.offsetLeft;229 this._inkbar.css({left: left + 'px', width: tabEl.offsetWidth + 'px'});230 }231};232/**233 * Returns an array of the current tabs.234 * @return {!Array<!NavItemController>}235 * @private236 */237MdNavBarController.prototype._getTabs = function() {238 var linkArray = Array.prototype.slice.call(239 this._navBarEl.querySelectorAll('.md-nav-item'));240 return linkArray.map(function(el) {241 return angular.element(el).controller('mdNavItem')242 });243};244/**245 * Returns the tab with the specified name.246 * @param {string} name The name of the tab, found in its name attribute.247 * @return {!NavItemController|undefined}248 * @private249 */250MdNavBarController.prototype._getTabByName = function(name) {251 return this._findTab(function(tab) {252 return tab.getName() == name;253 });254};255/**256 * Returns the selected tab.257 * @return {!NavItemController|undefined}258 * @private259 */260MdNavBarController.prototype._getSelectedTab = function() {261 return this._findTab(function(tab) {262 return tab.isSelected()263 });264};265/**266 * Returns the focused tab.267 * @return {!NavItemController|undefined}268 */269MdNavBarController.prototype.getFocusedTab = function() {270 return this._findTab(function(tab) {271 return tab.hasFocus()272 });273};274/**275 * Find a tab that matches the specified function.276 * @private277 */278MdNavBarController.prototype._findTab = function(fn) {279 var tabs = this._getTabs();280 for (var i = 0; i < tabs.length; i++) {281 if (fn(tabs[i])) {282 return tabs[i];283 }284 }285 return null;286};287/**288 * Direct focus to the selected tab when focus enters the nav bar.289 */290MdNavBarController.prototype.onFocus = function() {291 var tab = this._getSelectedTab();292 if (tab) {293 tab.setFocused(true);294 }295};296/**297 * Clear tab focus when focus leaves the nav bar.298 */299MdNavBarController.prototype.onBlur = function() {300 var tab = this.getFocusedTab();301 if (tab) {302 tab.setFocused(false);303 }304};305/**306 * Move focus from oldTab to newTab.307 * @param {!NavItemController} oldTab308 * @param {!NavItemController} newTab309 * @private310 */311MdNavBarController.prototype._moveFocus = function(oldTab, newTab) {312 oldTab.setFocused(false);313 newTab.setFocused(true);314};315/**316 * Responds to keypress events.317 * @param {!Event} e318 */319MdNavBarController.prototype.onKeydown = function(e) {320 var keyCodes = this._$mdConstant.KEY_CODE;321 var tabs = this._getTabs();322 var focusedTab = this.getFocusedTab();323 if (!focusedTab) return;324 var focusedTabIndex = tabs.indexOf(focusedTab);325 // use arrow keys to navigate between tabs326 switch (e.keyCode) {327 case keyCodes.UP_ARROW:328 case keyCodes.LEFT_ARROW:329 if (focusedTabIndex > 0) {330 this._moveFocus(focusedTab, tabs[focusedTabIndex - 1]);331 }332 break;333 case keyCodes.DOWN_ARROW:334 case keyCodes.RIGHT_ARROW:335 if (focusedTabIndex < tabs.length - 1) {336 this._moveFocus(focusedTab, tabs[focusedTabIndex + 1]);337 }338 break;339 case keyCodes.SPACE:340 case keyCodes.ENTER:341 // timeout to avoid a "digest already in progress" console error342 this._$timeout(function() {343 focusedTab.getButtonEl().click();344 });345 break;346 }347};348/**349 * ngInject350 */351function MdNavItem($$rAF) {352 return {353 restrict: 'E',354 require: ['mdNavItem', '^mdNavBar'],355 controller: MdNavItemController,356 bindToController: true,357 controllerAs: 'ctrl',358 replace: true,359 transclude: true,360 template:361 '<li class="md-nav-item" role="option" aria-selected="{{ctrl.isSelected()}}">' +362 '<md-button ng-if="ctrl.mdNavSref" class="_md-nav-button md-accent"' +363 'ng-class="ctrl.getNgClassMap()"' +364 'tabindex="-1"' +365 'ui-sref="{{ctrl.mdNavSref}}">' +366 '<span ng-transclude class="_md-nav-button-text"></span>' +367 '</md-button>' +368 '<md-button ng-if="ctrl.mdNavHref" class="_md-nav-button md-accent"' +369 'ng-class="ctrl.getNgClassMap()"' +370 'tabindex="-1"' +371 'ng-href="{{ctrl.mdNavHref}}">' +372 '<span ng-transclude class="_md-nav-button-text"></span>' +373 '</md-button>' +374 '<md-button ng-if="ctrl.mdNavClick" class="_md-nav-button md-accent"' +375 'ng-class="ctrl.getNgClassMap()"' +376 'tabindex="-1"' +377 'ng-click="ctrl.mdNavClick()">' +378 '<span ng-transclude class="_md-nav-button-text"></span>' +379 '</md-button>' +380 '</li>',381 scope: {382 'mdNavClick': '&?',383 'mdNavHref': '@?',384 'mdNavSref': '@?',385 'name': '@',386 },387 link: function(scope, element, attrs, controllers) {388 var mdNavItem = controllers[0];389 var mdNavBar = controllers[1];390 // When accessing the element's contents synchronously, they391 // may not be defined yet because of transclusion. There is a higher chance392 // that it will be accessible if we wait one frame.393 $$rAF(function() {394 if (!mdNavItem.name) {395 mdNavItem.name = angular.element(element[0].querySelector('._md-nav-button-text'))396 .text().trim();397 }398 var navButton = angular.element(element[0].querySelector('._md-nav-button'));399 navButton.on('click', function() {400 mdNavBar.mdSelectedNavItem = mdNavItem.name;401 scope.$apply();402 });403 });404 }405 };406}407/**408 * Controller for the nav-item component.409 * @param {!angular.JQLite} $element410 * @constructor411 * @final412 * ngInject413 */414function MdNavItemController($element) {415 /** @private @const {!angular.JQLite} */416 this._$element = $element;417 // Data-bound variables418 /** @const {?Function} */419 this.mdNavClick;420 /** @const {?string} */421 this.mdNavHref;422 /** @const {?string} */423 this.name;424 // State variables425 /** @private {boolean} */426 this._selected = false;427 /** @private {boolean} */428 this._focused = false;429 var hasNavClick = !!($element.attr('md-nav-click'));430 var hasNavHref = !!($element.attr('md-nav-href'));431 var hasNavSref = !!($element.attr('md-nav-sref'));432 // Cannot specify more than one nav attribute433 if ((hasNavClick ? 1:0) + (hasNavHref ? 1:0) + (hasNavSref ? 1:0) > 1) {434 throw Error(435 'Must specify exactly one of md-nav-click, md-nav-href, ' +436 'md-nav-sref for nav-item directive');437 }438}439/**440 * Returns a map of class names and values for use by ng-class.441 * @return {!Object<string,boolean>}442 */443MdNavItemController.prototype.getNgClassMap = function() {444 return {445 'md-active': this._selected,446 'md-primary': this._selected,447 'md-unselected': !this._selected,448 'md-focused': this._focused,449 };450};451/**452 * Get the name attribute of the tab.453 * @return {string}454 */455MdNavItemController.prototype.getName = function() {456 return this.name;457};458/**459 * Get the button element associated with the tab.460 * @return {!Element}461 */462MdNavItemController.prototype.getButtonEl = function() {463 return this._$element[0].querySelector('._md-nav-button');464};465/**466 * Set the selected state of the tab.467 * @param {boolean} isSelected468 */469MdNavItemController.prototype.setSelected = function(isSelected) {470 this._selected = isSelected;471};472/**473 * @return {boolean}474 */475MdNavItemController.prototype.isSelected = function() {476 return this._selected;477};478/**479 * Set the focused state of the tab.480 * @param {boolean} isFocused481 */482MdNavItemController.prototype.setFocused = function(isFocused) {483 this._focused = isFocused;484};485/**486 * @return {boolean}487 */488MdNavItemController.prototype.hasFocus = function() {489 return this._focused;490};...
button.js
Source:button.js
1/*!2 * Angular Material Design3 * https://github.com/angular/material4 * @license MIT5 * v1.1.16 */7goog.provide('ngmaterial.components.button');8goog.require('ngmaterial.core');9/**10 * @ngdoc module11 * @name material.components.button12 * @description13 *14 * Button15 */16MdButtonDirective.$inject = ["$mdButtonInkRipple", "$mdTheming", "$mdAria", "$timeout"];17MdAnchorDirective.$inject = ["$mdTheming"];18angular19 .module('material.components.button', [ 'material.core' ])20 .directive('mdButton', MdButtonDirective)21 .directive('a', MdAnchorDirective);22/**23 * @private24 * @restrict E25 *26 * @description27 * `a` is an anchor directive used to inherit theme colors for md-primary, md-accent, etc.28 *29 * @usage30 *31 * <hljs lang="html">32 * <md-content md-theme="myTheme">33 * <a href="#chapter1" class="md-accent"></a>34 * </md-content>35 * </hljs>36 */37function MdAnchorDirective($mdTheming) {38 return {39 restrict : 'E',40 link : function postLink(scope, element) {41 // Make sure to inherit theme so stand-alone anchors42 // support theme colors for md-primary, md-accent, etc.43 $mdTheming(element);44 }45 };46}47/**48 * @ngdoc directive49 * @name mdButton50 * @module material.components.button51 *52 * @restrict E53 *54 * @description55 * `<md-button>` is a button directive with optional ink ripples (default enabled).56 *57 * If you supply a `href` or `ng-href` attribute, it will become an `<a>` element. Otherwise, it58 * will become a `<button>` element. As per the59 * [Material Design specifications](https://material.google.com/style/color.html#color-color-palette)60 * the FAB button background is filled with the accent color [by default]. The primary color palette61 * may be used with the `md-primary` class.62 *63 * Developers can also change the color palette of the button, by using the following classes64 * - `md-primary`65 * - `md-accent`66 * - `md-warn`67 *68 * See for example69 *70 * <hljs lang="html">71 * <md-button class="md-primary">Primary Button</md-button>72 * </hljs>73 *74 * Button can be also raised, which means that they will use the current color palette to fill the button.75 *76 * <hljs lang="html">77 * <md-button class="md-accent md-raised">Raised and Accent Button</md-button>78 * </hljs>79 *80 * It is also possible to disable the focus effect on the button, by using the following markup.81 *82 * <hljs lang="html">83 * <md-button class="md-no-focus">No Focus Style</md-button>84 * </hljs>85 *86 * @param {boolean=} md-no-ink If present, disable ripple ink effects.87 * @param {expression=} ng-disabled En/Disable based on the expression88 * @param {string=} md-ripple-size Overrides the default ripple size logic. Options: `full`, `partial`, `auto`89 * @param {string=} aria-label Adds alternative text to button for accessibility, useful for icon buttons.90 * If no default text is found, a warning will be logged.91 *92 * @usage93 *94 * Regular buttons:95 *96 * <hljs lang="html">97 * <md-button> Flat Button </md-button>98 * <md-button href="http://google.com"> Flat link </md-button>99 * <md-button class="md-raised"> Raised Button </md-button>100 * <md-button ng-disabled="true"> Disabled Button </md-button>101 * <md-button>102 * <md-icon md-svg-src="your/icon.svg"></md-icon>103 * Register Now104 * </md-button>105 * </hljs>106 *107 * FAB buttons:108 *109 * <hljs lang="html">110 * <md-button class="md-fab" aria-label="FAB">111 * <md-icon md-svg-src="your/icon.svg"></md-icon>112 * </md-button>113 * <!-- mini-FAB -->114 * <md-button class="md-fab md-mini" aria-label="Mini FAB">115 * <md-icon md-svg-src="your/icon.svg"></md-icon>116 * </md-button>117 * <!-- Button with SVG Icon -->118 * <md-button class="md-icon-button" aria-label="Custom Icon Button">119 * <md-icon md-svg-icon="path/to/your.svg"></md-icon>120 * </md-button>121 * </hljs>122 */123function MdButtonDirective($mdButtonInkRipple, $mdTheming, $mdAria, $timeout) {124 return {125 restrict: 'EA',126 replace: true,127 transclude: true,128 template: getTemplate,129 link: postLink130 };131 function isAnchor(attr) {132 return angular.isDefined(attr.href) || angular.isDefined(attr.ngHref) || angular.isDefined(attr.ngLink) || angular.isDefined(attr.uiSref);133 }134 function getTemplate(element, attr) {135 if (isAnchor(attr)) {136 return '<a class="md-button" ng-transclude></a>';137 } else {138 //If buttons don't have type="button", they will submit forms automatically.139 var btnType = (typeof attr.type === 'undefined') ? 'button' : attr.type;140 return '<button class="md-button" type="' + btnType + '" ng-transclude></button>';141 }142 }143 function postLink(scope, element, attr) {144 $mdTheming(element);145 $mdButtonInkRipple.attach(scope, element);146 // Use async expect to support possible bindings in the button label147 $mdAria.expectWithoutText(element, 'aria-label');148 // For anchor elements, we have to set tabindex manually when the149 // element is disabled150 if (isAnchor(attr) && angular.isDefined(attr.ngDisabled) ) {151 scope.$watch(attr.ngDisabled, function(isDisabled) {152 element.attr('tabindex', isDisabled ? -1 : 0);153 });154 }155 // disabling click event when disabled is true156 element.on('click', function(e){157 if (attr.disabled === true) {158 e.preventDefault();159 e.stopImmediatePropagation();160 }161 });162 if (!element.hasClass('md-no-focus')) {163 // restrict focus styles to the keyboard164 scope.mouseActive = false;165 element.on('mousedown', function() {166 scope.mouseActive = true;167 $timeout(function(){168 scope.mouseActive = false;169 }, 100);170 })171 .on('focus', function() {172 if (scope.mouseActive === false) {173 element.addClass('md-focused');174 }175 })176 .on('blur', function(ev) {177 element.removeClass('md-focused');178 });179 }180 }181}...
card.js
Source:card.js
1/*!2 * Angular Material Design3 * https://github.com/angular/material4 * @license MIT5 * v1.1.16 */7goog.provide('ngmaterial.components.card');8goog.require('ngmaterial.core');9/**10 * @ngdoc module11 * @name material.components.card12 *13 * @description14 * Card components.15 */16mdCardDirective.$inject = ["$mdTheming"];17angular.module('material.components.card', [18 'material.core'19 ])20 .directive('mdCard', mdCardDirective);21/**22 * @ngdoc directive23 * @name mdCard24 * @module material.components.card25 *26 * @restrict E27 *28 * @description29 * The `<md-card>` directive is a container element used within `<md-content>` containers.30 *31 * An image included as a direct descendant will fill the card's width. If you want to avoid this,32 * you can add the `md-image-no-fill` class to the parent element. The `<md-card-content>`33 * container will wrap text content and provide padding. An `<md-card-footer>` element can be34 * optionally included to put content flush against the bottom edge of the card.35 *36 * Action buttons can be included in an `<md-card-actions>` element, similar to `<md-dialog-actions>`.37 * You can then position buttons using layout attributes.38 *39 * Card is built with:40 * * `<md-card-header>` - Header for the card, holds avatar, text and squared image41 * - `<md-card-avatar>` - Card avatar42 * - `md-user-avatar` - Class for user image43 * - `<md-icon>`44 * - `<md-card-header-text>` - Contains elements for the card description45 * - `md-title` - Class for the card title46 * - `md-subhead` - Class for the card sub header47 * * `<img>` - Image for the card48 * * `<md-card-title>` - Card content title49 * - `<md-card-title-text>`50 * - `md-headline` - Class for the card content title51 * - `md-subhead` - Class for the card content sub header52 * - `<md-card-title-media>` - Squared image within the title53 * - `md-media-sm` - Class for small image54 * - `md-media-md` - Class for medium image55 * - `md-media-lg` - Class for large image56 * * `<md-card-content>` - Card content57 * - `md-media-xl` - Class for extra large image58 * * `<md-card-actions>` - Card actions59 * - `<md-card-icon-actions>` - Icon actions60 *61 * Cards have constant width and variable heights; where the maximum height is limited to what can62 * fit within a single view on a platform, but it can temporarily expand as needed.63 *64 * @usage65 * ### Card with optional footer66 * <hljs lang="html">67 * <md-card>68 * <img src="card-image.png" class="md-card-image" alt="image caption">69 * <md-card-content>70 * <h2>Card headline</h2>71 * <p>Card content</p>72 * </md-card-content>73 * <md-card-footer>74 * Card footer75 * </md-card-footer>76 * </md-card>77 * </hljs>78 *79 * ### Card with actions80 * <hljs lang="html">81 * <md-card>82 * <img src="card-image.png" class="md-card-image" alt="image caption">83 * <md-card-content>84 * <h2>Card headline</h2>85 * <p>Card content</p>86 * </md-card-content>87 * <md-card-actions layout="row" layout-align="end center">88 * <md-button>Action 1</md-button>89 * <md-button>Action 2</md-button>90 * </md-card-actions>91 * </md-card>92 * </hljs>93 *94 * ### Card with header, image, title actions and content95 * <hljs lang="html">96 * <md-card>97 * <md-card-header>98 * <md-card-avatar>99 * <img class="md-user-avatar" src="avatar.png"/>100 * </md-card-avatar>101 * <md-card-header-text>102 * <span class="md-title">Title</span>103 * <span class="md-subhead">Sub header</span>104 * </md-card-header-text>105 * </md-card-header>106 * <img ng-src="card-image.png" class="md-card-image" alt="image caption">107 * <md-card-title>108 * <md-card-title-text>109 * <span class="md-headline">Card headline</span>110 * <span class="md-subhead">Card subheader</span>111 * </md-card-title-text>112 * </md-card-title>113 * <md-card-actions layout="row" layout-align="start center">114 * <md-button>Action 1</md-button>115 * <md-button>Action 2</md-button>116 * <md-card-icon-actions>117 * <md-button class="md-icon-button" aria-label="icon">118 * <md-icon md-svg-icon="icon"></md-icon>119 * </md-button>120 * </md-card-icon-actions>121 * </md-card-actions>122 * <md-card-content>123 * <p>124 * Card content125 * </p>126 * </md-card-content>127 * </md-card>128 * </hljs>129 */130function mdCardDirective($mdTheming) {131 return {132 restrict: 'E',133 link: function ($scope, $element, attr) {134 $element.addClass('_md'); // private md component indicator for styling135 $mdTheming($element);136 }137 };138}...
markDown.js
Source:markDown.js
1function parseMd(md){2 //ul3 md = md.replace(/^\s*\n\*/gm, '<ul>\n*');4 md = md.replace(/^(\*.+)\s*\n([^\*])/gm, '$1\n</ul>\n\n$2');5 md = md.replace(/^\*(.+)/gm, '<li>$1</li>');6 //ol7 md = md.replace(/^\s*\n\d\./gm, '<ol>\n1.');8 md = md.replace(/^(\d\..+)\s*\n([^\d\.])/gm, '$1\n</ol>\n\n$2');9 md = md.replace(/^\d\.(.+)/gm, '<li>$1</li>');10 //blockquote11 md = md.replace(/^\>(.+)/gm, '<blockquote>$1</blockquote>');12 //h13 md = md.replace(/[\#]{6}(.+)/g, '<h6>$1</h6>');14 md = md.replace(/[\#]{5}(.+)/g, '<h5>$1</h5>');15 md = md.replace(/[\#]{4}(.+)/g, '<h4>$1</h4>');16 md = md.replace(/[\#]{3}(.+)/g, '<h3>$1</h3>');17 md = md.replace(/[\#]{2}(.+)/g, '<h2>$1</h2>');18 md = md.replace(/[\#]{1}(.+)/g, '<h1>$1</h1>');19 //alt h20 md = md.replace(/^(.+)\n\=+/gm, '<h1>$1</h1>');21 md = md.replace(/^(.+)\n\-+/gm, '<h2>$1</h2>');22 //images23 md = md.replace(/\!\[([^\]]+)\]\(([^\)]+)\)/g, '<img src="$2" alt="$1" />');24 //links25 md = md.replace(/[\[]{1}([^\]]+)[\]]{1}[\(]{1}([^\)\"]+)(\"(.+)\")?[\)]{1}/g, '<a href="$2" title="$4">$1</a>');26 //font styles27 md = md.replace(/[\*\_]{2}([^\*\_]+)[\*\_]{2}/g, '<b>$1</b>');28 md = md.replace(/[\*\_]{1}([^\*\_]+)[\*\_]{1}/g, '<i>$1</i>');29 md = md.replace(/[\~]{2}([^\~]+)[\~]{2}/g, '<del>$1</del>');30 //pre31 md = md.replace(/^\s*\n\`\`\`(([^\s]+))?/gm, '<pre class="$2">');32 md = md.replace(/^\`\`\`\s*\n/gm, '</pre>\n\n');33 //code34 md = md.replace(/[\`]{1}([^\`]+)[\`]{1}/g, '<code>$1</code>');35 //p36 md = md.replace(/^\s*(\n)?(.+)/gm, function(m){37 return /\<(\/)?(h\d|ul|ol|li|blockquote|pre|img)/.test(m) ? m : '<p>'+m+'</p>';38 });39 //strip p from pre40 // md = md.replace(/(\<pre.+\>)\s*\n\<p\>(.+)\<\/p\>/gm, '$1$2');41 return md;42}43function openModal(filepath) {44 var modal = document.getElementById("modal");45 modal.style.display = "block";46 var input = document.getElementById("input");47 $.get(filepath, function(data) {48 html = parseMd(data);49 input.innerHTML = html;50 });51}52function closeModal() {53 var modal = document.getElementById("modal");54 modal.style.display = "none";55 var input = document.getElementById("input");56 input.innerHTML = "";57}58window.onclick = function(event) {59 if (event.target == modal) {60 closeModal();61 }...
Using AI Code Generation
1var BestBuyAPI = require('bestbuy')(apiKey);2var search = BestBuyAPI.products('(search=ipod)', {show: 'sku,name,salePrice', page: 2, pageSize: 10, sort: 'sku.asc'});3search.then(function(data){4 console.log(data);5});6var BestBuyAPI = require('bestbuy')(apiKey);7var search = BestBuyAPI.products('(search=ipod)', {show: 'sku,name,salePrice', page: 2, pageSize: 10, sort: 'sku.asc'});8search.then(function(data){9 console.log(data);10});
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!