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