Best JavaScript code snippet using playwright-internal
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
1const { md } = require("@playwright/test");2const { md } = require("@playwright/test");3const { md } = require("@playwright/test");4const { md } = require("@playwright/test");5const { md } = require("@playwright/test");6const { md } = require("@playwright/test");7const { md } = require("@playwright/test");8const { md } = require("@playwright/test");9const { md } = require("@playwright/test");10const { md } = require("@playwright/test");11const { md } = require("@playwright/test");12const { md } = require("@playwright/test");13const { md } = require("@playwright/test");14const { md } = require("@playwright/test");15const { md } = require("@playwright/test");16const { md } = require("@playwright/test");17const { md } = require("@playwright/test");18const { md } = require("@playwright/test");19const { md } = require("@playwright/test");20const { md } = require("@playwright/test");21const { md } = require("@
Using AI Code Generation
1const { md } = require("@playwright/test");2const { test, expect } = require("@playwright/test");3test("should be able to use md method", async ({ page }) => {4 await page.waitForSelector("text=Get started");5 const titleElement = await page.$("text=Get started");6 const title = await titleElement.textContent();7 expect(title).toBe("Get started");8 await page.screenshot({ path: `screenshots/${md(title)}.png` });9});
Using AI Code Generation
1const { md } = require('@playwright/test')2const { md } = require('@playwright/test')3const { md } = require('@playwright/test')4const { md } = require('@playwright/test')5const { md } = require('@playwright/test')6const { md } = require('@playwright/test')7const { md } = require('@playwright/test')8const { md } = require('@playwright/test')9const { md } = require('@playwright/test')10const { md } = require('@playwright/test')11const { md } = require('@playwright/test')12const { md } = require('@playwright/test')13const { md } = require('@playwright/test')14const { md } = require('@playwright/test')15const { md } = require('@playwright/test')16const { md } = require('@playwright/test')17const { md } = require('@playwright/test')18const { md } = require('@playwright/test')19const { md } = require('@playwright/test')20const { md } = require('@playwright/test')
Using AI Code Generation
1const { md } = require('@playwright/test');2console.log(md`# Hello World!`);3const { md } = require('@playwright/test');4test('example test', async ({ page }) => {5 const title = page.locator('text=Get started');6 console.log(md`# Hello World!`);7 expect(title).toBeVisible();8});
Using AI Code Generation
1const { md } = require('@playwright/test');2md`## My markdown header`;3md(`## My markdown header`);4md(['## My markdown header']);5md(['## My markdown header'], 'My markdown header');6md(['## My markdown header'], { header: 'My markdown header' });7md(['## My markdown header'], { header: 'My markdown header', timeout: 5000 });8md(['## My markdown header'], { header: 'My markdown header', timeout: 5000, timeoutMessage: 'My custom timeout message' });9md(['## My markdown header'], { header: 'My markdown header', timeout: 5000, timeoutMessage: 'My custom timeout message', waitFor: 'visible' });10md(['## My markdown header'], { header: 'My markdown header', timeout: 5000, timeoutMessage: 'My custom timeout message', waitFor: 'visible', timeoutOptions: { visibility: 'hidden' } });11md(['## My markdown header'], { header: 'My markdown header', timeout: 5000, timeoutMessage: 'My custom timeout message', waitFor: 'visible', timeoutOptions: { visibility: 'hidden' }, page: page });12md(['## My markdown header'], { header: 'My markdown header', timeout: 5000, timeoutMessage: 'My custom timeout message', waitFor: 'visible', timeoutOptions: { visibility: 'hidden' }, page: page, container: container });13md(['## My markdown header'], { header: 'My markdown header', timeout: 5000, timeoutMessage: 'My custom timeout message', waitFor: 'visible', timeoutOptions: { visibility: 'hidden' }, page: page, container: container, state: state });14md(['
Using AI Code Generation
1const { md } = require('playwright');2console.log(md);3const { md } = require('playwright');4const fs = require('fs');5(async () => {6 const browser = await chromium.launch();7 const context = await browser.newContext();8 const page = await context.newPage();9 await page.screenshot({ path: 'screenshot.png' });10 await page.pdf({ path: 'page.pdf' });11 const markdown = await md.generate();12 fs.writeFileSync('test.md', markdown);13 await browser.close();14})();15await md.generate({16});
LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!