@@ -15,20 +15,47 @@ import {
15
15
SimpleChanges ,
16
16
OnChanges ,
17
17
ViewEncapsulation ,
18
+ Optional ,
19
+ Inject ,
18
20
} from '@angular/core' ;
19
21
import { CanColor , mixinColor } from '@angular/material/core' ;
20
22
import { Platform } from '@angular/cdk/platform' ;
23
+ import { DOCUMENT } from '@angular/common' ;
21
24
22
25
/** Possible mode for a progress spinner. */
23
26
export type ProgressSpinnerMode = 'determinate' | 'indeterminate' ;
24
27
25
- // Boilerplate for applying mixins to MdProgressSpinner .
28
+ // Boilerplate for applying mixins to MatProgressSpinner .
26
29
/** @docs -private */
27
30
export class MatProgressSpinnerBase {
28
31
constructor ( public _renderer : Renderer2 , public _elementRef : ElementRef ) { }
29
32
}
30
33
export const _MatProgressSpinnerMixinBase = mixinColor ( MatProgressSpinnerBase , 'primary' ) ;
31
34
35
+ const INDETERMINATE_ANIMATION_TEMPLATE = `
36
+ @keyframes mat-progress-spinner-stroke-rotate-DIAMETER {
37
+ 0% { stroke-dashoffset: START_VALUE; transform: rotate(0); }
38
+ 12.5% { stroke-dashoffset: END_VALUE; transform: rotate(0); }
39
+ 12.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(72.5deg); }
40
+ 25% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(72.5deg); }
41
+
42
+ 25.1% { stroke-dashoffset: START_VALUE; transform: rotate(270deg); }
43
+ 37.5% { stroke-dashoffset: END_VALUE; transform: rotate(270deg); }
44
+ 37.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(161.5deg); }
45
+ 50% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(161.5deg); }
46
+
47
+ 50.01% { stroke-dashoffset: START_VALUE; transform: rotate(180deg); }
48
+ 62.5% { stroke-dashoffset: END_VALUE; transform: rotate(180deg); }
49
+ 62.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(251.5deg); }
50
+ 75% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(251.5deg); }
51
+
52
+ 75.01% { stroke-dashoffset: START_VALUE; transform: rotate(90deg); }
53
+ 87.5% { stroke-dashoffset: END_VALUE; transform: rotate(90deg); }
54
+ 87.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(341.5deg); }
55
+ 100% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(341.5deg); }
56
+ }
57
+ ` ;
58
+
32
59
/**
33
60
* <mat-progress-spinner> component.
34
61
*/
@@ -53,13 +80,30 @@ export const _MatProgressSpinnerMixinBase = mixinColor(MatProgressSpinnerBase, '
53
80
encapsulation : ViewEncapsulation . None ,
54
81
preserveWhitespaces : false ,
55
82
} )
56
- export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements CanColor , OnChanges {
83
+ export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements CanColor ,
84
+ OnChanges {
85
+
57
86
private _value : number ;
58
87
private readonly _baseSize = 100 ;
59
88
private readonly _baseStrokeWidth = 10 ;
89
+ private _fallbackAnimation = false ;
60
90
91
+ /** The width and height of the host element. Will grow with stroke width. **/
61
92
_elementSize = this . _baseSize ;
62
- _circleRadius = 45 ;
93
+
94
+ /** Tracks diameters of existing instances to de-dupe generated styles (default d = 100) */
95
+ static diameters = new Set < number > ( [ 100 ] ) ;
96
+
97
+ /** The diameter of the progress spinner (will set width and height of svg). */
98
+ @Input ( )
99
+ get diameter ( ) : number {
100
+ return this . _diameter ;
101
+ }
102
+
103
+ set diameter ( size : number ) {
104
+ this . _setDiameterAndInitStyles ( size ) ;
105
+ }
106
+ _diameter = this . _baseSize ;
63
107
64
108
/** Stroke width of the progress spinner. */
65
109
@Input ( ) strokeWidth : number = 10 ;
@@ -78,31 +122,76 @@ export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements
78
122
}
79
123
}
80
124
81
- constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ) {
82
- super ( renderer , elementRef ) ;
125
+ constructor ( public _renderer : Renderer2 , public _elementRef : ElementRef ,
126
+ platform : Platform , @Optional ( ) @Inject ( DOCUMENT ) private _document : any ) {
127
+ super ( _renderer , _elementRef ) ;
83
128
84
- // On IE and Edge we can't animate the `stroke-dashoffset`
129
+ this . _fallbackAnimation = platform . EDGE || platform . TRIDENT ;
130
+
131
+ // On IE and Edge, we can't animate the `stroke-dashoffset`
85
132
// reliably so we fall back to a non-spec animation.
86
- const animationClass = ( platform . EDGE || platform . TRIDENT ) ?
133
+ const animationClass = this . _fallbackAnimation ?
87
134
'mat-progress-spinner-indeterminate-fallback-animation' :
88
135
'mat-progress-spinner-indeterminate-animation' ;
89
136
90
- renderer . addClass ( elementRef . nativeElement , animationClass ) ;
137
+ _renderer . addClass ( _elementRef . nativeElement , animationClass ) ;
91
138
}
92
139
93
140
ngOnChanges ( changes : SimpleChanges ) {
94
- if ( changes . strokeWidth ) {
95
- this . _elementSize = this . _baseSize + Math . max ( this . strokeWidth - this . _baseStrokeWidth , 0 ) ;
141
+ if ( changes . strokeWidth || changes . diameter ) {
142
+ this . _elementSize =
143
+ this . _diameter + Math . max ( this . strokeWidth - this . _baseStrokeWidth , 0 ) ;
96
144
}
97
145
}
98
146
99
- _getStrokeDashOffset ( ) {
147
+ /** The radius of the spinner, adjusted for stroke width. */
148
+ get _circleRadius ( ) {
149
+ return ( this . diameter - this . _baseStrokeWidth ) / 2 ;
150
+ }
151
+
152
+ /** The view box of the spinner's svg element. */
153
+ get _viewBox ( ) {
154
+ return `0 0 ${ this . _elementSize } ${ this . _elementSize } ` ;
155
+ }
156
+
157
+ /** The stroke circumference of the svg circle. */
158
+ get _strokeCircumference ( ) : number {
159
+ return 2 * Math . PI * this . _circleRadius ;
160
+ }
161
+
162
+ /** The dash offset of the svg circle. */
163
+ get _strokeDashOffset ( ) {
100
164
if ( this . mode === 'determinate' ) {
101
- return 2 * Math . PI * this . _circleRadius * ( 100 - this . _value ) / 100 ;
165
+ return this . _strokeCircumference * ( 100 - this . _value ) / 100 ;
102
166
}
103
167
104
168
return null ;
105
169
}
170
+
171
+ /** Sets the diameter and adds diameter-specific styles if necessary. */
172
+ private _setDiameterAndInitStyles ( size : number ) : void {
173
+ this . _diameter = size ;
174
+ if ( ! MatProgressSpinner . diameters . has ( this . diameter ) && ! this . _fallbackAnimation ) {
175
+ this . _attachStyleNode ( ) ;
176
+ }
177
+ }
178
+
179
+ /** Dynamically generates a style tag containing the correct animation for this diameter. */
180
+ private _attachStyleNode ( ) : void {
181
+ const styleTag = this . _renderer . createElement ( 'style' ) ;
182
+ styleTag . textContent = this . _getAnimationText ( ) ;
183
+ this . _renderer . appendChild ( this . _document . head , styleTag ) ;
184
+ MatProgressSpinner . diameters . add ( this . diameter ) ;
185
+ }
186
+
187
+ /** Generates animation styles adjusted for the spinner's diameter. */
188
+ private _getAnimationText ( ) : string {
189
+ return INDETERMINATE_ANIMATION_TEMPLATE
190
+ // Animation should begin at 5% and end at 80%
191
+ . replace ( / S T A R T _ V A L U E / g, `${ 0.95 * this . _strokeCircumference } ` )
192
+ . replace ( / E N D _ V A L U E / g, `${ 0.2 * this . _strokeCircumference } ` )
193
+ . replace ( / D I A M E T E R / g, `${ this . diameter } ` ) ;
194
+ }
106
195
}
107
196
108
197
@@ -130,8 +219,9 @@ export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements
130
219
preserveWhitespaces : false ,
131
220
} )
132
221
export class MatSpinner extends MatProgressSpinner {
133
- constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ) {
134
- super ( renderer , elementRef , platform ) ;
222
+ constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ,
223
+ @Optional ( ) @Inject ( DOCUMENT ) document : any ) {
224
+ super ( renderer , elementRef , platform , document ) ;
135
225
this . mode = 'indeterminate' ;
136
226
}
137
227
}
0 commit comments