8602 lines
No EOL
272 KiB
JavaScript
8602 lines
No EOL
272 KiB
JavaScript
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
angular.module('ngMaterial', ["ng","ngAnimate","ngAria","material.core","material.core.theming.palette","material.core.theming","material.components.backdrop","material.components.bottomSheet","material.components.button","material.components.card","material.components.checkbox","material.components.content","material.components.dialog","material.components.divider","material.components.icon","material.components.input","material.components.list","material.components.progressCircular","material.components.progressLinear","material.components.radioButton","material.components.sidenav","material.components.slider","material.components.sticky","material.components.subheader","material.components.swipe","material.components.switch","material.components.tabs","material.components.textField","material.components.toast","material.components.toolbar","material.components.tooltip","material.components.whiteframe"]);
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* Initialization function that validates environment
|
||
* requirements.
|
||
*/
|
||
angular
|
||
.module('material.core', ['material.core.theming'])
|
||
.config(MdCoreConfigure);
|
||
|
||
|
||
function MdCoreConfigure($provide, $mdThemingProvider) {
|
||
$provide.decorator('$$rAF', ["$delegate", rAFDecorator]);
|
||
|
||
$mdThemingProvider.theme('default')
|
||
.primaryPalette('indigo')
|
||
.accentPalette('pink')
|
||
.warnPalette('red')
|
||
.backgroundPalette('grey');
|
||
}
|
||
MdCoreConfigure.$inject = ["$provide", "$mdThemingProvider"];
|
||
|
||
function rAFDecorator( $delegate ) {
|
||
/**
|
||
* Use this to throttle events that come in often.
|
||
* The throttled function will always use the *last* invocation before the
|
||
* coming frame.
|
||
*
|
||
* For example, window resize events that fire many times a second:
|
||
* If we set to use an raf-throttled callback on window resize, then
|
||
* our callback will only be fired once per frame, with the last resize
|
||
* event that happened before that frame.
|
||
*
|
||
* @param {function} callback function to debounce
|
||
*/
|
||
$delegate.throttle = function(cb) {
|
||
var queueArgs, alreadyQueued, queueCb, context;
|
||
return function debounced() {
|
||
queueArgs = arguments;
|
||
context = this;
|
||
queueCb = cb;
|
||
if (!alreadyQueued) {
|
||
alreadyQueued = true;
|
||
$delegate(function() {
|
||
queueCb.apply(context, queueArgs);
|
||
alreadyQueued = false;
|
||
});
|
||
}
|
||
};
|
||
};
|
||
return $delegate;
|
||
}
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.core')
|
||
.factory('$mdConstant', MdConstantFactory);
|
||
|
||
function MdConstantFactory($$rAF, $sniffer) {
|
||
|
||
var webkit = /webkit/i.test($sniffer.vendorPrefix);
|
||
function vendorProperty(name) {
|
||
return webkit ? ('webkit' + name.charAt(0).toUpperCase() + name.substring(1)) : name;
|
||
}
|
||
|
||
return {
|
||
KEY_CODE: {
|
||
ENTER: 13,
|
||
ESCAPE: 27,
|
||
SPACE: 32,
|
||
LEFT_ARROW : 37,
|
||
UP_ARROW : 38,
|
||
RIGHT_ARROW : 39,
|
||
DOWN_ARROW : 40
|
||
},
|
||
CSS: {
|
||
/* Constants */
|
||
TRANSITIONEND: 'transitionend' + (webkit ? ' webkitTransitionEnd' : ''),
|
||
ANIMATIONEND: 'animationend' + (webkit ? ' webkitAnimationEnd' : ''),
|
||
|
||
TRANSFORM: vendorProperty('transform'),
|
||
TRANSITION: vendorProperty('transition'),
|
||
TRANSITION_DURATION: vendorProperty('transitionDuration'),
|
||
ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'),
|
||
ANIMATION_DURATION: vendorProperty('animationDuration'),
|
||
ANIMATION_NAME: vendorProperty('animationName'),
|
||
ANIMATION_TIMING: vendorProperty('animationTimingFunction'),
|
||
ANIMATION_DIRECTION: vendorProperty('animationDirection')
|
||
},
|
||
MEDIA: {
|
||
'sm': '(max-width: 600px)',
|
||
'gt-sm': '(min-width: 600px)',
|
||
'md': '(min-width: 600px) and (max-width: 960px)',
|
||
'gt-md': '(min-width: 960px)',
|
||
'lg': '(min-width: 960px) and (max-width: 1200px)',
|
||
'gt-lg': '(min-width: 1200px)'
|
||
}
|
||
};
|
||
}
|
||
MdConstantFactory.$inject = ["$$rAF", "$sniffer"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function(){
|
||
|
||
angular
|
||
.module('material.core')
|
||
.config( ["$provide", function($provide){
|
||
$provide.decorator('$mdUtil', ['$delegate', function ($delegate){
|
||
/**
|
||
* Inject the iterator facade to easily support iteration and accessors
|
||
* @see iterator below
|
||
*/
|
||
$delegate.iterator = Iterator;
|
||
|
||
return $delegate;
|
||
}
|
||
]);
|
||
}]);
|
||
|
||
/**
|
||
* iterator is a list facade to easily support iteration and accessors
|
||
*
|
||
* @param items Array list which this iterator will enumerate
|
||
* @param reloop Boolean enables iterator to consider the list as an endless reloop
|
||
*/
|
||
function Iterator(items, reloop) {
|
||
var trueFn = function() { return true; };
|
||
|
||
reloop = !!reloop;
|
||
var _items = items || [ ];
|
||
|
||
// Published API
|
||
return {
|
||
items: getItems,
|
||
count: count,
|
||
|
||
inRange: inRange,
|
||
contains: contains,
|
||
indexOf: indexOf,
|
||
itemAt: itemAt,
|
||
|
||
findBy: findBy,
|
||
|
||
add: add,
|
||
remove: remove,
|
||
|
||
first: first,
|
||
last: last,
|
||
next: angular.bind(null, findSubsequentItem, false),
|
||
previous: angular.bind(null, findSubsequentItem, true),
|
||
|
||
hasPrevious: hasPrevious,
|
||
hasNext: hasNext
|
||
|
||
};
|
||
|
||
/**
|
||
* Publish copy of the enumerable set
|
||
* @returns {Array|*}
|
||
*/
|
||
function getItems() {
|
||
return [].concat(_items);
|
||
}
|
||
|
||
/**
|
||
* Determine length of the list
|
||
* @returns {Array.length|*|number}
|
||
*/
|
||
function count() {
|
||
return _items.length;
|
||
}
|
||
|
||
/**
|
||
* Is the index specified valid
|
||
* @param index
|
||
* @returns {Array.length|*|number|boolean}
|
||
*/
|
||
function inRange(index) {
|
||
return _items.length && ( index > -1 ) && (index < _items.length );
|
||
}
|
||
|
||
/**
|
||
* Can the iterator proceed to the next item in the list; relative to
|
||
* the specified item.
|
||
*
|
||
* @param item
|
||
* @returns {Array.length|*|number|boolean}
|
||
*/
|
||
function hasNext(item) {
|
||
return item ? inRange(indexOf(item) + 1) : false;
|
||
}
|
||
|
||
/**
|
||
* Can the iterator proceed to the previous item in the list; relative to
|
||
* the specified item.
|
||
*
|
||
* @param item
|
||
* @returns {Array.length|*|number|boolean}
|
||
*/
|
||
function hasPrevious(item) {
|
||
return item ? inRange(indexOf(item) - 1) : false;
|
||
}
|
||
|
||
/**
|
||
* Get item at specified index/position
|
||
* @param index
|
||
* @returns {*}
|
||
*/
|
||
function itemAt(index) {
|
||
return inRange(index) ? _items[index] : null;
|
||
}
|
||
|
||
/**
|
||
* Find all elements matching the key/value pair
|
||
* otherwise return null
|
||
*
|
||
* @param val
|
||
* @param key
|
||
*
|
||
* @return array
|
||
*/
|
||
function findBy(key, val) {
|
||
return _items.filter(function(item) {
|
||
return item[key] === val;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Add item to list
|
||
* @param item
|
||
* @param index
|
||
* @returns {*}
|
||
*/
|
||
function add(item, index) {
|
||
if ( !item ) return -1;
|
||
|
||
if (!angular.isNumber(index)) {
|
||
index = _items.length;
|
||
}
|
||
|
||
_items.splice(index, 0, item);
|
||
|
||
return indexOf(item);
|
||
}
|
||
|
||
/**
|
||
* Remove item from list...
|
||
* @param item
|
||
*/
|
||
function remove(item) {
|
||
if ( contains(item) ){
|
||
_items.splice(indexOf(item), 1);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get the zero-based index of the target item
|
||
* @param item
|
||
* @returns {*}
|
||
*/
|
||
function indexOf(item) {
|
||
return _items.indexOf(item);
|
||
}
|
||
|
||
/**
|
||
* Boolean existence check
|
||
* @param item
|
||
* @returns {boolean}
|
||
*/
|
||
function contains(item) {
|
||
return item && (indexOf(item) > -1);
|
||
}
|
||
|
||
/**
|
||
* Return first item in the list
|
||
* @returns {*}
|
||
*/
|
||
function first() {
|
||
return _items.length ? _items[0] : null;
|
||
}
|
||
|
||
/**
|
||
* Return last item in the list...
|
||
* @returns {*}
|
||
*/
|
||
function last() {
|
||
return _items.length ? _items[_items.length - 1] : null;
|
||
}
|
||
|
||
/**
|
||
* Find the next item. If reloop is true and at the end of the list, it will
|
||
* go back to the first item. If given ,the `validate` callback will be used
|
||
* determine whether the next item is valid. If not valid, it will try to find the
|
||
* next item again.
|
||
* @param item
|
||
* @param {optional} validate Validate function
|
||
* @param {optional} limit Recursion limit
|
||
* @returns {*}
|
||
*/
|
||
function findSubsequentItem(backwards, item, validate, limit) {
|
||
validate = validate || trueFn;
|
||
|
||
var curIndex = indexOf(item);
|
||
if (!inRange(curIndex)) {
|
||
return null;
|
||
}
|
||
|
||
var nextIndex = curIndex + (backwards ? -1 : 1);
|
||
var foundItem = null;
|
||
if (inRange(nextIndex)) {
|
||
foundItem = _items[nextIndex];
|
||
} else if (reloop) {
|
||
foundItem = backwards ? last() : first();
|
||
nextIndex = indexOf(foundItem);
|
||
}
|
||
|
||
if ((foundItem === null) || (nextIndex === limit)) {
|
||
return null;
|
||
}
|
||
|
||
if (angular.isUndefined(limit)) {
|
||
limit = nextIndex;
|
||
}
|
||
|
||
return validate(foundItem) ? foundItem : findSubsequentItem(backwards, foundItem, validate, limit);
|
||
}
|
||
}
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
angular.module('material.core')
|
||
.factory('$mdMedia', mdMediaFactory);
|
||
|
||
/**
|
||
* Exposes a function on the '$mdMedia' service which will return true or false,
|
||
* whether the given media query matches. Re-evaluates on resize. Allows presets
|
||
* for 'sm', 'md', 'lg'.
|
||
*
|
||
* @example $mdMedia('sm') == true if device-width <= sm
|
||
* @example $mdMedia('(min-width: 1200px)') == true if device-width >= 1200px
|
||
* @example $mdMedia('max-width: 300px') == true if device-width <= 300px (sanitizes input, adding parens)
|
||
*/
|
||
function mdMediaFactory($mdConstant, $rootScope, $window) {
|
||
var queries = {};
|
||
var results = {};
|
||
|
||
return $mdMedia;
|
||
|
||
function $mdMedia(query) {
|
||
var validated = queries[query];
|
||
if (angular.isUndefined(validated)) {
|
||
validated = queries[query] = validate(query);
|
||
}
|
||
|
||
var result = results[validated];
|
||
if (angular.isUndefined(result)) {
|
||
result = add(validated);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
function validate(query) {
|
||
return $mdConstant.MEDIA[query] ||
|
||
((query.charAt(0) !== '(') ? ('(' + query + ')') : query);
|
||
}
|
||
|
||
function add(query) {
|
||
var result = $window.matchMedia(query);
|
||
result.addListener(onQueryChange);
|
||
return (results[result.media] = !!result.matches);
|
||
}
|
||
|
||
function onQueryChange() {
|
||
var query = this;
|
||
$rootScope.$evalAsync(function() {
|
||
results[query.media] = !!query.matches;
|
||
});
|
||
}
|
||
|
||
}
|
||
mdMediaFactory.$inject = ["$mdConstant", "$rootScope", "$window"];
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/*
|
||
* This var has to be outside the angular factory, otherwise when
|
||
* there are multiple material apps on the same page, each app
|
||
* will create its own instance of this array and the app's IDs
|
||
* will not be unique.
|
||
*/
|
||
var nextUniqueId = ['0','0','0'];
|
||
|
||
angular.module('material.core')
|
||
.factory('$mdUtil', ["$document", "$timeout", function($document, $timeout) {
|
||
var Util;
|
||
|
||
return Util = {
|
||
now: window.performance ? angular.bind(window.performance, window.performance.now) : Date.now,
|
||
|
||
elementRect: function(element, offsetParent) {
|
||
var node = element[0];
|
||
offsetParent = offsetParent || node.offsetParent || document.body;
|
||
offsetParent = offsetParent[0] || offsetParent;
|
||
var nodeRect = node.getBoundingClientRect();
|
||
var parentRect = offsetParent.getBoundingClientRect();
|
||
return {
|
||
left: nodeRect.left - parentRect.left + offsetParent.scrollLeft,
|
||
top: nodeRect.top - parentRect.top + offsetParent.scrollTop,
|
||
width: nodeRect.width,
|
||
height: nodeRect.height
|
||
};
|
||
},
|
||
|
||
fakeNgModel: function() {
|
||
return {
|
||
$fake: true,
|
||
$setViewValue: function(value) {
|
||
this.$viewValue = value;
|
||
this.$render(value);
|
||
this.$viewChangeListeners.forEach(function(cb) { cb(); });
|
||
},
|
||
$isEmpty: function(value) {
|
||
return (''+value).length === 0;
|
||
},
|
||
$parsers: [],
|
||
$formatters: [],
|
||
$viewChangeListeners: [],
|
||
$render: angular.noop
|
||
};
|
||
},
|
||
|
||
// Returns a function, that, as long as it continues to be invoked, will not
|
||
// be triggered. The function will be called after it stops being called for
|
||
// N milliseconds.
|
||
// @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs
|
||
// @param invokeApply should the $timeout trigger $digest() dirty checking
|
||
debounce: function (func, wait, scope, invokeApply) {
|
||
var timer;
|
||
|
||
return function debounced() {
|
||
var context = scope,
|
||
args = Array.prototype.slice.call(arguments);
|
||
|
||
$timeout.cancel(timer);
|
||
timer = $timeout(function() {
|
||
|
||
timer = undefined;
|
||
func.apply(context, args);
|
||
|
||
}, wait || 10, invokeApply );
|
||
};
|
||
},
|
||
|
||
// Returns a function that can only be triggered every `delay` milliseconds.
|
||
// In other words, the function will not be called unless it has been more
|
||
// than `delay` milliseconds since the last call.
|
||
throttle: function throttle(func, delay) {
|
||
var recent;
|
||
return function throttled() {
|
||
var context = this;
|
||
var args = arguments;
|
||
var now = Util.now();
|
||
|
||
if (!recent || (now - recent > delay)) {
|
||
func.apply(context, args);
|
||
recent = now;
|
||
}
|
||
};
|
||
},
|
||
|
||
/**
|
||
* nextUid, from angular.js.
|
||
* A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
|
||
* characters such as '012ABC'. The reason why we are not using simply a number counter is that
|
||
* the number string gets longer over time, and it can also overflow, where as the nextId
|
||
* will grow much slower, it is a string, and it will never overflow.
|
||
*
|
||
* @returns an unique alpha-numeric string
|
||
*/
|
||
nextUid: function() {
|
||
var index = nextUniqueId.length;
|
||
var digit;
|
||
|
||
while(index) {
|
||
index--;
|
||
digit = nextUniqueId[index].charCodeAt(0);
|
||
if (digit == 57 /*'9'*/) {
|
||
nextUniqueId[index] = 'A';
|
||
return nextUniqueId.join('');
|
||
}
|
||
if (digit == 90 /*'Z'*/) {
|
||
nextUniqueId[index] = '0';
|
||
} else {
|
||
nextUniqueId[index] = String.fromCharCode(digit + 1);
|
||
return nextUniqueId.join('');
|
||
}
|
||
}
|
||
nextUniqueId.unshift('0');
|
||
return nextUniqueId.join('');
|
||
},
|
||
|
||
// Stop watchers and events from firing on a scope without destroying it,
|
||
// by disconnecting it from its parent and its siblings' linked lists.
|
||
disconnectScope: function disconnectScope(scope) {
|
||
if (!scope) return;
|
||
|
||
// we can't destroy the root scope or a scope that has been already destroyed
|
||
if (scope.$root === scope) return;
|
||
if (scope.$$destroyed ) return;
|
||
|
||
var parent = scope.$parent;
|
||
scope.$$disconnected = true;
|
||
|
||
// See Scope.$destroy
|
||
if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling;
|
||
if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling;
|
||
if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
|
||
if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
|
||
|
||
scope.$$nextSibling = scope.$$prevSibling = null;
|
||
|
||
},
|
||
|
||
// Undo the effects of disconnectScope above.
|
||
reconnectScope: function reconnectScope(scope) {
|
||
if (!scope) return;
|
||
|
||
// we can't disconnect the root node or scope already disconnected
|
||
if (scope.$root === scope) return;
|
||
if (!scope.$$disconnected) return;
|
||
|
||
var child = scope;
|
||
|
||
var parent = child.$parent;
|
||
child.$$disconnected = false;
|
||
// See Scope.$new for this logic...
|
||
child.$$prevSibling = parent.$$childTail;
|
||
if (parent.$$childHead) {
|
||
parent.$$childTail.$$nextSibling = child;
|
||
parent.$$childTail = child;
|
||
} else {
|
||
parent.$$childHead = parent.$$childTail = child;
|
||
}
|
||
},
|
||
/*
|
||
* getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName
|
||
*
|
||
* @param el Element to start walking the DOM from
|
||
* @param tagName Tag name to find closest to el, such as 'form'
|
||
*/
|
||
getClosest: function getClosest(el, tagName) {
|
||
tagName = tagName.toUpperCase();
|
||
do {
|
||
if (el.nodeName === tagName) {
|
||
return el;
|
||
}
|
||
} while (el = el.parentNode);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
}]);
|
||
|
||
/*
|
||
* Since removing jQuery from the demos, some code that uses `element.focus()` is broken.
|
||
*
|
||
* We need to add `element.focus()`, because it's testable unlike `element[0].focus`.
|
||
*
|
||
* TODO(ajoslin): This should be added in a better place later.
|
||
*/
|
||
|
||
angular.element.prototype.focus = angular.element.prototype.focus || function() {
|
||
if (this.length) {
|
||
this[0].focus();
|
||
}
|
||
return this;
|
||
};
|
||
angular.element.prototype.blur = angular.element.prototype.blur || function() {
|
||
if (this.length) {
|
||
this[0].blur();
|
||
}
|
||
return this;
|
||
};
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.core')
|
||
.service('$mdAria', AriaService);
|
||
|
||
function AriaService($$rAF, $log, $window) {
|
||
|
||
return {
|
||
expect: expect,
|
||
expectAsync: expectAsync,
|
||
expectWithText: expectWithText
|
||
};
|
||
|
||
/**
|
||
* Check if expected attribute has been specified on the target element or child
|
||
* @param element
|
||
* @param attrName
|
||
* @param {optional} defaultValue What to set the attr to if no value is found
|
||
*/
|
||
function expect(element, attrName, defaultValue) {
|
||
var node = element[0];
|
||
|
||
if (!node.hasAttribute(attrName) && !childHasAttribute(node, attrName)) {
|
||
|
||
defaultValue = angular.isString(defaultValue) && defaultValue.trim() || '';
|
||
if (defaultValue.length) {
|
||
element.attr(attrName, defaultValue);
|
||
} else {
|
||
$log.warn('ARIA: Attribute "', attrName, '", required for accessibility, is missing on node:', node);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
function expectAsync(element, attrName, defaultValueGetter) {
|
||
// Problem: when retrieving the element's contents synchronously to find the label,
|
||
// the text may not be defined yet in the case of a binding.
|
||
// There is a higher chance that a binding will be defined if we wait one frame.
|
||
$$rAF(function() {
|
||
expect(element, attrName, defaultValueGetter());
|
||
});
|
||
}
|
||
|
||
function expectWithText(element, attrName) {
|
||
expectAsync(element, attrName, function() {
|
||
return element.text().trim();
|
||
});
|
||
}
|
||
|
||
function childHasAttribute(node, attrName) {
|
||
var hasChildren = node.hasChildNodes(),
|
||
hasAttr = false;
|
||
|
||
function isHidden(el) {
|
||
var style = el.currentStyle ? el.currentStyle : $window.getComputedStyle(el);
|
||
return (style.display === 'none');
|
||
}
|
||
|
||
if(hasChildren) {
|
||
var children = node.childNodes;
|
||
for(var i=0; i<children.length; i++){
|
||
var child = children[i];
|
||
if(child.nodeType === 1 && child.hasAttribute(attrName)) {
|
||
if(!isHidden(child)){
|
||
hasAttr = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return hasAttr;
|
||
}
|
||
}
|
||
AriaService.$inject = ["$$rAF", "$log", "$window"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.core')
|
||
.service('$mdCompiler', mdCompilerService);
|
||
|
||
function mdCompilerService($q, $http, $injector, $compile, $controller, $templateCache) {
|
||
/* jshint validthis: true */
|
||
|
||
/*
|
||
* @ngdoc service
|
||
* @name $mdCompiler
|
||
* @module material.core
|
||
* @description
|
||
* The $mdCompiler service is an abstraction of angular's compiler, that allows the developer
|
||
* to easily compile an element with a templateUrl, controller, and locals.
|
||
*
|
||
* @usage
|
||
* <hljs lang="js">
|
||
* $mdCompiler.compile({
|
||
* templateUrl: 'modal.html',
|
||
* controller: 'ModalCtrl',
|
||
* locals: {
|
||
* modal: myModalInstance;
|
||
* }
|
||
* }).then(function(compileData) {
|
||
* compileData.element; // modal.html's template in an element
|
||
* compileData.link(myScope); //attach controller & scope to element
|
||
* });
|
||
* </hljs>
|
||
*/
|
||
|
||
/*
|
||
* @ngdoc method
|
||
* @name $mdCompiler#compile
|
||
* @description A helper to compile an HTML template/templateUrl with a given controller,
|
||
* locals, and scope.
|
||
* @param {object} options An options object, with the following properties:
|
||
*
|
||
* - `controller` - `{(string=|function()=}` Controller fn that should be associated with
|
||
* newly created scope or the name of a registered controller if passed as a string.
|
||
* - `controllerAs` - `{string=}` A controller alias name. If present the controller will be
|
||
* published to scope under the `controllerAs` name.
|
||
* - `template` - `{string=}` An html template as a string.
|
||
* - `templateUrl` - `{string=}` A path to an html template.
|
||
* - `transformTemplate` - `{function(template)=}` A function which transforms the template after
|
||
* it is loaded. It will be given the template string as a parameter, and should
|
||
* return a a new string representing the transformed template.
|
||
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
|
||
* be injected into the controller. If any of these dependencies are promises, the compiler
|
||
* will wait for them all to be resolved, or if one is rejected before the controller is
|
||
* instantiated `compile()` will fail..
|
||
* * `key` - `{string}`: a name of a dependency to be injected into the controller.
|
||
* * `factory` - `{string|function}`: If `string` then it is an alias for a service.
|
||
* Otherwise if function, then it is injected and the return value is treated as the
|
||
* dependency. If the result is a promise, it is resolved before its value is
|
||
* injected into the controller.
|
||
*
|
||
* @returns {object=} promise A promise, which will be resolved with a `compileData` object.
|
||
* `compileData` has the following properties:
|
||
*
|
||
* - `element` - `{element}`: an uncompiled element matching the provided template.
|
||
* - `link` - `{function(scope)}`: A link function, which, when called, will compile
|
||
* the element and instantiate the provided controller (if given).
|
||
* - `locals` - `{object}`: The locals which will be passed into the controller once `link` is
|
||
* called. If `bindToController` is true, they will be coppied to the ctrl instead
|
||
* - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in
|
||
*/
|
||
this.compile = function(options) {
|
||
var templateUrl = options.templateUrl;
|
||
var template = options.template || '';
|
||
var controller = options.controller;
|
||
var controllerAs = options.controllerAs;
|
||
var resolve = options.resolve || {};
|
||
var locals = options.locals || {};
|
||
var transformTemplate = options.transformTemplate || angular.identity;
|
||
var bindToController = options.bindToController;
|
||
|
||
// Take resolve values and invoke them.
|
||
// Resolves can either be a string (value: 'MyRegisteredAngularConst'),
|
||
// or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {})
|
||
angular.forEach(resolve, function(value, key) {
|
||
if (angular.isString(value)) {
|
||
resolve[key] = $injector.get(value);
|
||
} else {
|
||
resolve[key] = $injector.invoke(value);
|
||
}
|
||
});
|
||
//Add the locals, which are just straight values to inject
|
||
//eg locals: { three: 3 }, will inject three into the controller
|
||
angular.extend(resolve, locals);
|
||
|
||
if (templateUrl) {
|
||
resolve.$template = $http.get(templateUrl, {cache: $templateCache})
|
||
.then(function(response) {
|
||
return response.data;
|
||
});
|
||
} else {
|
||
resolve.$template = $q.when(template);
|
||
}
|
||
|
||
// Wait for all the resolves to finish if they are promises
|
||
return $q.all(resolve).then(function(locals) {
|
||
|
||
var template = transformTemplate(locals.$template);
|
||
var element = angular.element('<div>').html(template.trim()).contents();
|
||
var linkFn = $compile(element);
|
||
|
||
//Return a linking function that can be used later when the element is ready
|
||
return {
|
||
locals: locals,
|
||
element: element,
|
||
link: function link(scope) {
|
||
locals.$scope = scope;
|
||
|
||
//Instantiate controller if it exists, because we have scope
|
||
if (controller) {
|
||
var ctrl = $controller(controller, locals);
|
||
if (bindToController) {
|
||
angular.extend(ctrl, locals);
|
||
}
|
||
//See angular-route source for this logic
|
||
element.data('$ngControllerController', ctrl);
|
||
element.children().data('$ngControllerController', ctrl);
|
||
|
||
if (controllerAs) {
|
||
scope[controllerAs] = ctrl;
|
||
}
|
||
}
|
||
|
||
return linkFn(scope);
|
||
}
|
||
};
|
||
});
|
||
|
||
};
|
||
}
|
||
mdCompilerService.$inject = ["$q", "$http", "$injector", "$compile", "$controller", "$templateCache"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/*
|
||
* TODO: Add support for multiple fingers on the `pointer` object (enables pinch gesture)
|
||
*/
|
||
|
||
var START_EVENTS = 'mousedown touchstart pointerdown';
|
||
var MOVE_EVENTS = 'mousemove touchmove pointermove';
|
||
var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel';
|
||
var HANDLERS;
|
||
|
||
document.contains || (document.contains = function(node) {
|
||
return document.body.contains(node);
|
||
});
|
||
|
||
// TODO add windows phone to this
|
||
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||
var isIos = userAgent.match(/iPad/i) || userAgent.match(/iPhone/i) || userAgent.match(/iPod/i);
|
||
var isAndroid = userAgent.match(/Android/i);
|
||
var shouldHijackClicks = isIos || isAndroid;
|
||
|
||
if (shouldHijackClicks) {
|
||
document.addEventListener('click', function(ev) {
|
||
// Space/enter on a button, and submit events, can send clicks
|
||
var isKeyClick = ev.clientX === 0 && ev.clientY === 0;
|
||
if (isKeyClick || ev.$material) return;
|
||
|
||
// Prevent clicks unless they're sent by material
|
||
ev.preventDefault();
|
||
ev.stopPropagation();
|
||
}, true);
|
||
}
|
||
|
||
angular.element(document)
|
||
.on(START_EVENTS, gestureStart)
|
||
.on(MOVE_EVENTS, gestureMove)
|
||
.on(END_EVENTS, gestureEnd)
|
||
// For testing
|
||
.on('$$mdGestureReset', function() {
|
||
lastPointer = pointer = null;
|
||
});
|
||
|
||
// The state of the current and previous 'pointer' (user's hand)
|
||
var pointer, lastPointer;
|
||
|
||
function runHandlers(handlerEvent, event) {
|
||
var handler;
|
||
for (var handlerName in HANDLERS) {
|
||
handler = HANDLERS[handlerName];
|
||
if (handlerEvent === 'start') {
|
||
// Run cancel to reset any handlers' state
|
||
handler.cancel();
|
||
}
|
||
handler[handlerEvent](event, pointer);
|
||
}
|
||
}
|
||
|
||
function gestureStart(ev) {
|
||
// If we're already touched down, abort
|
||
if (pointer) return;
|
||
|
||
var now = +Date.now();
|
||
|
||
// iOS & old android bug: after a touch event, a click event is sent 350 ms later.
|
||
// If <400ms have passed, don't allow an event of a different type than the previous event
|
||
if (lastPointer && !typesMatch(ev, lastPointer) && (now - lastPointer.endTime < 1500)) {
|
||
return;
|
||
}
|
||
|
||
pointer = makeStartPointer(ev);
|
||
|
||
runHandlers('start', ev);
|
||
}
|
||
|
||
function gestureMove(ev) {
|
||
if (!pointer || !typesMatch(ev, pointer)) return;
|
||
|
||
updatePointerState(ev, pointer);
|
||
runHandlers('move', ev);
|
||
}
|
||
|
||
function gestureEnd(ev) {
|
||
if (!pointer || !typesMatch(ev, pointer)) return;
|
||
|
||
updatePointerState(ev, pointer);
|
||
pointer.endTime = +Date.now();
|
||
|
||
runHandlers('end', ev);
|
||
|
||
lastPointer = pointer;
|
||
pointer = null;
|
||
}
|
||
|
||
/******** Helpers *********/
|
||
function typesMatch(ev, pointer) {
|
||
return ev && pointer && ev.type.charAt(0) === pointer.type;
|
||
}
|
||
|
||
function getEventPoint(ev) {
|
||
ev = ev.originalEvent || ev; // support jQuery events
|
||
return (ev.touches && ev.touches[0]) ||
|
||
(ev.changedTouches && ev.changedTouches[0]) ||
|
||
ev;
|
||
}
|
||
|
||
function updatePointerState(ev, pointer) {
|
||
var point = getEventPoint(ev);
|
||
var x = pointer.x = point.pageX;
|
||
var y = pointer.y = point.pageY;
|
||
|
||
pointer.distanceX = x - pointer.startX;
|
||
pointer.distanceY = y - pointer.startY;
|
||
pointer.distance = Math.sqrt(
|
||
pointer.distanceX * pointer.distanceX + pointer.distanceY * pointer.distanceY
|
||
);
|
||
|
||
pointer.directionX = pointer.distanceX > 0 ? 'right' : pointer.distanceX < 0 ? 'left' : '';
|
||
pointer.directionY = pointer.distanceY > 0 ? 'up' : pointer.distanceY < 0 ? 'down' : '';
|
||
|
||
pointer.duration = +Date.now() - pointer.startTime;
|
||
pointer.velocityX = pointer.distanceX / pointer.duration;
|
||
pointer.velocityY = pointer.distanceY / pointer.duration;
|
||
}
|
||
|
||
|
||
function makeStartPointer(ev) {
|
||
var point = getEventPoint(ev);
|
||
var startPointer = {
|
||
startTime: +Date.now(),
|
||
target: ev.target,
|
||
// 'p' for pointer, 'm' for mouse, 't' for touch
|
||
type: ev.type.charAt(0)
|
||
};
|
||
startPointer.startX = startPointer.x = point.pageX;
|
||
startPointer.startY = startPointer.y = point.pageY;
|
||
return startPointer;
|
||
}
|
||
|
||
angular.module('material.core')
|
||
.run(["$mdGesture", function($mdGesture) {}]) // make sure $mdGesture is always instantiated
|
||
.factory('$mdGesture', ["$$MdGestureHandler", "$$rAF", "$timeout", function($$MdGestureHandler, $$rAF, $timeout) {
|
||
HANDLERS = {};
|
||
|
||
if (shouldHijackClicks) {
|
||
addHandler('click', {
|
||
options: {
|
||
maxDistance: 6
|
||
},
|
||
onEnd: function(ev, pointer) {
|
||
if (pointer.distance < this.state.options.maxDistance) {
|
||
this.dispatchEvent(ev, 'click', null, ev);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
addHandler('press', {
|
||
onStart: function(ev, pointer) {
|
||
this.dispatchEvent(ev, '$md.pressdown');
|
||
},
|
||
onEnd: function(ev, pointer) {
|
||
this.dispatchEvent(ev, '$md.pressup');
|
||
}
|
||
});
|
||
|
||
|
||
addHandler('hold', {
|
||
options: {
|
||
// If the user keeps his finger within the same <maxDistance> area for
|
||
// <delay> ms, dispatch a hold event.
|
||
maxDistance: 6,
|
||
delay: 500,
|
||
},
|
||
onCancel: function() {
|
||
$timeout.cancel(this.state.timeout);
|
||
},
|
||
onStart: function(ev, pointer) {
|
||
// For hold, require a parent to be registered with $mdGesture.register()
|
||
// Because we prevent scroll events, this is necessary.
|
||
if (!this.state.registeredParent) return this.cancel();
|
||
|
||
this.state.pos = {x: pointer.x, y: pointer.y};
|
||
this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() {
|
||
this.dispatchEvent(ev, '$md.hold');
|
||
this.cancel(); //we're done!
|
||
}), this.state.options.delay, false);
|
||
},
|
||
onMove: function(ev, pointer) {
|
||
// Don't scroll while waiting for hold
|
||
ev.preventDefault();
|
||
var dx = this.state.pos.x - pointer.x;
|
||
var dy = this.state.pos.y - pointer.y;
|
||
if (Math.sqrt(dx*dx + dy*dy) > this.options.maxDistance) {
|
||
this.cancel();
|
||
}
|
||
},
|
||
onEnd: function(ev, pointer) {
|
||
this.onCancel();
|
||
},
|
||
});
|
||
|
||
addHandler('drag', {
|
||
options: {
|
||
minDistance: 6,
|
||
horizontal: true,
|
||
},
|
||
onStart: function(ev) {
|
||
// For drag, require a parent to be registered with $mdGesture.register()
|
||
if (!this.state.registeredParent) this.cancel();
|
||
},
|
||
onMove: function(ev, pointer) {
|
||
var shouldStartDrag, shouldCancel;
|
||
// Don't allow touch events to scroll while we're dragging or
|
||
// deciding if this touchmove is a proper drag
|
||
ev.preventDefault();
|
||
|
||
if (!this.state.dragPointer) {
|
||
if (this.state.options.horizontal) {
|
||
shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance;
|
||
shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * 1.5;
|
||
} else {
|
||
shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance;
|
||
shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * 1.5;
|
||
}
|
||
|
||
if (shouldStartDrag) {
|
||
// Create a new pointer, starting at this point where the drag started.
|
||
this.state.dragPointer = makeStartPointer(ev);
|
||
updatePointerState(ev, this.state.dragPointer);
|
||
this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer);
|
||
|
||
} else if (shouldCancel) {
|
||
this.cancel();
|
||
}
|
||
} else {
|
||
this.dispatchDragMove(ev);
|
||
}
|
||
},
|
||
// Only dispatch these every frame; any more is unnecessray
|
||
dispatchDragMove: $$rAF.throttle(function(ev) {
|
||
// Make sure the drag didn't stop while waiting for the next frame
|
||
if (this.state.isRunning) {
|
||
updatePointerState(ev, this.state.dragPointer);
|
||
this.dispatchEvent(ev, '$md.drag', this.state.dragPointer);
|
||
}
|
||
}),
|
||
onEnd: function(ev, pointer) {
|
||
if (this.state.dragPointer) {
|
||
updatePointerState(ev, this.state.dragPointer);
|
||
this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer);
|
||
}
|
||
}
|
||
});
|
||
|
||
addHandler('swipe', {
|
||
options: {
|
||
minVelocity: 0.65,
|
||
minDistance: 10,
|
||
},
|
||
onEnd: function(ev, pointer) {
|
||
if (Math.abs(pointer.velocityX) > this.state.options.minVelocity &&
|
||
Math.abs(pointer.distanceX) > this.state.options.minDistance) {
|
||
var eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight';
|
||
this.dispatchEvent(ev, eventType);
|
||
}
|
||
}
|
||
});
|
||
|
||
var self;
|
||
return self = {
|
||
handler: addHandler,
|
||
register: register
|
||
};
|
||
|
||
function addHandler(name, definition) {
|
||
var handler = new $$MdGestureHandler(name);
|
||
angular.extend(handler, definition);
|
||
HANDLERS[name] = handler;
|
||
return self;
|
||
}
|
||
|
||
function register(element, handlerName, options) {
|
||
var handler = HANDLERS[ handlerName.replace(/^\$md./, '') ];
|
||
if (!handler) {
|
||
throw new Error('Failed to register element with handler ' + handlerName + '. ' +
|
||
'Available handlers: ' + Object.keys(HANDLERS).join(', '));
|
||
}
|
||
return handler.registerElement(element, options);
|
||
}
|
||
}])
|
||
.factory('$$MdGestureHandler', ["$$rAF", function($$rAF) {
|
||
|
||
function GestureHandler(name) {
|
||
this.name = name;
|
||
this.state = {};
|
||
}
|
||
GestureHandler.prototype = {
|
||
onStart: angular.noop,
|
||
onMove: angular.noop,
|
||
onEnd: angular.noop,
|
||
onCancel: angular.noop,
|
||
options: {},
|
||
|
||
dispatchEvent: dispatchEvent,
|
||
|
||
start: function(ev, pointer) {
|
||
if (this.state.isRunning) return;
|
||
var parentTarget = this.getNearestParent(ev.target);
|
||
var parentTargetOptions = parentTarget && parentTarget.$mdGesture[this.name] || {};
|
||
|
||
this.state = {
|
||
isRunning: true,
|
||
options: angular.extend({}, this.options, parentTargetOptions),
|
||
registeredParent: parentTarget
|
||
};
|
||
this.onStart(ev, pointer);
|
||
},
|
||
move: function(ev, pointer) {
|
||
if (!this.state.isRunning) return;
|
||
this.onMove(ev, pointer);
|
||
},
|
||
end: function(ev, pointer) {
|
||
if (!this.state.isRunning) return;
|
||
this.onEnd(ev, pointer);
|
||
this.state.isRunning = false;
|
||
},
|
||
cancel: function(ev, pointer) {
|
||
this.onCancel(ev, pointer);
|
||
this.state = {};
|
||
},
|
||
|
||
// Find and return the nearest parent element that has been registered via
|
||
// $mdGesture.register(element, 'handlerName').
|
||
getNearestParent: function(node) {
|
||
var current = node;
|
||
while (current) {
|
||
if ( (current.$mdGesture || {})[this.name] ) {
|
||
return current;
|
||
}
|
||
current = current.parentNode;
|
||
}
|
||
},
|
||
|
||
registerElement: function(element, options) {
|
||
var self = this;
|
||
element[0].$mdGesture = element[0].$mdGesture || {};
|
||
element[0].$mdGesture[this.name] = options || {};
|
||
element.on('$destroy', onDestroy);
|
||
|
||
return onDestroy;
|
||
|
||
function onDestroy() {
|
||
delete element[0].$mdGesture[self.name];
|
||
element.off('$destroy', onDestroy);
|
||
}
|
||
},
|
||
};
|
||
|
||
var customEventOptions = {
|
||
bubbles: true,
|
||
cancelable: true
|
||
};
|
||
/*
|
||
* NOTE: dispatchEvent is very performance sensitive.
|
||
*/
|
||
function dispatchEvent(srcEvent, eventType, eventPointer, /*original DOMEvent */ev) {
|
||
eventPointer = eventPointer || pointer;
|
||
var eventObj;
|
||
|
||
if (eventType === 'click') {
|
||
eventObj = document.createEvent('MouseEvents');
|
||
eventObj.initMouseEvent(
|
||
'click', true, true, window, ev.detail,
|
||
ev.screenX, ev.screenY, ev.clientX, ev.clientY,
|
||
ev.ctrlKey, ev.altKey, ev.shiftKey, ev.metaKey,
|
||
ev.button, ev.relatedTarget || null
|
||
);
|
||
|
||
} else {
|
||
eventObj = document.createEvent('CustomEvent');
|
||
eventObj.initCustomEvent(eventType, true, true, {});
|
||
}
|
||
eventObj.$material = true;
|
||
eventObj.pointer = eventPointer;
|
||
eventObj.srcEvent = srcEvent;
|
||
eventPointer.target.dispatchEvent(eventObj);
|
||
}
|
||
|
||
return GestureHandler;
|
||
}]);
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.core')
|
||
.provider('$$interimElement', InterimElementProvider);
|
||
|
||
/*
|
||
* @ngdoc service
|
||
* @name $$interimElement
|
||
* @module material.core
|
||
*
|
||
* @description
|
||
*
|
||
* Factory that contructs `$$interimElement.$service` services.
|
||
* Used internally in material design for elements that appear on screen temporarily.
|
||
* The service provides a promise-like API for interacting with the temporary
|
||
* elements.
|
||
*
|
||
* ```js
|
||
* app.service('$mdToast', function($$interimElement) {
|
||
* var $mdToast = $$interimElement(toastDefaultOptions);
|
||
* return $mdToast;
|
||
* });
|
||
* ```
|
||
* @param {object=} defaultOptions Options used by default for the `show` method on the service.
|
||
*
|
||
* @returns {$$interimElement.$service}
|
||
*
|
||
*/
|
||
|
||
function InterimElementProvider() {
|
||
createInterimElementProvider.$get = InterimElementFactory;
|
||
InterimElementFactory.$inject = ["$document", "$q", "$rootScope", "$timeout", "$rootElement", "$animate", "$interpolate", "$mdCompiler", "$mdTheming"];
|
||
return createInterimElementProvider;
|
||
|
||
/**
|
||
* Returns a new provider which allows configuration of a new interimElement
|
||
* service. Allows configuration of default options & methods for options,
|
||
* as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method)
|
||
*/
|
||
function createInterimElementProvider(interimFactoryName) {
|
||
var EXPOSED_METHODS = ['onHide', 'onShow', 'onRemove'];
|
||
var providerConfig = {
|
||
presets: {}
|
||
};
|
||
var provider = {
|
||
setDefaults: setDefaults,
|
||
addPreset: addPreset,
|
||
$get: factory
|
||
};
|
||
|
||
/**
|
||
* all interim elements will come with the 'build' preset
|
||
*/
|
||
provider.addPreset('build', {
|
||
methods: ['controller', 'controllerAs', 'resolve',
|
||
'template', 'templateUrl', 'themable', 'transformTemplate', 'parent']
|
||
});
|
||
|
||
factory.$inject = ["$$interimElement", "$animate", "$injector"];
|
||
return provider;
|
||
|
||
/**
|
||
* Save the configured defaults to be used when the factory is instantiated
|
||
*/
|
||
function setDefaults(definition) {
|
||
providerConfig.optionsFactory = definition.options;
|
||
providerConfig.methods = (definition.methods || []).concat(EXPOSED_METHODS);
|
||
return provider;
|
||
}
|
||
|
||
/**
|
||
* Save the configured preset to be used when the factory is instantiated
|
||
*/
|
||
function addPreset(name, definition) {
|
||
definition = definition || {};
|
||
definition.methods = definition.methods || [];
|
||
definition.options = definition.options || function() { return {}; };
|
||
|
||
if (/^cancel|hide|show$/.test(name)) {
|
||
throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!");
|
||
}
|
||
if (definition.methods.indexOf('_options') > -1) {
|
||
throw new Error("Method '_options' in " + interimFactoryName + " is reserved!");
|
||
}
|
||
providerConfig.presets[name] = {
|
||
methods: definition.methods.concat(EXPOSED_METHODS),
|
||
optionsFactory: definition.options,
|
||
argOption: definition.argOption
|
||
};
|
||
return provider;
|
||
}
|
||
|
||
/**
|
||
* Create a factory that has the given methods & defaults implementing interimElement
|
||
*/
|
||
/* @ngInject */
|
||
function factory($$interimElement, $animate, $injector) {
|
||
var defaultMethods;
|
||
var defaultOptions;
|
||
var interimElementService = $$interimElement();
|
||
|
||
/*
|
||
* publicService is what the developer will be using.
|
||
* It has methods hide(), cancel(), show(), build(), and any other
|
||
* presets which were set during the config phase.
|
||
*/
|
||
var publicService = {
|
||
hide: interimElementService.hide,
|
||
cancel: interimElementService.cancel,
|
||
show: showInterimElement
|
||
};
|
||
|
||
defaultMethods = providerConfig.methods || [];
|
||
// This must be invoked after the publicService is initialized
|
||
defaultOptions = invokeFactory(providerConfig.optionsFactory, {});
|
||
|
||
angular.forEach(providerConfig.presets, function(definition, name) {
|
||
var presetDefaults = invokeFactory(definition.optionsFactory, {});
|
||
var presetMethods = (definition.methods || []).concat(defaultMethods);
|
||
|
||
// Every interimElement built with a preset has a field called `$type`,
|
||
// which matches the name of the preset.
|
||
// Eg in preset 'confirm', options.$type === 'confirm'
|
||
angular.extend(presetDefaults, { $type: name });
|
||
|
||
// This creates a preset class which has setter methods for every
|
||
// method given in the `.addPreset()` function, as well as every
|
||
// method given in the `.setDefaults()` function.
|
||
//
|
||
// @example
|
||
// .setDefaults({
|
||
// methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'],
|
||
// options: dialogDefaultOptions
|
||
// })
|
||
// .addPreset('alert', {
|
||
// methods: ['title', 'ok'],
|
||
// options: alertDialogOptions
|
||
// })
|
||
//
|
||
// Set values will be passed to the options when interimElemnt.show() is called.
|
||
function Preset(opts) {
|
||
this._options = angular.extend({}, presetDefaults, opts);
|
||
}
|
||
angular.forEach(presetMethods, function(name) {
|
||
Preset.prototype[name] = function(value) {
|
||
this._options[name] = value;
|
||
return this;
|
||
};
|
||
});
|
||
|
||
// Create shortcut method for one-linear methods
|
||
if (definition.argOption) {
|
||
var methodName = 'show' + name.charAt(0).toUpperCase() + name.slice(1);
|
||
publicService[methodName] = function(arg) {
|
||
var config = publicService[name](arg);
|
||
return publicService.show(config);
|
||
};
|
||
}
|
||
|
||
// eg $mdDialog.alert() will return a new alert preset
|
||
publicService[name] = function(arg) {
|
||
// If argOption is supplied, eg `argOption: 'content'`, then we assume
|
||
// if the argument is not an options object then it is the `argOption` option.
|
||
//
|
||
// @example `$mdToast.simple('hello')` // sets options.content to hello
|
||
// // because argOption === 'content'
|
||
if (arguments.length && definition.argOption && !angular.isObject(arg) &&
|
||
!angular.isArray(arg)) {
|
||
return (new Preset())[definition.argOption](arg);
|
||
} else {
|
||
return new Preset(arg);
|
||
}
|
||
|
||
};
|
||
});
|
||
|
||
return publicService;
|
||
|
||
function showInterimElement(opts) {
|
||
// opts is either a preset which stores its options on an _options field,
|
||
// or just an object made up of options
|
||
if (opts && opts._options) opts = opts._options;
|
||
return interimElementService.show(
|
||
angular.extend({}, defaultOptions, opts)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Helper to call $injector.invoke with a local of the factory name for
|
||
* this provider.
|
||
* If an $mdDialog is providing options for a dialog and tries to inject
|
||
* $mdDialog, a circular dependency error will happen.
|
||
* We get around that by manually injecting $mdDialog as a local.
|
||
*/
|
||
function invokeFactory(factory, defaultVal) {
|
||
var locals = {};
|
||
locals[interimFactoryName] = publicService;
|
||
return $injector.invoke(factory || function() { return defaultVal; }, {}, locals);
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
/* @ngInject */
|
||
function InterimElementFactory($document, $q, $rootScope, $timeout, $rootElement, $animate,
|
||
$interpolate, $mdCompiler, $mdTheming ) {
|
||
var startSymbol = $interpolate.startSymbol(),
|
||
endSymbol = $interpolate.endSymbol(),
|
||
usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}')),
|
||
processTemplate = usesStandardSymbols ? angular.identity : replaceInterpolationSymbols;
|
||
|
||
return function createInterimElementService() {
|
||
/*
|
||
* @ngdoc service
|
||
* @name $$interimElement.$service
|
||
*
|
||
* @description
|
||
* A service used to control inserting and removing an element into the DOM.
|
||
*
|
||
*/
|
||
var stack = [];
|
||
var service;
|
||
return service = {
|
||
show: show,
|
||
hide: hide,
|
||
cancel: cancel
|
||
};
|
||
|
||
/*
|
||
* @ngdoc method
|
||
* @name $$interimElement.$service#show
|
||
* @kind function
|
||
*
|
||
* @description
|
||
* Adds the `$interimElement` to the DOM and returns a promise that will be resolved or rejected
|
||
* with hide or cancel, respectively.
|
||
*
|
||
* @param {*} options is hashMap of settings
|
||
* @returns a Promise
|
||
*
|
||
*/
|
||
function show(options) {
|
||
if (stack.length) {
|
||
service.cancel();
|
||
}
|
||
|
||
var interimElement = new InterimElement(options);
|
||
|
||
stack.push(interimElement);
|
||
return interimElement.show().then(function() {
|
||
return interimElement.deferred.promise;
|
||
});
|
||
}
|
||
|
||
/*
|
||
* @ngdoc method
|
||
* @name $$interimElement.$service#hide
|
||
* @kind function
|
||
*
|
||
* @description
|
||
* Removes the `$interimElement` from the DOM and resolves the promise returned from `show`
|
||
*
|
||
* @param {*} resolveParam Data to resolve the promise with
|
||
* @returns a Promise that will be resolved after the element has been removed.
|
||
*
|
||
*/
|
||
function hide(response) {
|
||
var interimElement = stack.shift();
|
||
interimElement && interimElement.remove().then(function() {
|
||
interimElement.deferred.resolve(response);
|
||
});
|
||
|
||
return interimElement ? interimElement.deferred.promise : $q.when(response);
|
||
}
|
||
|
||
/*
|
||
* @ngdoc method
|
||
* @name $$interimElement.$service#cancel
|
||
* @kind function
|
||
*
|
||
* @description
|
||
* Removes the `$interimElement` from the DOM and rejects the promise returned from `show`
|
||
*
|
||
* @param {*} reason Data to reject the promise with
|
||
* @returns Promise that will be rejected after the element has been removed.
|
||
*
|
||
*/
|
||
function cancel(reason) {
|
||
var interimElement = stack.shift();
|
||
interimElement && interimElement.remove().then(function() {
|
||
interimElement.deferred.reject(reason);
|
||
});
|
||
|
||
return interimElement ? interimElement.deferred.promise : $q.reject(reason);
|
||
}
|
||
|
||
|
||
/*
|
||
* Internal Interim Element Object
|
||
* Used internally to manage the DOM element and related data
|
||
*/
|
||
function InterimElement(options) {
|
||
var self;
|
||
var hideTimeout, element;
|
||
|
||
options = options || {};
|
||
options = angular.extend({
|
||
scope: options.scope || $rootScope.$new(options.isolateScope),
|
||
onShow: function(scope, element, options) {
|
||
return $animate.enter(element, options.parent);
|
||
},
|
||
onRemove: function(scope, element, options) {
|
||
// Element could be undefined if a new element is shown before
|
||
// the old one finishes compiling.
|
||
return element && $animate.leave(element) || $q.when();
|
||
}
|
||
}, options);
|
||
|
||
if (options.template) {
|
||
options.template = processTemplate(options.template);
|
||
}
|
||
|
||
return self = {
|
||
options: options,
|
||
deferred: $q.defer(),
|
||
show: function() {
|
||
return $mdCompiler.compile(options).then(function(compileData) {
|
||
angular.extend(compileData.locals, self.options);
|
||
|
||
// Search for parent at insertion time, if not specified
|
||
if (angular.isString(options.parent)) {
|
||
options.parent = angular.element($document[0].querySelector(options.parent));
|
||
} else if (!options.parent) {
|
||
options.parent = $rootElement.find('body');
|
||
if (!options.parent.length) options.parent = $rootElement;
|
||
}
|
||
|
||
element = compileData.link(options.scope);
|
||
if (options.themable) $mdTheming(element);
|
||
var ret = options.onShow(options.scope, element, options);
|
||
return $q.when(ret)
|
||
.then(function(){
|
||
// Issue onComplete callback when the `show()` finishes
|
||
(options.onComplete || angular.noop)(options.scope, element, options);
|
||
startHideTimeout();
|
||
});
|
||
|
||
function startHideTimeout() {
|
||
if (options.hideDelay) {
|
||
hideTimeout = $timeout(service.cancel, options.hideDelay) ;
|
||
}
|
||
}
|
||
});
|
||
},
|
||
cancelTimeout: function() {
|
||
if (hideTimeout) {
|
||
$timeout.cancel(hideTimeout);
|
||
hideTimeout = undefined;
|
||
}
|
||
},
|
||
remove: function() {
|
||
self.cancelTimeout();
|
||
var ret = options.onRemove(options.scope, element, options);
|
||
return $q.when(ret).then(function() {
|
||
options.scope.$destroy();
|
||
});
|
||
}
|
||
};
|
||
}
|
||
};
|
||
|
||
/*
|
||
* Replace `{{` and `}}` in a string (usually a template) with the actual start-/endSymbols used
|
||
* for interpolation. This allows pre-defined templates (for components such as dialog, toast etc)
|
||
* to continue to work in apps that use custom interpolation start-/endSymbols.
|
||
*
|
||
* @param {string} text The text in which to replace `{{` / `}}`
|
||
* @returns {string} The modified string using the actual interpolation start-/endSymbols
|
||
*/
|
||
function replaceInterpolationSymbols(text) {
|
||
if (!text || !angular.isString(text)) return text;
|
||
return text.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.core.componentRegistry
|
||
*
|
||
* @description
|
||
* A component instance registration service.
|
||
* Note: currently this as a private service in the SideNav component.
|
||
*/
|
||
angular.module('material.core')
|
||
.factory('$mdComponentRegistry', ComponentRegistry);
|
||
|
||
/*
|
||
* @private
|
||
* @ngdoc factory
|
||
* @name ComponentRegistry
|
||
* @module material.core.componentRegistry
|
||
*
|
||
*/
|
||
function ComponentRegistry($log, $q) {
|
||
|
||
var self;
|
||
var instances = [ ];
|
||
var pendings = { };
|
||
|
||
return self = {
|
||
/**
|
||
* Used to print an error when an instance for a handle isn't found.
|
||
*/
|
||
notFoundError: function(handle) {
|
||
$log.error('No instance found for handle', handle);
|
||
},
|
||
/**
|
||
* Return all registered instances as an array.
|
||
*/
|
||
getInstances: function() {
|
||
return instances;
|
||
},
|
||
|
||
/**
|
||
* Get a registered instance.
|
||
* @param handle the String handle to look up for a registered instance.
|
||
*/
|
||
get: function(handle) {
|
||
if ( !isValidID(handle) ) return null;
|
||
|
||
var i, j, instance;
|
||
for(i = 0, j = instances.length; i < j; i++) {
|
||
instance = instances[i];
|
||
if(instance.$$mdHandle === handle) {
|
||
return instance;
|
||
}
|
||
}
|
||
return null;
|
||
},
|
||
|
||
/**
|
||
* Register an instance.
|
||
* @param instance the instance to register
|
||
* @param handle the handle to identify the instance under.
|
||
*/
|
||
register: function(instance, handle) {
|
||
if ( !handle ) return angular.noop;
|
||
|
||
instance.$$mdHandle = handle;
|
||
instances.push(instance);
|
||
resolveWhen();
|
||
|
||
return deregister;
|
||
|
||
/**
|
||
* Remove registration for an instance
|
||
*/
|
||
function deregister() {
|
||
var index = instances.indexOf(instance);
|
||
if (index !== -1) {
|
||
instances.splice(index, 1);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Resolve any pending promises for this instance
|
||
*/
|
||
function resolveWhen() {
|
||
var dfd = pendings[handle];
|
||
if ( dfd ) {
|
||
dfd.resolve( instance );
|
||
delete pendings[handle];
|
||
}
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Async accessor to registered component instance
|
||
* If not available then a promise is created to notify
|
||
* all listeners when the instance is registered.
|
||
*/
|
||
when : function(handle) {
|
||
if ( isValidID(handle) ) {
|
||
var deferred = $q.defer();
|
||
var instance = self.get(handle);
|
||
|
||
if ( instance ) {
|
||
deferred.resolve( instance );
|
||
} else {
|
||
pendings[handle] = deferred;
|
||
}
|
||
|
||
return deferred.promise;
|
||
}
|
||
return $q.reject("Invalid `md-component-id` value.");
|
||
}
|
||
|
||
};
|
||
|
||
function isValidID(handle){
|
||
return handle && (handle !== "");
|
||
}
|
||
|
||
}
|
||
ComponentRegistry.$inject = ["$log", "$q"];
|
||
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.core')
|
||
.factory('$mdInkRipple', InkRippleService)
|
||
.directive('mdInkRipple', InkRippleDirective)
|
||
.directive('mdNoInk', attrNoDirective())
|
||
.directive('mdNoBar', attrNoDirective())
|
||
.directive('mdNoStretch', attrNoDirective());
|
||
|
||
function InkRippleDirective($mdInkRipple) {
|
||
return {
|
||
controller: angular.noop,
|
||
link: function (scope, element, attr) {
|
||
if (attr.hasOwnProperty('mdInkRippleCheckbox')) {
|
||
$mdInkRipple.attachCheckboxBehavior(scope, element);
|
||
} else {
|
||
$mdInkRipple.attachButtonBehavior(scope, element);
|
||
}
|
||
}
|
||
};
|
||
}
|
||
InkRippleDirective.$inject = ["$mdInkRipple"];
|
||
|
||
function InkRippleService($window, $timeout) {
|
||
|
||
return {
|
||
attachButtonBehavior: attachButtonBehavior,
|
||
attachCheckboxBehavior: attachCheckboxBehavior,
|
||
attachTabBehavior: attachTabBehavior,
|
||
attach: attach
|
||
};
|
||
|
||
function attachButtonBehavior(scope, element, options) {
|
||
return attach(scope, element, angular.extend({
|
||
isFAB: element.hasClass('md-fab'),
|
||
isMenuItem: element.hasClass('md-menu-item'),
|
||
center: false,
|
||
dimBackground: true
|
||
}, options));
|
||
}
|
||
|
||
function attachCheckboxBehavior(scope, element, options) {
|
||
return attach(scope, element, angular.extend({
|
||
center: true,
|
||
dimBackground: false,
|
||
fitRipple: true
|
||
}, options));
|
||
}
|
||
|
||
function attachTabBehavior(scope, element, options) {
|
||
return attach(scope, element, angular.extend({
|
||
center: false,
|
||
dimBackground: true,
|
||
outline: true
|
||
}, options));
|
||
}
|
||
|
||
function attach(scope, element, options) {
|
||
if (element.controller('mdNoInk')) return angular.noop;
|
||
|
||
options = angular.extend({
|
||
colorElement: element,
|
||
mousedown: true,
|
||
hover: true,
|
||
focus: true,
|
||
center: false,
|
||
mousedownPauseTime: 150,
|
||
dimBackground: false,
|
||
outline: false,
|
||
isFAB: false,
|
||
isMenuItem: false,
|
||
fitRipple: false
|
||
}, options);
|
||
|
||
var rippleSize,
|
||
controller = element.controller('mdInkRipple') || {},
|
||
counter = 0,
|
||
ripples = [],
|
||
states = [],
|
||
isActiveExpr = element.attr('md-highlight'),
|
||
isActive = false,
|
||
isHeld = false,
|
||
node = element[0],
|
||
rippleSizeSetting = element.attr('md-ripple-size'),
|
||
color = parseColor(element.attr('md-ink-ripple')) || parseColor($window.getComputedStyle(options.colorElement[0]).color || 'rgb(0, 0, 0)');
|
||
|
||
switch (rippleSizeSetting) {
|
||
case 'full':
|
||
options.isFAB = true;
|
||
break;
|
||
case 'partial':
|
||
options.isFAB = false;
|
||
break;
|
||
}
|
||
|
||
// expose onInput for ripple testing
|
||
if (options.mousedown) {
|
||
element.on('$md.pressdown', onPressDown)
|
||
.on('$md.pressup', onPressUp);
|
||
}
|
||
|
||
controller.createRipple = createRipple;
|
||
|
||
if (isActiveExpr) {
|
||
scope.$watch(isActiveExpr, function watchActive(newValue) {
|
||
isActive = newValue;
|
||
if (isActive && !ripples.length) {
|
||
$timeout(function () { createRipple(0, 0); }, 0, false);
|
||
}
|
||
angular.forEach(ripples, updateElement);
|
||
});
|
||
}
|
||
|
||
// Publish self-detach method if desired...
|
||
return function detach() {
|
||
element.off('$md.pressdown', onPressDown)
|
||
.off('$md.pressup', onPressUp);
|
||
getRippleContainer().remove();
|
||
};
|
||
|
||
/**
|
||
* Gets the current ripple container
|
||
* If there is no ripple container, it creates one and returns it
|
||
*
|
||
* @returns {angular.element} ripple container element
|
||
*/
|
||
function getRippleContainer() {
|
||
var container = element.data('$mdRippleContainer');
|
||
if (container) return container;
|
||
container = angular.element('<div class="md-ripple-container">');
|
||
element.append(container);
|
||
element.data('$mdRippleContainer', container);
|
||
return container;
|
||
}
|
||
|
||
function parseColor(color) {
|
||
if (!color) return;
|
||
if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, '0.1)');
|
||
if (color.indexOf('rgb') === 0) return rgbToRGBA(color);
|
||
if (color.indexOf('#') === 0) return hexToRGBA(color);
|
||
|
||
/**
|
||
* Converts a hex value to an rgba string
|
||
*
|
||
* @param {string} hex value (3 or 6 digits) to be converted
|
||
*
|
||
* @returns {string} rgba color with 0.1 alpha
|
||
*/
|
||
function hexToRGBA(color) {
|
||
var hex = color.charAt(0) === '#' ? color.substr(1) : color,
|
||
dig = hex.length / 3,
|
||
red = hex.substr(0, dig),
|
||
grn = hex.substr(dig, dig),
|
||
blu = hex.substr(dig * 2);
|
||
if (dig === 1) {
|
||
red += red;
|
||
grn += grn;
|
||
blu += blu;
|
||
}
|
||
return 'rgba(' + parseInt(red, 16) + ',' + parseInt(grn, 16) + ',' + parseInt(blu, 16) + ',0.1)';
|
||
}
|
||
|
||
/**
|
||
* Converts rgb value to rgba string
|
||
*
|
||
* @param {string} rgb color string
|
||
*
|
||
* @returns {string} rgba color with 0.1 alpha
|
||
*/
|
||
function rgbToRGBA(color) {
|
||
return color.replace(')', ', 0.1)').replace('(', 'a(');
|
||
}
|
||
|
||
}
|
||
|
||
function removeElement(elem, wait) {
|
||
ripples.splice(ripples.indexOf(elem), 1);
|
||
if (ripples.length === 0) {
|
||
getRippleContainer().css({ backgroundColor: '' });
|
||
}
|
||
$timeout(function () { elem.remove(); }, wait, false);
|
||
}
|
||
|
||
function updateElement(elem) {
|
||
var index = ripples.indexOf(elem),
|
||
state = states[index] || {},
|
||
elemIsActive = ripples.length > 1 ? false : isActive,
|
||
elemIsHeld = ripples.length > 1 ? false : isHeld;
|
||
if (elemIsActive || state.animating || elemIsHeld) {
|
||
elem.addClass('md-ripple-visible');
|
||
} else if (elem) {
|
||
elem.removeClass('md-ripple-visible');
|
||
if (options.outline) {
|
||
elem.css({
|
||
width: rippleSize + 'px',
|
||
height: rippleSize + 'px',
|
||
marginLeft: (rippleSize * -1) + 'px',
|
||
marginTop: (rippleSize * -1) + 'px'
|
||
});
|
||
}
|
||
removeElement(elem, options.outline ? 450 : 650);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Creates a ripple at the provided coordinates
|
||
*
|
||
* @param {number} left cursor position
|
||
* @param {number} top cursor position
|
||
*
|
||
* @returns {angular.element} the generated ripple element
|
||
*/
|
||
function createRipple(left, top) {
|
||
|
||
color = parseColor(element.attr('md-ink-ripple')) || parseColor($window.getComputedStyle(options.colorElement[0]).color || 'rgb(0, 0, 0)');
|
||
|
||
var container = getRippleContainer(),
|
||
size = getRippleSize(left, top),
|
||
css = getRippleCss(size, left, top),
|
||
elem = getRippleElement(css),
|
||
index = ripples.indexOf(elem),
|
||
state = states[index] || {};
|
||
|
||
rippleSize = size;
|
||
|
||
state.animating = true;
|
||
|
||
$timeout(function () {
|
||
if (options.dimBackground) {
|
||
container.css({ backgroundColor: color });
|
||
}
|
||
elem.addClass('md-ripple-placed md-ripple-scaled');
|
||
if (options.outline) {
|
||
elem.css({
|
||
borderWidth: (size * 0.5) + 'px',
|
||
marginLeft: (size * -0.5) + 'px',
|
||
marginTop: (size * -0.5) + 'px'
|
||
});
|
||
} else {
|
||
elem.css({ left: '50%', top: '50%' });
|
||
}
|
||
updateElement(elem);
|
||
$timeout(function () {
|
||
state.animating = false;
|
||
updateElement(elem);
|
||
}, (options.outline ? 450 : 225), false);
|
||
}, 0, false);
|
||
|
||
return elem;
|
||
|
||
/**
|
||
* Creates the ripple element with the provided css
|
||
*
|
||
* @param {object} css properties to be applied
|
||
*
|
||
* @returns {angular.element} the generated ripple element
|
||
*/
|
||
function getRippleElement(css) {
|
||
var elem = angular.element('<div class="md-ripple" data-counter="' + counter++ + '">');
|
||
ripples.unshift(elem);
|
||
states.unshift({ animating: true });
|
||
container.append(elem);
|
||
css && elem.css(css);
|
||
return elem;
|
||
}
|
||
|
||
/**
|
||
* Calculate the ripple size
|
||
*
|
||
* @returns {number} calculated ripple diameter
|
||
*/
|
||
function getRippleSize(left, top) {
|
||
var width = container.prop('offsetWidth'),
|
||
height = container.prop('offsetHeight'),
|
||
multiplier, size, rect;
|
||
if (options.isMenuItem) {
|
||
size = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||
} else if (options.outline) {
|
||
rect = node.getBoundingClientRect();
|
||
left -= rect.left;
|
||
top -= rect.top;
|
||
width = Math.max(left, width - left);
|
||
height = Math.max(top, height - top);
|
||
size = 2 * Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||
} else {
|
||
multiplier = options.isFAB ? 1.1 : 0.8;
|
||
size = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) * multiplier;
|
||
if (options.fitRipple) {
|
||
size = Math.min(height, width, size);
|
||
}
|
||
}
|
||
return size;
|
||
}
|
||
|
||
/**
|
||
* Generates the ripple css
|
||
*
|
||
* @param {number} the diameter of the ripple
|
||
* @param {number} the left cursor offset
|
||
* @param {number} the top cursor offset
|
||
*
|
||
* @returns {{backgroundColor: *, width: string, height: string, marginLeft: string, marginTop: string}}
|
||
*/
|
||
function getRippleCss(size, left, top) {
|
||
var rect,
|
||
css = {
|
||
backgroundColor: rgbaToRGB(color),
|
||
borderColor: rgbaToRGB(color),
|
||
width: size + 'px',
|
||
height: size + 'px'
|
||
};
|
||
|
||
if (options.outline) {
|
||
css.width = 0;
|
||
css.height = 0;
|
||
} else {
|
||
css.marginLeft = css.marginTop = (size * -0.5) + 'px';
|
||
}
|
||
|
||
if (options.center) {
|
||
css.left = css.top = '50%';
|
||
} else {
|
||
rect = node.getBoundingClientRect();
|
||
css.left = Math.round((left - rect.left) / container.prop('offsetWidth') * 100) + '%';
|
||
css.top = Math.round((top - rect.top) / container.prop('offsetHeight') * 100) + '%';
|
||
}
|
||
|
||
return css;
|
||
|
||
/**
|
||
* Converts rgba string to rgb, removing the alpha value
|
||
*
|
||
* @param {string} rgba color
|
||
*
|
||
* @returns {string} rgb color
|
||
*/
|
||
function rgbaToRGB(color) {
|
||
return color.replace('rgba', 'rgb').replace(/,[^\)\,]+\)/, ')');
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handles user input start and stop events
|
||
*
|
||
*/
|
||
function onPressDown(ev) {
|
||
if (!isRippleAllowed()) return;
|
||
|
||
var ripple = createRipple(ev.pointer.x, ev.pointer.y);
|
||
isHeld = true;
|
||
}
|
||
function onPressUp(ev) {
|
||
isHeld = false;
|
||
var ripple = ripples[ ripples.length - 1 ];
|
||
$timeout(function () { updateElement(ripple); }, 0, false);
|
||
}
|
||
|
||
/**
|
||
* Determines if the ripple is allowed
|
||
*
|
||
* @returns {boolean} true if the ripple is allowed, false if not
|
||
*/
|
||
function isRippleAllowed() {
|
||
var parent = node.parentNode;
|
||
var grandparent = parent && parent.parentNode;
|
||
var ancestor = grandparent && grandparent.parentNode;
|
||
return !isDisabled(node) && !isDisabled(parent) && !isDisabled(grandparent) && !isDisabled(ancestor);
|
||
function isDisabled (elem) {
|
||
return elem && elem.hasAttribute && elem.hasAttribute('disabled');
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
InkRippleService.$inject = ["$window", "$timeout"];
|
||
|
||
/**
|
||
* noink/nobar/nostretch directive: make any element that has one of
|
||
* these attributes be given a controller, so that other directives can
|
||
* `require:` these and see if there is a `no<xxx>` parent attribute.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <parent md-no-ink>
|
||
* <child detect-no>
|
||
* </child>
|
||
* </parent>
|
||
* </hljs>
|
||
*
|
||
* <hljs lang="js">
|
||
* myApp.directive('detectNo', function() {
|
||
* return {
|
||
* require: ['^?mdNoInk', ^?mdNoBar'],
|
||
* link: function(scope, element, attr, ctrls) {
|
||
* var noinkCtrl = ctrls[0];
|
||
* var nobarCtrl = ctrls[1];
|
||
* if (noInkCtrl) {
|
||
* alert("the md-no-ink flag has been specified on an ancestor!");
|
||
* }
|
||
* if (nobarCtrl) {
|
||
* alert("the md-no-bar flag has been specified on an ancestor!");
|
||
* }
|
||
* }
|
||
* };
|
||
* });
|
||
* </hljs>
|
||
*/
|
||
function attrNoDirective() {
|
||
return function() {
|
||
return {
|
||
controller: angular.noop
|
||
};
|
||
};
|
||
}
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.core.theming.palette', [])
|
||
.constant('$mdColorPalette', {
|
||
'red': {
|
||
'50': '#ffebee',
|
||
'100': '#ffcdd2',
|
||
'200': '#ef9a9a',
|
||
'300': '#e57373',
|
||
'400': '#ef5350',
|
||
'500': '#f44336',
|
||
'600': '#e53935',
|
||
'700': '#d32f2f',
|
||
'800': '#c62828',
|
||
'900': '#b71c1c',
|
||
'A100': '#ff8a80',
|
||
'A200': '#ff5252',
|
||
'A400': '#ff1744',
|
||
'A700': '#d50000',
|
||
'contrastDefaultColor': 'light',
|
||
'contrastDarkColors': '50 100 200 300 400 A100',
|
||
'contrastStrongLightColors': '500 600 700 A200 A400 A700'
|
||
},
|
||
'pink': {
|
||
'50': '#fce4ec',
|
||
'100': '#f8bbd0',
|
||
'200': '#f48fb1',
|
||
'300': '#f06292',
|
||
'400': '#ec407a',
|
||
'500': '#e91e63',
|
||
'600': '#d81b60',
|
||
'700': '#c2185b',
|
||
'800': '#ad1457',
|
||
'900': '#880e4f',
|
||
'A100': '#ff80ab',
|
||
'A200': '#ff4081',
|
||
'A400': '#f50057',
|
||
'A700': '#c51162',
|
||
'contrastDefaultColor': 'light',
|
||
'contrastDarkColors': '50 100 200 300 400 A100',
|
||
'contrastStrongLightColors': '500 600 A200 A400 A700'
|
||
},
|
||
'purple': {
|
||
'50': '#f3e5f5',
|
||
'100': '#e1bee7',
|
||
'200': '#ce93d8',
|
||
'300': '#ba68c8',
|
||
'400': '#ab47bc',
|
||
'500': '#9c27b0',
|
||
'600': '#8e24aa',
|
||
'700': '#7b1fa2',
|
||
'800': '#6a1b9a',
|
||
'900': '#4a148c',
|
||
'A100': '#ea80fc',
|
||
'A200': '#e040fb',
|
||
'A400': '#d500f9',
|
||
'A700': '#aa00ff',
|
||
'contrastDefaultColor': 'light',
|
||
'contrastDarkColors': '50 100 200 A100',
|
||
'contrastStrongLightColors': '300 400 A200 A400 A700'
|
||
},
|
||
'deep-purple': {
|
||
'50': '#ede7f6',
|
||
'100': '#d1c4e9',
|
||
'200': '#b39ddb',
|
||
'300': '#9575cd',
|
||
'400': '#7e57c2',
|
||
'500': '#673ab7',
|
||
'600': '#5e35b1',
|
||
'700': '#512da8',
|
||
'800': '#4527a0',
|
||
'900': '#311b92',
|
||
'A100': '#b388ff',
|
||
'A200': '#7c4dff',
|
||
'A400': '#651fff',
|
||
'A700': '#6200ea',
|
||
'contrastDefaultColor': 'light',
|
||
'contrastDarkColors': '50 100 200 A100',
|
||
'contrastStrongLightColors': '300 400 A200'
|
||
},
|
||
'indigo': {
|
||
'50': '#e8eaf6',
|
||
'100': '#c5cae9',
|
||
'200': '#9fa8da',
|
||
'300': '#7986cb',
|
||
'400': '#5c6bc0',
|
||
'500': '#3f51b5',
|
||
'600': '#3949ab',
|
||
'700': '#303f9f',
|
||
'800': '#283593',
|
||
'900': '#1a237e',
|
||
'A100': '#8c9eff',
|
||
'A200': '#536dfe',
|
||
'A400': '#3d5afe',
|
||
'A700': '#304ffe',
|
||
'contrastDefaultColor': 'light',
|
||
'contrastDarkColors': '50 100 200 A100',
|
||
'contrastStrongLightColors': '300 400 A200 A400'
|
||
},
|
||
'blue': {
|
||
'50': '#e3f2fd',
|
||
'100': '#bbdefb',
|
||
'200': '#90caf9',
|
||
'300': '#64b5f6',
|
||
'400': '#42a5f5',
|
||
'500': '#2196f3',
|
||
'600': '#1e88e5',
|
||
'700': '#1976d2',
|
||
'800': '#1565c0',
|
||
'900': '#0d47a1',
|
||
'A100': '#82b1ff',
|
||
'A200': '#448aff',
|
||
'A400': '#2979ff',
|
||
'A700': '#2962ff',
|
||
'contrastDefaultColor': 'light',
|
||
'contrastDarkColors': '100 200 300 400 A100',
|
||
'contrastStrongLightColors': '500 600 700 A200 A400 A700'
|
||
},
|
||
'light-blue': {
|
||
'50': '#e1f5fe',
|
||
'100': '#b3e5fc',
|
||
'200': '#81d4fa',
|
||
'300': '#4fc3f7',
|
||
'400': '#29b6f6',
|
||
'500': '#03a9f4',
|
||
'600': '#039be5',
|
||
'700': '#0288d1',
|
||
'800': '#0277bd',
|
||
'900': '#01579b',
|
||
'A100': '#80d8ff',
|
||
'A200': '#40c4ff',
|
||
'A400': '#00b0ff',
|
||
'A700': '#0091ea',
|
||
'contrastDefaultColor': 'dark',
|
||
'contrastLightColors': '500 600 700 800 900 A700',
|
||
'contrastStrongLightColors': '500 600 700 800 A700'
|
||
},
|
||
'cyan': {
|
||
'50': '#e0f7fa',
|
||
'100': '#b2ebf2',
|
||
'200': '#80deea',
|
||
'300': '#4dd0e1',
|
||
'400': '#26c6da',
|
||
'500': '#00bcd4',
|
||
'600': '#00acc1',
|
||
'700': '#0097a7',
|
||
'800': '#00838f',
|
||
'900': '#006064',
|
||
'A100': '#84ffff',
|
||
'A200': '#18ffff',
|
||
'A400': '#00e5ff',
|
||
'A700': '#00b8d4',
|
||
'contrastDefaultColor': 'dark',
|
||
'contrastLightColors': '500 600 700 800 900',
|
||
'contrastStrongLightColors': '500 600 700 800'
|
||
},
|
||
'teal': {
|
||
'50': '#e0f2f1',
|
||
'100': '#b2dfdb',
|
||
'200': '#80cbc4',
|
||
'300': '#4db6ac',
|
||
'400': '#26a69a',
|
||
'500': '#009688',
|
||
'600': '#00897b',
|
||
'700': '#00796b',
|
||
'800': '#00695c',
|
||
'900': '#004d40',
|
||
'A100': '#a7ffeb',
|
||
'A200': '#64ffda',
|
||
'A400': '#1de9b6',
|
||
'A700': '#00bfa5',
|
||
'contrastDefaultColor': 'dark',
|
||
'contrastLightColors': '500 600 700 800 900',
|
||
'contrastStrongLightColors': '500 600 700'
|
||
},
|
||
'green': {
|
||
'50': '#e8f5e9',
|
||
'100': '#c8e6c9',
|
||
'200': '#a5d6a7',
|
||
'300': '#81c784',
|
||
'400': '#66bb6a',
|
||
'500': '#4caf50',
|
||
'600': '#43a047',
|
||
'700': '#388e3c',
|
||
'800': '#2e7d32',
|
||
'900': '#1b5e20',
|
||
'A100': '#b9f6ca',
|
||
'A200': '#69f0ae',
|
||
'A400': '#00e676',
|
||
'A700': '#00c853',
|
||
'contrastDefaultColor': 'dark',
|
||
'contrastLightColors': '500 600 700 800 900',
|
||
'contrastStrongLightColors': '500 600 700'
|
||
},
|
||
'light-green': {
|
||
'50': '#f1f8e9',
|
||
'100': '#dcedc8',
|
||
'200': '#c5e1a5',
|
||
'300': '#aed581',
|
||
'400': '#9ccc65',
|
||
'500': '#8bc34a',
|
||
'600': '#7cb342',
|
||
'700': '#689f38',
|
||
'800': '#558b2f',
|
||
'900': '#33691e',
|
||
'A100': '#ccff90',
|
||
'A200': '#b2ff59',
|
||
'A400': '#76ff03',
|
||
'A700': '#64dd17',
|
||
'contrastDefaultColor': 'dark',
|
||
'contrastLightColors': '800 900',
|
||
'contrastStrongLightColors': '800 900'
|
||
},
|
||
'lime': {
|
||
'50': '#f9fbe7',
|
||
'100': '#f0f4c3',
|
||
'200': '#e6ee9c',
|
||
'300': '#dce775',
|
||
'400': '#d4e157',
|
||
'500': '#cddc39',
|
||
'600': '#c0ca33',
|
||
'700': '#afb42b',
|
||
'800': '#9e9d24',
|
||
'900': '#827717',
|
||
'A100': '#f4ff81',
|
||
'A200': '#eeff41',
|
||
'A400': '#c6ff00',
|
||
'A700': '#aeea00',
|
||
'contrastDefaultColor': 'dark',
|
||
'contrastLightColors': '900',
|
||
'contrastStrongLightColors': '900'
|
||
},
|
||
'yellow': {
|
||
'50': '#fffde7',
|
||
'100': '#fff9c4',
|
||
'200': '#fff59d',
|
||
'300': '#fff176',
|
||
'400': '#ffee58',
|
||
'500': '#ffeb3b',
|
||
'600': '#fdd835',
|
||
'700': '#fbc02d',
|
||
'800': '#f9a825',
|
||
'900': '#f57f17',
|
||
'A100': '#ffff8d',
|
||
'A200': '#ffff00',
|
||
'A400': '#ffea00',
|
||
'A700': '#ffd600',
|
||
'contrastDefaultColor': 'dark'
|
||
},
|
||
'amber': {
|
||
'50': '#fff8e1',
|
||
'100': '#ffecb3',
|
||
'200': '#ffe082',
|
||
'300': '#ffd54f',
|
||
'400': '#ffca28',
|
||
'500': '#ffc107',
|
||
'600': '#ffb300',
|
||
'700': '#ffa000',
|
||
'800': '#ff8f00',
|
||
'900': '#ff6f00',
|
||
'A100': '#ffe57f',
|
||
'A200': '#ffd740',
|
||
'A400': '#ffc400',
|
||
'A700': '#ffab00',
|
||
'contrastDefaultColor': 'dark'
|
||
},
|
||
'orange': {
|
||
'50': '#fff3e0',
|
||
'100': '#ffe0b2',
|
||
'200': '#ffcc80',
|
||
'300': '#ffb74d',
|
||
'400': '#ffa726',
|
||
'500': '#ff9800',
|
||
'600': '#fb8c00',
|
||
'700': '#f57c00',
|
||
'800': '#ef6c00',
|
||
'900': '#e65100',
|
||
'A100': '#ffd180',
|
||
'A200': '#ffab40',
|
||
'A400': '#ff9100',
|
||
'A700': '#ff6d00',
|
||
'contrastDefaultColor': 'dark',
|
||
'contrastLightColors': '800 900',
|
||
'contrastStrongLightColors': '800 900'
|
||
},
|
||
'deep-orange': {
|
||
'50': '#fbe9e7',
|
||
'100': '#ffccbc',
|
||
'200': '#ffab91',
|
||
'300': '#ff8a65',
|
||
'400': '#ff7043',
|
||
'500': '#ff5722',
|
||
'600': '#f4511e',
|
||
'700': '#e64a19',
|
||
'800': '#d84315',
|
||
'900': '#bf360c',
|
||
'A100': '#ff9e80',
|
||
'A200': '#ff6e40',
|
||
'A400': '#ff3d00',
|
||
'A700': '#dd2c00',
|
||
'contrastDefaultColor': 'light',
|
||
'contrastDarkColors': '50 100 200 300 400 A100 A200',
|
||
'contrastStrongLightColors': '500 600 700 800 900 A400 A700'
|
||
},
|
||
'brown': {
|
||
'50': '#efebe9',
|
||
'100': '#d7ccc8',
|
||
'200': '#bcaaa4',
|
||
'300': '#a1887f',
|
||
'400': '#8d6e63',
|
||
'500': '#795548',
|
||
'600': '#6d4c41',
|
||
'700': '#5d4037',
|
||
'800': '#4e342e',
|
||
'900': '#3e2723',
|
||
'A100': '#d7ccc8',
|
||
'A200': '#bcaaa4',
|
||
'A400': '#8d6e63',
|
||
'A700': '#5d4037',
|
||
'contrastDefaultColor': 'light',
|
||
'contrastDarkColors': '50 100 200',
|
||
'contrastStrongLightColors': '300 400'
|
||
},
|
||
'grey': {
|
||
'0': '#ffffff',
|
||
'50': '#fafafa',
|
||
'100': '#f5f5f5',
|
||
'200': '#eeeeee',
|
||
'300': '#e0e0e0',
|
||
'400': '#bdbdbd',
|
||
'500': '#9e9e9e',
|
||
'600': '#757575',
|
||
'700': '#616161',
|
||
'800': '#424242',
|
||
'900': '#212121',
|
||
'1000': '#000000',
|
||
'A100': '#ffffff',
|
||
'A200': '#eeeeee',
|
||
'A400': '#bdbdbd',
|
||
'A700': '#616161',
|
||
'contrastDefaultColor': 'dark',
|
||
'contrastLightColors': '600 700 800 900'
|
||
},
|
||
'blue-grey': {
|
||
'50': '#eceff1',
|
||
'100': '#cfd8dc',
|
||
'200': '#b0bec5',
|
||
'300': '#90a4ae',
|
||
'400': '#78909c',
|
||
'500': '#607d8b',
|
||
'600': '#546e7a',
|
||
'700': '#455a64',
|
||
'800': '#37474f',
|
||
'900': '#263238',
|
||
'A100': '#cfd8dc',
|
||
'A200': '#b0bec5',
|
||
'A400': '#78909c',
|
||
'A700': '#455a64',
|
||
'contrastDefaultColor': 'light',
|
||
'contrastDarkColors': '50 100 200 300',
|
||
'contrastStrongLightColors': '400 500'
|
||
}
|
||
});
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.core.theming', ['material.core.theming.palette'])
|
||
.directive('mdTheme', ThemingDirective)
|
||
.directive('mdThemable', ThemableDirective)
|
||
.provider('$mdTheming', ThemingProvider)
|
||
.run(generateThemes);
|
||
|
||
/**
|
||
* @ngdoc provider
|
||
* @name $mdThemingProvider
|
||
* @module material.core
|
||
*
|
||
* @description Provider to configure the `$mdTheming` service.
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdThemingProvider#setDefaultTheme
|
||
* @param {string} themeName Default theme name to be applied to elements. Default value is `default`.
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdThemingProvider#alwaysWatchTheme
|
||
* @param {boolean} watch Whether or not to always watch themes for changes and re-apply
|
||
* classes when they change. Default is `false`. Enabling can reduce performance.
|
||
*/
|
||
|
||
// In memory storage of defined themes and color palettes (both loaded by CSS, and user specified)
|
||
var PALETTES;
|
||
var THEMES;
|
||
var themingProvider;
|
||
var generationIsDone;
|
||
|
||
var DARK_FOREGROUND = {
|
||
name: 'dark',
|
||
'1': 'rgba(0,0,0,0.87)',
|
||
'2': 'rgba(0,0,0,0.54)',
|
||
'3': 'rgba(0,0,0,0.26)',
|
||
'4': 'rgba(0,0,0,0.12)'
|
||
};
|
||
var LIGHT_FOREGROUND = {
|
||
name: 'light',
|
||
'1': 'rgba(255,255,255,1.0)',
|
||
'2': 'rgba(255,255,255,0.7)',
|
||
'3': 'rgba(255,255,255,0.3)',
|
||
'4': 'rgba(255,255,255,0.12)'
|
||
};
|
||
|
||
var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)';
|
||
var LIGHT_SHADOW = '';
|
||
|
||
var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)');
|
||
var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87');
|
||
var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)');
|
||
|
||
var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background'];
|
||
var DEFAULT_COLOR_TYPE = 'primary';
|
||
|
||
// A color in a theme will use these hues by default, if not specified by user.
|
||
var LIGHT_DEFAULT_HUES = {
|
||
'accent': {
|
||
'default': 'A200',
|
||
'hue-1': 'A100',
|
||
'hue-2': 'A400',
|
||
'hue-3': 'A700'
|
||
}
|
||
};
|
||
var DARK_DEFAULT_HUES = {
|
||
'background': {
|
||
'default': '500',
|
||
'hue-1': '300',
|
||
'hue-2': '600',
|
||
'hue-3': '800'
|
||
}
|
||
};
|
||
THEME_COLOR_TYPES.forEach(function(colorType) {
|
||
// Color types with unspecified default hues will use these default hue values
|
||
var defaultDefaultHues = {
|
||
'default': '500',
|
||
'hue-1': '300',
|
||
'hue-2': '800',
|
||
'hue-3': 'A100'
|
||
};
|
||
if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues;
|
||
if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues;
|
||
});
|
||
|
||
var VALID_HUE_VALUES = [
|
||
'50', '100', '200', '300', '400', '500', '600',
|
||
'700', '800', '900', 'A100', 'A200', 'A400', 'A700'
|
||
];
|
||
|
||
function ThemingProvider($mdColorPalette) {
|
||
PALETTES = {};
|
||
THEMES = {};
|
||
var defaultTheme = 'default';
|
||
var alwaysWatchTheme = false;
|
||
|
||
// Load JS Defined Palettes
|
||
angular.extend(PALETTES, $mdColorPalette);
|
||
|
||
// Default theme defined in core.js
|
||
|
||
ThemingService.$inject = ["$rootScope", "$log"];
|
||
return themingProvider = {
|
||
definePalette: definePalette,
|
||
extendPalette: extendPalette,
|
||
theme: registerTheme,
|
||
|
||
setDefaultTheme: function(theme) {
|
||
defaultTheme = theme;
|
||
},
|
||
alwaysWatchTheme: function(alwaysWatch) {
|
||
alwaysWatchTheme = alwaysWatch;
|
||
},
|
||
$get: ThemingService,
|
||
_LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,
|
||
_DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,
|
||
_PALETTES: PALETTES,
|
||
_THEMES: THEMES,
|
||
_parseRules: parseRules,
|
||
_rgba: rgba
|
||
};
|
||
|
||
// Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... });
|
||
function definePalette(name, map) {
|
||
map = map || {};
|
||
PALETTES[name] = checkPaletteValid(name, map);
|
||
return themingProvider;
|
||
}
|
||
|
||
// Returns an new object which is a copy of a given palette `name` with variables from
|
||
// `map` overwritten
|
||
// Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' });
|
||
function extendPalette(name, map) {
|
||
return checkPaletteValid(name, angular.extend({}, PALETTES[name] || {}, map) );
|
||
}
|
||
|
||
// Make sure that palette has all required hues
|
||
function checkPaletteValid(name, map) {
|
||
var missingColors = VALID_HUE_VALUES.filter(function(field) {
|
||
return !map[field];
|
||
});
|
||
if (missingColors.length) {
|
||
throw new Error("Missing colors %1 in palette %2!"
|
||
.replace('%1', missingColors.join(', '))
|
||
.replace('%2', name));
|
||
}
|
||
|
||
return map;
|
||
}
|
||
|
||
// Register a theme (which is a collection of color palettes to use with various states
|
||
// ie. warn, accent, primary )
|
||
// Optionally inherit from an existing theme
|
||
// $mdThemingProvider.theme('custom-theme').primaryPalette('red');
|
||
function registerTheme(name, inheritFrom) {
|
||
inheritFrom = inheritFrom || 'default';
|
||
if (THEMES[name]) return THEMES[name];
|
||
|
||
var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom;
|
||
var theme = new Theme(name);
|
||
|
||
if (parentTheme) {
|
||
angular.forEach(parentTheme.colors, function(color, colorType) {
|
||
theme.colors[colorType] = {
|
||
name: color.name,
|
||
// Make sure a COPY of the hues is given to the child color,
|
||
// not the same reference.
|
||
hues: angular.extend({}, color.hues)
|
||
};
|
||
});
|
||
}
|
||
THEMES[name] = theme;
|
||
|
||
return theme;
|
||
}
|
||
|
||
function Theme(name) {
|
||
var self = this;
|
||
self.name = name;
|
||
self.colors = {};
|
||
|
||
self.dark = setDark;
|
||
setDark(false);
|
||
|
||
function setDark(isDark) {
|
||
isDark = arguments.length === 0 ? true : !!isDark;
|
||
|
||
// If no change, abort
|
||
if (isDark === self.isDark) return;
|
||
|
||
self.isDark = isDark;
|
||
|
||
self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND;
|
||
self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW;
|
||
|
||
// Light and dark themes have different default hues.
|
||
// Go through each existing color type for this theme, and for every
|
||
// hue value that is still the default hue value from the previous light/dark setting,
|
||
// set it to the default hue value from the new light/dark setting.
|
||
var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES;
|
||
var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES;
|
||
angular.forEach(newDefaultHues, function(newDefaults, colorType) {
|
||
var color = self.colors[colorType];
|
||
var oldDefaults = oldDefaultHues[colorType];
|
||
if (color) {
|
||
for (var hueName in color.hues) {
|
||
if (color.hues[hueName] === oldDefaults[hueName]) {
|
||
color.hues[hueName] = newDefaults[hueName];
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
return self;
|
||
}
|
||
|
||
THEME_COLOR_TYPES.forEach(function(colorType) {
|
||
var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType];
|
||
self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) {
|
||
var color = self.colors[colorType] = {
|
||
name: paletteName,
|
||
hues: angular.extend({}, defaultHues, hues)
|
||
};
|
||
|
||
Object.keys(color.hues).forEach(function(name) {
|
||
if (!defaultHues[name]) {
|
||
throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4"
|
||
.replace('%1', name)
|
||
.replace('%2', self.name)
|
||
.replace('%3', paletteName)
|
||
.replace('%4', Object.keys(defaultHues).join(', '))
|
||
);
|
||
}
|
||
});
|
||
Object.keys(color.hues).map(function(key) {
|
||
return color.hues[key];
|
||
}).forEach(function(hueValue) {
|
||
if (VALID_HUE_VALUES.indexOf(hueValue) == -1) {
|
||
throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5"
|
||
.replace('%1', hueValue)
|
||
.replace('%2', self.name)
|
||
.replace('%3', colorType)
|
||
.replace('%4', paletteName)
|
||
.replace('%5', VALID_HUE_VALUES.join(', '))
|
||
);
|
||
}
|
||
});
|
||
return self;
|
||
};
|
||
|
||
self[colorType + 'Color'] = function() {
|
||
var args = Array.prototype.slice.call(arguments);
|
||
console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been depricated. ' +
|
||
'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.');
|
||
return self[colorType + 'Palette'].apply(self, args);
|
||
};
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @ngdoc service
|
||
* @name $mdTheming
|
||
*
|
||
* @description
|
||
*
|
||
* Service that makes an element apply theming related classes to itself.
|
||
*
|
||
* ```js
|
||
* app.directive('myFancyDirective', function($mdTheming) {
|
||
* return {
|
||
* restrict: 'e',
|
||
* link: function(scope, el, attrs) {
|
||
* $mdTheming(el);
|
||
* }
|
||
* };
|
||
* });
|
||
* ```
|
||
* @param {el=} element to apply theming to
|
||
*/
|
||
/* @ngInject */
|
||
function ThemingService($rootScope, $log) {
|
||
applyTheme.inherit = function(el, parent) {
|
||
var ctrl = parent.controller('mdTheme');
|
||
|
||
var attrThemeValue = el.attr('md-theme-watch');
|
||
if ( (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false') {
|
||
var deregisterWatch = $rootScope.$watch(function() {
|
||
return ctrl && ctrl.$mdTheme || defaultTheme;
|
||
}, changeTheme);
|
||
el.on('$destroy', deregisterWatch);
|
||
} else {
|
||
var theme = ctrl && ctrl.$mdTheme || defaultTheme;
|
||
changeTheme(theme);
|
||
}
|
||
|
||
function changeTheme(theme) {
|
||
if (!registered(theme)) {
|
||
$log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' +
|
||
'Register it with $mdThemingProvider.theme().');
|
||
}
|
||
var oldTheme = el.data('$mdThemeName');
|
||
if (oldTheme) el.removeClass('md-' + oldTheme +'-theme');
|
||
el.addClass('md-' + theme + '-theme');
|
||
el.data('$mdThemeName', theme);
|
||
}
|
||
};
|
||
|
||
applyTheme.registered = registered;
|
||
|
||
return applyTheme;
|
||
|
||
function registered(theme) {
|
||
if (theme === undefined || theme === '') return true;
|
||
return THEMES[theme] !== undefined;
|
||
}
|
||
|
||
function applyTheme(scope, el) {
|
||
// Allow us to be invoked via a linking function signature.
|
||
if (el === undefined) {
|
||
el = scope;
|
||
scope = undefined;
|
||
}
|
||
if (scope === undefined) {
|
||
scope = $rootScope;
|
||
}
|
||
applyTheme.inherit(el, el);
|
||
}
|
||
}
|
||
}
|
||
ThemingProvider.$inject = ["$mdColorPalette"];
|
||
|
||
function ThemingDirective($mdTheming, $interpolate, $log) {
|
||
return {
|
||
priority: 100,
|
||
link: {
|
||
pre: function(scope, el, attrs) {
|
||
var ctrl = {
|
||
$setTheme: function(theme) {
|
||
if (!$mdTheming.registered(theme)) {
|
||
$log.warn('attempted to use unregistered theme \'' + theme + '\'');
|
||
}
|
||
ctrl.$mdTheme = theme;
|
||
}
|
||
};
|
||
el.data('$mdThemeController', ctrl);
|
||
ctrl.$setTheme($interpolate(attrs.mdTheme)(scope));
|
||
attrs.$observe('mdTheme', ctrl.$setTheme);
|
||
}
|
||
}
|
||
};
|
||
}
|
||
ThemingDirective.$inject = ["$mdTheming", "$interpolate", "$log"];
|
||
|
||
function ThemableDirective($mdTheming) {
|
||
return $mdTheming;
|
||
}
|
||
ThemableDirective.$inject = ["$mdTheming"];
|
||
|
||
function parseRules(theme, colorType, rules) {
|
||
checkValidPalette(theme, colorType);
|
||
|
||
rules = rules.replace(/THEME_NAME/g, theme.name);
|
||
var generatedRules = [];
|
||
var color = theme.colors[colorType];
|
||
|
||
var themeNameRegex = new RegExp('.md-' + theme.name + '-theme', 'g');
|
||
// Matches '{{ primary-color }}', etc
|
||
var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g');
|
||
var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow)-?(\d\.?\d*)?\s*\}\}'?"?/g;
|
||
var palette = PALETTES[color.name];
|
||
|
||
// find and replace simple variables where we use a specific hue, not angentire palette
|
||
// eg. "{{primary-100}}"
|
||
//\(' + THEME_COLOR_TYPES.join('\|') + '\)'
|
||
rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity) {
|
||
if (colorType === 'foreground') {
|
||
if (hue == 'shadow') {
|
||
return theme.foregroundShadow;
|
||
} else {
|
||
return theme.foregroundPalette[hue] || theme.foregroundPalette['1'];
|
||
}
|
||
}
|
||
if (hue.indexOf('hue') === 0) {
|
||
hue = theme.colors[colorType].hues[hue];
|
||
}
|
||
return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '').value, opacity );
|
||
});
|
||
|
||
// For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3)
|
||
angular.forEach(color.hues, function(hueValue, hueName) {
|
||
var newRule = rules
|
||
.replace(hueRegex, function(match, _, colorType, hueType, opacity) {
|
||
return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity);
|
||
});
|
||
if (hueName !== 'default') {
|
||
newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName);
|
||
}
|
||
generatedRules.push(newRule);
|
||
});
|
||
|
||
return generatedRules.join('');
|
||
}
|
||
|
||
// Generate our themes at run time given the state of THEMES and PALETTES
|
||
function generateThemes($injector) {
|
||
var themeCss = $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : '';
|
||
|
||
// MD_THEME_CSS is a string generated by the build process that includes all the themable
|
||
// components as templates
|
||
|
||
// Expose contrast colors for palettes to ensure that text is always readable
|
||
angular.forEach(PALETTES, sanitizePalette);
|
||
|
||
// Break the CSS into individual rules
|
||
var rules = themeCss.split(/\}(?!(\}|'|"|;))/)
|
||
.filter(function(rule) { return rule && rule.length; })
|
||
.map(function(rule) { return rule.trim() + '}'; });
|
||
|
||
var rulesByType = {};
|
||
THEME_COLOR_TYPES.forEach(function(type) {
|
||
rulesByType[type] = '';
|
||
});
|
||
var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g');
|
||
|
||
// Sort the rules based on type, allowing us to do color substitution on a per-type basis
|
||
rules.forEach(function(rule) {
|
||
var match = rule.match(ruleMatchRegex);
|
||
// First: test that if the rule has '.md-accent', it goes into the accent set of rules
|
||
for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) {
|
||
if (rule.indexOf('.md-' + type) > -1) {
|
||
return rulesByType[type] += rule;
|
||
}
|
||
}
|
||
|
||
// If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from
|
||
// there
|
||
for (i = 0; type = THEME_COLOR_TYPES[i]; i++) {
|
||
if (rule.indexOf(type) > -1) {
|
||
return rulesByType[type] += rule;
|
||
}
|
||
}
|
||
|
||
// Default to the primary array
|
||
return rulesByType[DEFAULT_COLOR_TYPE] += rule;
|
||
});
|
||
|
||
var styleString = '';
|
||
|
||
// For each theme, use the color palettes specified for `primary`, `warn` and `accent`
|
||
// to generate CSS rules.
|
||
angular.forEach(THEMES, function(theme) {
|
||
THEME_COLOR_TYPES.forEach(function(colorType) {
|
||
styleString += parseRules(theme, colorType, rulesByType[colorType] + '');
|
||
});
|
||
if (theme.colors.primary.name == theme.colors.accent.name) {
|
||
console.warn("$mdThemingProvider: Using the same palette for primary and" +
|
||
"accent. This violates the material design spec.");
|
||
}
|
||
});
|
||
|
||
// Insert our newly minted styles into the DOM
|
||
if (!generationIsDone) {
|
||
var style = document.createElement('style');
|
||
style.innerHTML = styleString;
|
||
var head = document.getElementsByTagName('head')[0];
|
||
head.insertBefore(style, head.firstElementChild);
|
||
generationIsDone = true;
|
||
}
|
||
|
||
// The user specifies a 'default' contrast color as either light or dark,
|
||
// then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light)
|
||
function sanitizePalette(palette) {
|
||
var defaultContrast = palette.contrastDefaultColor;
|
||
var lightColors = palette.contrastLightColors || [];
|
||
var strongLightColors = palette.contrastStrongLightColors || [];
|
||
var darkColors = palette.contrastDarkColors || [];
|
||
|
||
// These colors are provided as space-separated lists
|
||
if (typeof lightColors === 'string') lightColors = lightColors.split(' ');
|
||
if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' ');
|
||
if (typeof darkColors === 'string') darkColors = darkColors.split(' ');
|
||
|
||
// Cleanup after ourselves
|
||
delete palette.contrastDefaultColor;
|
||
delete palette.contrastLightColors;
|
||
delete palette.contrastStrongLightColors;
|
||
delete palette.contrastDarkColors;
|
||
|
||
// Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR }
|
||
angular.forEach(palette, function(hueValue, hueName) {
|
||
if (angular.isObject(hueValue)) return; // Already converted
|
||
// Map everything to rgb colors
|
||
var rgbValue = colorToRgbaArray(hueValue);
|
||
if (!rgbValue) {
|
||
throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected."
|
||
.replace('%1', hueValue)
|
||
.replace('%2', palette.name)
|
||
.replace('%3', hueName));
|
||
}
|
||
|
||
palette[hueName] = {
|
||
value: rgbValue,
|
||
contrast: getContrastColor()
|
||
};
|
||
function getContrastColor() {
|
||
if (defaultContrast === 'light') {
|
||
if (darkColors.indexOf(hueName) > -1) {
|
||
return DARK_CONTRAST_COLOR;
|
||
} else {
|
||
return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
|
||
: LIGHT_CONTRAST_COLOR;
|
||
}
|
||
} else {
|
||
if (lightColors.indexOf(hueName) > -1) {
|
||
return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR
|
||
: LIGHT_CONTRAST_COLOR;
|
||
} else {
|
||
return DARK_CONTRAST_COLOR;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
}
|
||
generateThemes.$inject = ["$injector"];
|
||
|
||
function checkValidPalette(theme, colorType) {
|
||
// If theme attempts to use a palette that doesnt exist, throw error
|
||
if (!PALETTES[ (theme.colors[colorType] || {}).name ]) {
|
||
throw new Error(
|
||
"You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3"
|
||
.replace('%1', theme.name)
|
||
.replace('%2', colorType)
|
||
.replace('%3', Object.keys(PALETTES).join(', '))
|
||
);
|
||
}
|
||
}
|
||
|
||
function colorToRgbaArray(clr) {
|
||
if (angular.isArray(clr) && clr.length == 3) return clr;
|
||
if (/^rgb/.test(clr)) {
|
||
return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) {
|
||
return i == 3 ? parseFloat(value, 10) : parseInt(value, 10);
|
||
});
|
||
}
|
||
if (clr.charAt(0) == '#') clr = clr.substring(1);
|
||
if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return;
|
||
|
||
var dig = clr.length / 3;
|
||
var red = clr.substr(0, dig);
|
||
var grn = clr.substr(dig, dig);
|
||
var blu = clr.substr(dig * 2);
|
||
if (dig === 1) {
|
||
red += red;
|
||
grn += grn;
|
||
blu += blu;
|
||
}
|
||
return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)];
|
||
}
|
||
|
||
function rgba(rgbArray, opacity) {
|
||
if (rgbArray.length == 4) {
|
||
rgbArray = angular.copy(rgbArray);
|
||
opacity ? rgbArray.pop() : opacity = rgbArray.pop();
|
||
}
|
||
return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ?
|
||
'rgba(' + rgbArray.join(',') + ',' + opacity + ')' :
|
||
'rgb(' + rgbArray.join(',') + ')';
|
||
}
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/*
|
||
* @ngdoc module
|
||
* @name material.components.backdrop
|
||
* @description Backdrop
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdBackdrop
|
||
* @module material.components.backdrop
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* `<md-backdrop>` is a backdrop element used by other coponents, such as dialog and bottom sheet.
|
||
* Apply class `opaque` to make the backdrop use the theme backdrop color.
|
||
*
|
||
*/
|
||
|
||
angular.module('material.components.backdrop', [
|
||
'material.core'
|
||
])
|
||
.directive('mdBackdrop', BackdropDirective);
|
||
|
||
function BackdropDirective($mdTheming) {
|
||
return $mdTheming;
|
||
}
|
||
BackdropDirective.$inject = ["$mdTheming"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.bottomSheet
|
||
* @description
|
||
* BottomSheet
|
||
*/
|
||
angular.module('material.components.bottomSheet', [
|
||
'material.core',
|
||
'material.components.backdrop'
|
||
])
|
||
.directive('mdBottomSheet', MdBottomSheetDirective)
|
||
.provider('$mdBottomSheet', MdBottomSheetProvider);
|
||
|
||
function MdBottomSheetDirective() {
|
||
return {
|
||
restrict: 'E'
|
||
};
|
||
}
|
||
|
||
/**
|
||
* @ngdoc service
|
||
* @name $mdBottomSheet
|
||
* @module material.components.bottomSheet
|
||
*
|
||
* @description
|
||
* `$mdBottomSheet` opens a bottom sheet over the app and provides a simple promise API.
|
||
*
|
||
* ### Restrictions
|
||
*
|
||
* - The bottom sheet's template must have an outer `<md-bottom-sheet>` element.
|
||
* - Add the `md-grid` class to the bottom sheet for a grid layout.
|
||
* - Add the `md-list` class to the bottom sheet for a list layout.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <div ng-controller="MyController">
|
||
* <md-button ng-click="openBottomSheet()">
|
||
* Open a Bottom Sheet!
|
||
* </md-button>
|
||
* </div>
|
||
* </hljs>
|
||
* <hljs lang="js">
|
||
* var app = angular.module('app', ['ngMaterial']);
|
||
* app.controller('MyController', function($scope, $mdBottomSheet) {
|
||
* $scope.openBottomSheet = function() {
|
||
* $mdBottomSheet.show({
|
||
* template: '<md-bottom-sheet>Hello!</md-bottom-sheet>'
|
||
* });
|
||
* };
|
||
* });
|
||
* </hljs>
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdBottomSheet#show
|
||
*
|
||
* @description
|
||
* Show a bottom sheet with the specified options.
|
||
*
|
||
* @param {object} options An options object, with the following properties:
|
||
*
|
||
* - `templateUrl` - `{string=}`: The url of an html template file that will
|
||
* be used as the content of the bottom sheet. Restrictions: the template must
|
||
* have an outer `md-bottom-sheet` element.
|
||
* - `template` - `{string=}`: Same as templateUrl, except this is an actual
|
||
* template string.
|
||
* - `controller` - `{string=}`: The controller to associate with this bottom sheet.
|
||
* - `locals` - `{string=}`: An object containing key/value pairs. The keys will
|
||
* be used as names of values to inject into the controller. For example,
|
||
* `locals: {three: 3}` would inject `three` into the controller with the value
|
||
* of 3.
|
||
* - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option,
|
||
* the location of the click will be used as the starting point for the opening animation
|
||
* of the the dialog.
|
||
* - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
|
||
* and the bottom sheet will not open until the promises resolve.
|
||
* - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
|
||
* - `parent` - `{element=}`: The element to append the bottom sheet to. Defaults to appending
|
||
* to the root element of the application.
|
||
* - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the bottom sheet is open.
|
||
* Default true.
|
||
*
|
||
* @returns {promise} A promise that can be resolved with `$mdBottomSheet.hide()` or
|
||
* rejected with `$mdBottomSheet.cancel()`.
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdBottomSheet#hide
|
||
*
|
||
* @description
|
||
* Hide the existing bottom sheet and resolve the promise returned from
|
||
* `$mdBottomSheet.show()`.
|
||
*
|
||
* @param {*=} response An argument for the resolved promise.
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdBottomSheet#cancel
|
||
*
|
||
* @description
|
||
* Hide the existing bottom sheet and reject the promise returned from
|
||
* `$mdBottomSheet.show()`.
|
||
*
|
||
* @param {*=} response An argument for the rejected promise.
|
||
*
|
||
*/
|
||
|
||
function MdBottomSheetProvider($$interimElementProvider) {
|
||
// how fast we need to flick down to close the sheet, pixels/ms
|
||
var CLOSING_VELOCITY = 0.5;
|
||
var PADDING = 80; // same as css
|
||
|
||
bottomSheetDefaults.$inject = ["$animate", "$mdConstant", "$timeout", "$$rAF", "$compile", "$mdTheming", "$mdBottomSheet", "$rootElement", "$rootScope", "$mdGesture"];
|
||
return $$interimElementProvider('$mdBottomSheet')
|
||
.setDefaults({
|
||
methods: ['disableParentScroll', 'escapeToClose', 'targetEvent'],
|
||
options: bottomSheetDefaults
|
||
});
|
||
|
||
/* @ngInject */
|
||
function bottomSheetDefaults($animate, $mdConstant, $timeout, $$rAF, $compile, $mdTheming, $mdBottomSheet, $rootElement, $rootScope, $mdGesture) {
|
||
var backdrop;
|
||
|
||
return {
|
||
themable: true,
|
||
targetEvent: null,
|
||
onShow: onShow,
|
||
onRemove: onRemove,
|
||
escapeToClose: true,
|
||
disableParentScroll: true
|
||
};
|
||
|
||
function onShow(scope, element, options) {
|
||
// Add a backdrop that will close on click
|
||
backdrop = $compile('<md-backdrop class="md-opaque md-bottom-sheet-backdrop">')(scope);
|
||
backdrop.on('click', function() {
|
||
$timeout($mdBottomSheet.cancel);
|
||
});
|
||
|
||
$mdTheming.inherit(backdrop, options.parent);
|
||
|
||
$animate.enter(backdrop, options.parent, null);
|
||
|
||
var bottomSheet = new BottomSheet(element, options.parent);
|
||
options.bottomSheet = bottomSheet;
|
||
|
||
// Give up focus on calling item
|
||
options.targetEvent && angular.element(options.targetEvent.target).blur();
|
||
$mdTheming.inherit(bottomSheet.element, options.parent);
|
||
|
||
if (options.disableParentScroll) {
|
||
options.lastOverflow = options.parent.css('overflow');
|
||
options.parent.css('overflow', 'hidden');
|
||
}
|
||
|
||
return $animate.enter(bottomSheet.element, options.parent)
|
||
.then(function() {
|
||
var focusable = angular.element(
|
||
element[0].querySelector('button') ||
|
||
element[0].querySelector('a') ||
|
||
element[0].querySelector('[ng-click]')
|
||
);
|
||
focusable.focus();
|
||
|
||
if (options.escapeToClose) {
|
||
options.rootElementKeyupCallback = function(e) {
|
||
if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
|
||
$timeout($mdBottomSheet.cancel);
|
||
}
|
||
};
|
||
$rootElement.on('keyup', options.rootElementKeyupCallback);
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
function onRemove(scope, element, options) {
|
||
var bottomSheet = options.bottomSheet;
|
||
|
||
|
||
$animate.leave(backdrop);
|
||
return $animate.leave(bottomSheet.element).then(function() {
|
||
if (options.disableParentScroll) {
|
||
options.parent.css('overflow', options.lastOverflow);
|
||
delete options.lastOverflow;
|
||
}
|
||
|
||
bottomSheet.cleanup();
|
||
|
||
// Restore focus
|
||
options.targetEvent && angular.element(options.targetEvent.target).focus();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* BottomSheet class to apply bottom-sheet behavior to an element
|
||
*/
|
||
function BottomSheet(element, parent) {
|
||
var deregister = $mdGesture.register(parent, 'drag', { horizontal: false });
|
||
parent.on('$md.dragstart', onDragStart)
|
||
.on('$md.drag', onDrag)
|
||
.on('$md.dragend', onDragEnd);
|
||
|
||
return {
|
||
element: element,
|
||
cleanup: function cleanup() {
|
||
deregister();
|
||
parent.off('$md.dragstart', onDragStart)
|
||
.off('$md.drag', onDrag)
|
||
.off('$md.dragend', onDragEnd);
|
||
}
|
||
};
|
||
|
||
function onDragStart(ev) {
|
||
// Disable transitions on transform so that it feels fast
|
||
element.css($mdConstant.CSS.TRANSITION_DURATION, '0ms');
|
||
}
|
||
|
||
function onDrag(ev) {
|
||
var transform = ev.pointer.distanceY;
|
||
if (transform < 5) {
|
||
// Slow down drag when trying to drag up, and stop after PADDING
|
||
transform = Math.max(-PADDING, transform / 2);
|
||
}
|
||
element.css($mdConstant.CSS.TRANSFORM, 'translate3d(0,' + (PADDING + transform) + 'px,0)');
|
||
}
|
||
|
||
function onDragEnd(ev) {
|
||
if (ev.pointer.distanceY > 0 &&
|
||
(ev.pointer.distanceY > 20 || Math.abs(ev.pointer.velocityY) > CLOSING_VELOCITY)) {
|
||
var distanceRemaining = element.prop('offsetHeight') - ev.pointer.distanceY;
|
||
var transitionDuration = Math.min(distanceRemaining / ev.pointer.velocityY * 0.75, 500);
|
||
element.css($mdConstant.CSS.TRANSITION_DURATION, transitionDuration + 'ms');
|
||
$timeout($mdBottomSheet.cancel);
|
||
} else {
|
||
element.css($mdConstant.CSS.TRANSITION_DURATION, '');
|
||
element.css($mdConstant.CSS.TRANSFORM, '');
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
MdBottomSheetProvider.$inject = ["$$interimElementProvider"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.button
|
||
* @description
|
||
*
|
||
* Button
|
||
*/
|
||
angular.module('material.components.button', [
|
||
'material.core'
|
||
])
|
||
.directive('mdButton', MdButtonDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdButton
|
||
* @module material.components.button
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* `<md-button>` is a button directive with optional ink ripples (default enabled).
|
||
*
|
||
* If you supply a `href` or `ng-href` attribute, it will become an `<a>` element. Otherwise, it will
|
||
* become a `<button>` element.
|
||
*
|
||
* As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
|
||
* the FAB button is in the accent color by default. The primary color palette may be used with
|
||
* the `md-primary` class.
|
||
*
|
||
* @param {boolean=} md-no-ink If present, disable ripple ink effects.
|
||
* @param {expression=} ng-disabled En/Disable based on the expression
|
||
* @param {string=} md-ripple-size Overrides the default ripple size logic. Options: `full`, `partial`, `auto`
|
||
* @param {string=} aria-label Adds alternative text to button for accessibility, useful for icon buttons.
|
||
* If no default text is found, a warning will be logged.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-button>
|
||
* Button
|
||
* </md-button>
|
||
* <md-button href="http://google.com" class="md-button-colored">
|
||
* I'm a link
|
||
* </md-button>
|
||
* <md-button ng-disabled="true" class="md-colored">
|
||
* I'm a disabled button
|
||
* </md-button>
|
||
* </hljs>
|
||
*/
|
||
function MdButtonDirective($mdInkRipple, $mdTheming, $mdAria) {
|
||
|
||
return {
|
||
restrict: 'E',
|
||
replace: true,
|
||
transclude: true,
|
||
template: getTemplate,
|
||
link: postLink
|
||
};
|
||
|
||
function isAnchor(attr) {
|
||
return angular.isDefined(attr.href) || angular.isDefined(attr.ngHref);
|
||
}
|
||
|
||
function getTemplate(element, attr) {
|
||
return isAnchor(attr) ?
|
||
'<a class="md-button" ng-transclude></a>' :
|
||
'<button class="md-button" ng-transclude></button>';
|
||
}
|
||
|
||
function postLink(scope, element, attr) {
|
||
var node = element[0];
|
||
$mdTheming(element);
|
||
$mdInkRipple.attachButtonBehavior(scope, element);
|
||
|
||
var elementHasText = node.textContent.trim();
|
||
if (!elementHasText) {
|
||
$mdAria.expect(element, 'aria-label');
|
||
}
|
||
|
||
// For anchor elements, we have to set tabindex manually when the
|
||
// element is disabled
|
||
if (isAnchor(attr) && angular.isDefined(attr.ngDisabled) ) {
|
||
scope.$watch(attr.ngDisabled, function(isDisabled) {
|
||
element.attr('tabindex', isDisabled ? -1 : 0);
|
||
});
|
||
}
|
||
}
|
||
|
||
}
|
||
MdButtonDirective.$inject = ["$mdInkRipple", "$mdTheming", "$mdAria"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.card
|
||
*
|
||
* @description
|
||
* Card components.
|
||
*/
|
||
angular.module('material.components.card', [
|
||
'material.core'
|
||
])
|
||
.directive('mdCard', mdCardDirective);
|
||
|
||
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdCard
|
||
* @module material.components.card
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The `<md-card>` directive is a container element used within `<md-content>` containers.
|
||
*
|
||
* Cards have constant width and variable heights; where the maximum height is limited to what can
|
||
* fit within a single view on a platform, but it can temporarily expand as needed
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-card>
|
||
* <img src="img/washedout.png" class="md-card-image">
|
||
* <h2>Paracosm</h2>
|
||
* <p>
|
||
* The titles of Washed Out's breakthrough song and the first single from Paracosm share the * two most important words in Ernest Greene's musical language: feel it. It's a simple request, as well...
|
||
* </p>
|
||
* </md-card>
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function mdCardDirective($mdTheming) {
|
||
return {
|
||
restrict: 'E',
|
||
link: function($scope, $element, $attr) {
|
||
$mdTheming($element);
|
||
}
|
||
};
|
||
}
|
||
mdCardDirective.$inject = ["$mdTheming"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.checkbox
|
||
* @description Checkbox module!
|
||
*/
|
||
angular.module('material.components.checkbox', [
|
||
'material.core'
|
||
])
|
||
.directive('mdCheckbox', MdCheckboxDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdCheckbox
|
||
* @module material.components.checkbox
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The checkbox directive is used like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
|
||
*
|
||
* As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
|
||
* the checkbox is in the accent color by default. The primary color palette may be used with
|
||
* the `md-primary` class.
|
||
*
|
||
* @param {string} ng-model Assignable angular expression to data-bind to.
|
||
* @param {string=} name Property name of the form under which the control is published.
|
||
* @param {expression=} ng-true-value The value to which the expression should be set when selected.
|
||
* @param {expression=} ng-false-value The value to which the expression should be set when not selected.
|
||
* @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element.
|
||
* @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects
|
||
* @param {string=} aria-label Adds label to checkbox for accessibility.
|
||
* Defaults to checkbox's text. If no default text is found, a warning will be logged.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-checkbox ng-model="isChecked" aria-label="Finished?">
|
||
* Finished ?
|
||
* </md-checkbox>
|
||
*
|
||
* <md-checkbox md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
|
||
* No Ink Effects
|
||
* </md-checkbox>
|
||
*
|
||
* <md-checkbox ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
|
||
* Disabled
|
||
* </md-checkbox>
|
||
*
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function MdCheckboxDirective(inputDirective, $mdInkRipple, $mdAria, $mdConstant, $mdTheming, $mdUtil) {
|
||
inputDirective = inputDirective[0];
|
||
var CHECKED_CSS = 'md-checked';
|
||
|
||
return {
|
||
restrict: 'E',
|
||
transclude: true,
|
||
require: '?ngModel',
|
||
template:
|
||
'<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
|
||
'<div class="md-icon"></div>' +
|
||
'</div>' +
|
||
'<div ng-transclude class="md-label"></div>',
|
||
compile: compile
|
||
};
|
||
|
||
// **********************************************************
|
||
// Private Methods
|
||
// **********************************************************
|
||
|
||
function compile (tElement, tAttrs) {
|
||
|
||
tAttrs.type = 'checkbox';
|
||
tAttrs.tabIndex = 0;
|
||
tElement.attr('role', tAttrs.type);
|
||
|
||
return function postLink(scope, element, attr, ngModelCtrl) {
|
||
ngModelCtrl = ngModelCtrl || $mdUtil.fakeNgModel();
|
||
var checked = false;
|
||
$mdTheming(element);
|
||
|
||
$mdAria.expectWithText(element, 'aria-label');
|
||
|
||
// Reuse the original input[type=checkbox] directive from Angular core.
|
||
// This is a bit hacky as we need our own event listener and own render
|
||
// function.
|
||
inputDirective.link.pre(scope, {
|
||
on: angular.noop,
|
||
0: {}
|
||
}, attr, [ngModelCtrl]);
|
||
|
||
element.on('click', listener)
|
||
.on('keypress', keypressHandler);
|
||
ngModelCtrl.$render = render;
|
||
|
||
function keypressHandler(ev) {
|
||
if(ev.which === $mdConstant.KEY_CODE.SPACE) {
|
||
ev.preventDefault();
|
||
listener(ev);
|
||
}
|
||
}
|
||
function listener(ev) {
|
||
if (element[0].hasAttribute('disabled')) return;
|
||
|
||
scope.$apply(function() {
|
||
checked = !checked;
|
||
ngModelCtrl.$setViewValue(checked, ev && ev.type);
|
||
ngModelCtrl.$render();
|
||
});
|
||
}
|
||
|
||
function render() {
|
||
checked = ngModelCtrl.$viewValue;
|
||
if(checked) {
|
||
element.addClass(CHECKED_CSS);
|
||
} else {
|
||
element.removeClass(CHECKED_CSS);
|
||
}
|
||
}
|
||
};
|
||
}
|
||
}
|
||
MdCheckboxDirective.$inject = ["inputDirective", "$mdInkRipple", "$mdAria", "$mdConstant", "$mdTheming", "$mdUtil"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.content
|
||
*
|
||
* @description
|
||
* Scrollable content
|
||
*/
|
||
angular.module('material.components.content', [
|
||
'material.core'
|
||
])
|
||
.directive('mdContent', mdContentDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdContent
|
||
* @module material.components.content
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The `<md-content>` directive is a container element useful for scrollable content
|
||
*
|
||
* ### Restrictions
|
||
*
|
||
* - Add the `md-padding` class to make the content padded.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-content class="md-padding">
|
||
* Lorem ipsum dolor sit amet, ne quod novum mei.
|
||
* </md-content>
|
||
* </hljs>
|
||
*
|
||
*/
|
||
|
||
function mdContentDirective($mdTheming) {
|
||
return {
|
||
restrict: 'E',
|
||
controller: ['$scope', '$element', ContentController],
|
||
link: function(scope, element, attr) {
|
||
var node = element[0];
|
||
|
||
$mdTheming(element);
|
||
scope.$broadcast('$mdContentLoaded', element);
|
||
|
||
iosScrollFix(element[0]);
|
||
}
|
||
};
|
||
|
||
function ContentController($scope, $element) {
|
||
this.$scope = $scope;
|
||
this.$element = $element;
|
||
}
|
||
}
|
||
mdContentDirective.$inject = ["$mdTheming"];
|
||
|
||
function iosScrollFix(node) {
|
||
// IOS FIX:
|
||
// If we scroll where there is no more room for the webview to scroll,
|
||
// by default the webview itself will scroll up and down, this looks really
|
||
// bad. So if we are scrolling to the very top or bottom, add/subtract one
|
||
angular.element(node).on('$md.pressdown', function(ev) {
|
||
// Only touch events
|
||
if (ev.pointer.type !== 't') return;
|
||
// Don't let a child content's touchstart ruin it for us.
|
||
if (ev.$materialScrollFixed) return;
|
||
ev.$materialScrollFixed = true;
|
||
|
||
if (node.scrollTop === 0) {
|
||
node.scrollTop = 1;
|
||
} else if (node.scrollHeight === node.scrollTop + node.offsetHeight) {
|
||
node.scrollTop -= 1;
|
||
}
|
||
});
|
||
}
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.dialog
|
||
*/
|
||
angular.module('material.components.dialog', [
|
||
'material.core',
|
||
'material.components.backdrop'
|
||
])
|
||
.directive('mdDialog', MdDialogDirective)
|
||
.provider('$mdDialog', MdDialogProvider);
|
||
|
||
function MdDialogDirective($$rAF, $mdTheming) {
|
||
return {
|
||
restrict: 'E',
|
||
link: function(scope, element, attr) {
|
||
$mdTheming(element);
|
||
$$rAF(function() {
|
||
var content = element[0].querySelector('md-content');
|
||
if (content && content.scrollHeight > content.clientHeight) {
|
||
element.addClass('md-content-overflow');
|
||
}
|
||
});
|
||
}
|
||
};
|
||
}
|
||
MdDialogDirective.$inject = ["$$rAF", "$mdTheming"];
|
||
|
||
/**
|
||
* @ngdoc service
|
||
* @name $mdDialog
|
||
* @module material.components.dialog
|
||
*
|
||
* @description
|
||
* `$mdDialog` opens a dialog over the app and provides a simple promise API.
|
||
*
|
||
* ### Restrictions
|
||
*
|
||
* - The dialog is always given an isolate scope.
|
||
* - The dialog's template must have an outer `<md-dialog>` element.
|
||
* Inside, use an `<md-content>` element for the dialog's content, and use
|
||
* an element with class `md-actions` for the dialog's actions.
|
||
*
|
||
* @usage
|
||
* #### HTML
|
||
*
|
||
* <hljs lang="html">
|
||
* <div ng-app="demoApp" ng-controller="EmployeeController">
|
||
* <md-button ng-click="showAlert()" class="md-raised md-warn">
|
||
* Employee Alert!
|
||
* </md-button>
|
||
* <md-button ng-click="closeAlert()" ng-disabled="!hasAlert()" class="md-raised">
|
||
* Close Alert
|
||
* </md-button>
|
||
* <md-button ng-click="showGreeting($event)" class="md-raised md-primary" >
|
||
* Greet Employee
|
||
* </md-button>
|
||
* </div>
|
||
* </hljs>
|
||
*
|
||
* #### JavaScript
|
||
*
|
||
* <hljs lang="js">
|
||
* (function(angular, undefined){
|
||
* "use strict";
|
||
*
|
||
* angular
|
||
* .module('demoApp', ['ngMaterial'])
|
||
* .controller('EmployeeController', EmployeeEditor)
|
||
* .controller('GreetingController', GreetingController);
|
||
*
|
||
* // Fictitious Employee Editor to show how to use simple and complex dialogs.
|
||
*
|
||
* function EmployeeEditor($scope, $mdDialog) {
|
||
* var alert;
|
||
*
|
||
* $scope.showAlert = showAlert;
|
||
* $scope.closeAlert = closeAlert;
|
||
* $scope.showGreeting = showCustomGreeting;
|
||
*
|
||
* $scope.hasAlert = function() { return !!alert };
|
||
* $scope.userName = $scope.userName || 'Bobby';
|
||
*
|
||
* // Dialog #1 - Show simple alert dialog and cache
|
||
* // reference to dialog instance
|
||
*
|
||
* function showAlert() {
|
||
* alert = $mdDialog.alert()
|
||
* .title('Attention, ' + $scope.userName)
|
||
* .content('This is an example of how easy dialogs can be!')
|
||
* .ok('Close');
|
||
*
|
||
* $mdDialog
|
||
* .show( alert )
|
||
* .finally(function() {
|
||
* alert = undefined;
|
||
* });
|
||
* }
|
||
*
|
||
* // Close the specified dialog instance and resolve with 'finished' flag
|
||
* // Normally this is not needed, just use '$mdDialog.hide()' to close
|
||
* // the most recent dialog popup.
|
||
*
|
||
* function closeAlert() {
|
||
* $mdDialog.hide( alert, "finished" );
|
||
* alert = undefined;
|
||
* }
|
||
*
|
||
* // Dialog #2 - Demonstrate more complex dialogs construction and popup.
|
||
*
|
||
* function showCustomGreeting($event) {
|
||
* $mdDialog.show({
|
||
* targetEvent: $event,
|
||
* template:
|
||
* '<md-dialog>' +
|
||
*
|
||
* ' <md-content>Hello {{ employee }}!</md-content>' +
|
||
*
|
||
* ' <div class="md-actions">' +
|
||
* ' <md-button ng-click="closeDialog()">' +
|
||
* ' Close Greeting' +
|
||
*
|
||
* ' </md-button>' +
|
||
* ' </div>' +
|
||
* '</md-dialog>',
|
||
* controller: 'GreetingController',
|
||
* onComplete: afterShowAnimation,
|
||
* locals: { employee: $scope.userName }
|
||
* });
|
||
*
|
||
* // When the 'enter' animation finishes...
|
||
*
|
||
* function afterShowAnimation(scope, element, options) {
|
||
* // post-show code here: DOM element focus, etc.
|
||
* }
|
||
* }
|
||
* }
|
||
*
|
||
* // Greeting controller used with the more complex 'showCustomGreeting()' custom dialog
|
||
*
|
||
* function GreetingController($scope, $mdDialog, employee) {
|
||
* // Assigned from construction <code>locals</code> options...
|
||
* $scope.employee = employee;
|
||
*
|
||
* $scope.closeDialog = function() {
|
||
* // Easily hides most recent dialog shown...
|
||
* // no specific instance reference is needed.
|
||
* $mdDialog.hide();
|
||
* };
|
||
* }
|
||
*
|
||
* })(angular);
|
||
* </hljs>
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdDialog#alert
|
||
*
|
||
* @description
|
||
* Builds a preconfigured dialog with the specified message.
|
||
*
|
||
* @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
|
||
*
|
||
* - $mdDialogPreset#title(string) - sets title to string
|
||
* - $mdDialogPreset#content(string) - sets content / message to string
|
||
* - $mdDialogPreset#ok(string) - sets okay button text to string
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdDialog#confirm
|
||
*
|
||
* @description
|
||
* Builds a preconfigured dialog with the specified message. You can call show and the promise returned
|
||
* will be resolved only if the user clicks the confirm action on the dialog.
|
||
*
|
||
* @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
|
||
*
|
||
* Additionally, it supports the following methods:
|
||
*
|
||
* - $mdDialogPreset#title(string) - sets title to string
|
||
* - $mdDialogPreset#content(string) - sets content / message to string
|
||
* - $mdDialogPreset#ok(string) - sets okay button text to string
|
||
* - $mdDialogPreset#cancel(string) - sets cancel button text to string
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdDialog#show
|
||
*
|
||
* @description
|
||
* Show a dialog with the specified options.
|
||
*
|
||
* @param {object} optionsOrPreset Either provide an `$mdDialogPreset` returned from `alert()`, and
|
||
* `confirm()`, or an options object with the following properties:
|
||
* - `templateUrl` - `{string=}`: The url of a template that will be used as the content
|
||
* of the dialog.
|
||
* - `template` - `{string=}`: Same as templateUrl, except this is an actual template string.
|
||
* - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option,
|
||
* the location of the click will be used as the starting point for the opening animation
|
||
* of the the dialog.
|
||
* - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the dialog is open.
|
||
* Default true.
|
||
* - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog.
|
||
* Default true.
|
||
* - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the dialog to
|
||
* close it. Default true.
|
||
* - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the dialog.
|
||
* Default true.
|
||
* - `controller` - `{string=}`: The controller to associate with the dialog. The controller
|
||
* will be injected with the local `$hideDialog`, which is a function used to hide the dialog.
|
||
* - `locals` - `{object=}`: An object containing key/value pairs. The keys will be used as names
|
||
* of values to inject into the controller. For example, `locals: {three: 3}` would inject
|
||
* `three` into the controller, with the value 3. If `bindToController` is true, they will be
|
||
* copied to the controller instead.
|
||
* - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in
|
||
* - `resolve` - `{object=}`: Similar to locals, except it takes promises as values, and the
|
||
* dialog will not open until all of the promises resolve.
|
||
* - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
|
||
* - `parent` - `{element=}`: The element to append the dialog to. Defaults to appending
|
||
* to the root element of the application.
|
||
* - `onComplete` `{function=}`: Callback function used to announce when the show() action is
|
||
* finished.
|
||
*
|
||
* @returns {promise} A promise that can be resolved with `$mdDialog.hide()` or
|
||
* rejected with `mdDialog.cancel()`.
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdDialog#hide
|
||
*
|
||
* @description
|
||
* Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`.
|
||
*
|
||
* @param {*=} response An argument for the resolved promise.
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdDialog#cancel
|
||
*
|
||
* @description
|
||
* Hide an existing dialog and reject the promise returned from `$mdDialog.show()`.
|
||
*
|
||
* @param {*=} response An argument for the rejected promise.
|
||
*/
|
||
|
||
function MdDialogProvider($$interimElementProvider) {
|
||
|
||
var alertDialogMethods = ['title', 'content', 'ariaLabel', 'ok'];
|
||
|
||
advancedDialogOptions.$inject = ["$mdDialog"];
|
||
dialogDefaultOptions.$inject = ["$timeout", "$rootElement", "$compile", "$animate", "$mdAria", "$document", "$mdUtil", "$mdConstant", "$mdTheming", "$$rAF", "$q", "$mdDialog"];
|
||
return $$interimElementProvider('$mdDialog')
|
||
.setDefaults({
|
||
methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'],
|
||
options: dialogDefaultOptions
|
||
})
|
||
.addPreset('alert', {
|
||
methods: ['title', 'content', 'ariaLabel', 'ok'],
|
||
options: advancedDialogOptions
|
||
})
|
||
.addPreset('confirm', {
|
||
methods: ['title', 'content', 'ariaLabel', 'ok', 'cancel'],
|
||
options: advancedDialogOptions
|
||
});
|
||
|
||
/* @ngInject */
|
||
function advancedDialogOptions($mdDialog) {
|
||
return {
|
||
template: [
|
||
'<md-dialog aria-label="{{ dialog.ariaLabel }}">',
|
||
'<md-content>',
|
||
'<h2>{{ dialog.title }}</h2>',
|
||
'<p>{{ dialog.content }}</p>',
|
||
'</md-content>',
|
||
'<div class="md-actions">',
|
||
'<md-button ng-if="dialog.$type == \'confirm\'" ng-click="dialog.abort()">',
|
||
'{{ dialog.cancel }}',
|
||
'</md-button>',
|
||
'<md-button ng-click="dialog.hide()" class="md-primary">',
|
||
'{{ dialog.ok }}',
|
||
'</md-button>',
|
||
'</div>',
|
||
'</md-dialog>'
|
||
].join(''),
|
||
controller: function mdDialogCtrl() {
|
||
this.hide = function() {
|
||
$mdDialog.hide(true);
|
||
};
|
||
this.abort = function() {
|
||
$mdDialog.cancel();
|
||
};
|
||
},
|
||
controllerAs: 'dialog',
|
||
bindToController: true
|
||
};
|
||
}
|
||
|
||
/* @ngInject */
|
||
function dialogDefaultOptions($timeout, $rootElement, $compile, $animate, $mdAria, $document,
|
||
$mdUtil, $mdConstant, $mdTheming, $$rAF, $q, $mdDialog) {
|
||
return {
|
||
hasBackdrop: true,
|
||
isolateScope: true,
|
||
onShow: onShow,
|
||
onRemove: onRemove,
|
||
clickOutsideToClose: true,
|
||
escapeToClose: true,
|
||
targetEvent: null,
|
||
disableParentScroll: true,
|
||
transformTemplate: function(template) {
|
||
return '<div class="md-dialog-container">' + template + '</div>';
|
||
}
|
||
};
|
||
|
||
|
||
// On show method for dialogs
|
||
function onShow(scope, element, options) {
|
||
// Incase the user provides a raw dom element, always wrap it in jqLite
|
||
options.parent = angular.element(options.parent);
|
||
|
||
options.popInTarget = angular.element((options.targetEvent || {}).target);
|
||
var closeButton = findCloseButton();
|
||
|
||
configureAria(element.find('md-dialog'));
|
||
|
||
if (options.hasBackdrop) {
|
||
var parentOffset = options.parent.prop('scrollTop');
|
||
options.backdrop = angular.element('<md-backdrop class="md-dialog-backdrop md-opaque">');
|
||
$mdTheming.inherit(options.backdrop, options.parent);
|
||
$animate.enter(options.backdrop, options.parent);
|
||
element.css('top', parentOffset +'px');
|
||
}
|
||
|
||
if (options.disableParentScroll) {
|
||
options.lastOverflow = options.parent.css('overflow');
|
||
options.parent.css('overflow', 'hidden');
|
||
}
|
||
|
||
return dialogPopIn(
|
||
element,
|
||
options.parent,
|
||
options.popInTarget && options.popInTarget.length && options.popInTarget
|
||
)
|
||
.then(function() {
|
||
if (options.escapeToClose) {
|
||
options.rootElementKeyupCallback = function(e) {
|
||
if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
|
||
$timeout($mdDialog.cancel);
|
||
}
|
||
};
|
||
$rootElement.on('keyup', options.rootElementKeyupCallback);
|
||
}
|
||
|
||
if (options.clickOutsideToClose) {
|
||
options.dialogClickOutsideCallback = function(ev) {
|
||
// Only close if we click the flex container outside the backdrop
|
||
if (ev.target === element[0]) {
|
||
$timeout($mdDialog.cancel);
|
||
}
|
||
};
|
||
element.on('click', options.dialogClickOutsideCallback);
|
||
}
|
||
closeButton.focus();
|
||
});
|
||
|
||
|
||
function findCloseButton() {
|
||
//If no element with class dialog-close, try to find the last
|
||
//button child in md-actions and assume it is a close button
|
||
var closeButton = element[0].querySelector('.dialog-close');
|
||
if (!closeButton) {
|
||
var actionButtons = element[0].querySelectorAll('.md-actions button');
|
||
closeButton = actionButtons[ actionButtons.length - 1 ];
|
||
}
|
||
return angular.element(closeButton);
|
||
}
|
||
|
||
}
|
||
|
||
// On remove function for all dialogs
|
||
function onRemove(scope, element, options) {
|
||
|
||
if (options.backdrop) {
|
||
$animate.leave(options.backdrop);
|
||
}
|
||
if (options.disableParentScroll) {
|
||
options.parent.css('overflow', options.lastOverflow);
|
||
delete options.lastOverflow;
|
||
}
|
||
if (options.escapeToClose) {
|
||
$rootElement.off('keyup', options.rootElementKeyupCallback);
|
||
}
|
||
if (options.clickOutsideToClose) {
|
||
element.off('click', options.dialogClickOutsideCallback);
|
||
}
|
||
return dialogPopOut(
|
||
element,
|
||
options.parent,
|
||
options.popInTarget && options.popInTarget.length && options.popInTarget
|
||
).then(function() {
|
||
options.scope.$destroy();
|
||
element.remove();
|
||
options.popInTarget && options.popInTarget.focus();
|
||
});
|
||
|
||
}
|
||
|
||
/**
|
||
* Inject ARIA-specific attributes appropriate for Dialogs
|
||
*/
|
||
function configureAria(element) {
|
||
element.attr({
|
||
'role': 'dialog'
|
||
});
|
||
|
||
var dialogContent = element.find('md-content');
|
||
if (dialogContent.length === 0){
|
||
dialogContent = element;
|
||
}
|
||
$mdAria.expectAsync(element, 'aria-label', function() {
|
||
var words = dialogContent.text().split(/\s+/);
|
||
if (words.length > 3) words = words.slice(0,3).concat('...');
|
||
return words.join(' ');
|
||
});
|
||
}
|
||
|
||
function dialogPopIn(container, parentElement, clickElement) {
|
||
var dialogEl = container.find('md-dialog');
|
||
|
||
parentElement.append(container);
|
||
transformToClickElement(dialogEl, clickElement);
|
||
|
||
$$rAF(function() {
|
||
dialogEl.addClass('transition-in')
|
||
.css($mdConstant.CSS.TRANSFORM, '');
|
||
});
|
||
|
||
return dialogTransitionEnd(dialogEl);
|
||
}
|
||
|
||
function dialogPopOut(container, parentElement, clickElement) {
|
||
var dialogEl = container.find('md-dialog');
|
||
|
||
dialogEl.addClass('transition-out').removeClass('transition-in');
|
||
transformToClickElement(dialogEl, clickElement);
|
||
|
||
return dialogTransitionEnd(dialogEl);
|
||
}
|
||
|
||
function transformToClickElement(dialogEl, clickElement) {
|
||
if (clickElement) {
|
||
var clickRect = clickElement[0].getBoundingClientRect();
|
||
var dialogRect = dialogEl[0].getBoundingClientRect();
|
||
|
||
var scaleX = Math.min(0.5, clickRect.width / dialogRect.width);
|
||
var scaleY = Math.min(0.5, clickRect.height / dialogRect.height);
|
||
|
||
dialogEl.css($mdConstant.CSS.TRANSFORM, 'translate3d(' +
|
||
(-dialogRect.left + clickRect.left + clickRect.width/2 - dialogRect.width/2) + 'px,' +
|
||
(-dialogRect.top + clickRect.top + clickRect.height/2 - dialogRect.height/2) + 'px,' +
|
||
'0) scale(' + scaleX + ',' + scaleY + ')'
|
||
);
|
||
}
|
||
}
|
||
|
||
function dialogTransitionEnd(dialogEl) {
|
||
var deferred = $q.defer();
|
||
dialogEl.on($mdConstant.CSS.TRANSITIONEND, finished);
|
||
function finished(ev) {
|
||
//Make sure this transitionend didn't bubble up from a child
|
||
if (ev.target === dialogEl[0]) {
|
||
dialogEl.off($mdConstant.CSS.TRANSITIONEND, finished);
|
||
deferred.resolve();
|
||
}
|
||
}
|
||
return deferred.promise;
|
||
}
|
||
|
||
}
|
||
}
|
||
MdDialogProvider.$inject = ["$$interimElementProvider"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.divider
|
||
* @description Divider module!
|
||
*/
|
||
angular.module('material.components.divider', [
|
||
'material.core'
|
||
])
|
||
.directive('mdDivider', MdDividerDirective);
|
||
|
||
function MdDividerController(){}
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdDivider
|
||
* @module material.components.divider
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* Dividers group and separate content within lists and page layouts using strong visual and spatial distinctions. This divider is a thin rule, lightweight enough to not distract the user from content.
|
||
*
|
||
* @param {boolean=} md-inset Add this attribute to activate the inset divider style.
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-divider></md-divider>
|
||
*
|
||
* <md-divider md-inset></md-divider>
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function MdDividerDirective($mdTheming) {
|
||
return {
|
||
restrict: 'E',
|
||
link: $mdTheming,
|
||
controller: [MdDividerController]
|
||
};
|
||
}
|
||
MdDividerDirective.$inject = ["$mdTheming"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/*
|
||
* @ngdoc module
|
||
* @name material.components.icon
|
||
* @description
|
||
* Icon
|
||
*/
|
||
angular.module('material.components.icon', [
|
||
'material.core'
|
||
])
|
||
.directive('mdIcon', mdIconDirective);
|
||
|
||
/*
|
||
* @ngdoc directive
|
||
* @name mdIcon
|
||
* @module material.components.icon
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The `<md-icon>` directive is an element useful for SVG icons
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-icon icon="/img/icons/ic_access_time_24px.svg">
|
||
* </md-icon>
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function mdIconDirective() {
|
||
return {
|
||
restrict: 'E',
|
||
template: '<object class="md-icon"></object>',
|
||
compile: function(element, attr) {
|
||
var object = angular.element(element[0].children[0]);
|
||
if(angular.isDefined(attr.icon)) {
|
||
object.attr('data', attr.icon);
|
||
}
|
||
}
|
||
};
|
||
}
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.input
|
||
*/
|
||
|
||
angular.module('material.components.input', [
|
||
'material.core'
|
||
])
|
||
.directive('mdInputContainer', mdInputContainerDirective)
|
||
.directive('label', labelDirective)
|
||
.directive('input', inputTextareaDirective)
|
||
.directive('textarea', inputTextareaDirective)
|
||
.directive('mdMaxlength', mdMaxlengthDirective)
|
||
.directive('placeholder', placeholderDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdInputContainer
|
||
* @module material.components.input
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* `<md-input-container>` is the parent of any input or textarea element.
|
||
*
|
||
* Input and textarea elements will not behave properly unless the md-input-container
|
||
* parent is provided.
|
||
*
|
||
* @param md-is-error {expression=} When the given expression evaluates to true, the input container will go into error state. Defaults to erroring if the input has been touched and is invalid.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
*
|
||
* <md-input-container>
|
||
* <label>Username</label>
|
||
* <input type="text" ng-model="user.name">
|
||
* </md-input-container>
|
||
*
|
||
* <md-input-container>
|
||
* <label>Description</label>
|
||
* <textarea ng-model="user.description"></textarea>
|
||
* </md-input-container>
|
||
*
|
||
* </hljs>
|
||
*/
|
||
function mdInputContainerDirective($mdTheming, $parse) {
|
||
ContainerCtrl.$inject = ["$scope", "$element", "$attrs"];
|
||
return {
|
||
restrict: 'E',
|
||
link: postLink,
|
||
controller: ContainerCtrl
|
||
};
|
||
|
||
function postLink(scope, element, attr) {
|
||
$mdTheming(element);
|
||
}
|
||
function ContainerCtrl($scope, $element, $attrs) {
|
||
var self = this;
|
||
|
||
self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);
|
||
|
||
self.element = $element;
|
||
self.setFocused = function(isFocused) {
|
||
$element.toggleClass('md-input-focused', !!isFocused);
|
||
};
|
||
self.setHasValue = function(hasValue) {
|
||
$element.toggleClass('md-input-has-value', !!hasValue);
|
||
};
|
||
self.setInvalid = function(isInvalid) {
|
||
$element.toggleClass('md-input-invalid', !!isInvalid);
|
||
};
|
||
$scope.$watch(function() {
|
||
return self.label && self.input;
|
||
}, function(hasLabelAndInput) {
|
||
if (hasLabelAndInput && !self.label.attr('for')) {
|
||
self.label.attr('for', self.input.attr('id'));
|
||
}
|
||
});
|
||
}
|
||
}
|
||
mdInputContainerDirective.$inject = ["$mdTheming", "$parse"];
|
||
|
||
function labelDirective() {
|
||
return {
|
||
restrict: 'E',
|
||
require: '^?mdInputContainer',
|
||
link: function(scope, element, attr, containerCtrl) {
|
||
if (!containerCtrl) return;
|
||
|
||
containerCtrl.label = element;
|
||
scope.$on('$destroy', function() {
|
||
containerCtrl.label = null;
|
||
});
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name input
|
||
* @restrict E
|
||
* @module material.components.input
|
||
*
|
||
* @description
|
||
* Must be placed as a child of an `<md-input-container>`.
|
||
*
|
||
* Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-input-container>
|
||
* <label>Color</label>
|
||
* <input type="text" ng-model="color" required md-maxlength="10">
|
||
* </md-input-container>
|
||
* </hljs>
|
||
* <h3>With Errors (uses [ngMessages](https://docs.angularjs.org/api/ngMessages))</h3>
|
||
* <hljs lang="html">
|
||
* <form name="userForm">
|
||
* <md-input-container>
|
||
* <label>Last Name</label>
|
||
* <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
|
||
* <div ng-messages="userForm.lastName.$error" ng-show="userForm.bio.$dirty">
|
||
* <div ng-message="required">This is required!</div>
|
||
* <div ng-message="md-maxlength">That's too long!</div>
|
||
* <div ng-message="minlength">That's too short!</div>
|
||
* </div>
|
||
* </md-input-container>
|
||
* </form>
|
||
* </hljs>
|
||
*
|
||
* @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is specified, a character counter will be shown underneath the input.
|
||
*/
|
||
/**
|
||
* @ngdoc directive
|
||
* @name textarea
|
||
* @restrict E
|
||
* @module material.components.input
|
||
*
|
||
* @description
|
||
* Must be placed as a child of an `<md-input-container>`.
|
||
*
|
||
* Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/textarea).
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-input-container>
|
||
* <label>Description</label>
|
||
* <textarea ng-model="description" required minlength="15" md-maxlength="20"></textarea>
|
||
* </md-input-container>
|
||
* </hljs>
|
||
* <h3>With Errors (uses [ngMessages](https://docs.angularjs.org/api/ngMessages))</h3>
|
||
* <hljs lang="html">
|
||
* <form name="userForm">
|
||
* <md-input-container>
|
||
* <label>Biography</label>
|
||
* <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
|
||
* <div ng-messages="userForm.bio.$error" ng-show="userForm.bio.$dirty">
|
||
* <div ng-message="required">This is required!</div>
|
||
* <div ng-message="md-maxlength">That's too long!</div>
|
||
* </div>
|
||
* </md-input-container>
|
||
* </form>
|
||
* </hljs>
|
||
*
|
||
* @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is specified, a character counter will be shown underneath the input.
|
||
*/
|
||
function inputTextareaDirective($mdUtil, $window, $compile, $animate) {
|
||
return {
|
||
restrict: 'E',
|
||
require: ['^?mdInputContainer', '?ngModel'],
|
||
link: postLink
|
||
};
|
||
|
||
function postLink(scope, element, attr, ctrls) {
|
||
|
||
var containerCtrl = ctrls[0];
|
||
var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
|
||
var isReadonly = angular.isDefined(attr.readonly);
|
||
|
||
if ( !containerCtrl ) return;
|
||
if (containerCtrl.input) {
|
||
throw new Error("<md-input-container> can only have *one* <input> or <textarea> child element!");
|
||
}
|
||
containerCtrl.input = element;
|
||
|
||
element.addClass('md-input');
|
||
if (!element.attr('id')) {
|
||
element.attr('id', 'input_' + $mdUtil.nextUid());
|
||
}
|
||
|
||
if (element[0].tagName.toLowerCase() === 'textarea') {
|
||
setupTextarea();
|
||
}
|
||
|
||
function ngModelPipelineCheckValue(arg) {
|
||
containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
|
||
return arg;
|
||
}
|
||
function inputCheckValue() {
|
||
// An input's value counts if its length > 0,
|
||
// or if the input's validity state says it has bad input (eg string in a number input)
|
||
containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity||{}).badInput);
|
||
}
|
||
|
||
|
||
var isErrorGetter = containerCtrl.isErrorGetter || function() {
|
||
return ngModelCtrl.$invalid && ngModelCtrl.$touched;
|
||
};
|
||
scope.$watch(isErrorGetter, containerCtrl.setInvalid);
|
||
|
||
ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
|
||
ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);
|
||
|
||
element.on('input', inputCheckValue);
|
||
|
||
if (!isReadonly) {
|
||
element
|
||
.on('focus', function(ev) {
|
||
containerCtrl.setFocused(true);
|
||
})
|
||
.on('blur', function(ev) {
|
||
containerCtrl.setFocused(false);
|
||
inputCheckValue();
|
||
});
|
||
}
|
||
|
||
scope.$on('$destroy', function() {
|
||
containerCtrl.setFocused(false);
|
||
containerCtrl.setHasValue(false);
|
||
containerCtrl.input = null;
|
||
});
|
||
|
||
function setupTextarea() {
|
||
var node = element[0];
|
||
var onChangeTextarea = $mdUtil.debounce(growTextarea, 1);
|
||
|
||
function pipelineListener(value) {
|
||
onChangeTextarea();
|
||
return value;
|
||
}
|
||
|
||
if (ngModelCtrl) {
|
||
ngModelCtrl.$formatters.push(pipelineListener);
|
||
ngModelCtrl.$viewChangeListeners.push(pipelineListener);
|
||
} else {
|
||
onChangeTextarea();
|
||
}
|
||
element.on('keydown input', onChangeTextarea);
|
||
element.on('scroll', onScroll);
|
||
angular.element($window).on('resize', onChangeTextarea);
|
||
|
||
scope.$on('$destroy', function() {
|
||
angular.element($window).off('resize', onChangeTextarea);
|
||
});
|
||
|
||
function growTextarea() {
|
||
node.style.height = "auto";
|
||
var line = node.scrollHeight - node.offsetHeight;
|
||
node.scrollTop = 0;
|
||
var height = node.offsetHeight + (line > 0 ? line : 0);
|
||
node.style.height = height + 'px';
|
||
}
|
||
|
||
function onScroll(e) {
|
||
node.scrollTop = 0;
|
||
// for smooth new line adding
|
||
var line = node.scrollHeight - node.offsetHeight;
|
||
var height = node.offsetHeight + line;
|
||
node.style.height = height + 'px';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
inputTextareaDirective.$inject = ["$mdUtil", "$window", "$compile", "$animate"];
|
||
|
||
function mdMaxlengthDirective($animate) {
|
||
return {
|
||
restrict: 'A',
|
||
require: ['ngModel', '^mdInputContainer'],
|
||
link: postLink
|
||
};
|
||
|
||
function postLink(scope, element, attr, ctrls) {
|
||
var maxlength;
|
||
var ngModelCtrl = ctrls[0];
|
||
var containerCtrl = ctrls[1];
|
||
var charCountEl = angular.element('<div class="md-char-counter">');
|
||
|
||
// Stop model from trimming. This makes it so whitespace
|
||
// over the maxlength still counts as invalid.
|
||
attr.$set('ngTrim', 'false');
|
||
containerCtrl.element.append(charCountEl);
|
||
|
||
ngModelCtrl.$formatters.push(renderCharCount);
|
||
ngModelCtrl.$viewChangeListeners.push(renderCharCount);
|
||
element.on('input keydown', function() {
|
||
renderCharCount(); //make sure it's called with no args
|
||
});
|
||
|
||
scope.$watch(attr.mdMaxlength, function(value) {
|
||
maxlength = value;
|
||
if (angular.isNumber(value) && value > 0) {
|
||
if (!charCountEl.parent().length) {
|
||
$animate.enter(charCountEl, containerCtrl.element,
|
||
angular.element(containerCtrl.element[0].lastElementChild));
|
||
}
|
||
renderCharCount();
|
||
} else {
|
||
$animate.leave(charCountEl);
|
||
}
|
||
});
|
||
|
||
ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
|
||
if (!angular.isNumber(maxlength) || maxlength < 0) {
|
||
return true;
|
||
}
|
||
return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
|
||
};
|
||
|
||
function renderCharCount(value) {
|
||
charCountEl.text( ( element.val() || value || '' ).length + '/' + maxlength );
|
||
return value;
|
||
}
|
||
}
|
||
}
|
||
mdMaxlengthDirective.$inject = ["$animate"];
|
||
|
||
function placeholderDirective() {
|
||
return {
|
||
restrict: 'A',
|
||
require: '^^?mdInputContainer',
|
||
link: postLink
|
||
};
|
||
|
||
function postLink(scope, element, attr, inputContainer) {
|
||
if (!inputContainer) return;
|
||
|
||
var placeholderText = attr.placeholder;
|
||
element.removeAttr('placeholder');
|
||
|
||
inputContainer.element.append('<div class="md-placeholder">' + placeholderText + '</div>');
|
||
}
|
||
}
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.list
|
||
* @description
|
||
* List module
|
||
*/
|
||
angular.module('material.components.list', [
|
||
'material.core'
|
||
])
|
||
.directive('mdList', mdListDirective)
|
||
.directive('mdItem', mdItemDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdList
|
||
* @module material.components.list
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The `<md-list>` directive is a list container for 1..n `<md-item>` tags.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-list>
|
||
* <md-item ng-repeat="item in todos">
|
||
* <md-item-content>
|
||
* <div class="md-tile-left">
|
||
* <img ng-src="{{item.face}}" class="face" alt="{{item.who}}">
|
||
* </div>
|
||
* <div class="md-tile-content">
|
||
* <h3>{{item.what}}</h3>
|
||
* <h4>{{item.who}}</h4>
|
||
* <p>
|
||
* {{item.notes}}
|
||
* </p>
|
||
* </div>
|
||
* </md-item-content>
|
||
* </md-item>
|
||
* </md-list>
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function mdListDirective() {
|
||
return {
|
||
restrict: 'E',
|
||
link: function($scope, $element, $attr) {
|
||
$element.attr({
|
||
'role' : 'list'
|
||
});
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdItem
|
||
* @module material.components.list
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The `<md-item>` directive is a container intended for row items in a `<md-list>` container.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-list>
|
||
* <md-item>
|
||
* Item content in list
|
||
* </md-item>
|
||
* </md-list>
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function mdItemDirective() {
|
||
return {
|
||
restrict: 'E',
|
||
link: function($scope, $element, $attr) {
|
||
$element.attr({
|
||
'role' : 'listitem'
|
||
});
|
||
}
|
||
};
|
||
}
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.progressCircular
|
||
* @description Circular Progress module!
|
||
*/
|
||
angular.module('material.components.progressCircular', [
|
||
'material.core'
|
||
])
|
||
.directive('mdProgressCircular', MdProgressCircularDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdProgressCircular
|
||
* @module material.components.progressCircular
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The circular progress directive is used to make loading content in your app as delightful and painless as possible by minimizing the amount of visual change a user sees before they can view and interact with content.
|
||
*
|
||
* For operations where the percentage of the operation completed can be determined, use a determinate indicator. They give users a quick sense of how long an operation will take.
|
||
*
|
||
* For operations where the user is asked to wait a moment while something finishes up, and it’s not necessary to expose what's happening behind the scenes and how long it will take, use an indeterminate indicator.
|
||
*
|
||
* @param {string} md-mode Select from one of two modes: determinate and indeterminate.
|
||
* @param {number=} value In determinate mode, this number represents the percentage of the circular progress. Default: 0
|
||
* @param {number=} md-diameter This specifies the diamter of the circular progress. Default: 48
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
|
||
*
|
||
* <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
|
||
*
|
||
* <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
|
||
*
|
||
* <md-progress-circular md-mode="indeterminate"></md-progress-circular>
|
||
* </hljs>
|
||
*/
|
||
function MdProgressCircularDirective($$rAF, $mdConstant, $mdTheming) {
|
||
var fillRotations = new Array(101),
|
||
fixRotations = new Array(101);
|
||
|
||
for (var i = 0; i < 101; i++) {
|
||
var percent = i / 100;
|
||
var rotation = Math.floor(percent * 180);
|
||
|
||
fillRotations[i] = 'rotate(' + rotation.toString() + 'deg)';
|
||
fixRotations[i] = 'rotate(' + (rotation * 2).toString() + 'deg)';
|
||
}
|
||
|
||
return {
|
||
restrict: 'E',
|
||
template:
|
||
'<div class="md-spinner-wrapper">' +
|
||
'<div class="md-inner">' +
|
||
'<div class="md-gap"></div>' +
|
||
'<div class="md-left">' +
|
||
'<div class="md-half-circle"></div>' +
|
||
'</div>' +
|
||
'<div class="md-right">' +
|
||
'<div class="md-half-circle"></div>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'</div>',
|
||
compile: compile
|
||
};
|
||
|
||
function compile(tElement, tAttrs, transclude) {
|
||
tElement.attr('aria-valuemin', 0);
|
||
tElement.attr('aria-valuemax', 100);
|
||
tElement.attr('role', 'progressbar');
|
||
|
||
return postLink;
|
||
}
|
||
|
||
function postLink(scope, element, attr) {
|
||
$mdTheming(element);
|
||
var circle = element[0],
|
||
fill = circle.querySelectorAll('.md-fill, .md-mask.md-full'),
|
||
fix = circle.querySelectorAll('.md-fill.md-fix'),
|
||
i, clamped, fillRotation, fixRotation;
|
||
|
||
var diameter = attr.mdDiameter || 48;
|
||
var scale = diameter/48;
|
||
|
||
circle.style[$mdConstant.CSS.TRANSFORM] = 'scale(' + scale.toString() + ')';
|
||
|
||
attr.$observe('value', function(value) {
|
||
clamped = clamp(value);
|
||
fillRotation = fillRotations[clamped];
|
||
fixRotation = fixRotations[clamped];
|
||
|
||
element.attr('aria-valuenow', clamped);
|
||
|
||
for (i = 0; i < fill.length; i++) {
|
||
fill[i].style[$mdConstant.CSS.TRANSFORM] = fillRotation;
|
||
}
|
||
|
||
for (i = 0; i < fix.length; i++) {
|
||
fix[i].style[$mdConstant.CSS.TRANSFORM] = fixRotation;
|
||
}
|
||
});
|
||
}
|
||
|
||
function clamp(value) {
|
||
if (value > 100) {
|
||
return 100;
|
||
}
|
||
|
||
if (value < 0) {
|
||
return 0;
|
||
}
|
||
|
||
return Math.ceil(value || 0);
|
||
}
|
||
}
|
||
MdProgressCircularDirective.$inject = ["$$rAF", "$mdConstant", "$mdTheming"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.progressLinear
|
||
* @description Linear Progress module!
|
||
*/
|
||
angular.module('material.components.progressLinear', [
|
||
'material.core'
|
||
])
|
||
.directive('mdProgressLinear', MdProgressLinearDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdProgressLinear
|
||
* @module material.components.progressLinear
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The linear progress directive is used to make loading content in your app as delightful and painless as possible by minimizing the amount of visual change a user sees before they can view and interact with content. Each operation should only be represented by one activity indicator—for example, one refresh operation should not display both a refresh bar and an activity circle.
|
||
*
|
||
* For operations where the percentage of the operation completed can be determined, use a determinate indicator. They give users a quick sense of how long an operation will take.
|
||
*
|
||
* For operations where the user is asked to wait a moment while something finishes up, and it’s not necessary to expose what's happening behind the scenes and how long it will take, use an indeterminate indicator.
|
||
*
|
||
* @param {string} md-mode Select from one of four modes: determinate, indeterminate, buffer or query.
|
||
* @param {number=} value In determinate and buffer modes, this number represents the percentage of the primary progress bar. Default: 0
|
||
* @param {number=} md-buffer-value In the buffer mode, this number represents the precentage of the secondary progress bar. Default: 0
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-progress-linear md-mode="determinate" value="..."></md-progress-linear>
|
||
*
|
||
* <md-progress-linear md-mode="determinate" ng-value="..."></md-progress-linear>
|
||
*
|
||
* <md-progress-linear md-mode="indeterminate"></md-progress-linear>
|
||
*
|
||
* <md-progress-linear md-mode="buffer" value="..." md-buffer-value="..."></md-progress-linear>
|
||
*
|
||
* <md-progress-linear md-mode="query"></md-progress-linear>
|
||
* </hljs>
|
||
*/
|
||
function MdProgressLinearDirective($$rAF, $mdConstant, $mdTheming) {
|
||
|
||
return {
|
||
restrict: 'E',
|
||
template: '<div class="md-container">' +
|
||
'<div class="md-dashed"></div>' +
|
||
'<div class="md-bar md-bar1"></div>' +
|
||
'<div class="md-bar md-bar2"></div>' +
|
||
'</div>',
|
||
compile: compile
|
||
};
|
||
|
||
function compile(tElement, tAttrs, transclude) {
|
||
tElement.attr('aria-valuemin', 0);
|
||
tElement.attr('aria-valuemax', 100);
|
||
tElement.attr('role', 'progressbar');
|
||
|
||
return postLink;
|
||
}
|
||
function postLink(scope, element, attr) {
|
||
$mdTheming(element);
|
||
var bar1Style = element[0].querySelector('.md-bar1').style,
|
||
bar2Style = element[0].querySelector('.md-bar2').style,
|
||
container = angular.element(element[0].querySelector('.md-container'));
|
||
|
||
attr.$observe('value', function(value) {
|
||
if (attr.mdMode == 'query') {
|
||
return;
|
||
}
|
||
|
||
var clamped = clamp(value);
|
||
element.attr('aria-valuenow', clamped);
|
||
bar2Style[$mdConstant.CSS.TRANSFORM] = transforms[clamped];
|
||
});
|
||
|
||
attr.$observe('mdBufferValue', function(value) {
|
||
bar1Style[$mdConstant.CSS.TRANSFORM] = transforms[clamp(value)];
|
||
});
|
||
|
||
$$rAF(function() {
|
||
container.addClass('md-ready');
|
||
});
|
||
}
|
||
|
||
function clamp(value) {
|
||
if (value > 100) {
|
||
return 100;
|
||
}
|
||
|
||
if (value < 0) {
|
||
return 0;
|
||
}
|
||
|
||
return Math.ceil(value || 0);
|
||
}
|
||
}
|
||
MdProgressLinearDirective.$inject = ["$$rAF", "$mdConstant", "$mdTheming"];
|
||
|
||
|
||
// **********************************************************
|
||
// Private Methods
|
||
// **********************************************************
|
||
var transforms = (function() {
|
||
var values = new Array(101);
|
||
for(var i = 0; i < 101; i++){
|
||
values[i] = makeTransform(i);
|
||
}
|
||
|
||
return values;
|
||
|
||
function makeTransform(value){
|
||
var scale = value/100;
|
||
var translateX = (value-100)/2;
|
||
return 'translateX(' + translateX.toString() + '%) scale(' + scale.toString() + ', 1)';
|
||
}
|
||
})();
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.radioButton
|
||
* @description radioButton module!
|
||
*/
|
||
angular.module('material.components.radioButton', [
|
||
'material.core'
|
||
])
|
||
.directive('mdRadioGroup', mdRadioGroupDirective)
|
||
.directive('mdRadioButton', mdRadioButtonDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @module material.components.radioButton
|
||
* @name mdRadioGroup
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The `<md-radio-group>` directive identifies a grouping
|
||
* container for the 1..n grouped radio buttons; specified using nested
|
||
* `<md-radio-button>` tags.
|
||
*
|
||
* As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
|
||
* the radio button is in the accent color by default. The primary color palette may be used with
|
||
* the `md-primary` class.
|
||
*
|
||
* Note: `<md-radio-group>` and `<md-radio-button>` handle tabindex differently
|
||
* than the native `<input type='radio'>` controls. Whereas the native controls
|
||
* force the user to tab through all the radio buttons, `<md-radio-group>`
|
||
* is focusable, and by default the `<md-radio-button>`s are not.
|
||
*
|
||
* @param {string} ng-model Assignable angular expression to data-bind to.
|
||
* @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-radio-group ng-model="selected">
|
||
*
|
||
* <md-radio-button
|
||
* ng-repeat="d in colorOptions"
|
||
* ng-value="d.value" aria-label="{{ d.label }}">
|
||
*
|
||
* {{ d.label }}
|
||
*
|
||
* </md-radio-button>
|
||
*
|
||
* </md-radio-group>
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming) {
|
||
RadioGroupController.prototype = createRadioGroupControllerProto();
|
||
|
||
return {
|
||
restrict: 'E',
|
||
controller: ['$element', RadioGroupController],
|
||
require: ['mdRadioGroup', '?ngModel'],
|
||
link: { pre: linkRadioGroup }
|
||
};
|
||
|
||
function linkRadioGroup(scope, element, attr, ctrls) {
|
||
$mdTheming(element);
|
||
var rgCtrl = ctrls[0];
|
||
var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
|
||
|
||
function keydownListener(ev) {
|
||
switch(ev.keyCode) {
|
||
case $mdConstant.KEY_CODE.LEFT_ARROW:
|
||
case $mdConstant.KEY_CODE.UP_ARROW:
|
||
ev.preventDefault();
|
||
rgCtrl.selectPrevious();
|
||
break;
|
||
|
||
case $mdConstant.KEY_CODE.RIGHT_ARROW:
|
||
case $mdConstant.KEY_CODE.DOWN_ARROW:
|
||
ev.preventDefault();
|
||
rgCtrl.selectNext();
|
||
break;
|
||
|
||
case $mdConstant.KEY_CODE.ENTER:
|
||
var form = angular.element($mdUtil.getClosest(element[0], 'form'));
|
||
if (form.length > 0) {
|
||
form.triggerHandler('submit');
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
rgCtrl.init(ngModelCtrl);
|
||
|
||
element.attr({
|
||
'role': 'radiogroup',
|
||
'tabIndex': element.attr('tabindex') || '0'
|
||
})
|
||
.on('keydown', keydownListener);
|
||
|
||
}
|
||
|
||
function RadioGroupController($element) {
|
||
this._radioButtonRenderFns = [];
|
||
this.$element = $element;
|
||
}
|
||
|
||
function createRadioGroupControllerProto() {
|
||
return {
|
||
init: function(ngModelCtrl) {
|
||
this._ngModelCtrl = ngModelCtrl;
|
||
this._ngModelCtrl.$render = angular.bind(this, this.render);
|
||
},
|
||
add: function(rbRender) {
|
||
this._radioButtonRenderFns.push(rbRender);
|
||
},
|
||
remove: function(rbRender) {
|
||
var index = this._radioButtonRenderFns.indexOf(rbRender);
|
||
if (index !== -1) {
|
||
this._radioButtonRenderFns.splice(index, 1);
|
||
}
|
||
},
|
||
render: function() {
|
||
this._radioButtonRenderFns.forEach(function(rbRender) {
|
||
rbRender();
|
||
});
|
||
},
|
||
setViewValue: function(value, eventType) {
|
||
this._ngModelCtrl.$setViewValue(value, eventType);
|
||
// update the other radio buttons as well
|
||
this.render();
|
||
},
|
||
getViewValue: function() {
|
||
return this._ngModelCtrl.$viewValue;
|
||
},
|
||
selectNext: function() {
|
||
return changeSelectedButton(this.$element, 1);
|
||
},
|
||
selectPrevious : function() {
|
||
return changeSelectedButton(this.$element, -1);
|
||
},
|
||
setActiveDescendant: function (radioId) {
|
||
this.$element.attr('aria-activedescendant', radioId);
|
||
}
|
||
};
|
||
}
|
||
/**
|
||
* Change the radio group's selected button by a given increment.
|
||
* If no button is selected, select the first button.
|
||
*/
|
||
function changeSelectedButton(parent, increment) {
|
||
// Coerce all child radio buttons into an array, then wrap then in an iterator
|
||
var buttons = $mdUtil.iterator(
|
||
Array.prototype.slice.call(parent[0].querySelectorAll('md-radio-button')),
|
||
true
|
||
);
|
||
|
||
if (buttons.count()) {
|
||
var validate = function (button) {
|
||
// If disabled, then NOT valid
|
||
return !angular.element(button).attr("disabled");
|
||
};
|
||
var selected = parent[0].querySelector('md-radio-button.md-checked');
|
||
var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
|
||
// Activate radioButton's click listener (triggerHandler won't create a real click event)
|
||
angular.element(target).triggerHandler('click');
|
||
|
||
|
||
}
|
||
}
|
||
|
||
}
|
||
mdRadioGroupDirective.$inject = ["$mdUtil", "$mdConstant", "$mdTheming"];
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @module material.components.radioButton
|
||
* @name mdRadioButton
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
|
||
*
|
||
* While similar to the `<input type="radio" ng-model="" value="">` directive,
|
||
* the `<md-radio-button>` directive provides ink effects, ARIA support, and
|
||
* supports use within named radio groups.
|
||
*
|
||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||
* interaction with the input element.
|
||
* @param {string} ngValue Angular expression which sets the value to which the expression should
|
||
* be set when selected.*
|
||
* @param {string} value The value to which the expression should be set when selected.
|
||
* @param {string=} name Property name of the form under which the control is published.
|
||
* @param {string=} ariaLabel Adds label to radio button for accessibility.
|
||
* Defaults to radio button's text. If no default text is found, a warning will be logged.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
*
|
||
* <md-radio-button value="1" aria-label="Label 1">
|
||
* Label 1
|
||
* </md-radio-button>
|
||
*
|
||
* <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
|
||
* Green
|
||
* </md-radio-button>
|
||
*
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
|
||
|
||
var CHECKED_CSS = 'md-checked';
|
||
|
||
return {
|
||
restrict: 'E',
|
||
require: '^mdRadioGroup',
|
||
transclude: true,
|
||
template: '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
|
||
'<div class="md-off"></div>' +
|
||
'<div class="md-on"></div>' +
|
||
'</div>' +
|
||
'<div ng-transclude class="md-label"></div>',
|
||
link: link
|
||
};
|
||
|
||
function link(scope, element, attr, rgCtrl) {
|
||
var lastChecked;
|
||
|
||
$mdTheming(element);
|
||
configureAria(element, scope);
|
||
|
||
rgCtrl.add(render);
|
||
attr.$observe('value', render);
|
||
|
||
element
|
||
.on('click', listener)
|
||
.on('$destroy', function() {
|
||
rgCtrl.remove(render);
|
||
});
|
||
|
||
function listener(ev) {
|
||
if (element[0].hasAttribute('disabled')) return;
|
||
|
||
scope.$apply(function() {
|
||
rgCtrl.setViewValue(attr.value, ev && ev.type);
|
||
});
|
||
}
|
||
|
||
function render() {
|
||
var checked = (rgCtrl.getViewValue() == attr.value);
|
||
if (checked === lastChecked) {
|
||
return;
|
||
}
|
||
lastChecked = checked;
|
||
element.attr('aria-checked', checked);
|
||
if (checked) {
|
||
element.addClass(CHECKED_CSS);
|
||
rgCtrl.setActiveDescendant(element.attr('id'));
|
||
} else {
|
||
element.removeClass(CHECKED_CSS);
|
||
}
|
||
}
|
||
/**
|
||
* Inject ARIA-specific attributes appropriate for each radio button
|
||
*/
|
||
function configureAria( element, scope ){
|
||
scope.ariaId = buildAriaID();
|
||
|
||
element.attr({
|
||
'id' : scope.ariaId,
|
||
'role' : 'radio',
|
||
'aria-checked' : 'false'
|
||
});
|
||
|
||
$mdAria.expectWithText(element, 'aria-label');
|
||
|
||
/**
|
||
* Build a unique ID for each radio button that will be used with aria-activedescendant.
|
||
* Preserve existing ID if already specified.
|
||
* @returns {*|string}
|
||
*/
|
||
function buildAriaID() {
|
||
return attr.id || ( 'radio' + "_" + $mdUtil.nextUid() );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
mdRadioButtonDirective.$inject = ["$mdAria", "$mdUtil", "$mdTheming"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.sidenav
|
||
*
|
||
* @description
|
||
* A Sidenav QP component.
|
||
*/
|
||
angular.module('material.components.sidenav', [
|
||
'material.core',
|
||
'material.components.backdrop'
|
||
])
|
||
.factory('$mdSidenav', SidenavService )
|
||
.directive('mdSidenav', SidenavDirective)
|
||
.controller('$mdSidenavController', SidenavController);
|
||
|
||
|
||
/**
|
||
* @private
|
||
* @ngdoc service
|
||
* @name $mdSidenav
|
||
* @module material.components.sidenav
|
||
*
|
||
* @description
|
||
* `$mdSidenav` makes it easy to interact with multiple sidenavs
|
||
* in an app.
|
||
*
|
||
* @usage
|
||
* <hljs lang="js">
|
||
* // Toggle the given sidenav
|
||
* $mdSidenav(componentId).toggle();
|
||
* </hljs>
|
||
* <hljs lang="js">
|
||
* // Open the given sidenav
|
||
* $mdSidenav(componentId).open();
|
||
* </hljs>
|
||
* <hljs lang="js">
|
||
* // Close the given sidenav
|
||
* $mdSidenav(componentId).close();
|
||
* </hljs>
|
||
* <hljs lang="js">
|
||
* // Exposes whether given sidenav is set to be open
|
||
* $mdSidenav(componentId).isOpen();
|
||
* </hljs>
|
||
* <hljs lang="js">
|
||
* // Exposes whether given sidenav is locked open
|
||
* // If this is true, the sidenav will be open regardless of isOpen()
|
||
* $mdSidenav(componentId).isLockedOpen();
|
||
* </hljs>
|
||
*/
|
||
function SidenavService($mdComponentRegistry, $q) {
|
||
return function(handle) {
|
||
var errorMsg = "SideNav '" + handle + "' is not available!";
|
||
|
||
// Lookup the controller instance for the specified sidNav instance
|
||
var instance = $mdComponentRegistry.get(handle);
|
||
if(!instance) {
|
||
$mdComponentRegistry.notFoundError(handle);
|
||
}
|
||
|
||
return {
|
||
isOpen: function() {
|
||
return instance && instance.isOpen();
|
||
},
|
||
isLockedOpen: function() {
|
||
return instance && instance.isLockedOpen();
|
||
},
|
||
toggle: function() {
|
||
return instance ? instance.toggle() : $q.reject(errorMsg);
|
||
},
|
||
open: function() {
|
||
return instance ? instance.open() : $q.reject(errorMsg);
|
||
},
|
||
close: function() {
|
||
return instance ? instance.close() : $q.reject(errorMsg);
|
||
}
|
||
};
|
||
};
|
||
}
|
||
SidenavService.$inject = ["$mdComponentRegistry", "$q"];
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdSidenav
|
||
* @module material.components.sidenav
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
*
|
||
* A Sidenav component that can be opened and closed programatically.
|
||
*
|
||
* By default, upon opening it will slide out on top of the main content area.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <div layout="row" ng-controller="MyController">
|
||
* <md-sidenav md-component-id="left" class="md-sidenav-left">
|
||
* Left Nav!
|
||
* </md-sidenav>
|
||
*
|
||
* <md-content>
|
||
* Center Content
|
||
* <md-button ng-click="openLeftMenu()">
|
||
* Open Left Menu
|
||
* </md-button>
|
||
* </md-content>
|
||
*
|
||
* <md-sidenav md-component-id="right"
|
||
* md-is-locked-open="$media('min-width: 333px')"
|
||
* class="md-sidenav-right">
|
||
* Right Nav!
|
||
* </md-sidenav>
|
||
* </div>
|
||
* </hljs>
|
||
*
|
||
* <hljs lang="js">
|
||
* var app = angular.module('myApp', ['ngMaterial']);
|
||
* app.controller('MyController', function($scope, $mdSidenav) {
|
||
* $scope.openLeftMenu = function() {
|
||
* $mdSidenav('left').toggle();
|
||
* };
|
||
* });
|
||
* </hljs>
|
||
*
|
||
* @param {expression=} md-is-open A model bound to whether the sidenav is opened.
|
||
* @param {string=} md-component-id componentId to use with $mdSidenav service.
|
||
* @param {expression=} md-is-locked-open When this expression evalutes to true,
|
||
* the sidenav 'locks open': it falls into the content's flow instead
|
||
* of appearing over it. This overrides the `is-open` attribute.
|
||
*
|
||
* A $media() function is exposed to the is-locked-open attribute, which
|
||
* can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
|
||
* Examples:
|
||
*
|
||
* - `<md-sidenav md-is-locked-open="shouldLockOpen"></md-sidenav>`
|
||
* - `<md-sidenav md-is-locked-open="$media('min-width: 1000px')"></md-sidenav>`
|
||
* - `<md-sidenav md-is-locked-open="$media('sm')"></md-sidenav>` (locks open on small screens)
|
||
*/
|
||
function SidenavDirective($timeout, $animate, $parse, $mdMedia, $mdConstant, $compile, $mdTheming, $q, $document) {
|
||
return {
|
||
restrict: 'E',
|
||
scope: {
|
||
isOpen: '=?mdIsOpen'
|
||
},
|
||
controller: '$mdSidenavController',
|
||
compile: function(element) {
|
||
element.addClass('md-closed');
|
||
element.attr('tabIndex', '-1');
|
||
return postLink;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Directive Post Link function...
|
||
*/
|
||
function postLink(scope, element, attr, sidenavCtrl) {
|
||
var triggeringElement = null;
|
||
var promise = $q.when(true);
|
||
|
||
var isLockedOpenParsed = $parse(attr.mdIsLockedOpen);
|
||
var isLocked = function() {
|
||
return isLockedOpenParsed(scope.$parent, {
|
||
$media: $mdMedia
|
||
});
|
||
};
|
||
var backdrop = $compile(
|
||
'<md-backdrop class="md-sidenav-backdrop md-opaque ng-enter">'
|
||
)(scope);
|
||
|
||
element.on('$destroy', sidenavCtrl.destroy);
|
||
$mdTheming.inherit(backdrop, element);
|
||
|
||
scope.$watch(isLocked, updateIsLocked);
|
||
scope.$watch('isOpen', updateIsOpen);
|
||
|
||
|
||
// Publish special accessor for the Controller instance
|
||
sidenavCtrl.$toggleOpen = toggleOpen;
|
||
|
||
/**
|
||
* Toggle the DOM classes to indicate `locked`
|
||
* @param isLocked
|
||
*/
|
||
function updateIsLocked(isLocked, oldValue) {
|
||
scope.isLockedOpen = isLocked;
|
||
if (isLocked === oldValue) {
|
||
element.toggleClass('md-locked-open', !!isLocked);
|
||
} else {
|
||
$animate[isLocked ? 'addClass' : 'removeClass'](element, 'md-locked-open');
|
||
}
|
||
backdrop.toggleClass('md-locked-open', !!isLocked);
|
||
}
|
||
|
||
/**
|
||
* Toggle the SideNav view and attach/detach listeners
|
||
* @param isOpen
|
||
*/
|
||
function updateIsOpen(isOpen) {
|
||
var parent = element.parent();
|
||
|
||
parent[isOpen ? 'on' : 'off']('keydown', onKeyDown);
|
||
backdrop[isOpen ? 'on' : 'off']('click', close);
|
||
|
||
if ( isOpen ) {
|
||
// Capture upon opening..
|
||
triggeringElement = $document[0].activeElement;
|
||
}
|
||
|
||
return promise = $q.all([
|
||
$animate[isOpen ? 'enter' : 'leave'](backdrop, parent),
|
||
$animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed').then(function() {
|
||
// If we opened, and haven't closed again before the animation finished
|
||
if (scope.isOpen) {
|
||
element.focus();
|
||
}
|
||
})
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Toggle the sideNav view and publish a promise to be resolved when
|
||
* the view animation finishes.
|
||
*
|
||
* @param isOpen
|
||
* @returns {*}
|
||
*/
|
||
function toggleOpen( isOpen ) {
|
||
if (scope.isOpen == isOpen ) {
|
||
|
||
return $q.when(true);
|
||
|
||
} else {
|
||
var deferred = $q.defer();
|
||
|
||
// Toggle value to force an async `updateIsOpen()` to run
|
||
scope.isOpen = isOpen;
|
||
|
||
$timeout(function() {
|
||
|
||
// When the current `updateIsOpen()` animation finishes
|
||
promise.then(function(result){
|
||
|
||
if ( !scope.isOpen ) {
|
||
// reset focus to originating element (if available) upon close
|
||
triggeringElement && triggeringElement.focus();
|
||
triggeringElement = null;
|
||
}
|
||
|
||
deferred.resolve(result);
|
||
});
|
||
|
||
},0,false);
|
||
|
||
return deferred.promise;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Auto-close sideNav when the `escape` key is pressed.
|
||
* @param evt
|
||
*/
|
||
function onKeyDown(ev) {
|
||
var isEscape = (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE);
|
||
return isEscape ? close(ev) : $q.when(true);
|
||
}
|
||
|
||
/**
|
||
* With backdrop `clicks` or `escape` key-press, immediately
|
||
* apply the CSS close transition... Then notify the controller
|
||
* to close() and perform its own actions.
|
||
*/
|
||
function close(ev) {
|
||
ev.preventDefault();
|
||
ev.stopPropagation();
|
||
|
||
return sidenavCtrl.close();
|
||
}
|
||
|
||
}
|
||
}
|
||
SidenavDirective.$inject = ["$timeout", "$animate", "$parse", "$mdMedia", "$mdConstant", "$compile", "$mdTheming", "$q", "$document"];
|
||
|
||
/*
|
||
* @private
|
||
* @ngdoc controller
|
||
* @name SidenavController
|
||
* @module material.components.sidenav
|
||
*
|
||
*/
|
||
function SidenavController($scope, $element, $attrs, $mdComponentRegistry, $q) {
|
||
|
||
var self = this;
|
||
|
||
// Use Default internal method until overridden by directive postLink
|
||
|
||
self.$toggleOpen = function() { return $q.when($scope.isOpen); };
|
||
self.isOpen = function() { return !!$scope.isOpen; };
|
||
self.isLockedOpen = function() { return !!$scope.isLockedOpen; };
|
||
self.open = function() { return self.$toggleOpen( true ); };
|
||
self.close = function() { return self.$toggleOpen( false ); };
|
||
self.toggle = function() { return self.$toggleOpen( !$scope.isOpen ); };
|
||
|
||
self.destroy = $mdComponentRegistry.register(self, $attrs.mdComponentId);
|
||
}
|
||
SidenavController.$inject = ["$scope", "$element", "$attrs", "$mdComponentRegistry", "$q"];
|
||
|
||
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.slider
|
||
*/
|
||
angular.module('material.components.slider', [
|
||
'material.core'
|
||
])
|
||
.directive('mdSlider', SliderDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdSlider
|
||
* @module material.components.slider
|
||
* @restrict E
|
||
* @description
|
||
* The `<md-slider>` component allows the user to choose from a range of
|
||
* values.
|
||
*
|
||
* As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
|
||
* the slider is in the accent color by default. The primary color palette may be used with
|
||
* the `md-primary` class.
|
||
*
|
||
* It has two modes: 'normal' mode, where the user slides between a wide range
|
||
* of values, and 'discrete' mode, where the user slides between only a few
|
||
* select values.
|
||
*
|
||
* To enable discrete mode, add the `md-discrete` attribute to a slider,
|
||
* and use the `step` attribute to change the distance between
|
||
* values the user is allowed to pick.
|
||
*
|
||
* @usage
|
||
* <h4>Normal Mode</h4>
|
||
* <hljs lang="html">
|
||
* <md-slider ng-model="myValue" min="5" max="500">
|
||
* </md-slider>
|
||
* </hljs>
|
||
* <h4>Discrete Mode</h4>
|
||
* <hljs lang="html">
|
||
* <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
|
||
* </md-slider>
|
||
* </hljs>
|
||
*
|
||
* @param {boolean=} md-discrete Whether to enable discrete mode.
|
||
* @param {number=} step The distance between values the user is allowed to pick. Default 1.
|
||
* @param {number=} min The minimum value the user is allowed to pick. Default 0.
|
||
* @param {number=} max The maximum value the user is allowed to pick. Default 100.
|
||
*/
|
||
function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdTheming, $mdGesture, $parse) {
|
||
return {
|
||
scope: {},
|
||
require: '?ngModel',
|
||
template:
|
||
'<div class="md-track-container">' +
|
||
'<div class="md-track"></div>' +
|
||
'<div class="md-track md-track-fill"></div>' +
|
||
'<div class="md-track-ticks"></div>' +
|
||
'</div>' +
|
||
'<div class="md-thumb-container">' +
|
||
'<div class="md-thumb"></div>' +
|
||
'<div class="md-focus-thumb"></div>' +
|
||
'<div class="md-focus-ring"></div>' +
|
||
'<div class="md-sign">' +
|
||
'<span class="md-thumb-text"></span>' +
|
||
'</div>' +
|
||
'<div class="md-disabled-thumb"></div>' +
|
||
'</div>',
|
||
link: postLink
|
||
};
|
||
|
||
function postLink(scope, element, attr, ngModelCtrl) {
|
||
$mdTheming(element);
|
||
ngModelCtrl = ngModelCtrl || {
|
||
// Mock ngModelController if it doesn't exist to give us
|
||
// the minimum functionality needed
|
||
$setViewValue: function(val) {
|
||
this.$viewValue = val;
|
||
this.$viewChangeListeners.forEach(function(cb) { cb(); });
|
||
},
|
||
$parsers: [],
|
||
$formatters: [],
|
||
$viewChangeListeners: []
|
||
};
|
||
|
||
var isDisabledParsed = attr.ngDisabled && $parse(attr.ngDisabled);
|
||
var isDisabledGetter = isDisabledParsed ?
|
||
function() { return isDisabledParsed(scope.$parent); } :
|
||
angular.noop;
|
||
var thumb = angular.element(element[0].querySelector('.md-thumb'));
|
||
var thumbText = angular.element(element[0].querySelector('.md-thumb-text'));
|
||
var thumbContainer = thumb.parent();
|
||
var trackContainer = angular.element(element[0].querySelector('.md-track-container'));
|
||
var activeTrack = angular.element(element[0].querySelector('.md-track-fill'));
|
||
var tickContainer = angular.element(element[0].querySelector('.md-track-ticks'));
|
||
var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
|
||
|
||
// Default values, overridable by attrs
|
||
attr.min ? attr.$observe('min', updateMin) : updateMin(0);
|
||
attr.max ? attr.$observe('max', updateMax) : updateMax(100);
|
||
attr.step ? attr.$observe('step', updateStep) : updateStep(1);
|
||
|
||
// We have to manually stop the $watch on ngDisabled because it exists
|
||
// on the parent scope, and won't be automatically destroyed when
|
||
// the component is destroyed.
|
||
var stopDisabledWatch = angular.noop;
|
||
if (attr.ngDisabled) {
|
||
stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
|
||
}
|
||
|
||
$mdAria.expect(element, 'aria-label');
|
||
|
||
$mdGesture.register(element, 'drag');
|
||
element
|
||
.attr({
|
||
tabIndex: 0,
|
||
role: 'slider'
|
||
})
|
||
.on('keydown', keydownListener)
|
||
.on('$md.pressdown', onPressDown)
|
||
.on('$md.pressup', onPressUp)
|
||
.on('$md.dragstart', onDragStart)
|
||
.on('$md.drag', onDrag)
|
||
.on('$md.dragend', onDragEnd);
|
||
|
||
// On resize, recalculate the slider's dimensions and re-render
|
||
function updateAll() {
|
||
refreshSliderDimensions();
|
||
ngModelRender();
|
||
redrawTicks();
|
||
}
|
||
setTimeout(updateAll);
|
||
|
||
var debouncedUpdateAll = $$rAF.throttle(updateAll);
|
||
angular.element($window).on('resize', debouncedUpdateAll);
|
||
|
||
scope.$on('$destroy', function() {
|
||
angular.element($window).off('resize', debouncedUpdateAll);
|
||
stopDisabledWatch();
|
||
});
|
||
|
||
ngModelCtrl.$render = ngModelRender;
|
||
ngModelCtrl.$viewChangeListeners.push(ngModelRender);
|
||
ngModelCtrl.$formatters.push(minMaxValidator);
|
||
ngModelCtrl.$formatters.push(stepValidator);
|
||
|
||
/**
|
||
* Attributes
|
||
*/
|
||
var min;
|
||
var max;
|
||
var step;
|
||
function updateMin(value) {
|
||
min = parseFloat(value);
|
||
element.attr('aria-valuemin', value);
|
||
updateAll();
|
||
}
|
||
function updateMax(value) {
|
||
max = parseFloat(value);
|
||
element.attr('aria-valuemax', value);
|
||
updateAll();
|
||
}
|
||
function updateStep(value) {
|
||
step = parseFloat(value);
|
||
redrawTicks();
|
||
}
|
||
function updateAriaDisabled(isDisabled) {
|
||
element.attr('aria-disabled', !!isDisabled);
|
||
}
|
||
|
||
// Draw the ticks with canvas.
|
||
// The alternative to drawing ticks with canvas is to draw one element for each tick,
|
||
// which could quickly become a performance bottleneck.
|
||
var tickCanvas, tickCtx;
|
||
function redrawTicks() {
|
||
if (!angular.isDefined(attr.mdDiscrete)) return;
|
||
|
||
var numSteps = Math.floor( (max - min) / step );
|
||
if (!tickCanvas) {
|
||
var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
|
||
tickCanvas = angular.element('<canvas style="position:absolute;">');
|
||
tickCtx = tickCanvas[0].getContext('2d');
|
||
tickCtx.fillStyle = trackTicksStyle.backgroundColor || 'black';
|
||
tickContainer.append(tickCanvas);
|
||
}
|
||
var dimensions = getSliderDimensions();
|
||
tickCanvas[0].width = dimensions.width;
|
||
tickCanvas[0].height = dimensions.height;
|
||
|
||
var distance;
|
||
for (var i = 0; i <= numSteps; i++) {
|
||
distance = Math.floor(dimensions.width * (i / numSteps));
|
||
tickCtx.fillRect(distance - 1, 0, 2, dimensions.height);
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Refreshing Dimensions
|
||
*/
|
||
var sliderDimensions = {};
|
||
refreshSliderDimensions();
|
||
function refreshSliderDimensions() {
|
||
sliderDimensions = trackContainer[0].getBoundingClientRect();
|
||
}
|
||
function getSliderDimensions() {
|
||
throttledRefreshDimensions();
|
||
return sliderDimensions;
|
||
}
|
||
|
||
/**
|
||
* left/right arrow listener
|
||
*/
|
||
function keydownListener(ev) {
|
||
if(element[0].hasAttribute('disabled')) {
|
||
return;
|
||
}
|
||
|
||
var changeAmount;
|
||
if (ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
|
||
changeAmount = -step;
|
||
} else if (ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
|
||
changeAmount = step;
|
||
}
|
||
if (changeAmount) {
|
||
if (ev.metaKey || ev.ctrlKey || ev.altKey) {
|
||
changeAmount *= 4;
|
||
}
|
||
ev.preventDefault();
|
||
ev.stopPropagation();
|
||
scope.$evalAsync(function() {
|
||
setModelValue(ngModelCtrl.$viewValue + changeAmount);
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ngModel setters and validators
|
||
*/
|
||
function setModelValue(value) {
|
||
ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
|
||
}
|
||
function ngModelRender() {
|
||
if (isNaN(ngModelCtrl.$viewValue)) {
|
||
ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
|
||
}
|
||
|
||
var percent = (ngModelCtrl.$viewValue - min) / (max - min);
|
||
scope.modelValue = ngModelCtrl.$viewValue;
|
||
element.attr('aria-valuenow', ngModelCtrl.$viewValue);
|
||
setSliderPercent(percent);
|
||
thumbText.text( ngModelCtrl.$viewValue );
|
||
}
|
||
|
||
function minMaxValidator(value) {
|
||
if (angular.isNumber(value)) {
|
||
return Math.max(min, Math.min(max, value));
|
||
}
|
||
}
|
||
function stepValidator(value) {
|
||
if (angular.isNumber(value)) {
|
||
return Math.round(value / step) * step;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param percent 0-1
|
||
*/
|
||
function setSliderPercent(percent) {
|
||
activeTrack.css('width', (percent * 100) + '%');
|
||
thumbContainer.css(
|
||
$mdConstant.CSS.TRANSFORM,
|
||
'translate3d(' + (percent * 100) + '%,0,0)'
|
||
);
|
||
element.toggleClass('md-min', percent === 0);
|
||
}
|
||
|
||
|
||
/**
|
||
* Slide listeners
|
||
*/
|
||
var isDragging = false;
|
||
var isDiscrete = angular.isDefined(attr.mdDiscrete);
|
||
|
||
function onPressDown(ev) {
|
||
if (isDisabledGetter()) return;
|
||
|
||
element.addClass('active');
|
||
element[0].focus();
|
||
refreshSliderDimensions();
|
||
|
||
var exactVal = percentToValue( positionToPercent( ev.pointer.x ));
|
||
var closestVal = minMaxValidator( stepValidator(exactVal) );
|
||
scope.$apply(function() {
|
||
setModelValue( closestVal );
|
||
setSliderPercent( valueToPercent(closestVal));
|
||
});
|
||
}
|
||
function onPressUp(ev) {
|
||
if (isDisabledGetter()) return;
|
||
|
||
element.removeClass('dragging active');
|
||
|
||
var exactVal = percentToValue( positionToPercent( ev.pointer.x ));
|
||
var closestVal = minMaxValidator( stepValidator(exactVal) );
|
||
scope.$apply(function() {
|
||
setModelValue(closestVal);
|
||
});
|
||
}
|
||
function onDragStart(ev) {
|
||
if (isDisabledGetter()) return;
|
||
isDragging = true;
|
||
ev.stopPropagation();
|
||
|
||
element.addClass('dragging');
|
||
setSliderFromEvent(ev);
|
||
}
|
||
function onDrag(ev) {
|
||
if (!isDragging) return;
|
||
ev.stopPropagation();
|
||
setSliderFromEvent(ev);
|
||
}
|
||
function onDragEnd(ev) {
|
||
if (!isDragging) return;
|
||
ev.stopPropagation();
|
||
isDragging = false;
|
||
}
|
||
|
||
function setSliderFromEvent(ev) {
|
||
// While panning discrete, update only the
|
||
// visual positioning but not the model value.
|
||
if ( isDiscrete ) adjustThumbPosition( ev.pointer.x );
|
||
else doSlide( ev.pointer.x );
|
||
}
|
||
|
||
/**
|
||
* Slide the UI by changing the model value
|
||
* @param x
|
||
*/
|
||
function doSlide( x ) {
|
||
scope.$evalAsync( function() {
|
||
setModelValue( percentToValue( positionToPercent(x) ));
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Slide the UI without changing the model (while dragging/panning)
|
||
* @param x
|
||
*/
|
||
function adjustThumbPosition( x ) {
|
||
var exactVal = percentToValue( positionToPercent( x ));
|
||
var closestVal = minMaxValidator( stepValidator(exactVal) );
|
||
setSliderPercent( positionToPercent(x) );
|
||
thumbText.text( closestVal );
|
||
}
|
||
|
||
/**
|
||
* Convert horizontal position on slider to percentage value of offset from beginning...
|
||
* @param x
|
||
* @returns {number}
|
||
*/
|
||
function positionToPercent( x ) {
|
||
return Math.max(0, Math.min(1, (x - sliderDimensions.left) / (sliderDimensions.width)));
|
||
}
|
||
|
||
/**
|
||
* Convert percentage offset on slide to equivalent model value
|
||
* @param percent
|
||
* @returns {*}
|
||
*/
|
||
function percentToValue( percent ) {
|
||
return (min + percent * (max - min));
|
||
}
|
||
|
||
function valueToPercent( val ) {
|
||
return (val - min)/(max - min);
|
||
}
|
||
}
|
||
}
|
||
SliderDirective.$inject = ["$$rAF", "$window", "$mdAria", "$mdUtil", "$mdConstant", "$mdTheming", "$mdGesture", "$parse"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/*
|
||
* @ngdoc module
|
||
* @name material.components.sticky
|
||
* @description
|
||
*
|
||
* Sticky effects for md
|
||
*/
|
||
|
||
angular.module('material.components.sticky', [
|
||
'material.core',
|
||
'material.components.content'
|
||
])
|
||
.factory('$mdSticky', MdSticky);
|
||
|
||
/*
|
||
* @ngdoc service
|
||
* @name $mdSticky
|
||
* @module material.components.sticky
|
||
*
|
||
* @description
|
||
* The `$mdSticky`service provides a mixin to make elements sticky.
|
||
*
|
||
* @returns A `$mdSticky` function that takes three arguments:
|
||
* - `scope`
|
||
* - `element`: The element that will be 'sticky'
|
||
* - `elementClone`: A clone of the element, that will be shown
|
||
* when the user starts scrolling past the original element.
|
||
* If not provided, it will use the result of `element.clone()`.
|
||
*/
|
||
|
||
function MdSticky($document, $mdConstant, $compile, $$rAF, $mdUtil) {
|
||
|
||
var browserStickySupport = checkStickySupport();
|
||
|
||
/**
|
||
* Registers an element as sticky, used internally by directives to register themselves
|
||
*/
|
||
return function registerStickyElement(scope, element, stickyClone) {
|
||
var contentCtrl = element.controller('mdContent');
|
||
if (!contentCtrl) return;
|
||
|
||
if (browserStickySupport) {
|
||
element.css({
|
||
position: browserStickySupport,
|
||
top: 0,
|
||
'z-index': 2
|
||
});
|
||
} else {
|
||
var $$sticky = contentCtrl.$element.data('$$sticky');
|
||
if (!$$sticky) {
|
||
$$sticky = setupSticky(contentCtrl);
|
||
contentCtrl.$element.data('$$sticky', $$sticky);
|
||
}
|
||
|
||
var deregister = $$sticky.add(element, stickyClone || element.clone());
|
||
scope.$on('$destroy', deregister);
|
||
}
|
||
};
|
||
|
||
function setupSticky(contentCtrl) {
|
||
var contentEl = contentCtrl.$element;
|
||
|
||
// Refresh elements is very expensive, so we use the debounced
|
||
// version when possible.
|
||
var debouncedRefreshElements = $$rAF.throttle(refreshElements);
|
||
|
||
// setupAugmentedScrollEvents gives us `$scrollstart` and `$scroll`,
|
||
// more reliable than `scroll` on android.
|
||
setupAugmentedScrollEvents(contentEl);
|
||
contentEl.on('$scrollstart', debouncedRefreshElements);
|
||
contentEl.on('$scroll', onScroll);
|
||
|
||
var self;
|
||
var stickyBaseoffset = contentEl.prop('offsetTop');
|
||
return self = {
|
||
prev: null,
|
||
current: null, //the currently stickied item
|
||
next: null,
|
||
items: [],
|
||
add: add,
|
||
refreshElements: refreshElements
|
||
};
|
||
|
||
/***************
|
||
* Public
|
||
***************/
|
||
// Add an element and its sticky clone to this content's sticky collection
|
||
function add(element, stickyClone) {
|
||
stickyClone.addClass('md-sticky-clone');
|
||
stickyClone.css('top', stickyBaseoffset + 'px');
|
||
|
||
var item = {
|
||
element: element,
|
||
clone: stickyClone
|
||
};
|
||
self.items.push(item);
|
||
|
||
contentEl.parent().prepend(item.clone);
|
||
|
||
debouncedRefreshElements();
|
||
|
||
return function remove() {
|
||
self.items.forEach(function(item, index) {
|
||
if (item.element[0] === element[0]) {
|
||
self.items.splice(index, 1);
|
||
item.clone.remove();
|
||
}
|
||
});
|
||
debouncedRefreshElements();
|
||
};
|
||
}
|
||
|
||
function refreshElements() {
|
||
// Sort our collection of elements by their current position in the DOM.
|
||
// We need to do this because our elements' order of being added may not
|
||
// be the same as their order of display.
|
||
self.items.forEach(refreshPosition);
|
||
self.items = self.items.sort(function(a, b) {
|
||
return a.top < b.top ? -1 : 1;
|
||
});
|
||
|
||
// Find which item in the list should be active,
|
||
// based upon the content's current scroll position
|
||
var item;
|
||
var currentScrollTop = contentEl.prop('scrollTop');
|
||
for (var i = self.items.length - 1; i >= 0; i--) {
|
||
if (currentScrollTop > self.items[i].top) {
|
||
item = self.items[i];
|
||
break;
|
||
}
|
||
}
|
||
setCurrentItem(item);
|
||
}
|
||
|
||
|
||
/***************
|
||
* Private
|
||
***************/
|
||
|
||
// Find the `top` of an item relative to the content element,
|
||
// and also the height.
|
||
function refreshPosition(item) {
|
||
// Find the top of an item by adding to the offsetHeight until we reach the
|
||
// content element.
|
||
var current = item.element[0];
|
||
item.top = 0;
|
||
item.left = 0;
|
||
while (current && current !== contentEl[0]) {
|
||
item.top += current.offsetTop;
|
||
item.left += current.offsetLeft;
|
||
current = current.offsetParent;
|
||
}
|
||
item.height = item.element.prop('offsetHeight');
|
||
item.clone.css('margin-left', item.left + 'px');
|
||
}
|
||
|
||
|
||
// As we scroll, push in and select the correct sticky element.
|
||
function onScroll() {
|
||
var scrollTop = contentEl.prop('scrollTop');
|
||
var isScrollingDown = scrollTop > (onScroll.prevScrollTop || 0);
|
||
onScroll.prevScrollTop = scrollTop;
|
||
|
||
// At the top?
|
||
if (scrollTop === 0) {
|
||
setCurrentItem(null);
|
||
|
||
// Going to next item?
|
||
} else if (isScrollingDown && self.next) {
|
||
if (self.next.top - scrollTop <= 0) {
|
||
// Sticky the next item if we've scrolled past its position.
|
||
setCurrentItem(self.next);
|
||
} else if (self.current) {
|
||
// Push the current item up when we're almost at the next item.
|
||
if (self.next.top - scrollTop <= self.next.height) {
|
||
translate(self.current, self.next.top - self.next.height - scrollTop);
|
||
} else {
|
||
translate(self.current, null);
|
||
}
|
||
}
|
||
|
||
// Scrolling up with a current sticky item?
|
||
} else if (!isScrollingDown && self.current) {
|
||
if (scrollTop < self.current.top) {
|
||
// Sticky the previous item if we've scrolled up past
|
||
// the original position of the currently stickied item.
|
||
setCurrentItem(self.prev);
|
||
}
|
||
// Scrolling up, and just bumping into the item above (just set to current)?
|
||
// If we have a next item bumping into the current item, translate
|
||
// the current item up from the top as it scrolls into view.
|
||
if (self.current && self.next) {
|
||
if (scrollTop >= self.next.top - self.current.height) {
|
||
translate(self.current, self.next.top - scrollTop - self.current.height);
|
||
} else {
|
||
translate(self.current, null);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function setCurrentItem(item) {
|
||
if (self.current === item) return;
|
||
// Deactivate currently active item
|
||
if (self.current) {
|
||
translate(self.current, null);
|
||
setStickyState(self.current, null);
|
||
}
|
||
|
||
// Activate new item if given
|
||
if (item) {
|
||
setStickyState(item, 'active');
|
||
}
|
||
|
||
self.current = item;
|
||
var index = self.items.indexOf(item);
|
||
// If index === -1, index + 1 = 0. It works out.
|
||
self.next = self.items[index + 1];
|
||
self.prev = self.items[index - 1];
|
||
setStickyState(self.next, 'next');
|
||
setStickyState(self.prev, 'prev');
|
||
}
|
||
|
||
function setStickyState(item, state) {
|
||
if (!item || item.state === state) return;
|
||
if (item.state) {
|
||
item.clone.attr('sticky-prev-state', item.state);
|
||
item.element.attr('sticky-prev-state', item.state);
|
||
}
|
||
item.clone.attr('sticky-state', state);
|
||
item.element.attr('sticky-state', state);
|
||
item.state = state;
|
||
}
|
||
|
||
function translate(item, amount) {
|
||
if (!item) return;
|
||
if (amount === null || amount === undefined) {
|
||
if (item.translateY) {
|
||
item.translateY = null;
|
||
item.clone.css($mdConstant.CSS.TRANSFORM, '');
|
||
}
|
||
} else {
|
||
item.translateY = amount;
|
||
item.clone.css(
|
||
$mdConstant.CSS.TRANSFORM,
|
||
'translate3d(' + item.left + 'px,' + amount + 'px,0)'
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Function to check for browser sticky support
|
||
function checkStickySupport($el) {
|
||
var stickyProp;
|
||
var testEl = angular.element('<div>');
|
||
$document[0].body.appendChild(testEl[0]);
|
||
|
||
var stickyProps = ['sticky', '-webkit-sticky'];
|
||
for (var i = 0; i < stickyProps.length; ++i) {
|
||
testEl.css({position: stickyProps[i], top: 0, 'z-index': 2});
|
||
if (testEl.css('position') == stickyProps[i]) {
|
||
stickyProp = stickyProps[i];
|
||
break;
|
||
}
|
||
}
|
||
testEl.remove();
|
||
return stickyProp;
|
||
}
|
||
|
||
// Android 4.4 don't accurately give scroll events.
|
||
// To fix this problem, we setup a fake scroll event. We say:
|
||
// > If a scroll or touchmove event has happened in the last DELAY milliseconds,
|
||
// then send a `$scroll` event every animationFrame.
|
||
// Additionally, we add $scrollstart and $scrollend events.
|
||
function setupAugmentedScrollEvents(element) {
|
||
var SCROLL_END_DELAY = 200;
|
||
var isScrolling;
|
||
var lastScrollTime;
|
||
element.on('scroll touchmove', function() {
|
||
if (!isScrolling) {
|
||
isScrolling = true;
|
||
$$rAF(loopScrollEvent);
|
||
element.triggerHandler('$scrollstart');
|
||
}
|
||
element.triggerHandler('$scroll');
|
||
lastScrollTime = +$mdUtil.now();
|
||
});
|
||
|
||
function loopScrollEvent() {
|
||
if (+$mdUtil.now() - lastScrollTime > SCROLL_END_DELAY) {
|
||
isScrolling = false;
|
||
element.triggerHandler('$scrollend');
|
||
} else {
|
||
element.triggerHandler('$scroll');
|
||
$$rAF(loopScrollEvent);
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
MdSticky.$inject = ["$document", "$mdConstant", "$compile", "$$rAF", "$mdUtil"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.subheader
|
||
* @description
|
||
* SubHeader module
|
||
*
|
||
* Subheaders are special list tiles that delineate distinct sections of a
|
||
* list or grid list and are typically related to the current filtering or
|
||
* sorting criteria. Subheader tiles are either displayed inline with tiles or
|
||
* can be associated with content, for example, in an adjacent column.
|
||
*
|
||
* Upon scrolling, subheaders remain pinned to the top of the screen and remain
|
||
* pinned until pushed on or off screen by the next subheader. @see [Material
|
||
* Design Specifications](https://www.google.com/design/spec/components/subheaders.html)
|
||
*
|
||
* > To improve the visual grouping of content, use the system color for your subheaders.
|
||
*
|
||
*/
|
||
angular.module('material.components.subheader', [
|
||
'material.core',
|
||
'material.components.sticky'
|
||
])
|
||
.directive('mdSubheader', MdSubheaderDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdSubheader
|
||
* @module material.components.subheader
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The `<md-subheader>` directive is a subheader for a section
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-subheader>Online Friends</md-subheader>
|
||
* </hljs>
|
||
*/
|
||
|
||
function MdSubheaderDirective($mdSticky, $compile, $mdTheming) {
|
||
return {
|
||
restrict: 'E',
|
||
replace: true,
|
||
transclude: true,
|
||
template:
|
||
'<h2 class="md-subheader">' +
|
||
'<span class="md-subheader-content"></span>' +
|
||
'</h2>',
|
||
compile: function(element, attr, transclude) {
|
||
var outerHTML = element[0].outerHTML;
|
||
return function postLink(scope, element, attr) {
|
||
$mdTheming(element);
|
||
function getContent(el) {
|
||
return angular.element(el[0].querySelector('.md-subheader-content'));
|
||
}
|
||
|
||
// Transclude the user-given contents of the subheader
|
||
// the conventional way.
|
||
transclude(scope, function(clone) {
|
||
getContent(element).append(clone);
|
||
});
|
||
|
||
// Create another clone, that uses the outer and inner contents
|
||
// of the element, that will be 'stickied' as the user scrolls.
|
||
transclude(scope, function(clone) {
|
||
var stickyClone = $compile(angular.element(outerHTML))(scope);
|
||
$mdTheming(stickyClone);
|
||
getContent(stickyClone).append(clone);
|
||
$mdSticky(scope, element, stickyClone);
|
||
});
|
||
};
|
||
}
|
||
};
|
||
}
|
||
MdSubheaderDirective.$inject = ["$mdSticky", "$compile", "$mdTheming"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.swipe
|
||
* @description Swipe module!
|
||
*/
|
||
/**
|
||
* @ngdoc directive
|
||
* @module material.components.swipe
|
||
* @name mdSwipeLeft
|
||
*
|
||
* @restrict A
|
||
*
|
||
* @description
|
||
* The md-swipe-left directives allows you to specify custom behavior when an element is swiped
|
||
* left.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <div md-swipe-left="onSwipeLeft()">Swipe me left!</div>
|
||
* </hljs>
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @module material.components.swipe
|
||
* @name mdSwipeRight
|
||
*
|
||
* @restrict A
|
||
*
|
||
* @description
|
||
* The md-swipe-right directives allows you to specify custom behavior when an element is swiped
|
||
* right.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <div md-swipe-right="onSwipeRight()">Swipe me right!</div>
|
||
* </hljs>
|
||
*/
|
||
|
||
var module = angular.module('material.components.swipe',[]);
|
||
|
||
['SwipeLeft', 'SwipeRight'].forEach(function(name) {
|
||
var directiveName = 'md' + name;
|
||
var eventName = '$md.' + name.toLowerCase();
|
||
|
||
module.directive(directiveName, /*@ngInject*/ ["$parse", function($parse) {
|
||
return {
|
||
restrict: 'A',
|
||
link: postLink
|
||
};
|
||
|
||
function postLink(scope, element, attr) {
|
||
var fn = $parse(attr[directiveName]);
|
||
|
||
element.on(eventName, function(ev) {
|
||
scope.$apply(function() {
|
||
fn(scope, {
|
||
$event: ev
|
||
});
|
||
});
|
||
});
|
||
|
||
}
|
||
}]);
|
||
});
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @private
|
||
* @ngdoc module
|
||
* @name material.components.switch
|
||
*/
|
||
|
||
angular.module('material.components.switch', [
|
||
'material.core',
|
||
'material.components.checkbox'
|
||
])
|
||
.directive('mdSwitch', MdSwitch);
|
||
|
||
/**
|
||
* @private
|
||
* @ngdoc directive
|
||
* @module material.components.switch
|
||
* @name mdSwitch
|
||
* @restrict E
|
||
*
|
||
* The switch directive is used very much like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
|
||
*
|
||
* As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
|
||
* the switch is in the accent color by default. The primary color palette may be used with
|
||
* the `md-primary` class.
|
||
*
|
||
* @param {string} ng-model Assignable angular expression to data-bind to.
|
||
* @param {string=} name Property name of the form under which the control is published.
|
||
* @param {expression=} ng-true-value The value to which the expression should be set when selected.
|
||
* @param {expression=} ng-false-value The value to which the expression should be set when not selected.
|
||
* @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element.
|
||
* @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects.
|
||
* @param {string=} aria-label Publish the button label used by screen-readers for accessibility. Defaults to the switch's text.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-switch ng-model="isActive" aria-label="Finished?">
|
||
* Finished ?
|
||
* </md-switch>
|
||
*
|
||
* <md-switch md-no-ink ng-model="hasInk" aria-label="No Ink Effects">
|
||
* No Ink Effects
|
||
* </md-switch>
|
||
*
|
||
* <md-switch ng-disabled="true" ng-model="isDisabled" aria-label="Disabled">
|
||
* Disabled
|
||
* </md-switch>
|
||
*
|
||
* </hljs>
|
||
*/
|
||
function MdSwitch(mdCheckboxDirective, $mdTheming, $mdUtil, $document, $mdConstant, $parse, $$rAF, $mdGesture) {
|
||
var checkboxDirective = mdCheckboxDirective[0];
|
||
|
||
return {
|
||
restrict: 'E',
|
||
transclude: true,
|
||
template:
|
||
'<div class="md-container">' +
|
||
'<div class="md-bar"></div>' +
|
||
'<div class="md-thumb-container">' +
|
||
'<div class="md-thumb" md-ink-ripple md-ink-ripple-checkbox></div>' +
|
||
'</div>'+
|
||
'</div>' +
|
||
'<div ng-transclude class="md-label">' +
|
||
'</div>',
|
||
require: '?ngModel',
|
||
compile: compile
|
||
};
|
||
|
||
function compile(element, attr) {
|
||
var checkboxLink = checkboxDirective.compile(element, attr);
|
||
// no transition on initial load
|
||
element.addClass('md-dragging');
|
||
|
||
return function (scope, element, attr, ngModel) {
|
||
ngModel = ngModel || $mdUtil.fakeNgModel();
|
||
var disabledGetter = $parse(attr.ngDisabled);
|
||
var thumbContainer = angular.element(element[0].querySelector('.md-thumb-container'));
|
||
var switchContainer = angular.element(element[0].querySelector('.md-container'));
|
||
|
||
// no transition on initial load
|
||
$$rAF(function() {
|
||
element.removeClass('md-dragging');
|
||
});
|
||
|
||
checkboxLink(scope, element, attr, ngModel);
|
||
|
||
if (angular.isDefined(attr.ngDisabled)) {
|
||
scope.$watch(disabledGetter, function(isDisabled) {
|
||
element.attr('tabindex', isDisabled ? -1 : 0);
|
||
});
|
||
}
|
||
|
||
// These events are triggered by setup drag
|
||
$mdGesture.register(switchContainer, 'drag');
|
||
switchContainer
|
||
.on('$md.dragstart', onDragStart)
|
||
.on('$md.drag', onDrag)
|
||
.on('$md.dragend', onDragEnd);
|
||
|
||
var drag;
|
||
function onDragStart(ev) {
|
||
// Don't go if ng-disabled===true
|
||
if (disabledGetter(scope)) return;
|
||
ev.stopPropagation();
|
||
|
||
element.addClass('md-dragging');
|
||
drag = {
|
||
width: thumbContainer.prop('offsetWidth')
|
||
};
|
||
element.removeClass('transition');
|
||
}
|
||
|
||
function onDrag(ev) {
|
||
if (!drag) return;
|
||
ev.stopPropagation();
|
||
ev.srcEvent && ev.srcEvent.preventDefault();
|
||
|
||
var percent = ev.pointer.distanceX / drag.width;
|
||
|
||
//if checked, start from right. else, start from left
|
||
var translate = ngModel.$viewValue ? 1 + percent : percent;
|
||
// Make sure the switch stays inside its bounds, 0-1%
|
||
translate = Math.max(0, Math.min(1, translate));
|
||
|
||
thumbContainer.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + (100*translate) + '%,0,0)');
|
||
drag.translate = translate;
|
||
}
|
||
|
||
function onDragEnd(ev) {
|
||
if (!drag) return;
|
||
ev.stopPropagation();
|
||
|
||
element.removeClass('md-dragging');
|
||
thumbContainer.css($mdConstant.CSS.TRANSFORM, '');
|
||
|
||
// We changed if there is no distance (this is a click a click),
|
||
// or if the drag distance is >50% of the total.
|
||
var isChanged = ngModel.$viewValue ? drag.translate < 0.5 : drag.translate > 0.5;
|
||
if (isChanged) {
|
||
applyModelValue(!ngModel.$viewValue);
|
||
}
|
||
drag = null;
|
||
}
|
||
|
||
function applyModelValue(newValue) {
|
||
scope.$apply(function() {
|
||
ngModel.$setViewValue(newValue);
|
||
ngModel.$render();
|
||
});
|
||
}
|
||
|
||
};
|
||
}
|
||
|
||
|
||
}
|
||
MdSwitch.$inject = ["mdCheckboxDirective", "$mdTheming", "$mdUtil", "$document", "$mdConstant", "$parse", "$$rAF", "$mdGesture"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.tabs
|
||
* @description
|
||
*
|
||
* Tabs, created with the `<md-tabs>` directive provide *tabbed* navigation with different styles.
|
||
* The Tabs component consists of clickable tabs that are aligned horizontally side-by-side.
|
||
*
|
||
* Features include support for:
|
||
*
|
||
* - static or dynamic tabs,
|
||
* - responsive designs,
|
||
* - accessibility support (ARIA),
|
||
* - tab pagination,
|
||
* - external or internal tab content,
|
||
* - focus indicators and arrow-key navigations,
|
||
* - programmatic lookup and access to tab controllers, and
|
||
* - dynamic transitions through different tab contents.
|
||
*
|
||
*/
|
||
/*
|
||
* @see js folder for tabs implementation
|
||
*/
|
||
angular.module('material.components.tabs', [
|
||
'material.core'
|
||
]);
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.textField
|
||
* @description
|
||
* Form
|
||
*/
|
||
angular.module('material.components.textField', [
|
||
'material.core'
|
||
])
|
||
.directive('mdInputGroup', mdInputGroupDirective)
|
||
.directive('mdInput', mdInputDirective)
|
||
.directive('mdTextFloat', mdTextFloatDirective);
|
||
|
||
|
||
function mdTextFloatDirective($mdTheming, $mdUtil, $parse, $log) {
|
||
return {
|
||
restrict: 'E',
|
||
replace: true,
|
||
scope : {
|
||
fid : '@?mdFid',
|
||
label : '@?',
|
||
value : '=ngModel'
|
||
},
|
||
compile : function(element, attr) {
|
||
|
||
$log.warn('<md-text-float> is deprecated. Please use `<md-input-container>` and `<input>`.' +
|
||
'More information at http://material.angularjs.org/#/api/material.components.input/directive/mdInputContainer');
|
||
|
||
if ( angular.isUndefined(attr.mdFid) ) {
|
||
attr.mdFid = $mdUtil.nextUid();
|
||
}
|
||
|
||
return {
|
||
pre : function(scope, element, attrs) {
|
||
var disabledParsed = $parse(attrs.ngDisabled);
|
||
scope.isDisabled = function() {
|
||
return disabledParsed(scope.$parent);
|
||
};
|
||
|
||
scope.inputType = attrs.type || "text";
|
||
},
|
||
post: $mdTheming
|
||
};
|
||
},
|
||
template:
|
||
'<md-input-group tabindex="-1">' +
|
||
' <label for="{{fid}}" >{{label}}</label>' +
|
||
' <md-input id="{{fid}}" ng-disabled="isDisabled()" ng-model="value" type="{{inputType}}"></md-input>' +
|
||
'</md-input-group>'
|
||
};
|
||
}
|
||
mdTextFloatDirective.$inject = ["$mdTheming", "$mdUtil", "$parse", "$log"];
|
||
|
||
function mdInputGroupDirective($log) {
|
||
return {
|
||
restrict: 'CE',
|
||
controller: ['$element', function($element) {
|
||
|
||
$log.warn('<md-input-group> is deprecated. Please use `<md-input-container>` and `<input>`.' +
|
||
'More information at http://material.angularjs.org/#/api/material.components.input/directive/mdInputContainer');
|
||
this.setFocused = function(isFocused) {
|
||
$element.toggleClass('md-input-focused', !!isFocused);
|
||
};
|
||
this.setHasValue = function(hasValue) {
|
||
$element.toggleClass('md-input-has-value', hasValue );
|
||
};
|
||
}]
|
||
};
|
||
|
||
}
|
||
mdInputGroupDirective.$inject = ["$log"];
|
||
|
||
function mdInputDirective($mdUtil, $log) {
|
||
return {
|
||
restrict: 'E',
|
||
replace: true,
|
||
template: '<input >',
|
||
require: ['^?mdInputGroup', '?ngModel'],
|
||
link: function(scope, element, attr, ctrls) {
|
||
if ( !ctrls[0] ) return;
|
||
|
||
$log.warn('<md-input> is deprecated. Please use `<md-input-container>` and `<input>`.' +
|
||
'More information at http://material.angularjs.org/#/api/material.components.input/directive/mdInputContainer');
|
||
|
||
var inputGroupCtrl = ctrls[0];
|
||
var ngModelCtrl = ctrls[1];
|
||
|
||
scope.$watch(scope.isDisabled, function(isDisabled) {
|
||
element.attr('aria-disabled', !!isDisabled);
|
||
element.attr('tabindex', !!isDisabled);
|
||
});
|
||
element.attr('type', attr.type || element.parent().attr('type') || "text");
|
||
|
||
// When the input value changes, check if it "has" a value, and
|
||
// set the appropriate class on the input group
|
||
if (ngModelCtrl) {
|
||
//Add a $formatter so we don't use up the render function
|
||
ngModelCtrl.$formatters.push(function(value) {
|
||
inputGroupCtrl.setHasValue( isNotEmpty(value) );
|
||
return value;
|
||
});
|
||
}
|
||
|
||
element
|
||
.on('input', function() {
|
||
inputGroupCtrl.setHasValue( isNotEmpty() );
|
||
})
|
||
.on('focus', function(e) {
|
||
// When the input focuses, add the focused class to the group
|
||
inputGroupCtrl.setFocused(true);
|
||
})
|
||
.on('blur', function(e) {
|
||
// When the input blurs, remove the focused class from the group
|
||
inputGroupCtrl.setFocused(false);
|
||
inputGroupCtrl.setHasValue( isNotEmpty() );
|
||
});
|
||
|
||
scope.$on('$destroy', function() {
|
||
inputGroupCtrl.setFocused(false);
|
||
inputGroupCtrl.setHasValue(false);
|
||
});
|
||
|
||
|
||
function isNotEmpty(value) {
|
||
value = angular.isUndefined(value) ? element.val() : value;
|
||
return (angular.isDefined(value) && (value!==null) &&
|
||
(value.toString().trim() !== ""));
|
||
}
|
||
}
|
||
};
|
||
}
|
||
mdInputDirective.$inject = ["$mdUtil", "$log"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.toast
|
||
* @description
|
||
* Toast
|
||
*/
|
||
angular.module('material.components.toast', [
|
||
'material.core',
|
||
'material.components.button'
|
||
])
|
||
.directive('mdToast', MdToastDirective)
|
||
.provider('$mdToast', MdToastProvider);
|
||
|
||
function MdToastDirective() {
|
||
return {
|
||
restrict: 'E'
|
||
};
|
||
}
|
||
|
||
/**
|
||
* @ngdoc service
|
||
* @name $mdToast
|
||
* @module material.components.toast
|
||
*
|
||
* @description
|
||
* `$mdToast` is a service to build a toast nofication on any position
|
||
* on the screen with an optional duration, and provides a simple promise API.
|
||
*
|
||
*
|
||
* ### Restrictions on custom toasts
|
||
* - The toast's template must have an outer `<md-toast>` element.
|
||
* - For a toast action, use element with class `md-action`.
|
||
* - Add the class `md-capsule` for curved corners.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <div ng-controller="MyController">
|
||
* <md-button ng-click="openToast()">
|
||
* Open a Toast!
|
||
* </md-button>
|
||
* </div>
|
||
* </hljs>
|
||
*
|
||
* <hljs lang="js">
|
||
* var app = angular.module('app', ['ngMaterial']);
|
||
* app.controller('MyController', function($scope, $mdToast) {
|
||
* $scope.openToast = function($event) {
|
||
* $mdToast.show($mdToast.simple().content('Hello!'));
|
||
* // Could also do $mdToast.showSimple('Hello');
|
||
* };
|
||
* });
|
||
* </hljs>
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdToast#showSimple
|
||
*
|
||
* @description
|
||
* Convenience method which builds and shows a simple toast.
|
||
*
|
||
* @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
|
||
* rejected with `$mdToast.cancel()`.
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdToast#simple
|
||
*
|
||
* @description
|
||
* Builds a preconfigured toast.
|
||
*
|
||
* @returns {obj} a `$mdToastPreset` with the chainable configuration methods:
|
||
*
|
||
* - $mdToastPreset#content(string) - sets toast content to string
|
||
* - $mdToastPreset#action(string) - adds an action button, which resolves the promise returned from `show()` if clicked.
|
||
* - $mdToastPreset#highlightAction(boolean) - sets action button to be highlighted
|
||
* - $mdToastPreset#capsule(boolean) - adds 'md-capsule' class to the toast (curved corners)
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdToast#build
|
||
*
|
||
* @description
|
||
* Creates a custom `$mdToastPreset` that you can configure.
|
||
*
|
||
* @returns {obj} a `$mdToastPreset` with the chainable configuration methods for shows' options (see below).
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdToast#show
|
||
*
|
||
* @description Shows the toast.
|
||
*
|
||
* @param {object} optionsOrPreset Either provide an `$mdToastPreset` returned from `simple()`
|
||
* and `build()`, or an options object with the following properties:
|
||
*
|
||
* - `templateUrl` - `{string=}`: The url of an html template file that will
|
||
* be used as the content of the toast. Restrictions: the template must
|
||
* have an outer `md-toast` element.
|
||
* - `template` - `{string=}`: Same as templateUrl, except this is an actual
|
||
* template string.
|
||
* - `hideDelay` - `{number=}`: How many milliseconds the toast should stay
|
||
* active before automatically closing. Set to 0 or false to have the toast stay open until
|
||
* closed manually. Default: 3000.
|
||
* - `position` - `{string=}`: Where to place the toast. Available: any combination
|
||
* of 'bottom', 'left', 'top', 'right', 'fit'. Default: 'bottom left'.
|
||
* - `controller` - `{string=}`: The controller to associate with this toast.
|
||
* The controller will be injected the local `$hideToast`, which is a function
|
||
* used to hide the toast.
|
||
* - `locals` - `{string=}`: An object containing key/value pairs. The keys will
|
||
* be used as names of values to inject into the controller. For example,
|
||
* `locals: {three: 3}` would inject `three` into the controller with the value
|
||
* of 3.
|
||
* - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
|
||
* and the toast will not open until the promises resolve.
|
||
* - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
|
||
* - `parent` - `{element=}`: The element to append the toast to. Defaults to appending
|
||
* to the root element of the application.
|
||
*
|
||
* @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
|
||
* rejected with `$mdToast.cancel()`.
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdToast#hide
|
||
*
|
||
* @description
|
||
* Hide an existing toast and resolve the promise returned from `$mdToast.show()`.
|
||
*
|
||
* @param {*=} response An argument for the resolved promise.
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name $mdToast#cancel
|
||
*
|
||
* @description
|
||
* Hide the existing toast and reject the promise returned from
|
||
* `$mdToast.show()`.
|
||
*
|
||
* @param {*=} response An argument for the rejected promise.
|
||
*
|
||
*/
|
||
|
||
function MdToastProvider($$interimElementProvider) {
|
||
|
||
toastDefaultOptions.$inject = ["$timeout", "$animate", "$mdTheming", "$mdToast"];
|
||
return $$interimElementProvider('$mdToast')
|
||
.setDefaults({
|
||
methods: ['position', 'hideDelay', 'capsule'],
|
||
options: toastDefaultOptions
|
||
})
|
||
.addPreset('simple', {
|
||
argOption: 'content',
|
||
methods: ['content', 'action', 'highlightAction'],
|
||
options: /* @ngInject */ ["$mdToast", function($mdToast) {
|
||
return {
|
||
template: [
|
||
'<md-toast ng-class="{\'md-capsule\': toast.capsule}">',
|
||
'<span flex>{{ toast.content }}</span>',
|
||
'<md-button class="md-action" ng-if="toast.action" ng-click="toast.resolve()" ng-class="{\'md-highlight\': toast.highlightAction}">',
|
||
'{{ toast.action }}',
|
||
'</md-button>',
|
||
'</md-toast>'
|
||
].join(''),
|
||
controller: function mdToastCtrl() {
|
||
this.resolve = function() {
|
||
$mdToast.hide();
|
||
};
|
||
},
|
||
controllerAs: 'toast',
|
||
bindToController: true
|
||
};
|
||
}]
|
||
});
|
||
|
||
/* @ngInject */
|
||
function toastDefaultOptions($timeout, $animate, $mdTheming, $mdToast) {
|
||
return {
|
||
onShow: onShow,
|
||
onRemove: onRemove,
|
||
position: 'bottom left',
|
||
themable: true,
|
||
hideDelay: 3000
|
||
};
|
||
|
||
function onShow(scope, element, options) {
|
||
// 'top left' -> 'md-top md-left'
|
||
element.addClass(options.position.split(' ').map(function(pos) {
|
||
return 'md-' + pos;
|
||
}).join(' '));
|
||
options.parent.addClass(toastOpenClass(options.position));
|
||
|
||
options.onSwipe = function(ev, gesture) {
|
||
//Add swipeleft/swiperight class to element so it can animate correctly
|
||
element.addClass('md-' + ev.type.replace('$md.',''));
|
||
$timeout($mdToast.cancel);
|
||
};
|
||
element.on('$md.swipeleft $md.swiperight', options.onSwipe);
|
||
return $animate.enter(element, options.parent);
|
||
}
|
||
|
||
function onRemove(scope, element, options) {
|
||
element.off('$md.swipeleft $md.swiperight', options.onSwipe);
|
||
options.parent.removeClass(toastOpenClass(options.position));
|
||
return $animate.leave(element);
|
||
}
|
||
|
||
function toastOpenClass(position) {
|
||
return 'md-toast-open-' +
|
||
(position.indexOf('top') > -1 ? 'top' : 'bottom');
|
||
}
|
||
}
|
||
|
||
}
|
||
MdToastProvider.$inject = ["$$interimElementProvider"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.toolbar
|
||
*/
|
||
angular.module('material.components.toolbar', [
|
||
'material.core',
|
||
'material.components.content'
|
||
])
|
||
.directive('mdToolbar', mdToolbarDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdToolbar
|
||
* @module material.components.toolbar
|
||
* @restrict E
|
||
* @description
|
||
* `md-toolbar` is used to place a toolbar in your app.
|
||
*
|
||
* Toolbars are usually used above a content area to display the title of the
|
||
* current page, and show relevant action buttons for that page.
|
||
*
|
||
* You can change the height of the toolbar by adding either the
|
||
* `md-medium-tall` or `md-tall` class to the toolbar.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <div layout="column" layout-fill>
|
||
* <md-toolbar>
|
||
*
|
||
* <div class="md-toolbar-tools">
|
||
* <span>My App's Title</span>
|
||
*
|
||
* <!-- fill up the space between left and right area -->
|
||
* <span flex></span>
|
||
*
|
||
* <md-button>
|
||
* Right Bar Button
|
||
* </md-button>
|
||
* </div>
|
||
*
|
||
* </md-toolbar>
|
||
* <md-content>
|
||
* Hello!
|
||
* </md-content>
|
||
* </div>
|
||
* </hljs>
|
||
*
|
||
* @param {boolean=} md-scroll-shrink Whether the header should shrink away as
|
||
* the user scrolls down, and reveal itself as the user scrolls up.
|
||
* Note: for scrollShrink to work, the toolbar must be a sibling of a
|
||
* `md-content` element, placed before it. See the scroll shrink demo.
|
||
*
|
||
*
|
||
* @param {number=} md-shrink-speed-factor How much to change the speed of the toolbar's
|
||
* shrinking by. For example, if 0.25 is given then the toolbar will shrink
|
||
* at one fourth the rate at which the user scrolls down. Default 0.5.
|
||
*/
|
||
function mdToolbarDirective($$rAF, $mdConstant, $mdUtil, $mdTheming) {
|
||
|
||
return {
|
||
restrict: 'E',
|
||
controller: angular.noop,
|
||
link: function(scope, element, attr) {
|
||
$mdTheming(element);
|
||
|
||
if (angular.isDefined(attr.mdScrollShrink)) {
|
||
setupScrollShrink();
|
||
}
|
||
|
||
function setupScrollShrink() {
|
||
// Current "y" position of scroll
|
||
var y = 0;
|
||
// Store the last scroll top position
|
||
var prevScrollTop = 0;
|
||
|
||
var shrinkSpeedFactor = attr.mdShrinkSpeedFactor || 0.5;
|
||
|
||
var toolbarHeight;
|
||
var contentElement;
|
||
|
||
var debouncedContentScroll = $$rAF.throttle(onContentScroll);
|
||
var debouncedUpdateHeight = $mdUtil.debounce(updateToolbarHeight, 5 * 1000);
|
||
|
||
// Wait for $mdContentLoaded event from mdContent directive.
|
||
// If the mdContent element is a sibling of our toolbar, hook it up
|
||
// to scroll events.
|
||
scope.$on('$mdContentLoaded', onMdContentLoad);
|
||
|
||
function onMdContentLoad($event, newContentEl) {
|
||
// Toolbar and content must be siblings
|
||
if (element.parent()[0] === newContentEl.parent()[0]) {
|
||
// unhook old content event listener if exists
|
||
if (contentElement) {
|
||
contentElement.off('scroll', debouncedContentScroll);
|
||
}
|
||
|
||
newContentEl.on('scroll', debouncedContentScroll);
|
||
newContentEl.attr('scroll-shrink', 'true');
|
||
|
||
contentElement = newContentEl;
|
||
$$rAF(updateToolbarHeight);
|
||
}
|
||
}
|
||
|
||
function updateToolbarHeight() {
|
||
toolbarHeight = element.prop('offsetHeight');
|
||
// Add a negative margin-top the size of the toolbar to the content el.
|
||
// The content will start transformed down the toolbarHeight amount,
|
||
// so everything looks normal.
|
||
//
|
||
// As the user scrolls down, the content will be transformed up slowly
|
||
// to put the content underneath where the toolbar was.
|
||
contentElement.css(
|
||
'margin-top',
|
||
(-toolbarHeight * shrinkSpeedFactor) + 'px'
|
||
);
|
||
onContentScroll();
|
||
}
|
||
|
||
function onContentScroll(e) {
|
||
var scrollTop = e ? e.target.scrollTop : prevScrollTop;
|
||
|
||
debouncedUpdateHeight();
|
||
|
||
y = Math.min(
|
||
toolbarHeight / shrinkSpeedFactor,
|
||
Math.max(0, y + scrollTop - prevScrollTop)
|
||
);
|
||
|
||
element.css(
|
||
$mdConstant.CSS.TRANSFORM,
|
||
'translate3d(0,' + (-y * shrinkSpeedFactor) + 'px,0)'
|
||
);
|
||
contentElement.css(
|
||
$mdConstant.CSS.TRANSFORM,
|
||
'translate3d(0,' + ((toolbarHeight - y) * shrinkSpeedFactor) + 'px,0)'
|
||
);
|
||
|
||
prevScrollTop = scrollTop;
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
};
|
||
|
||
}
|
||
mdToolbarDirective.$inject = ["$$rAF", "$mdConstant", "$mdUtil", "$mdTheming"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.tooltip
|
||
*/
|
||
angular.module('material.components.tooltip', [
|
||
'material.core'
|
||
])
|
||
.directive('mdTooltip', MdTooltipDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdTooltip
|
||
* @module material.components.tooltip
|
||
* @description
|
||
* Tooltips are used to describe elements that are interactive and primarily graphical (not textual).
|
||
*
|
||
* Place a `<md-tooltip>` as a child of the element it describes.
|
||
*
|
||
* A tooltip will activate when the user focuses, hovers over, or touches the parent.
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-icon icon="/img/icons/ic_play_arrow_24px.svg">
|
||
* <md-tooltip>
|
||
* Play Music
|
||
* </md-tooltip>
|
||
* </md-icon>
|
||
* </hljs>
|
||
*
|
||
* @param {expression=} md-visible Boolean bound to whether the tooltip is
|
||
* currently visible.
|
||
* @param {number=} md-delay How many milliseconds to wait to show the tooltip after the user focuses, hovers, or touches the parent. Defaults to 400ms.
|
||
*/
|
||
function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdTheming, $rootElement) {
|
||
|
||
var TOOLTIP_SHOW_DELAY = 400;
|
||
var TOOLTIP_WINDOW_EDGE_SPACE = 8;
|
||
|
||
return {
|
||
restrict: 'E',
|
||
transclude: true,
|
||
template:
|
||
'<div class="md-background"></div>' +
|
||
'<div class="md-content" ng-transclude></div>',
|
||
scope: {
|
||
visible: '=?mdVisible',
|
||
delay: '=?mdDelay'
|
||
},
|
||
link: postLink
|
||
};
|
||
|
||
function postLink(scope, element, attr, contentCtrl) {
|
||
$mdTheming(element);
|
||
var parent = element.parent();
|
||
|
||
// Look for the nearest parent md-content, stopping at the rootElement.
|
||
var current = element.parent()[0];
|
||
while (current && current !== $rootElement[0] && current !== document.body) {
|
||
if (current.tagName && current.tagName.toLowerCase() == 'md-content') break;
|
||
current = current.parentNode;
|
||
}
|
||
var tooltipParent = angular.element(current || document.body);
|
||
|
||
if (!angular.isDefined(attr.mdDelay)) {
|
||
scope.delay = TOOLTIP_SHOW_DELAY;
|
||
}
|
||
|
||
// We will re-attach tooltip when visible
|
||
element.detach();
|
||
element.attr('role', 'tooltip');
|
||
element.attr('id', attr.id || ('tooltip_' + $mdUtil.nextUid()));
|
||
|
||
parent.on('focus mouseenter touchstart', function() {
|
||
setVisible(true);
|
||
});
|
||
parent.on('blur mouseleave touchend touchcancel', function() {
|
||
// Don't hide the tooltip if the parent is still focused.
|
||
if ($document[0].activeElement === parent[0]) return;
|
||
setVisible(false);
|
||
});
|
||
|
||
scope.$watch('visible', function(isVisible) {
|
||
if (isVisible) showTooltip();
|
||
else hideTooltip();
|
||
});
|
||
|
||
var debouncedOnResize = $$rAF.throttle(function windowResize() {
|
||
// Reposition on resize
|
||
if (scope.visible) positionTooltip();
|
||
});
|
||
angular.element($window).on('resize', debouncedOnResize);
|
||
|
||
// Be sure to completely cleanup the element on destroy
|
||
scope.$on('$destroy', function() {
|
||
scope.visible = false;
|
||
element.remove();
|
||
angular.element($window).off('resize', debouncedOnResize);
|
||
});
|
||
|
||
// *******
|
||
// Methods
|
||
// *******
|
||
|
||
// If setting visible to true, debounce to scope.delay ms
|
||
// If setting visible to false and no timeout is active, instantly hide the tooltip.
|
||
function setVisible(value) {
|
||
setVisible.value = !!value;
|
||
|
||
if (!setVisible.queued) {
|
||
if (value) {
|
||
setVisible.queued = true;
|
||
$timeout(function() {
|
||
scope.visible = setVisible.value;
|
||
setVisible.queued = false;
|
||
}, scope.delay);
|
||
|
||
} else {
|
||
$timeout(function() { scope.visible = false; });
|
||
}
|
||
}
|
||
}
|
||
|
||
function showTooltip() {
|
||
// Insert the element before positioning it, so we can get position
|
||
// (tooltip is hidden by default)
|
||
element.removeClass('md-hide');
|
||
parent.attr('aria-describedby', element.attr('id'));
|
||
tooltipParent.append(element);
|
||
|
||
// Wait until the element has been in the dom for two frames before
|
||
// fading it in.
|
||
// Additionally, we position the tooltip twice to avoid positioning bugs
|
||
positionTooltip();
|
||
$$rAF(function() {
|
||
|
||
$$rAF(function() {
|
||
positionTooltip();
|
||
if (!scope.visible) return;
|
||
element.addClass('md-show');
|
||
});
|
||
|
||
});
|
||
}
|
||
|
||
function hideTooltip() {
|
||
element.removeClass('md-show').addClass('md-hide');
|
||
parent.removeAttr('aria-describedby');
|
||
$timeout(function() {
|
||
if (scope.visible) return;
|
||
element.detach();
|
||
}, 200, false);
|
||
}
|
||
|
||
function positionTooltip() {
|
||
var tipRect = $mdUtil.elementRect(element, tooltipParent);
|
||
var parentRect = $mdUtil.elementRect(parent, tooltipParent);
|
||
|
||
// Default to bottom position if possible
|
||
var tipDirection = 'bottom';
|
||
var newPosition = {
|
||
left: parentRect.left + parentRect.width / 2 - tipRect.width / 2,
|
||
top: parentRect.top + parentRect.height
|
||
};
|
||
|
||
// If element bleeds over left/right of the window, place it on the edge of the window.
|
||
newPosition.left = Math.min(
|
||
newPosition.left,
|
||
tooltipParent.prop('scrollWidth') - tipRect.width - TOOLTIP_WINDOW_EDGE_SPACE
|
||
);
|
||
newPosition.left = Math.max(newPosition.left, TOOLTIP_WINDOW_EDGE_SPACE);
|
||
|
||
// If element bleeds over the bottom of the window, place it above the parent.
|
||
if (newPosition.top + tipRect.height > tooltipParent.prop('scrollHeight')) {
|
||
newPosition.top = parentRect.top - tipRect.height;
|
||
tipDirection = 'top';
|
||
}
|
||
|
||
element.css({top: newPosition.top + 'px', left: newPosition.left + 'px'});
|
||
// Tell the CSS the size of this tooltip, as a multiple of 32.
|
||
element.attr('width-32', Math.ceil(tipRect.width / 32));
|
||
element.attr('md-direction', tipDirection);
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
MdTooltipDirective.$inject = ["$timeout", "$window", "$$rAF", "$document", "$mdUtil", "$mdTheming", "$rootElement"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* @ngdoc module
|
||
* @name material.components.whiteframe
|
||
*/
|
||
angular.module('material.components.whiteframe', []);
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* Conditionally configure ink bar animations when the
|
||
* tab selection changes. If `mdNoBar` then do not show the
|
||
* bar nor animate.
|
||
*/
|
||
angular.module('material.components.tabs')
|
||
.directive('mdTabsInkBar', MdTabInkDirective);
|
||
|
||
function MdTabInkDirective($$rAF) {
|
||
|
||
var lastIndex = 0;
|
||
|
||
return {
|
||
restrict: 'E',
|
||
require: ['^?mdNoBar', '^mdTabs'],
|
||
link: postLink
|
||
};
|
||
|
||
function postLink(scope, element, attr, ctrls) {
|
||
var mdNoBar = !!ctrls[0];
|
||
|
||
var tabsCtrl = ctrls[1],
|
||
debouncedUpdateBar = $$rAF.throttle(updateBar);
|
||
|
||
tabsCtrl.inkBarElement = element;
|
||
|
||
scope.$on('$mdTabsPaginationChanged', debouncedUpdateBar);
|
||
|
||
function updateBar() {
|
||
var selected = tabsCtrl.getSelectedItem();
|
||
var hideInkBar = !selected || tabsCtrl.count() < 2 || mdNoBar;
|
||
|
||
element.css('display', hideInkBar ? 'none' : 'block');
|
||
|
||
if (hideInkBar) return;
|
||
|
||
if (scope.pagination && scope.pagination.tabData) {
|
||
var index = tabsCtrl.getSelectedIndex();
|
||
var data = scope.pagination.tabData.tabs[index] || { left: 0, right: 0, width: 0 };
|
||
var right = element.parent().prop('offsetWidth') - data.right;
|
||
var classNames = ['md-transition-left', 'md-transition-right', 'md-no-transition'];
|
||
var classIndex = lastIndex > index ? 0 : lastIndex < index ? 1 : 2;
|
||
|
||
element
|
||
.removeClass(classNames.join(' '))
|
||
.addClass(classNames[classIndex])
|
||
.css({ left: data.left + 'px', right: right + 'px' });
|
||
|
||
lastIndex = index;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
MdTabInkDirective.$inject = ["$$rAF"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.components.tabs')
|
||
.directive('mdTabsPagination', TabPaginationDirective);
|
||
|
||
function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout, $mdMedia) {
|
||
|
||
// Must match (2 * width of paginators) in scss
|
||
var PAGINATORS_WIDTH = (8 * 4) * 2;
|
||
|
||
return {
|
||
restrict: 'A',
|
||
require: '^mdTabs',
|
||
link: postLink
|
||
};
|
||
|
||
function postLink(scope, element, attr, tabsCtrl) {
|
||
|
||
var tabs = element[0].getElementsByTagName('md-tab');
|
||
var debouncedUpdatePagination = $$rAF.throttle(updatePagination);
|
||
var tabsParent = element.children();
|
||
var locked = false;
|
||
var state = scope.pagination = {
|
||
page: -1,
|
||
active: false,
|
||
clickNext: function() { locked || userChangePage(+1); },
|
||
clickPrevious: function() { locked || userChangePage(-1); }
|
||
};
|
||
|
||
scope.$on('$mdTabsChanged', debouncedUpdatePagination);
|
||
angular.element($window).on('resize', debouncedUpdatePagination);
|
||
|
||
scope.$on('$destroy', function() {
|
||
angular.element($window).off('resize', debouncedUpdatePagination);
|
||
});
|
||
|
||
scope.$watch(function() { return tabsCtrl.tabToFocus; }, onTabFocus);
|
||
|
||
// Make sure we don't focus an element on the next page
|
||
// before it's in view
|
||
function onTabFocus(tab, oldTab) {
|
||
if (!tab) return;
|
||
|
||
var pageIndex = getPageForTab(tab);
|
||
if (!state.active || pageIndex === state.page) {
|
||
tab.element.focus();
|
||
} else {
|
||
// Go to the new page, wait for the page transition to end, then focus.
|
||
oldTab && oldTab.element.blur();
|
||
setPage(pageIndex).then(function() {
|
||
locked = false;
|
||
tab.element.focus();
|
||
});
|
||
}
|
||
}
|
||
|
||
// Called when page is changed by a user action (click)
|
||
function userChangePage(increment) {
|
||
var sizeData = state.tabData;
|
||
var newPage = Math.max(0, Math.min(sizeData.pages.length - 1, state.page + increment));
|
||
var newTabIndex = sizeData.pages[newPage][ increment > 0 ? 'firstTabIndex' : 'lastTabIndex' ];
|
||
var newTab = tabsCtrl.itemAt(newTabIndex);
|
||
locked = true;
|
||
onTabFocus(newTab);
|
||
}
|
||
|
||
function updatePagination() {
|
||
if (!element.prop('offsetParent')) {
|
||
var watcher = waitForVisible();
|
||
return;
|
||
}
|
||
|
||
var tabs = element.find('md-tab');
|
||
|
||
disablePagination();
|
||
|
||
var sizeData = state.tabData = calculateTabData();
|
||
var needPagination = state.active = sizeData.pages.length > 1;
|
||
|
||
if (needPagination) { enablePagination(); }
|
||
|
||
scope.$evalAsync(function () { scope.$broadcast('$mdTabsPaginationChanged'); });
|
||
|
||
function enablePagination() {
|
||
tabsParent.css('width', '9999px');
|
||
|
||
//-- apply filler margins
|
||
angular.forEach(sizeData.tabs, function (tab) {
|
||
angular.element(tab.element).css('margin-left', tab.filler + 'px');
|
||
});
|
||
|
||
setPage(getPageForTab(tabsCtrl.getSelectedItem()));
|
||
}
|
||
|
||
function disablePagination() {
|
||
slideTabButtons(0);
|
||
tabsParent.css('width', '');
|
||
tabs.css('width', '');
|
||
tabs.css('margin-left', '');
|
||
state.page = null;
|
||
state.active = false;
|
||
}
|
||
|
||
function waitForVisible() {
|
||
return watcher || scope.$watch(
|
||
function () {
|
||
$timeout(function () {
|
||
if (element[0].offsetParent) {
|
||
if (angular.isFunction(watcher)) {
|
||
watcher();
|
||
}
|
||
debouncedUpdatePagination();
|
||
watcher = null;
|
||
}
|
||
}, 0, false);
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
function slideTabButtons(x) {
|
||
if (tabsCtrl.pagingOffset === x) {
|
||
// Resolve instantly if no change
|
||
return $$q.when();
|
||
}
|
||
|
||
var deferred = $$q.defer();
|
||
|
||
tabsCtrl.$$pagingOffset = x;
|
||
tabsParent.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + x + 'px,0,0)');
|
||
tabsParent.on($mdConstant.CSS.TRANSITIONEND, onTabsParentTransitionEnd);
|
||
|
||
return deferred.promise;
|
||
|
||
function onTabsParentTransitionEnd(ev) {
|
||
// Make sure this event didn't bubble up from an animation in a child element.
|
||
if (ev.target === tabsParent[0]) {
|
||
tabsParent.off($mdConstant.CSS.TRANSITIONEND, onTabsParentTransitionEnd);
|
||
deferred.resolve();
|
||
}
|
||
}
|
||
}
|
||
|
||
function shouldStretchTabs() {
|
||
switch (scope.stretchTabs) {
|
||
case 'never': return false;
|
||
case 'always': return true;
|
||
default: return $mdMedia('sm');
|
||
}
|
||
}
|
||
|
||
function calculateTabData(noAdjust) {
|
||
var clientWidth = element.parent().prop('offsetWidth');
|
||
var tabsWidth = clientWidth - PAGINATORS_WIDTH - 1;
|
||
var $tabs = angular.element(tabs);
|
||
var totalWidth = 0;
|
||
var max = 0;
|
||
var tabData = [];
|
||
var pages = [];
|
||
var currentPage;
|
||
|
||
$tabs.css('max-width', '');
|
||
angular.forEach(tabs, function (tab, index) {
|
||
var tabWidth = Math.min(tabsWidth, tab.offsetWidth);
|
||
var data = {
|
||
element: tab,
|
||
left: totalWidth,
|
||
width: tabWidth,
|
||
right: totalWidth + tabWidth,
|
||
filler: 0
|
||
};
|
||
|
||
//-- This calculates the page for each tab. The first page will use the clientWidth, which
|
||
// does not factor in the pagination items. After the first page, tabsWidth is used
|
||
// because at this point, we know that the pagination buttons will be shown.
|
||
data.page = Math.ceil(data.right / ( pages.length === 1 && index === tabs.length - 1 ? clientWidth : tabsWidth )) - 1;
|
||
|
||
if (data.page >= pages.length) {
|
||
data.filler = (tabsWidth * data.page) - data.left;
|
||
data.right += data.filler;
|
||
data.left += data.filler;
|
||
currentPage = {
|
||
left: data.left,
|
||
firstTabIndex: index,
|
||
lastTabIndex: index,
|
||
tabs: [ data ]
|
||
};
|
||
pages.push(currentPage);
|
||
} else {
|
||
currentPage.lastTabIndex = index;
|
||
currentPage.tabs.push(data);
|
||
}
|
||
totalWidth = data.right;
|
||
max = Math.max(max, tabWidth);
|
||
tabData.push(data);
|
||
});
|
||
$tabs.css('max-width', tabsWidth + 'px');
|
||
|
||
if (!noAdjust && shouldStretchTabs()) {
|
||
return adjustForStretchedTabs();
|
||
} else {
|
||
return {
|
||
width: totalWidth,
|
||
max: max,
|
||
tabs: tabData,
|
||
pages: pages,
|
||
tabElements: tabs
|
||
};
|
||
}
|
||
|
||
|
||
function adjustForStretchedTabs() {
|
||
var canvasWidth = pages.length === 1 ? clientWidth : tabsWidth;
|
||
var tabsPerPage = Math.min(Math.floor(canvasWidth / max), tabs.length);
|
||
var tabWidth = Math.floor(canvasWidth / tabsPerPage);
|
||
$tabs.css('width', tabWidth + 'px');
|
||
return calculateTabData(true);
|
||
}
|
||
}
|
||
|
||
function getPageForTab(tab) {
|
||
var tabIndex = tabsCtrl.indexOf(tab);
|
||
if (tabIndex === -1) return 0;
|
||
|
||
var sizeData = state.tabData;
|
||
|
||
return sizeData ? sizeData.tabs[tabIndex].page : 0;
|
||
}
|
||
|
||
function setPage(page) {
|
||
if (page === state.page) return;
|
||
|
||
var lastPage = state.tabData.pages.length - 1;
|
||
|
||
if (page < 0) page = 0;
|
||
if (page > lastPage) page = lastPage;
|
||
|
||
state.hasPrev = page > 0;
|
||
state.hasNext = page < lastPage;
|
||
|
||
state.page = page;
|
||
|
||
scope.$broadcast('$mdTabsPaginationChanged');
|
||
|
||
return slideTabButtons(-state.tabData.pages[page].left);
|
||
}
|
||
}
|
||
|
||
}
|
||
TabPaginationDirective.$inject = ["$mdConstant", "$window", "$$rAF", "$$q", "$timeout", "$mdMedia"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
|
||
angular.module('material.components.tabs')
|
||
.controller('$mdTab', TabItemController);
|
||
|
||
function TabItemController($scope, $element, $attrs, $compile, $animate, $mdUtil, $parse, $timeout) {
|
||
var self = this;
|
||
var tabsCtrl = $element.controller('mdTabs');
|
||
|
||
// Properties
|
||
self.contentContainer = angular.element('<div class="md-tab-content ng-hide">');
|
||
self.element = $element;
|
||
|
||
// Methods
|
||
self.isDisabled = isDisabled;
|
||
self.onAdd = onAdd;
|
||
self.onRemove = onRemove;
|
||
self.onSelect = onSelect;
|
||
self.onDeselect = onDeselect;
|
||
|
||
var disabledParsed = $parse($attrs.ngDisabled);
|
||
function isDisabled() {
|
||
return disabledParsed($scope.$parent);
|
||
}
|
||
|
||
/**
|
||
* Add the tab's content to the DOM container area in the tabs,
|
||
* @param contentArea the contentArea to add the content of the tab to
|
||
*/
|
||
function onAdd(contentArea, shouldDisconnectScope) {
|
||
if (self.content.length) {
|
||
self.contentContainer.append(self.content);
|
||
self.contentScope = $scope.$parent.$new();
|
||
contentArea.append(self.contentContainer);
|
||
|
||
$compile(self.contentContainer)(self.contentScope);
|
||
if (shouldDisconnectScope === true) {
|
||
$timeout(function () {
|
||
$mdUtil.disconnectScope(self.contentScope);
|
||
}, 0, false);
|
||
}
|
||
}
|
||
}
|
||
|
||
function onRemove() {
|
||
$animate.leave(self.contentContainer).then(function() {
|
||
self.contentScope && self.contentScope.$destroy();
|
||
self.contentScope = null;
|
||
});
|
||
}
|
||
|
||
function toggleAnimationClass(rightToLeft) {
|
||
self.contentContainer[rightToLeft ? 'addClass' : 'removeClass']('md-transition-rtl');
|
||
}
|
||
|
||
function onSelect(rightToLeft) {
|
||
// Resume watchers and events firing when tab is selected
|
||
$mdUtil.reconnectScope(self.contentScope);
|
||
|
||
$element
|
||
.addClass('active')
|
||
.attr({
|
||
'aria-selected': true,
|
||
'tabIndex': 0
|
||
})
|
||
.on('$md.swipeleft $md.swiperight', onSwipe);
|
||
|
||
toggleAnimationClass(rightToLeft);
|
||
$animate.removeClass(self.contentContainer, 'ng-hide');
|
||
|
||
$scope.onSelect();
|
||
}
|
||
|
||
function onDeselect(rightToLeft) {
|
||
// Stop watchers & events from firing while tab is deselected
|
||
$mdUtil.disconnectScope(self.contentScope);
|
||
|
||
$element
|
||
.removeClass('active')
|
||
.attr({
|
||
'aria-selected': false,
|
||
'tabIndex': -1
|
||
})
|
||
.off('$md.swipeleft $md.swiperight', onSwipe);
|
||
|
||
toggleAnimationClass(rightToLeft);
|
||
$animate.addClass(self.contentContainer, 'ng-hide');
|
||
|
||
$scope.onDeselect();
|
||
}
|
||
|
||
///// Private functions
|
||
|
||
function onSwipe(ev) {
|
||
$scope.$apply(function() {
|
||
if (/left/.test(ev.type)) {
|
||
tabsCtrl.select(tabsCtrl.next());
|
||
} else {
|
||
tabsCtrl.select(tabsCtrl.previous());
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
}
|
||
TabItemController.$inject = ["$scope", "$element", "$attrs", "$compile", "$animate", "$mdUtil", "$parse", "$timeout"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.components.tabs')
|
||
.directive('mdTab', MdTabDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdTab
|
||
* @module material.components.tabs
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* `<md-tab>` is the nested directive used [within `<md-tabs>`] to specify each tab with a **label** and optional *view content*.
|
||
*
|
||
* If the `label` attribute is not specified, then an optional `<md-tab-label>` tag can be used to specify more
|
||
* complex tab header markup. If neither the **label** nor the **md-tab-label** are specified, then the nested
|
||
* markup of the `<md-tab>` is used as the tab header markup.
|
||
*
|
||
* If a tab **label** has been identified, then any **non-**`<md-tab-label>` markup
|
||
* will be considered tab content and will be transcluded to the internal `<div class="md-tabs-content">` container.
|
||
*
|
||
* This container is used by the TabsController to show/hide the active tab's content view. This synchronization is
|
||
* automatically managed by the internal TabsController whenever the tab selection changes. Selection changes can
|
||
* be initiated via data binding changes, programmatic invocation, or user gestures.
|
||
*
|
||
* @param {string=} label Optional attribute to specify a simple string as the tab label
|
||
* @param {boolean=} md-active When evaluteing to true, selects the tab.
|
||
* @param {boolean=} disabled If present, disabled tab selection.
|
||
* @param {expression=} md-on-deselect Expression to be evaluated after the tab has been de-selected.
|
||
* @param {expression=} md-on-select Expression to be evaluated after the tab has been selected.
|
||
*
|
||
*
|
||
* @usage
|
||
*
|
||
* <hljs lang="html">
|
||
* <md-tab label="" disabled="" md-on-select="" md-on-deselect="" >
|
||
* <h3>My Tab content</h3>
|
||
* </md-tab>
|
||
*
|
||
* <md-tab >
|
||
* <md-tab-label>
|
||
* <h3>My Tab content</h3>
|
||
* </md-tab-label>
|
||
* <p>
|
||
* Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
|
||
* totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
|
||
* dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
|
||
* sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
|
||
* </p>
|
||
* </md-tab>
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function MdTabDirective($mdInkRipple, $compile, $mdUtil, $mdConstant, $timeout) {
|
||
return {
|
||
restrict: 'E',
|
||
require: ['mdTab', '^mdTabs'],
|
||
controller: '$mdTab',
|
||
scope: {
|
||
onSelect: '&mdOnSelect',
|
||
onDeselect: '&mdOnDeselect',
|
||
label: '@'
|
||
},
|
||
compile: compile
|
||
};
|
||
|
||
function compile(element, attr) {
|
||
var tabLabel = element.find('md-tab-label');
|
||
|
||
if (tabLabel.length) {
|
||
// If a tab label element is found, remove it for later re-use.
|
||
tabLabel.remove();
|
||
|
||
} else if (angular.isDefined(attr.label)) {
|
||
// Otherwise, try to use attr.label as the label
|
||
tabLabel = angular.element('<md-tab-label>').html(attr.label);
|
||
|
||
} else {
|
||
// If nothing is found, use the tab's content as the label
|
||
tabLabel = angular.element('<md-tab-label>')
|
||
.append(element.contents().remove());
|
||
}
|
||
|
||
// Everything that's left as a child is the tab's content.
|
||
var tabContent = element.contents().remove();
|
||
|
||
return function postLink(scope, element, attr, ctrls) {
|
||
|
||
var tabItemCtrl = ctrls[0]; // Controller for THIS tabItemCtrl
|
||
var tabsCtrl = ctrls[1]; // Controller for ALL tabs
|
||
|
||
$timeout(element.addClass.bind(element, 'md-tab-themed'), 0, false);
|
||
|
||
scope.$watch(
|
||
function () { return attr.label; },
|
||
function () { $timeout(function () { tabsCtrl.scope.$broadcast('$mdTabsChanged'); }, 0, false); }
|
||
);
|
||
|
||
transcludeTabContent();
|
||
configureAria();
|
||
|
||
var detachRippleFn = $mdInkRipple.attachTabBehavior(scope, element, {
|
||
colorElement: tabsCtrl.inkBarElement
|
||
});
|
||
tabsCtrl.add(tabItemCtrl);
|
||
scope.$on('$destroy', function() {
|
||
detachRippleFn();
|
||
tabsCtrl.remove(tabItemCtrl);
|
||
});
|
||
element.on('$destroy', function () {
|
||
//-- wait for item to be removed from the dom
|
||
$timeout(function () {
|
||
tabsCtrl.scope.$broadcast('$mdTabsChanged');
|
||
}, 0, false);
|
||
});
|
||
|
||
if (!angular.isDefined(attr.ngClick)) {
|
||
element.on('click', defaultClickListener);
|
||
}
|
||
element.on('keydown', keydownListener);
|
||
|
||
if (angular.isNumber(scope.$parent.$index)) {
|
||
watchNgRepeatIndex();
|
||
}
|
||
if (angular.isDefined(attr.mdActive)) {
|
||
watchActiveAttribute();
|
||
}
|
||
watchDisabled();
|
||
|
||
function transcludeTabContent() {
|
||
// Clone the label we found earlier, and $compile and append it
|
||
var label = tabLabel.clone();
|
||
element.append(label);
|
||
$compile(label)(scope.$parent);
|
||
|
||
// Clone the content we found earlier, and mark it for later placement into
|
||
// the proper content area.
|
||
tabItemCtrl.content = tabContent.clone();
|
||
}
|
||
|
||
//defaultClickListener isn't applied if the user provides an ngClick expression.
|
||
function defaultClickListener() {
|
||
scope.$apply(function() {
|
||
tabsCtrl.select(tabItemCtrl);
|
||
tabsCtrl.focus(tabItemCtrl);
|
||
});
|
||
}
|
||
function keydownListener(ev) {
|
||
if (ev.keyCode == $mdConstant.KEY_CODE.SPACE || ev.keyCode == $mdConstant.KEY_CODE.ENTER ) {
|
||
// Fire the click handler to do normal selection if space is pressed
|
||
element.triggerHandler('click');
|
||
ev.preventDefault();
|
||
} else if (ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
|
||
scope.$evalAsync(function() {
|
||
tabsCtrl.focus(tabsCtrl.previous(tabItemCtrl));
|
||
});
|
||
} else if (ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
|
||
scope.$evalAsync(function() {
|
||
tabsCtrl.focus(tabsCtrl.next(tabItemCtrl));
|
||
});
|
||
}
|
||
}
|
||
|
||
// If tabItemCtrl is part of an ngRepeat, move the tabItemCtrl in our internal array
|
||
// when its $index changes
|
||
function watchNgRepeatIndex() {
|
||
// The tabItemCtrl has an isolate scope, so we watch the $index on the parent.
|
||
scope.$watch('$parent.$index', function $indexWatchAction(newIndex) {
|
||
tabsCtrl.move(tabItemCtrl, newIndex);
|
||
});
|
||
}
|
||
|
||
function watchActiveAttribute() {
|
||
var unwatch = scope.$parent.$watch('!!(' + attr.mdActive + ')', activeWatchAction);
|
||
scope.$on('$destroy', unwatch);
|
||
|
||
function activeWatchAction(isActive) {
|
||
var isSelected = tabsCtrl.getSelectedItem() === tabItemCtrl;
|
||
|
||
if (isActive && !isSelected) {
|
||
tabsCtrl.select(tabItemCtrl);
|
||
} else if (!isActive && isSelected) {
|
||
tabsCtrl.deselect(tabItemCtrl);
|
||
}
|
||
}
|
||
}
|
||
|
||
function watchDisabled() {
|
||
scope.$watch(tabItemCtrl.isDisabled, disabledWatchAction);
|
||
|
||
function disabledWatchAction(isDisabled) {
|
||
element.attr('aria-disabled', isDisabled);
|
||
|
||
// Auto select `next` tab when disabled
|
||
var isSelected = (tabsCtrl.getSelectedItem() === tabItemCtrl);
|
||
if (isSelected && isDisabled) {
|
||
tabsCtrl.select(tabsCtrl.next() || tabsCtrl.previous());
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
function configureAria() {
|
||
// Link together the content area and tabItemCtrl with an id
|
||
var tabId = attr.id || ('tab_' + $mdUtil.nextUid());
|
||
|
||
element.attr({
|
||
id: tabId,
|
||
role: 'tab',
|
||
tabIndex: -1 //this is also set on select/deselect in tabItemCtrl
|
||
});
|
||
|
||
// Only setup the contentContainer's aria attributes if tab content is provided
|
||
if (tabContent.length) {
|
||
var tabContentId = 'content_' + tabId;
|
||
if (!element.attr('aria-controls')) {
|
||
element.attr('aria-controls', tabContentId);
|
||
}
|
||
tabItemCtrl.contentContainer.attr({
|
||
id: tabContentId,
|
||
role: 'tabpanel',
|
||
'aria-labelledby': tabId
|
||
});
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
}
|
||
|
||
}
|
||
MdTabDirective.$inject = ["$mdInkRipple", "$compile", "$mdUtil", "$mdConstant", "$timeout"];
|
||
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.components.tabs')
|
||
.controller('$mdTabs', MdTabsController);
|
||
|
||
function MdTabsController($scope, $element, $mdUtil, $timeout) {
|
||
|
||
var tabsList = $mdUtil.iterator([], false);
|
||
var self = this;
|
||
|
||
// Properties
|
||
self.$element = $element;
|
||
self.scope = $scope;
|
||
// The section containing the tab content $elements
|
||
var contentArea = self.contentArea = angular.element($element[0].querySelector('.md-tabs-content'));
|
||
|
||
// Methods from iterator
|
||
var inRange = self.inRange = tabsList.inRange;
|
||
var indexOf = self.indexOf = tabsList.indexOf;
|
||
var itemAt = self.itemAt = tabsList.itemAt;
|
||
self.count = tabsList.count;
|
||
|
||
self.getSelectedItem = getSelectedItem;
|
||
self.getSelectedIndex = getSelectedIndex;
|
||
self.add = add;
|
||
self.remove = remove;
|
||
self.move = move;
|
||
self.select = select;
|
||
self.focus = focus;
|
||
self.deselect = deselect;
|
||
|
||
self.next = next;
|
||
self.previous = previous;
|
||
|
||
$scope.$on('$destroy', function() {
|
||
deselect(getSelectedItem());
|
||
for (var i = tabsList.count() - 1; i >= 0; i--) {
|
||
remove(tabsList[i], true);
|
||
}
|
||
});
|
||
|
||
// Get the selected tab
|
||
function getSelectedItem() {
|
||
return itemAt($scope.selectedIndex);
|
||
}
|
||
|
||
function getSelectedIndex() {
|
||
return $scope.selectedIndex;
|
||
}
|
||
|
||
// Add a new tab.
|
||
// Returns a method to remove the tab from the list.
|
||
function add(tab, index) {
|
||
tabsList.add(tab, index);
|
||
|
||
// Select the new tab if we don't have a selectedIndex, or if the
|
||
// selectedIndex we've been waiting for is this tab
|
||
if (!angular.isDefined(tab.element.attr('md-active')) && ($scope.selectedIndex === -1 || !angular.isNumber($scope.selectedIndex) ||
|
||
$scope.selectedIndex === self.indexOf(tab))) {
|
||
tab.onAdd(self.contentArea, false);
|
||
self.select(tab);
|
||
} else {
|
||
tab.onAdd(self.contentArea, true);
|
||
}
|
||
|
||
$scope.$broadcast('$mdTabsChanged');
|
||
}
|
||
|
||
function remove(tab, noReselect) {
|
||
if (!tabsList.contains(tab)) return;
|
||
if (noReselect) return;
|
||
var isSelectedItem = getSelectedItem() === tab,
|
||
newTab = previous() || next();
|
||
|
||
deselect(tab);
|
||
tabsList.remove(tab);
|
||
tab.onRemove();
|
||
|
||
$scope.$broadcast('$mdTabsChanged');
|
||
|
||
if (isSelectedItem) { select(newTab); }
|
||
}
|
||
|
||
// Move a tab (used when ng-repeat order changes)
|
||
function move(tab, toIndex) {
|
||
var isSelected = getSelectedItem() === tab;
|
||
|
||
tabsList.remove(tab);
|
||
tabsList.add(tab, toIndex);
|
||
if (isSelected) select(tab);
|
||
|
||
$scope.$broadcast('$mdTabsChanged');
|
||
}
|
||
|
||
function select(tab, rightToLeft) {
|
||
if (!tab || tab.isSelected || tab.isDisabled()) return;
|
||
if (!tabsList.contains(tab)) return;
|
||
|
||
if (!angular.isDefined(rightToLeft)) {
|
||
rightToLeft = indexOf(tab) < $scope.selectedIndex;
|
||
}
|
||
deselect(getSelectedItem(), rightToLeft);
|
||
|
||
$scope.selectedIndex = indexOf(tab);
|
||
tab.isSelected = true;
|
||
tab.onSelect(rightToLeft);
|
||
|
||
$scope.$broadcast('$mdTabsChanged');
|
||
}
|
||
|
||
function focus(tab) {
|
||
// this variable is watched by pagination
|
||
self.tabToFocus = tab;
|
||
}
|
||
|
||
function deselect(tab, rightToLeft) {
|
||
if (!tab || !tab.isSelected) return;
|
||
if (!tabsList.contains(tab)) return;
|
||
|
||
$scope.selectedIndex = -1;
|
||
tab.isSelected = false;
|
||
tab.onDeselect(rightToLeft);
|
||
}
|
||
|
||
function next(tab, filterFn) {
|
||
return tabsList.next(tab || getSelectedItem(), filterFn || isTabEnabled);
|
||
}
|
||
function previous(tab, filterFn) {
|
||
return tabsList.previous(tab || getSelectedItem(), filterFn || isTabEnabled);
|
||
}
|
||
|
||
function isTabEnabled(tab) {
|
||
return tab && !tab.isDisabled();
|
||
}
|
||
|
||
}
|
||
MdTabsController.$inject = ["$scope", "$element", "$mdUtil", "$timeout"];
|
||
})();
|
||
|
||
/*!
|
||
* Angular Material Design
|
||
* https://github.com/angular/material
|
||
* @license MIT
|
||
* v0.7.1
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
angular.module('material.components.tabs')
|
||
.directive('mdTabs', TabsDirective);
|
||
|
||
/**
|
||
* @ngdoc directive
|
||
* @name mdTabs
|
||
* @module material.components.tabs
|
||
*
|
||
* @restrict E
|
||
*
|
||
* @description
|
||
* The `<md-tabs>` directive serves as the container for 1..n `<md-tab>` child directives to produces a Tabs components.
|
||
* In turn, the nested `<md-tab>` directive is used to specify a tab label for the **header button** and a [optional] tab view
|
||
* content that will be associated with each tab button.
|
||
*
|
||
* Below is the markup for its simplest usage:
|
||
*
|
||
* <hljs lang="html">
|
||
* <md-tabs>
|
||
* <md-tab label="Tab #1"></md-tab>
|
||
* <md-tab label="Tab #2"></md-tab>
|
||
* <md-tab label="Tab #3"></md-tab>
|
||
* <md-tabs>
|
||
* </hljs>
|
||
*
|
||
* Tabs supports three (3) usage scenarios:
|
||
*
|
||
* 1. Tabs (buttons only)
|
||
* 2. Tabs with internal view content
|
||
* 3. Tabs with external view content
|
||
*
|
||
* **Tab-only** support is useful when tab buttons are used for custom navigation regardless of any other components, content, or views.
|
||
* **Tabs with internal views** are the traditional usages where each tab has associated view content and the view switching is managed internally by the Tabs component.
|
||
* **Tabs with external view content** is often useful when content associated with each tab is independently managed and data-binding notifications announce tab selection changes.
|
||
*
|
||
* > As a performance bonus, if the tab content is managed internally then the non-active (non-visible) tab contents are temporarily disconnected from the `$scope.$digest()` processes; which restricts and optimizes DOM updates to only the currently active tab.
|
||
*
|
||
* Additional features also include:
|
||
*
|
||
* * Content can include any markup.
|
||
* * If a tab is disabled while active/selected, then the next tab will be auto-selected.
|
||
* * If the currently active tab is the last tab, then next() action will select the first tab.
|
||
* * Any markup (other than **`<md-tab>`** tags) will be transcluded into the tab header area BEFORE the tab buttons.
|
||
*
|
||
* ### Explanation of tab stretching
|
||
*
|
||
* Initially, tabs will have an inherent size. This size will either be defined by how much space is needed to accommodate their text or set by the user through CSS. Calculations will be based on this size.
|
||
*
|
||
* On mobile devices, tabs will be expanded to fill the available horizontal space. When this happens, all tabs will become the same size.
|
||
*
|
||
* On desktops, by default, stretching will never occur.
|
||
*
|
||
* This default behavior can be overridden through the `md-stretch-tabs` attribute. Here is a table showing when stretching will occur:
|
||
*
|
||
* `md-stretch-tabs` | mobile | desktop
|
||
* ------------------|-----------|--------
|
||
* `auto` | stretched | ---
|
||
* `always` | stretched | stretched
|
||
* `never` | --- | ---
|
||
*
|
||
* @param {integer=} md-selected Index of the active/selected tab
|
||
* @param {boolean=} md-no-ink If present, disables ink ripple effects.
|
||
* @param {boolean=} md-no-bar If present, disables the selection ink bar.
|
||
* @param {string=} md-align-tabs Attribute to indicate position of tab buttons: `bottom` or `top`; default is `top`
|
||
* @param {string=} md-stretch-tabs Attribute to indicate whether or not to stretch tabs: `auto`, `always`, or `never`; default is `auto`
|
||
*
|
||
* @usage
|
||
* <hljs lang="html">
|
||
* <md-tabs md-selected="selectedIndex" >
|
||
* <img ng-src="img/angular.png" class="centered">
|
||
*
|
||
* <md-tab
|
||
* ng-repeat="tab in tabs | orderBy:predicate:reversed"
|
||
* md-on-select="onTabSelected(tab)"
|
||
* md-on-deselect="announceDeselected(tab)"
|
||
* disabled="tab.disabled" >
|
||
*
|
||
* <md-tab-label>
|
||
* {{tab.title}}
|
||
* <img src="img/removeTab.png"
|
||
* ng-click="removeTab(tab)"
|
||
* class="delete" >
|
||
* </md-tab-label>
|
||
*
|
||
* {{tab.content}}
|
||
*
|
||
* </md-tab>
|
||
*
|
||
* </md-tabs>
|
||
* </hljs>
|
||
*
|
||
*/
|
||
function TabsDirective($mdTheming) {
|
||
return {
|
||
restrict: 'E',
|
||
controller: '$mdTabs',
|
||
require: 'mdTabs',
|
||
transclude: true,
|
||
scope: {
|
||
selectedIndex: '=?mdSelected'
|
||
},
|
||
template:
|
||
'<section class="md-header" ' +
|
||
'ng-class="{\'md-paginating\': pagination.active}">' +
|
||
|
||
'<button class="md-paginator md-prev" ' +
|
||
'ng-if="pagination.active && pagination.hasPrev" ' +
|
||
'ng-click="pagination.clickPrevious()" ' +
|
||
'aria-hidden="true">' +
|
||
'</button>' +
|
||
|
||
// overflow: hidden container when paginating
|
||
'<div class="md-header-items-container" md-tabs-pagination>' +
|
||
// flex container for <md-tab> elements
|
||
'<div class="md-header-items">' +
|
||
'<md-tabs-ink-bar></md-tabs-ink-bar>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
|
||
'<button class="md-paginator md-next" ' +
|
||
'ng-if="pagination.active && pagination.hasNext" ' +
|
||
'ng-click="pagination.clickNext()" ' +
|
||
'aria-hidden="true">' +
|
||
'</button>' +
|
||
|
||
'</section>' +
|
||
'<section class="md-tabs-content"></section>',
|
||
link: postLink
|
||
};
|
||
|
||
function postLink(scope, element, attr, tabsCtrl, transclude) {
|
||
|
||
scope.stretchTabs = attr.hasOwnProperty('mdStretchTabs') ? attr.mdStretchTabs || 'always' : 'auto';
|
||
|
||
$mdTheming(element);
|
||
configureAria();
|
||
watchSelected();
|
||
|
||
transclude(scope.$parent, function(clone) {
|
||
angular.element(element[0].querySelector('.md-header-items')).append(clone);
|
||
});
|
||
|
||
function configureAria() {
|
||
element.attr('role', 'tablist');
|
||
}
|
||
|
||
function watchSelected() {
|
||
scope.$watch('selectedIndex', function watchSelectedIndex(newIndex, oldIndex) {
|
||
if (oldIndex == newIndex) return;
|
||
var rightToLeft = oldIndex > newIndex;
|
||
tabsCtrl.deselect(tabsCtrl.itemAt(oldIndex), rightToLeft);
|
||
|
||
if (tabsCtrl.inRange(newIndex)) {
|
||
var newTab = tabsCtrl.itemAt(newIndex);
|
||
while (newTab && newTab.isDisabled()) {
|
||
newTab = newIndex > oldIndex
|
||
? tabsCtrl.next(newTab)
|
||
: tabsCtrl.previous(newTab);
|
||
}
|
||
tabsCtrl.select(newTab, rightToLeft);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
TabsDirective.$inject = ["$mdTheming"];
|
||
})();
|
||
|
||
angular.module("material.core").constant("$MD_THEME_CSS", "md-backdrop.md-opaque.md-THEME_NAME-theme { background-color: '{{foreground-4-0.5}}'; }md-bottom-sheet.md-THEME_NAME-theme { background-color: '{{background-50}}'; border-top-color: '{{background-300}}'; } md-bottom-sheet.md-THEME_NAME-theme.md-list md-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-toolbar .md-button.md-THEME_NAME-theme.md-fab { background-color: white; }.md-button.md-THEME_NAME-theme { border-radius: 3px; } .md-button.md-THEME_NAME-theme:not([disabled]):hover, .md-button.md-THEME_NAME-theme:not([disabled]):focus { background-color: '{{background-500-0.2}}'; } .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]):hover, .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]):focus, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]):focus { background-color: '{{primary-600}}'; } .md-button.md-THEME_NAME-theme.md-fab { border-radius: 50%; background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):focus { background-color: '{{accent-A700}}'; } .md-button.md-THEME_NAME-theme.md-raised { color: '{{background-contrast}}'; background-color: '{{background-50}}'; } .md-button.md-THEME_NAME-theme.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-raised:not([disabled]):focus { 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]):hover, .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]):focus, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]):focus { background-color: '{{warn-700}}'; } .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]):hover, .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]):focus, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]):focus { background-color: '{{accent-700}}'; } .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] { color: '{{foreground-3}}'; background-color: transparent; cursor: not-allowed; }md-card.md-THEME_NAME-theme { border-radius: 2px; } md-card.md-THEME_NAME-theme .md-card-image { border-radius: 2px 2px 0 0; }md-checkbox.md-THEME_NAME-theme .md-ripple { color: '{{accent-600}}'; }md-checkbox.md-THEME_NAME-theme.md-checked .md-ripple { color: '{{background-600}}'; }md-checkbox.md-THEME_NAME-theme .md-icon { border-color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme.md-checked .md-icon { background-color: '{{accent-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme.md-checked .md-icon:after { border-color: '{{background-200}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-ripple { color: '{{primary-600}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ripple { color: '{{background-600}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-icon { border-color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-icon { background-color: '{{primary-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-icon:after { border-color: '{{background-200}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-ripple { color: '{{warn-600}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-icon { border-color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-icon { background-color: '{{warn-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-icon:after { border-color: '{{background-200}}'; }md-checkbox.md-THEME_NAME-theme[disabled] .md-icon { border-color: '{{foreground-3}}'; }md-checkbox.md-THEME_NAME-theme[disabled].md-checked .md-icon { background-color: '{{foreground-3}}'; }md-content.md-THEME_NAME-theme { background-color: '{{background-hue-3}}'; }md-dialog.md-THEME_NAME-theme { border-radius: 4px; background-color: '{{background-hue-3}}'; } md-dialog.md-THEME_NAME-theme.md-content-overflow .md-actions { border-top-color: '{{foreground-4}}'; }md-divider.md-THEME_NAME-theme { border-top-color: '{{foreground-4}}'; }md-input-container.md-THEME_NAME-theme .md-input { color: '{{foreground-1}}'; border-color: '{{foreground-4}}'; text-shadow: '{{foreground-shadow}}'; } md-input-container.md-THEME_NAME-theme .md-input::-webkit-input-placeholder, md-input-container.md-THEME_NAME-theme .md-input::-moz-placeholder, md-input-container.md-THEME_NAME-theme .md-input:-moz-placeholder, md-input-container.md-THEME_NAME-theme .md-input:-ms-input-placeholder { color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme label, md-input-container.md-THEME_NAME-theme .md-placeholder { text-shadow: '{{foreground-shadow}}'; color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-has-value label { color: '{{foreground-2}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused .md-input { border-color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused label { color: '{{primary-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input { border-color: '{{accent-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent label { color: '{{accent-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input { border-color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn label { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input { border-color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid label { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid data-ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid x-ng-message, md-input-container.md-THEME_NAME-theme.md-input-invalid [ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid [data-ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid [x-ng-message], md-input-container.md-THEME_NAME-theme.md-input-invalid .md-char-counter { color: '{{warn-500}}'; }md-input-container.md-THEME_NAME-theme .md-input[disabled] { border-bottom-color: transparent; color: '{{foreground-3}}'; background-image: linear-gradient(to right, '{{foreground-4}}' 0%, '{{foreground-4}}' 33%, transparent 0%); }md-progress-circular.md-THEME_NAME-theme { background-color: transparent; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-gap { border-top-color: '{{primary-color}}'; border-bottom-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme .md-inner .md-right .md-half-circle { border-top-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-right .md-half-circle { border-right-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme .md-inner .md-left .md-half-circle { border-left-color: '{{primary-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-gap { border-top-color: '{{warn-color}}'; border-bottom-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-right .md-half-circle { border-top-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-right .md-half-circle { border-right-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-warn .md-inner .md-left .md-half-circle { border-left-color: '{{warn-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-gap { border-top-color: '{{accent-color}}'; border-bottom-color: '{{accent-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-left .md-half-circle, md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-right .md-half-circle { border-top-color: '{{accent-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-right .md-half-circle { border-right-color: '{{accent-color}}'; } md-progress-circular.md-THEME_NAME-theme.md-accent .md-inner .md-left .md-half-circle { border-left-color: '{{accent-color}}'; }md-progress-linear.md-THEME_NAME-theme .md-container { background-color: '{{primary-100}}'; }md-progress-linear.md-THEME_NAME-theme .md-bar { background-color: '{{primary-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn .md-container { background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn .md-bar { background-color: '{{warn-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent .md-container { background-color: '{{accent-100}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent .md-bar { background-color: '{{accent-color}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-bar1 { background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn .md-dashed:before { background: radial-gradient('{{warn-100}}' 0%, '{{warn-100}}' 16%, transparent 42%); }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-bar1 { background-color: '{{accent-100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent .md-dashed:before { background: radial-gradient('{{accent-100}}' 0%, '{{accent-100}}' 16%, transparent 42%); }md-radio-button.md-THEME_NAME-theme .md-off { border-color: '{{foreground-2}}'; }md-radio-button.md-THEME_NAME-theme .md-on { background-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked .md-off { border-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked .md-ink-ripple { color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme .md-container .md-ripple { color: '{{accent-600}}'; }md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-on { background-color: '{{primary-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-off { border-color: '{{primary-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple { color: '{{primary-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-container .md-ripple { color: '{{primary-600}}'; }md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-on { background-color: '{{warn-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-off { border-color: '{{warn-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple { color: '{{warn-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-container .md-ripple { color: '{{warn-600}}'; }md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-off { border-color: '{{foreground-3}}'; }md-radio-button.md-THEME_NAME-theme[disabled] .md-container .md-on { border-color: '{{foreground-3}}'; }md-radio-group.md-THEME_NAME-theme:focus:not(:empty) { border-color: '{{foreground-1}}'; }md-sidenav.md-THEME_NAME-theme { background-color: '{{background-hue-3}}'; }md-slider.md-THEME_NAME-theme .md-track { background-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme .md-track-ticks { background-color: '{{foreground-4}}'; }md-slider.md-THEME_NAME-theme .md-focus-thumb { background-color: '{{foreground-2}}'; }md-slider.md-THEME_NAME-theme .md-focus-ring { border-color: '{{foreground-4}}'; }md-slider.md-THEME_NAME-theme .md-disabled-thumb { border-color: '{{background-hue-3}}'; }md-slider.md-THEME_NAME-theme.md-min .md-thumb:after { background-color: '{{background-hue-3}}'; }md-slider.md-THEME_NAME-theme .md-track.md-track-fill { background-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme .md-thumb:after { border-color: '{{primary-color}}'; background-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme .md-sign { background-color: '{{primary-color}}'; } md-slider.md-THEME_NAME-theme .md-sign:after { border-top-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme .md-thumb-text { color: '{{primary-contrast}}'; }md-slider.md-THEME_NAME-theme.md-warn .md-track-fill { background-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn .md-thumb:after { border-color: '{{warn-color}}'; background-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn .md-sign { background-color: '{{warn-color}}'; } md-slider.md-THEME_NAME-theme.md-warn .md-sign:after { border-top-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn .md-thumb-text { color: '{{warn-contrast}}'; }md-slider.md-THEME_NAME-theme.md-primary .md-track-fill { background-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary .md-thumb:after { border-color: '{{primary-color}}'; background-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary .md-sign { background-color: '{{primary-color}}'; } md-slider.md-THEME_NAME-theme.md-primary .md-sign:after { border-top-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary .md-thumb-text { color: '{{primary-contrast}}'; }md-slider.md-THEME_NAME-theme[disabled] .md-thumb:after { border-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme[disabled]:not(.md-min) .md-thumb:after { background-color: '{{foreground-3}}'; }.md-subheader.md-THEME_NAME-theme { color: '{{ foreground-2-0.23 }}'; background-color: '{{background-hue-3}}'; } .md-subheader.md-THEME_NAME-theme.md-primary { color: '{{primary-color}}'; } .md-subheader.md-THEME_NAME-theme.md-accent { color: '{{accent-color}}'; } .md-subheader.md-THEME_NAME-theme.md-warn { color: '{{warn-color}}'; }md-switch.md-THEME_NAME-theme .md-thumb { background-color: '{{background-50}}'; }md-switch.md-THEME_NAME-theme .md-bar { background-color: '{{background-500}}'; }md-switch.md-THEME_NAME-theme.md-checked .md-thumb { background-color: '{{accent-color}}'; }md-switch.md-THEME_NAME-theme.md-checked .md-bar { background-color: '{{accent-color-0.5}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-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-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[disabled] .md-thumb { background-color: '{{background-400}}'; }md-switch.md-THEME_NAME-theme[disabled] .md-bar { background-color: '{{foreground-4}}'; }md-switch.md-THEME_NAME-theme:focus .md-label:not(:empty) { border-color: '{{foreground-1}}'; border-style: dotted; }md-tabs.md-THEME_NAME-theme .md-header { background-color: '{{primary-color}}'; }md-tabs.md-THEME_NAME-theme.md-accent .md-header { background-color: '{{accent-color}}'; }md-tabs.md-THEME_NAME-theme.md-accent md-tab:not([disabled]) { color: '{{accent-100}}'; } md-tabs.md-THEME_NAME-theme.md-accent md-tab:not([disabled]).active { color: '{{accent-contrast}}'; }md-tabs.md-THEME_NAME-theme.md-warn .md-header { background-color: '{{warn-color}}'; }md-tabs.md-THEME_NAME-theme.md-warn md-tab:not([disabled]) { color: '{{warn-100}}'; } md-tabs.md-THEME_NAME-theme.md-warn md-tab:not([disabled]).active { color: '{{warn-contrast}}'; }md-tabs.md-THEME_NAME-theme md-tabs-ink-bar { color: '{{primary-contrast}}'; background: '{{primary-contrast}}'; }md-tabs.md-THEME_NAME-theme md-tab { color: '{{primary-100}}'; } md-tabs.md-THEME_NAME-theme md-tab.active { color: '{{primary-contrast}}'; } md-tabs.md-THEME_NAME-theme md-tab[disabled] { color: '{{foreground-4}}'; } md-tabs.md-THEME_NAME-theme md-tab:focus { color: '{{primary-contrast}}'; background-color: '{{primary-contrast-0.1}}'; } md-tabs.md-THEME_NAME-theme md-tab .md-ripple-container { color: '{{primary-contrast}}'; }md-input-group.md-THEME_NAME-theme input, md-input-group.md-THEME_NAME-theme textarea { text-shadow: '{{foreground-shadow}}'; } md-input-group.md-THEME_NAME-theme input::-webkit-input-placeholder, md-input-group.md-THEME_NAME-theme input::-moz-placeholder, md-input-group.md-THEME_NAME-theme input:-moz-placeholder, md-input-group.md-THEME_NAME-theme input:-ms-input-placeholder, md-input-group.md-THEME_NAME-theme textarea::-webkit-input-placeholder, md-input-group.md-THEME_NAME-theme textarea::-moz-placeholder, md-input-group.md-THEME_NAME-theme textarea:-moz-placeholder, md-input-group.md-THEME_NAME-theme textarea:-ms-input-placeholder { color: '{{foreground-3}}'; }md-input-group.md-THEME_NAME-theme label { text-shadow: '{{foreground-shadow}}'; color: '{{foreground-3}}'; }md-input-group.md-THEME_NAME-theme input, md-input-group.md-THEME_NAME-theme textarea { color: '{{foreground-1}}'; border-color: '{{foreground-4}}'; }md-input-group.md-THEME_NAME-theme.md-input-focused input, md-input-group.md-THEME_NAME-theme.md-input-focused textarea { border-color: '{{primary-500}}'; }md-input-group.md-THEME_NAME-theme.md-input-focused label { color: '{{primary-500}}'; }md-input-group.md-THEME_NAME-theme.md-input-focused.md-accent input, md-input-group.md-THEME_NAME-theme.md-input-focused.md-accent textarea { border-color: '{{accent-500}}'; }md-input-group.md-THEME_NAME-theme.md-input-focused.md-accent label { color: '{{accent-500}}'; }md-input-group.md-THEME_NAME-theme.md-input-has-value:not(.md-input-focused) label { color: '{{foreground-2}}'; }md-input-group.md-THEME_NAME-theme .md-input[disabled] { border-bottom-color: '{{foreground-4}}'; color: '{{foreground-3}}'; }md-toast.md-THEME_NAME-theme { background-color: '{{foreground-1}}'; color: '{{background-50}}'; } md-toast.md-THEME_NAME-theme .md-button { color: '{{background-50}}'; } md-toast.md-THEME_NAME-theme .md-button.md-highlight { color: '{{primary-A200}}'; } md-toast.md-THEME_NAME-theme .md-button.md-highlight.md-accent { color: '{{accent-A200}}'; } md-toast.md-THEME_NAME-theme .md-button.md-highlight.md-warn { color: '{{warn-A200}}'; }md-toolbar.md-THEME_NAME-theme { background-color: '{{primary-color}}'; color: '{{primary-contrast}}'; } md-toolbar.md-THEME_NAME-theme .md-button { color: '{{primary-contrast}}'; } md-toolbar.md-THEME_NAME-theme.md-accent { background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } md-toolbar.md-THEME_NAME-theme.md-warn { background-color: '{{warn-color}}'; color: '{{warn-contrast}}'; }md-tooltip.md-THEME_NAME-theme { color: '{{background-A100}}'; } md-tooltip.md-THEME_NAME-theme .md-background { background-color: '{{foreground-2}}'; }"); |