77 */
88
99import {
10+ AfterViewInit ,
1011 Component ,
1112 ChangeDetectionStrategy ,
1213 Input ,
1314 ElementRef ,
1415 Renderer2 ,
15- Directive ,
16- ViewChild ,
1716 SimpleChanges ,
1817 OnChanges ,
1918 ViewEncapsulation ,
19+ Optional ,
20+ Inject ,
2021} from '@angular/core' ;
2122import { CanColor , mixinColor } from '@angular/material/core' ;
2223import { Platform } from '@angular/cdk/platform' ;
24+ import { DOCUMENT } from '@angular/common' ;
2325
2426/** Possible mode for a progress spinner. */
2527export type ProgressSpinnerMode = 'determinate' | 'indeterminate' ;
@@ -31,6 +33,38 @@ export class MdProgressSpinnerBase {
3133}
3234export const _MdProgressSpinnerMixinBase = mixinColor ( MdProgressSpinnerBase , 'primary' ) ;
3335
36+ /* tslint:disable:max-line-length */
37+ const INDETERMINATE_ANIMATION_TEMPLATE = `
38+ .mat-progress-spinner-DIAMETER.mat-progress-spinner-indeterminate-animation[mode="indeterminate"] circle {
39+ animation: mat-progress-spinner-stroke-rotate-DIAMETER 4s
40+ cubic-bezier(0.35, 0, 0.25, 1) infinite;
41+ transition-property: stroke;
42+ }
43+
44+ @keyframes mat-progress-spinner-stroke-rotate-DIAMETER {
45+ 0% { stroke-dashoffset: START_VALUE; transform: rotate(0); }
46+ 12.5% { stroke-dashoffset: END_VALUE; transform: rotate(0); }
47+ 12.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(72.5deg); }
48+ 25% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(72.5deg); }
49+
50+ 25.1% { stroke-dashoffset: START_VALUE; transform: rotate(270deg); }
51+ 37.5% { stroke-dashoffset: END_VALUE; transform: rotate(270deg); }
52+ 37.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(161.5deg); }
53+ 50% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(161.5deg); }
54+
55+ 50.01% { stroke-dashoffset: START_VALUE; transform: rotate(180deg); }
56+ 62.5% { stroke-dashoffset: END_VALUE; transform: rotate(180deg); }
57+ 62.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(251.5deg); }
58+ 75% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(251.5deg); }
59+
60+ 75.01% { stroke-dashoffset: START_VALUE; transform: rotate(90deg); }
61+ 87.5% { stroke-dashoffset: END_VALUE; transform: rotate(90deg); }
62+ 87.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(341.5deg); }
63+ 100% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(341.5deg); }
64+ }
65+ ` ;
66+ /* tslint:enable:max-line-length */
67+
3468/**
3569 * <md-progress-spinner> component.
3670 */
@@ -54,13 +88,30 @@ export const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase, 'pr
5488 encapsulation : ViewEncapsulation . None ,
5589 preserveWhitespaces : false ,
5690} )
57- export class MdProgressSpinner extends _MdProgressSpinnerMixinBase implements CanColor , OnChanges {
91+ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase implements CanColor ,
92+ OnChanges , AfterViewInit {
93+
5894 private _value : number ;
5995 private readonly _baseSize = 100 ;
6096 private readonly _baseStrokeWidth = 10 ;
97+ private _fallbackAnimation = false ;
6198
99+ /** The width and height of the host element. Will grow with stroke width. **/
62100 _elementSize = this . _baseSize ;
63- _circleRadius = 45 ;
101+
102+ /** Tracks diameters of existing instances to de-dupe generated styles (default d = 100) */
103+ static diameters = new Set < number > ( [ 100 ] ) ;
104+
105+ /** The diameter of the progress spinner (will set width and height of svg). */
106+ @Input ( )
107+ get diameter ( ) : number {
108+ return this . _diameter ;
109+ }
110+
111+ set diameter ( size : number ) {
112+ this . _setDiameterAndInitStyles ( size ) ;
113+ }
114+ _diameter = this . _baseSize ;
64115
65116 /** Stroke width of the progress spinner. */
66117 @Input ( ) strokeWidth : number = 10 ;
@@ -79,31 +130,89 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase implements Ca
79130 }
80131 }
81132
82- constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ) {
83- super ( renderer , elementRef ) ;
133+ constructor ( public _renderer : Renderer2 , public _elementRef : ElementRef ,
134+ platform : Platform , @Optional ( ) @Inject ( DOCUMENT ) private _document : any ) {
135+ super ( _renderer , _elementRef ) ;
136+
137+ this . _fallbackAnimation = platform . EDGE || platform . TRIDENT ;
84138
85- // On IE and Edge we can't animate the `stroke-dashoffset`
139+ // On IE and Edge, we can't animate the `stroke-dashoffset`
86140 // reliably so we fall back to a non-spec animation.
87- const animationClass = ( platform . EDGE || platform . TRIDENT ) ?
141+ const animationClass = this . _fallbackAnimation ?
88142 'mat-progress-spinner-indeterminate-fallback-animation' :
89143 'mat-progress-spinner-indeterminate-animation' ;
90144
91- renderer . addClass ( elementRef . nativeElement , animationClass ) ;
145+ _renderer . addClass ( _elementRef . nativeElement , animationClass ) ;
146+ }
147+
148+ ngAfterViewInit ( ) {
149+ this . _setDiameterClass ( this . diameter ) ;
92150 }
93151
94152 ngOnChanges ( changes : SimpleChanges ) {
95- if ( changes . strokeWidth ) {
96- this . _elementSize = this . _baseSize + Math . max ( this . strokeWidth - this . _baseStrokeWidth , 0 ) ;
153+ if ( changes . strokeWidth || changes . diameter ) {
154+ this . _elementSize =
155+ this . _diameter + Math . max ( this . strokeWidth - this . _baseStrokeWidth , 0 ) ;
97156 }
98157 }
99158
100- _getStrokeDashOffset ( ) {
159+ /** The radius of the spinner, adjusted for stroke width. */
160+ get _circleRadius ( ) {
161+ return ( this . diameter - this . _baseStrokeWidth ) / 2 ;
162+ }
163+
164+ /** The view box of the spinner's svg element. */
165+ get _viewBox ( ) {
166+ return `0 0 ${ this . _elementSize } ${ this . _elementSize } ` ;
167+ }
168+
169+ /** The stroke circumference of the svg circle. */
170+ get _strokeCircumference ( ) : number {
171+ return 2 * Math . PI * this . _circleRadius ;
172+ }
173+
174+ /** The dash offset of the svg circle. */
175+ get _strokeDashOffset ( ) {
101176 if ( this . mode === 'determinate' ) {
102- return 2 * Math . PI * this . _circleRadius * ( 100 - this . _value ) / 100 ;
177+ return this . _strokeCircumference * ( 100 - this . _value ) / 100 ;
103178 }
104179
105180 return null ;
106181 }
182+
183+ /** Sets the diameter and adds diameter-specific styles if necessary. */
184+ private _setDiameterAndInitStyles ( size : number ) : void {
185+ this . _setDiameterClass ( size ) ;
186+ this . _diameter = size ;
187+ if ( ! MdProgressSpinner . diameters . has ( this . diameter ) && ! this . _fallbackAnimation ) {
188+ this . _attachStyleNode ( ) ;
189+ }
190+ }
191+
192+ /** Adds a diameter-specific class and removes any existing diameter classes. */
193+ private _setDiameterClass ( size : number ) : void {
194+ this . _renderer . removeClass (
195+ this . _elementRef . nativeElement , `mat-progress-spinner-${ this . diameter } ` ) ;
196+ this . _renderer . addClass (
197+ this . _elementRef . nativeElement , `mat-progress-spinner-${ size } ` ) ;
198+ }
199+
200+ /** Dynamically generates a style tag containing the correct animation for this diameter. */
201+ private _attachStyleNode ( ) : void {
202+ const styleTag = this . _renderer . createElement ( 'style' ) ;
203+ styleTag . textContent = this . _getAnimationText ( ) ;
204+ this . _renderer . appendChild ( this . _document . head , styleTag ) ;
205+ MdProgressSpinner . diameters . add ( this . diameter ) ;
206+ }
207+
208+ /** Generates animation styles adjusted for the spinner's diameter. */
209+ private _getAnimationText ( ) : string {
210+ return INDETERMINATE_ANIMATION_TEMPLATE
211+ // Animation should begin at 5% and end at 80%
212+ . replace ( / S T A R T _ V A L U E / g, `${ 0.95 * this . _strokeCircumference } ` )
213+ . replace ( / E N D _ V A L U E / g, `${ 0.2 * this . _strokeCircumference } ` )
214+ . replace ( / D I A M E T E R / g, `${ this . diameter } ` ) ;
215+ }
107216}
108217
109218
@@ -131,8 +240,9 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase implements Ca
131240 preserveWhitespaces : false ,
132241} )
133242export class MdSpinner extends MdProgressSpinner {
134- constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ) {
135- super ( renderer , elementRef , platform ) ;
243+ constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ,
244+ @Optional ( ) @Inject ( DOCUMENT ) document : any ) {
245+ super ( renderer , elementRef , platform , document ) ;
136246 this . mode = 'indeterminate' ;
137247 }
138248}
0 commit comments