|
|
/*! * Angular Material Design * https://github.com/angular/material
* @license MIT * v1.1.1 */ (function( window, angular, undefined ){ "use strict";
/** * @ngdoc module * @name material.components.progressCircular * @description Module for a circular progressbar */
angular.module('material.components.progressCircular', ['material.core']);
/** * @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'**. * * Note: if the `md-mode` value is set as undefined or specified as not 1 of the two (2) valid modes, then **'indeterminate'** * will be auto-applied as the mode. * * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute. * If `value=""` is also specified, however, then `md-mode="determinate"` would be auto-injected instead. * @param {number=} value In determinate mode, this number represents the percentage of the * circular progress. Default: 0 * @param {number=} md-diameter This specifies the diameter of the circular progress. The value * should be a pixel-size value (eg '100'). If this attribute is * not present then a default value of '50px' is assumed. * * @param {boolean=} ng-disabled Determines whether to disable the progress element. * * @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> */
MdProgressCircularDirective.$inject = ["$window", "$mdProgressCircular", "$mdTheming", "$mdUtil", "$interval", "$log"]; angular .module('material.components.progressCircular') .directive('mdProgressCircular', MdProgressCircularDirective);
/* ngInject */ function MdProgressCircularDirective($window, $mdProgressCircular, $mdTheming, $mdUtil, $interval, $log) {
// Note that this shouldn't use use $$rAF, because it can cause an infinite loop
// in any tests that call $animate.flush.
var rAF = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || angular.noop;
var cAF = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame || angular.noop;
var DEGREE_IN_RADIANS = $window.Math.PI / 180; var MODE_DETERMINATE = 'determinate'; var MODE_INDETERMINATE = 'indeterminate'; var DISABLED_CLASS = '_md-progress-circular-disabled'; var INDETERMINATE_CLASS = 'md-mode-indeterminate';
return { restrict: 'E', scope: { value: '@', mdDiameter: '@', mdMode: '@' }, template: '<svg xmlns="http://www.w3.org/2000/svg">' + '<path fill="none"/>' + '</svg>', compile: function(element, attrs) { element.attr({ 'aria-valuemin': 0, 'aria-valuemax': 100, 'role': 'progressbar' });
if (angular.isUndefined(attrs.mdMode)) { var hasValue = angular.isDefined(attrs.value); var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE; var info = "Auto-adding the missing md-mode='{0}' to the ProgressCircular element";
// $log.debug( $mdUtil.supplant(info, [mode]) );
attrs.$set('mdMode', mode); } else { attrs.$set('mdMode', attrs.mdMode.trim()); }
return MdProgressCircularLink; } };
function MdProgressCircularLink(scope, element, attrs) { var node = element[0]; var svg = angular.element(node.querySelector('svg')); var path = angular.element(node.querySelector('path')); var startIndeterminate = $mdProgressCircular.startIndeterminate; var endIndeterminate = $mdProgressCircular.endIndeterminate; var rotationIndeterminate = 0; var lastAnimationId = 0; var lastDrawFrame; var interval;
$mdTheming(element); element.toggleClass(DISABLED_CLASS, attrs.hasOwnProperty('disabled'));
// If the mode is indeterminate, it doesn't need to
// wait for the next digest. It can start right away.
if(scope.mdMode === MODE_INDETERMINATE){ startIndeterminateAnimation(); }
scope.$on('$destroy', function(){ cleanupIndeterminateAnimation();
if (lastDrawFrame) { cAF(lastDrawFrame); } });
scope.$watchGroup(['value', 'mdMode', function() { var isDisabled = node.disabled;
// Sometimes the browser doesn't return a boolean, in
// which case we should check whether the attribute is
// present.
if (isDisabled === true || isDisabled === false){ return isDisabled; } return angular.isDefined(element.attr('disabled'));
}], function(newValues, oldValues) { var mode = newValues[1]; var isDisabled = newValues[2]; var wasDisabled = oldValues[2];
if (isDisabled !== wasDisabled) { element.toggleClass(DISABLED_CLASS, !!isDisabled); }
if (isDisabled) { cleanupIndeterminateAnimation(); } else { if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) { mode = MODE_INDETERMINATE; attrs.$set('mdMode', mode); }
if (mode === MODE_INDETERMINATE) { startIndeterminateAnimation(); } else { var newValue = clamp(newValues[0]);
cleanupIndeterminateAnimation();
element.attr('aria-valuenow', newValue); renderCircle(clamp(oldValues[0]), newValue); } }
});
// This is in a separate watch in order to avoid layout, unless
// the value has actually changed.
scope.$watch('mdDiameter', function(newValue) { var diameter = getSize(newValue); var strokeWidth = getStroke(diameter); var transformOrigin = (diameter / 2) + 'px'; var dimensions = { width: diameter + 'px', height: diameter + 'px' };
// The viewBox has to be applied via setAttribute, because it is
// case-sensitive. If jQuery is included in the page, `.attr` lowercases
// all attribute names.
svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);
// Usually viewBox sets the dimensions for the SVG, however that doesn't
// seem to be the case on IE10.
// Important! The transform origin has to be set from here and it has to
// be in the format of "Ypx Ypx Ypx", otherwise the rotation wobbles in
// IE and Edge, because they don't account for the stroke width when
// rotating. Also "center" doesn't help in this case, it has to be a
// precise value.
svg .css(dimensions) .css('transform-origin', transformOrigin + ' ' + transformOrigin + ' ' + transformOrigin);
element.css(dimensions); path.css('stroke-width', strokeWidth + 'px'); });
function renderCircle(animateFrom, animateTo, easing, duration, rotation) { var id = ++lastAnimationId; var startTime = $mdUtil.now(); var changeInValue = animateTo - animateFrom; var diameter = getSize(scope.mdDiameter); var pathDiameter = diameter - getStroke(diameter); var ease = easing || $mdProgressCircular.easeFn; var animationDuration = duration || $mdProgressCircular.duration;
// No need to animate it if the values are the same
if (animateTo === animateFrom) { path.attr('d', getSvgArc(animateTo, diameter, pathDiameter, rotation)); } else { lastDrawFrame = rAF(function animation() { var currentTime = $window.Math.max(0, $window.Math.min($mdUtil.now() - startTime, animationDuration));
path.attr('d', getSvgArc( ease(currentTime, animateFrom, changeInValue, animationDuration), diameter, pathDiameter, rotation ));
// Do not allow overlapping animations
if (id === lastAnimationId && currentTime < animationDuration) { lastDrawFrame = rAF(animation); } }); } }
function animateIndeterminate() { renderCircle( startIndeterminate, endIndeterminate, $mdProgressCircular.easeFnIndeterminate, $mdProgressCircular.durationIndeterminate, rotationIndeterminate );
// The % 100 technically isn't necessary, but it keeps the rotation
// under 100, instead of becoming a crazy large number.
rotationIndeterminate = (rotationIndeterminate + endIndeterminate) % 100;
var temp = startIndeterminate; startIndeterminate = -endIndeterminate; endIndeterminate = -temp; }
function startIndeterminateAnimation() { if (!interval) { // Note that this interval isn't supposed to trigger a digest.
interval = $interval( animateIndeterminate, $mdProgressCircular.durationIndeterminate + 50, 0, false );
animateIndeterminate();
element .addClass(INDETERMINATE_CLASS) .removeAttr('aria-valuenow'); } }
function cleanupIndeterminateAnimation() { if (interval) { $interval.cancel(interval); interval = null; element.removeClass(INDETERMINATE_CLASS); } } }
/** * Generates an arc following the SVG arc syntax. * Syntax spec: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
* * @param {number} current Current value between 0 and 100. * @param {number} diameter Diameter of the container. * @param {number} pathDiameter Diameter of the path element. * @param {number=0} rotation The point at which the semicircle should start rendering. * Used for doing the indeterminate animation. * * @returns {string} String representation of an SVG arc. */ function getSvgArc(current, diameter, pathDiameter, rotation) { // The angle can't be exactly 360, because the arc becomes hidden.
var maximumAngle = 359.99 / 100; var startPoint = rotation || 0; var radius = diameter / 2; var pathRadius = pathDiameter / 2;
var startAngle = startPoint * maximumAngle; var endAngle = current * maximumAngle; var start = polarToCartesian(radius, pathRadius, startAngle); var end = polarToCartesian(radius, pathRadius, endAngle + startAngle); var arcSweep = endAngle < 0 ? 0 : 1; var largeArcFlag;
if (endAngle < 0) { largeArcFlag = endAngle >= -180 ? 0 : 1; } else { largeArcFlag = endAngle <= 180 ? 0 : 1; }
return 'M' + start + 'A' + pathRadius + ',' + pathRadius + ' 0 ' + largeArcFlag + ',' + arcSweep + ' ' + end; }
/** * Converts Polar coordinates to Cartesian. * * @param {number} radius Radius of the container. * @param {number} pathRadius Radius of the path element * @param {number} angleInDegress Angle at which to place the point. * * @returns {string} Cartesian coordinates in the format of `x,y`. */ function polarToCartesian(radius, pathRadius, angleInDegrees) { var angleInRadians = (angleInDegrees - 90) * DEGREE_IN_RADIANS;
return (radius + (pathRadius * $window.Math.cos(angleInRadians))) + ',' + (radius + (pathRadius * $window.Math.sin(angleInRadians))); }
/** * Limits a value between 0 and 100. */ function clamp(value) { return $window.Math.max(0, $window.Math.min(value || 0, 100)); }
/** * Determines the size of a progress circle, based on the provided * value in the following formats: `X`, `Ypx`, `Z%`. */ function getSize(value) { var defaultValue = $mdProgressCircular.progressSize;
if (value) { var parsed = parseFloat(value);
if (value.lastIndexOf('%') === value.length - 1) { parsed = (parsed / 100) * defaultValue; }
return parsed; }
return defaultValue; }
/** * Determines the circle's stroke width, based on * the provided diameter. */ function getStroke(diameter) { return $mdProgressCircular.strokeWidth / 100 * diameter; } }
/** * @ngdoc service * @name $mdProgressCircular * @module material.components.progressCircular * * @description * Allows the user to specify the default options for the `progressCircular` directive. * * @property {number} progressSize Diameter of the progress circle in pixels. * @property {number} strokeWidth Width of the circle's stroke as a percentage of the circle's size. * @property {number} duration Length of the circle animation in milliseconds. * @property {function} easeFn Default easing animation function. * @property {object} easingPresets Collection of pre-defined easing functions. * * @property {number} durationIndeterminate Duration of the indeterminate animation. * @property {number} startIndeterminate Indeterminate animation start point. * @property {number} endIndeterminate Indeterminate animation end point. * @property {function} easeFnIndeterminate Easing function to be used when animating * between the indeterminate values. * * @property {(function(object): object)} configure Used to modify the default options. * * @usage * <hljs lang="js"> * myAppModule.config(function($mdProgressCircularProvider) { * * // Example of changing the default progress options.
* $mdProgressCircularProvider.configure({ * progressSize: 100, * strokeWidth: 20, * duration: 800 * }); * }); * </hljs> * */
angular .module('material.components.progressCircular') .provider("$mdProgressCircular", MdProgressCircularProvider);
function MdProgressCircularProvider() { var progressConfig = { progressSize: 50, strokeWidth: 10, duration: 100, easeFn: linearEase,
durationIndeterminate: 500, startIndeterminate: 3, endIndeterminate: 80, easeFnIndeterminate: materialEase,
easingPresets: { linearEase: linearEase, materialEase: materialEase } };
return { configure: function(options) { progressConfig = angular.extend(progressConfig, options || {}); return progressConfig; }, $get: function() { return progressConfig; } };
function linearEase(t, b, c, d) { return c * t / d + b; }
function materialEase(t, b, c, d) { // via http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
// with settings of [0, 0, 1, 1]
var ts = (t /= d) * t; var tc = ts * t; return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc); } }
})(window, window.angular);
|