arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
commonroutesApp/www/lib/angular-material/modules/js/progressCircular/progressCircular.js
www/lib/angular-material/modules/js/progressCircular/progressCircular.jsRaw
  1/*!
  2 * Angular Material Design
  3 * https://github.com/angular/material
  4 * @license MIT
  5 * v1.1.1
  6 */
  7(function( window, angular, undefined ){
  8"use strict";
  9
 10/**
 11 * @ngdoc module
 12 * @name material.components.progressCircular
 13 * @description Module for a circular progressbar
 14 */
 15
 16angular.module('material.components.progressCircular', ['material.core']);
 17
 18/**
 19 * @ngdoc directive
 20 * @name mdProgressCircular
 21 * @module material.components.progressCircular
 22 * @restrict E
 23 *
 24 * @description
 25 * The circular progress directive is used to make loading content in your app as delightful and
 26 * painless as possible by minimizing the amount of visual change a user sees before they can view
 27 * and interact with content.
 28 *
 29 * For operations where the percentage of the operation completed can be determined, use a
 30 * determinate indicator. They give users a quick sense of how long an operation will take.
 31 *
 32 * For operations where the user is asked to wait a moment while something finishes up, and it’s
 33 * not necessary to expose what's happening behind the scenes and how long it will take, use an
 34 * indeterminate indicator.
 35 *
 36 * @param {string} md-mode Select from one of two modes: **'determinate'** and **'indeterminate'**.
 37 *
 38 * Note: if the `md-mode` value is set as undefined or specified as not 1 of the two (2) valid modes, then **'indeterminate'**
 39 * will be auto-applied as the mode.
 40 *
 41 * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute.
 42 * If `value=""` is also specified, however, then `md-mode="determinate"` would be auto-injected instead.
 43 * @param {number=} value In determinate mode, this number represents the percentage of the
 44 *     circular progress. Default: 0
 45 * @param {number=} md-diameter This specifies the diameter of the circular progress. The value
 46 * should be a pixel-size value (eg '100'). If this attribute is
 47 * not present then a default value of '50px' is assumed.
 48 *
 49 * @param {boolean=} ng-disabled Determines whether to disable the progress element.
 50 *
 51 * @usage
 52 * <hljs lang="html">
 53 * <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
 54 *
 55 * <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
 56 *
 57 * <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
 58 *
 59 * <md-progress-circular md-mode="indeterminate"></md-progress-circular>
 60 * </hljs>
 61 */
 62
 63MdProgressCircularDirective.$inject = ["$window", "$mdProgressCircular", "$mdTheming", "$mdUtil", "$interval", "$log"];
 64angular
 65  .module('material.components.progressCircular')
 66  .directive('mdProgressCircular', MdProgressCircularDirective);
 67
 68/* ngInject */
 69function MdProgressCircularDirective($window, $mdProgressCircular, $mdTheming,
 70                                     $mdUtil, $interval, $log) {
 71
 72  // Note that this shouldn't use use $$rAF, because it can cause an infinite loop
 73  // in any tests that call $animate.flush.
 74  var rAF = $window.requestAnimationFrame ||
 75            $window.webkitRequestAnimationFrame ||
 76            angular.noop;
 77
 78  var cAF = $window.cancelAnimationFrame ||
 79            $window.webkitCancelAnimationFrame ||
 80            $window.webkitCancelRequestAnimationFrame ||
 81            angular.noop;
 82
 83  var DEGREE_IN_RADIANS = $window.Math.PI / 180;
 84  var MODE_DETERMINATE = 'determinate';
 85  var MODE_INDETERMINATE = 'indeterminate';
 86  var DISABLED_CLASS = '_md-progress-circular-disabled';
 87  var INDETERMINATE_CLASS = 'md-mode-indeterminate';
 88
 89  return {
 90    restrict: 'E',
 91    scope: {
 92      value: '@',
 93      mdDiameter: '@',
 94      mdMode: '@'
 95    },
 96    template:
 97      '<svg xmlns="http://www.w3.org/2000/svg">' +
 98        '<path fill="none"/>' +
 99      '</svg>',
100    compile: function(element, attrs) {
101      element.attr({
102        'aria-valuemin': 0,
103        'aria-valuemax': 100,
104        'role': 'progressbar'
105      });
106
107      if (angular.isUndefined(attrs.mdMode)) {
108        var hasValue = angular.isDefined(attrs.value);
109        var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE;
110        var info = "Auto-adding the missing md-mode='{0}' to the ProgressCircular element";
111
112          // $log.debug( $mdUtil.supplant(info, [mode]) );
113        attrs.$set('mdMode', mode);
114      } else {
115        attrs.$set('mdMode', attrs.mdMode.trim());
116      }
117
118      return MdProgressCircularLink;
119    }
120  };
121
122  function MdProgressCircularLink(scope, element, attrs) {
123    var node = element[0];
124    var svg = angular.element(node.querySelector('svg'));
125    var path = angular.element(node.querySelector('path'));
126    var startIndeterminate = $mdProgressCircular.startIndeterminate;
127    var endIndeterminate = $mdProgressCircular.endIndeterminate;
128    var rotationIndeterminate = 0;
129    var lastAnimationId = 0;
130    var lastDrawFrame;
131    var interval;
132
133    $mdTheming(element);
134    element.toggleClass(DISABLED_CLASS, attrs.hasOwnProperty('disabled'));
135
136    // If the mode is indeterminate, it doesn't need to
137    // wait for the next digest. It can start right away.
138    if(scope.mdMode === MODE_INDETERMINATE){
139      startIndeterminateAnimation();
140    }
141
142    scope.$on('$destroy', function(){
143      cleanupIndeterminateAnimation();
144
145      if (lastDrawFrame) {
146        cAF(lastDrawFrame);
147      }
148    });
149
150    scope.$watchGroup(['value', 'mdMode', function() {
151      var isDisabled = node.disabled;
152
153      // Sometimes the browser doesn't return a boolean, in
154      // which case we should check whether the attribute is
155      // present.
156      if (isDisabled === true || isDisabled === false){
157        return isDisabled;
158      }
159      return angular.isDefined(element.attr('disabled'));
160
161    }], function(newValues, oldValues) {
162      var mode = newValues[1];
163      var isDisabled = newValues[2];
164      var wasDisabled = oldValues[2];
165
166      if (isDisabled !== wasDisabled) {
167        element.toggleClass(DISABLED_CLASS, !!isDisabled);
168      }
169
170      if (isDisabled) {
171        cleanupIndeterminateAnimation();
172      } else {
173        if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
174          mode = MODE_INDETERMINATE;
175          attrs.$set('mdMode', mode);
176        }
177
178        if (mode === MODE_INDETERMINATE) {
179          startIndeterminateAnimation();
180        } else {
181          var newValue = clamp(newValues[0]);
182
183          cleanupIndeterminateAnimation();
184
185          element.attr('aria-valuenow', newValue);
186          renderCircle(clamp(oldValues[0]), newValue);
187        }
188      }
189
190    });
191
192    // This is in a separate watch in order to avoid layout, unless
193    // the value has actually changed.
194    scope.$watch('mdDiameter', function(newValue) {
195      var diameter = getSize(newValue);
196      var strokeWidth = getStroke(diameter);
197      var transformOrigin = (diameter / 2) + 'px';
198      var dimensions = {
199        width: diameter + 'px',
200        height: diameter + 'px'
201      };
202
203      // The viewBox has to be applied via setAttribute, because it is
204      // case-sensitive. If jQuery is included in the page, `.attr` lowercases
205      // all attribute names.
206      svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);
207
208      // Usually viewBox sets the dimensions for the SVG, however that doesn't
209      // seem to be the case on IE10.
210      // Important! The transform origin has to be set from here and it has to
211      // be in the format of "Ypx Ypx Ypx", otherwise the rotation wobbles in
212      // IE and Edge, because they don't account for the stroke width when
213      // rotating. Also "center" doesn't help in this case, it has to be a
214      // precise value.
215      svg
216        .css(dimensions)
217        .css('transform-origin', transformOrigin + ' ' + transformOrigin + ' ' + transformOrigin);
218
219      element.css(dimensions);
220      path.css('stroke-width',  strokeWidth + 'px');
221    });
222
223    function renderCircle(animateFrom, animateTo, easing, duration, rotation) {
224      var id = ++lastAnimationId;
225      var startTime = $mdUtil.now();
226      var changeInValue = animateTo - animateFrom;
227      var diameter = getSize(scope.mdDiameter);
228      var pathDiameter = diameter - getStroke(diameter);
229      var ease = easing || $mdProgressCircular.easeFn;
230      var animationDuration = duration || $mdProgressCircular.duration;
231
232      // No need to animate it if the values are the same
233      if (animateTo === animateFrom) {
234        path.attr('d', getSvgArc(animateTo, diameter, pathDiameter, rotation));
235      } else {
236        lastDrawFrame = rAF(function animation() {
237          var currentTime = $window.Math.max(0, $window.Math.min($mdUtil.now() - startTime, animationDuration));
238
239          path.attr('d', getSvgArc(
240            ease(currentTime, animateFrom, changeInValue, animationDuration),
241            diameter,
242            pathDiameter,
243            rotation
244          ));
245
246          // Do not allow overlapping animations
247          if (id === lastAnimationId && currentTime < animationDuration) {
248            lastDrawFrame = rAF(animation);
249          }
250        });
251      }
252    }
253
254    function animateIndeterminate() {
255      renderCircle(
256        startIndeterminate,
257        endIndeterminate,
258        $mdProgressCircular.easeFnIndeterminate,
259        $mdProgressCircular.durationIndeterminate,
260        rotationIndeterminate
261      );
262
263      // The % 100 technically isn't necessary, but it keeps the rotation
264      // under 100, instead of becoming a crazy large number.
265      rotationIndeterminate = (rotationIndeterminate + endIndeterminate) % 100;
266
267      var temp = startIndeterminate;
268      startIndeterminate = -endIndeterminate;
269      endIndeterminate = -temp;
270    }
271
272    function startIndeterminateAnimation() {
273      if (!interval) {
274        // Note that this interval isn't supposed to trigger a digest.
275        interval = $interval(
276          animateIndeterminate,
277          $mdProgressCircular.durationIndeterminate + 50,
278          0,
279          false
280        );
281
282        animateIndeterminate();
283
284        element
285          .addClass(INDETERMINATE_CLASS)
286          .removeAttr('aria-valuenow');
287      }
288    }
289
290    function cleanupIndeterminateAnimation() {
291      if (interval) {
292        $interval.cancel(interval);
293        interval = null;
294        element.removeClass(INDETERMINATE_CLASS);
295      }
296    }
297  }
298
299  /**
300   * Generates an arc following the SVG arc syntax.
301   * Syntax spec: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
302   *
303   * @param {number} current Current value between 0 and 100.
304   * @param {number} diameter Diameter of the container.
305   * @param {number} pathDiameter Diameter of the path element.
306   * @param {number=0} rotation The point at which the semicircle should start rendering.
307   * Used for doing the indeterminate animation.
308   *
309   * @returns {string} String representation of an SVG arc.
310   */
311  function getSvgArc(current, diameter, pathDiameter, rotation) {
312    // The angle can't be exactly 360, because the arc becomes hidden.
313    var maximumAngle = 359.99 / 100;
314    var startPoint = rotation || 0;
315    var radius = diameter / 2;
316    var pathRadius = pathDiameter / 2;
317
318    var startAngle = startPoint * maximumAngle;
319    var endAngle = current * maximumAngle;
320    var start = polarToCartesian(radius, pathRadius, startAngle);
321    var end = polarToCartesian(radius, pathRadius, endAngle + startAngle);
322    var arcSweep = endAngle < 0 ? 0 : 1;
323    var largeArcFlag;
324
325    if (endAngle < 0) {
326      largeArcFlag = endAngle >= -180 ? 0 : 1;
327    } else {
328      largeArcFlag = endAngle <= 180 ? 0 : 1;
329    }
330
331    return 'M' + start + 'A' + pathRadius + ',' + pathRadius +
332      ' 0 ' + largeArcFlag + ',' + arcSweep + ' ' + end;
333  }
334
335  /**
336   * Converts Polar coordinates to Cartesian.
337   *
338   * @param {number} radius Radius of the container.
339   * @param {number} pathRadius Radius of the path element
340   * @param {number} angleInDegress Angle at which to place the point.
341   *
342   * @returns {string} Cartesian coordinates in the format of `x,y`.
343   */
344  function polarToCartesian(radius, pathRadius, angleInDegrees) {
345    var angleInRadians = (angleInDegrees - 90) * DEGREE_IN_RADIANS;
346
347    return (radius + (pathRadius * $window.Math.cos(angleInRadians))) +
348      ',' + (radius + (pathRadius * $window.Math.sin(angleInRadians)));
349  }
350
351  /**
352   * Limits a value between 0 and 100.
353   */
354  function clamp(value) {
355    return $window.Math.max(0, $window.Math.min(value || 0, 100));
356  }
357
358  /**
359   * Determines the size of a progress circle, based on the provided
360   * value in the following formats: `X`, `Ypx`, `Z%`.
361   */
362  function getSize(value) {
363    var defaultValue = $mdProgressCircular.progressSize;
364
365    if (value) {
366      var parsed = parseFloat(value);
367
368      if (value.lastIndexOf('%') === value.length - 1) {
369        parsed = (parsed / 100) * defaultValue;
370      }
371
372      return parsed;
373    }
374
375    return defaultValue;
376  }
377
378  /**
379   * Determines the circle's stroke width, based on
380   * the provided diameter.
381   */
382  function getStroke(diameter) {
383    return $mdProgressCircular.strokeWidth / 100 * diameter;
384  }
385}
386
387/**
388 * @ngdoc service
389 * @name $mdProgressCircular
390 * @module material.components.progressCircular
391 *
392 * @description
393 * Allows the user to specify the default options for the `progressCircular` directive.
394 *
395 * @property {number} progressSize Diameter of the progress circle in pixels.
396 * @property {number} strokeWidth Width of the circle's stroke as a percentage of the circle's size.
397 * @property {number} duration Length of the circle animation in milliseconds.
398 * @property {function} easeFn Default easing animation function.
399 * @property {object} easingPresets Collection of pre-defined easing functions.
400 *
401 * @property {number} durationIndeterminate Duration of the indeterminate animation.
402 * @property {number} startIndeterminate Indeterminate animation start point.
403 * @property {number} endIndeterminate Indeterminate animation end point.
404 * @property {function} easeFnIndeterminate Easing function to be used when animating
405 * between the indeterminate values.
406 *
407 * @property {(function(object): object)} configure Used to modify the default options.
408 *
409 * @usage
410 * <hljs lang="js">
411 *   myAppModule.config(function($mdProgressCircularProvider) {
412 *
413 *     // Example of changing the default progress options.
414 *     $mdProgressCircularProvider.configure({
415 *       progressSize: 100,
416 *       strokeWidth: 20,
417 *       duration: 800
418 *     });
419 * });
420 * </hljs>
421 *
422 */
423
424angular
425  .module('material.components.progressCircular')
426  .provider("$mdProgressCircular", MdProgressCircularProvider);
427
428function MdProgressCircularProvider() {
429  var progressConfig = {
430    progressSize: 50,
431    strokeWidth: 10,
432    duration: 100,
433    easeFn: linearEase,
434
435    durationIndeterminate: 500,
436    startIndeterminate: 3,
437    endIndeterminate: 80,
438    easeFnIndeterminate: materialEase,
439
440    easingPresets: {
441      linearEase: linearEase,
442      materialEase: materialEase
443    }
444  };
445
446  return {
447    configure: function(options) {
448      progressConfig = angular.extend(progressConfig, options || {});
449      return progressConfig;
450    },
451    $get: function() { return progressConfig; }
452  };
453
454  function linearEase(t, b, c, d) {
455    return c * t / d + b;
456  }
457
458  function materialEase(t, b, c, d) {
459    // via http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
460    // with settings of [0, 0, 1, 1]
461    var ts = (t /= d) * t;
462    var tc = ts * t;
463    return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
464  }
465}
466
467})(window, window.angular);