@angular/material
Version:
Angular Material
439 lines (434 loc) • 17.1 kB
JavaScript
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { ChangeDetectionStrategy, Component, Directive, ElementRef, Input, NgModule, NgZone, Renderer2, ViewChild, ViewEncapsulation } from '@angular/core';
import { MdCommonModule, mixinColor } from '@angular/material/core';
/**
* A single degree in radians.
*/
const DEGREE_IN_RADIANS = Math.PI / 180;
/**
* Duration of the indeterminate animation.
*/
const DURATION_INDETERMINATE = 667;
/**
* Duration of the indeterminate animation.
*/
const DURATION_DETERMINATE = 225;
/**
* Start animation value of the indeterminate animation
*/
const startIndeterminate = 3;
/**
* End animation value of the indeterminate animation
*/
const endIndeterminate = 80;
/**
* Maximum angle for the arc. The angle can't be exactly 360, because the arc becomes hidden.
*/
const MAX_ANGLE = 359.99 / 100;
/**
* Whether the user's browser supports requestAnimationFrame.
*/
const HAS_RAF = typeof requestAnimationFrame !== 'undefined';
/**
* Default stroke width as a percentage of the viewBox.
*/
const PROGRESS_SPINNER_STROKE_WIDTH = 10;
/**
* Directive whose purpose is to add the mat- CSS styling to this selector.
* \@docs-private
*/
class MdProgressSpinnerCssMatStyler {
}
MdProgressSpinnerCssMatStyler.decorators = [
{ type: Directive, args: [{
selector: 'md-progress-spinner, mat-progress-spinner',
host: { 'class': 'mat-progress-spinner' }
},] },
];
/**
* @nocollapse
*/
MdProgressSpinnerCssMatStyler.ctorParameters = () => [];
/**
* \@docs-private
*/
class MdProgressSpinnerBase {
/**
* @param {?} _renderer
* @param {?} _elementRef
*/
constructor(_renderer, _elementRef) {
this._renderer = _renderer;
this._elementRef = _elementRef;
}
}
const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase, 'primary');
/**
* <md-progress-spinner> component.
*/
class MdProgressSpinner extends _MdProgressSpinnerMixinBase {
/**
* @param {?} renderer
* @param {?} elementRef
* @param {?} _ngZone
*/
constructor(renderer, elementRef, _ngZone) {
super(renderer, elementRef);
this._ngZone = _ngZone;
/**
* The id of the last requested animation.
*/
this._lastAnimationId = 0;
this._mode = 'determinate';
/**
* Stroke width of the progress spinner. By default uses 10px as stroke width.
*/
this.strokeWidth = PROGRESS_SPINNER_STROKE_WIDTH;
}
/**
* Values for aria max and min are only defined as numbers when in a determinate mode. We do this
* because voiceover does not report the progress indicator as indeterminate if the aria min
* and/or max value are number values.
* @return {?}
*/
get _ariaValueMin() {
return this.mode == 'determinate' ? 0 : null;
}
/**
* @return {?}
*/
get _ariaValueMax() {
return this.mode == 'determinate' ? 100 : null;
}
/**
* \@docs-private
* @return {?}
*/
get interdeterminateInterval() {
return this._interdeterminateInterval;
}
/**
* \@docs-private
* @param {?} interval
* @return {?}
*/
set interdeterminateInterval(interval) {
if (this._interdeterminateInterval) {
clearInterval(this._interdeterminateInterval);
}
this._interdeterminateInterval = interval;
}
/**
* Clean up any animations that were running.
* @return {?}
*/
ngOnDestroy() {
this._cleanupIndeterminateAnimation();
}
/**
* Value of the progress circle. It is bound to the host as the attribute aria-valuenow.
* @return {?}
*/
get value() {
if (this.mode == 'determinate') {
return this._value;
}
return 0;
}
/**
* @param {?} v
* @return {?}
*/
set value(v) {
if (v != null && this.mode == 'determinate') {
let /** @type {?} */ newValue = clamp(v);
this._animateCircle(this.value || 0, newValue);
this._value = newValue;
}
}
/**
* Mode of the progress circle
*
* Input must be one of the values from ProgressMode, defaults to 'determinate'.
* mode is bound to the host as the attribute host.
* @return {?}
*/
get mode() { return this._mode; }
/**
* @param {?} mode
* @return {?}
*/
set mode(mode) {
if (mode !== this._mode) {
if (mode === 'indeterminate') {
this._startIndeterminateAnimation();
}
else {
this._cleanupIndeterminateAnimation();
this._animateCircle(0, this._value);
}
this._mode = mode;
}
}
/**
* Animates the circle from one percentage value to another.
*
* @param {?} animateFrom The percentage of the circle filled starting the animation.
* @param {?} animateTo The percentage of the circle filled ending the animation.
* @param {?=} ease The easing function to manage the pace of change in the animation.
* @param {?=} duration The length of time to show the animation, in milliseconds.
* @param {?=} rotation The starting angle of the circle fill, with 0° represented at the top center
* of the circle.
* @return {?}
*/
_animateCircle(animateFrom, animateTo, ease = linearEase, duration = DURATION_DETERMINATE, rotation = 0) {
let /** @type {?} */ id = ++this._lastAnimationId;
let /** @type {?} */ startTime = Date.now();
let /** @type {?} */ changeInValue = animateTo - animateFrom;
// No need to animate it if the values are the same
if (animateTo === animateFrom) {
this._renderArc(animateTo, rotation);
}
else {
let /** @type {?} */ animation = () => {
// If there is no requestAnimationFrame, skip ahead to the end of the animation.
let /** @type {?} */ elapsedTime = HAS_RAF ?
Math.max(0, Math.min(Date.now() - startTime, duration)) :
duration;
this._renderArc(ease(elapsedTime, animateFrom, changeInValue, duration), rotation);
// Prevent overlapping animations by checking if a new animation has been called for and
// if the animation has lasted longer than the animation duration.
if (id === this._lastAnimationId && elapsedTime < duration) {
requestAnimationFrame(animation);
}
};
// Run the animation outside of Angular's zone, in order to avoid
// hitting ZoneJS and change detection on each frame.
this._ngZone.runOutsideAngular(animation);
}
}
/**
* Starts the indeterminate animation interval, if it is not already running.
* @return {?}
*/
_startIndeterminateAnimation() {
let /** @type {?} */ rotationStartPoint = 0;
let /** @type {?} */ start = startIndeterminate;
let /** @type {?} */ end = endIndeterminate;
let /** @type {?} */ duration = DURATION_INDETERMINATE;
let /** @type {?} */ animate = () => {
this._animateCircle(start, end, materialEase, duration, rotationStartPoint);
// Prevent rotation from reaching Number.MAX_SAFE_INTEGER.
rotationStartPoint = (rotationStartPoint + end) % 100;
let /** @type {?} */ temp = start;
start = -end;
end = -temp;
};
if (!this.interdeterminateInterval) {
this._ngZone.runOutsideAngular(() => {
this.interdeterminateInterval = setInterval(animate, duration + 50, 0, false);
animate();
});
}
}
/**
* Removes interval, ending the animation.
* @return {?}
*/
_cleanupIndeterminateAnimation() {
this.interdeterminateInterval = null;
}
/**
* Renders the arc onto the SVG element. Proxies `getArc` while setting the proper
* DOM attribute on the `<path>`.
* @param {?} currentValue
* @param {?=} rotation
* @return {?}
*/
_renderArc(currentValue, rotation = 0) {
if (this._path) {
const /** @type {?} */ svgArc = getSvgArc(currentValue, rotation, this.strokeWidth);
this._renderer.setAttribute(this._path.nativeElement, 'd', svgArc);
}
}
}
MdProgressSpinner.decorators = [
{ type: Component, args: [{selector: 'md-progress-spinner, mat-progress-spinner',
host: {
'role': 'progressbar',
'class': 'mat-progress-spinner',
'[attr.aria-valuemin]': '_ariaValueMin',
'[attr.aria-valuemax]': '_ariaValueMax',
'[attr.aria-valuenow]': 'value',
'[attr.mode]': 'mode',
},
inputs: ['color'],
template: "<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid meet\" focusable=\"false\"><path #path [style.strokeWidth]=\"strokeWidth\"></path></svg>",
styles: [".mat-progress-spinner{display:block;height:100px;width:100px;overflow:hidden}.mat-progress-spinner svg{height:100%;width:100%;transform-origin:center}.mat-progress-spinner path{fill:transparent;transition:stroke .3s cubic-bezier(.35,0,.25,1)}.mat-progress-spinner[mode=indeterminate] svg{animation-duration:5.25s,2.887s;animation-name:mat-progress-spinner-sporadic-rotate,mat-progress-spinner-linear-rotate;animation-timing-function:cubic-bezier(.35,0,.25,1),linear;animation-iteration-count:infinite;transition:none}@keyframes mat-progress-spinner-linear-rotate{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes mat-progress-spinner-sporadic-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}100%{transform:rotate(1080deg)}}"],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
},] },
];
/**
* @nocollapse
*/
MdProgressSpinner.ctorParameters = () => [
{ type: Renderer2, },
{ type: ElementRef, },
{ type: NgZone, },
];
MdProgressSpinner.propDecorators = {
'_path': [{ type: ViewChild, args: ['path',] },],
'strokeWidth': [{ type: Input },],
'value': [{ type: Input },],
'mode': [{ type: Input },],
};
/**
* <md-spinner> component.
*
* This is a component definition to be used as a convenience reference to create an
* indeterminate <md-progress-spinner> instance.
*/
class MdSpinner extends MdProgressSpinner {
/**
* @param {?} elementRef
* @param {?} ngZone
* @param {?} renderer
*/
constructor(elementRef, ngZone, renderer) {
super(renderer, elementRef, ngZone);
this.mode = 'indeterminate';
}
}
MdSpinner.decorators = [
{ type: Component, args: [{selector: 'md-spinner, mat-spinner',
host: {
'role': 'progressbar',
'mode': 'indeterminate',
'class': 'mat-spinner mat-progress-spinner',
},
inputs: ['color'],
template: "<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid meet\" focusable=\"false\"><path #path [style.strokeWidth]=\"strokeWidth\"></path></svg>",
styles: [".mat-progress-spinner{display:block;height:100px;width:100px;overflow:hidden}.mat-progress-spinner svg{height:100%;width:100%;transform-origin:center}.mat-progress-spinner path{fill:transparent;transition:stroke .3s cubic-bezier(.35,0,.25,1)}.mat-progress-spinner[mode=indeterminate] svg{animation-duration:5.25s,2.887s;animation-name:mat-progress-spinner-sporadic-rotate,mat-progress-spinner-linear-rotate;animation-timing-function:cubic-bezier(.35,0,.25,1),linear;animation-iteration-count:infinite;transition:none}@keyframes mat-progress-spinner-linear-rotate{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes mat-progress-spinner-sporadic-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}100%{transform:rotate(1080deg)}}"],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
},] },
];
/**
* @nocollapse
*/
MdSpinner.ctorParameters = () => [
{ type: ElementRef, },
{ type: NgZone, },
{ type: Renderer2, },
];
/**
* Clamps a value to be between 0 and 100.
* @param {?} v
* @return {?}
*/
function clamp(v) {
return Math.max(0, Math.min(100, v));
}
/**
* Converts Polar coordinates to Cartesian.
* @param {?} radius
* @param {?} pathRadius
* @param {?} angleInDegrees
* @return {?}
*/
function polarToCartesian(radius, pathRadius, angleInDegrees) {
let /** @type {?} */ angleInRadians = (angleInDegrees - 90) * DEGREE_IN_RADIANS;
return (radius + (pathRadius * Math.cos(angleInRadians))) +
',' + (radius + (pathRadius * Math.sin(angleInRadians)));
}
/**
* Easing function for linear animation.
* @param {?} currentTime
* @param {?} startValue
* @param {?} changeInValue
* @param {?} duration
* @return {?}
*/
function linearEase(currentTime, startValue, changeInValue, duration) {
return changeInValue * currentTime / duration + startValue;
}
/**
* Easing function to match material design indeterminate animation.
* @param {?} currentTime
* @param {?} startValue
* @param {?} changeInValue
* @param {?} duration
* @return {?}
*/
function materialEase(currentTime, startValue, changeInValue, duration) {
let /** @type {?} */ time = currentTime / duration;
let /** @type {?} */ timeCubed = Math.pow(time, 3);
let /** @type {?} */ timeQuad = Math.pow(time, 4);
let /** @type {?} */ timeQuint = Math.pow(time, 5);
return startValue + changeInValue * ((6 * timeQuint) + (-15 * timeQuad) + (10 * timeCubed));
}
/**
* Determines the path value to define the arc. Converting percentage values to to polar
* coordinates on the circle, and then to cartesian coordinates in the viewport.
*
* @param {?} currentValue The current percentage value of the progress circle, the percentage of the
* circle to fill.
* @param {?} rotation The starting point of the circle with 0 being the 0 degree point.
* @param {?} strokeWidth Stroke width of the progress spinner arc.
* @return {?} A string for an SVG path representing a circle filled from the starting point to the
* percentage value provided.
*/
function getSvgArc(currentValue, rotation, strokeWidth) {
let /** @type {?} */ startPoint = rotation || 0;
let /** @type {?} */ radius = 50;
let /** @type {?} */ pathRadius = radius - strokeWidth;
let /** @type {?} */ startAngle = startPoint * MAX_ANGLE;
let /** @type {?} */ endAngle = currentValue * MAX_ANGLE;
let /** @type {?} */ start = polarToCartesian(radius, pathRadius, startAngle);
let /** @type {?} */ end = polarToCartesian(radius, pathRadius, endAngle + startAngle);
let /** @type {?} */ arcSweep = endAngle < 0 ? 0 : 1;
let /** @type {?} */ 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}`;
}
class MdProgressSpinnerModule {
}
MdProgressSpinnerModule.decorators = [
{ type: NgModule, args: [{
imports: [MdCommonModule],
exports: [
MdProgressSpinner,
MdSpinner,
MdCommonModule,
MdProgressSpinnerCssMatStyler
],
declarations: [
MdProgressSpinner,
MdSpinner,
MdProgressSpinnerCssMatStyler
],
},] },
];
/**
* @nocollapse
*/
MdProgressSpinnerModule.ctorParameters = () => [];
/**
* Generated bundle index. Do not edit.
*/
export { MdProgressSpinnerModule, PROGRESS_SPINNER_STROKE_WIDTH, MdProgressSpinnerCssMatStyler, MdProgressSpinnerBase, _MdProgressSpinnerMixinBase, MdProgressSpinner, MdSpinner, MdProgressSpinner as MatProgressSpinner, MdProgressSpinnerBase as MatProgressSpinnerBase, MdProgressSpinnerCssMatStyler as MatProgressSpinnerCssMatStyler, MdProgressSpinnerModule as MatProgressSpinnerModule, MdSpinner as MatSpinner };
//# sourceMappingURL=progress-spinner.js.map