8
8
9
9
import { coerceNumberProperty , NumberInput } from '@angular/cdk/coercion' ;
10
10
import { Platform , _getShadowRoot } from '@angular/cdk/platform' ;
11
+ import { ViewportRuler } from '@angular/cdk/scrolling' ;
11
12
import { DOCUMENT } from '@angular/common' ;
12
13
import {
13
14
ChangeDetectionStrategy ,
@@ -19,9 +20,13 @@ import {
19
20
Optional ,
20
21
ViewEncapsulation ,
21
22
OnInit ,
23
+ ChangeDetectorRef ,
24
+ OnDestroy ,
25
+ NgZone ,
22
26
} from '@angular/core' ;
23
27
import { CanColor , mixinColor } from '@angular/material/core' ;
24
28
import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations' ;
29
+ import { Subscription } from 'rxjs' ;
25
30
26
31
27
32
/** Possible mode for a progress spinner. */
@@ -124,10 +129,12 @@ const INDETERMINATE_ANIMATION_TEMPLATE = `
124
129
changeDetection : ChangeDetectionStrategy . OnPush ,
125
130
encapsulation : ViewEncapsulation . None ,
126
131
} )
127
- export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , CanColor {
132
+ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , OnDestroy ,
133
+ CanColor {
128
134
private _diameter = BASE_SIZE ;
129
135
private _value = 0 ;
130
136
private _strokeWidth : number ;
137
+ private _resizeSubscription = Subscription . EMPTY ;
131
138
132
139
/**
133
140
* Element to which we should add the generated style tags for the indeterminate animation.
@@ -185,15 +192,19 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
185
192
}
186
193
187
194
constructor ( elementRef : ElementRef < HTMLElement > ,
188
- /**
189
- * @deprecated `_platform` parameter no longer being used.
190
- * @breaking -change 14.0.0
191
- */
192
- _platform : Platform ,
195
+ private _platform : Platform ,
193
196
@Optional ( ) @Inject ( DOCUMENT ) private _document : any ,
194
197
@Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
195
198
@Inject ( MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS )
196
- defaults ?: MatProgressSpinnerDefaultOptions ) {
199
+ defaults ?: MatProgressSpinnerDefaultOptions ,
200
+ /**
201
+ * @deprecated `changeDetectorRef`, `viewportRuler` and `ngZone`
202
+ * parameters to become required.
203
+ * @breaking -change 14.0.0
204
+ */
205
+ changeDetectorRef ?: ChangeDetectorRef ,
206
+ viewportRuler ?: ViewportRuler ,
207
+ ngZone ?: NgZone ) {
197
208
198
209
super ( elementRef ) ;
199
210
@@ -218,6 +229,22 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
218
229
this . strokeWidth = defaults . strokeWidth ;
219
230
}
220
231
}
232
+
233
+ // Safari has an issue where the circle isn't positioned correctly when the page has a
234
+ // different zoom level from the default. This handler triggers a recalculation of the
235
+ // `transform-origin` when the page zoom level changes.
236
+ // See `_getCircleTransformOrigin` for more info.
237
+ // @breaking -change 14.0.0 Remove null checks for `_changeDetectorRef`,
238
+ // `viewportRuler` and `ngZone`.
239
+ if ( _platform . isBrowser && _platform . SAFARI && viewportRuler && changeDetectorRef && ngZone ) {
240
+ this . _resizeSubscription = viewportRuler . change ( 150 ) . subscribe ( ( ) => {
241
+ // When the window is resize while the spinner is in `indeterminate` mode, we
242
+ // have to mark for check so the transform origin of the circle can be recomputed.
243
+ if ( this . mode === 'indeterminate' ) {
244
+ ngZone . run ( ( ) => changeDetectorRef . markForCheck ( ) ) ;
245
+ }
246
+ } ) ;
247
+ }
221
248
}
222
249
223
250
ngOnInit ( ) {
@@ -231,6 +258,10 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
231
258
element . classList . add ( 'mat-progress-spinner-indeterminate-animation' ) ;
232
259
}
233
260
261
+ ngOnDestroy ( ) {
262
+ this . _resizeSubscription . unsubscribe ( ) ;
263
+ }
264
+
234
265
/** The radius of the spinner, adjusted for stroke width. */
235
266
_getCircleRadius ( ) {
236
267
return ( this . diameter - BASE_STROKE_WIDTH ) / 2 ;
@@ -261,6 +292,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
261
292
return this . strokeWidth / this . diameter * 100 ;
262
293
}
263
294
295
+ /** Gets the `transform-origin` for the inner circle element. */
296
+ _getCircleTransformOrigin ( svg : HTMLElement ) : string {
297
+ // Safari has an issue where the `transform-origin` doesn't work as expected when the page
298
+ // has a different zoom level from the default. The problem appears to be that a zoom
299
+ // is applied on the `svg` node itself. We can work around it by calculating the origin
300
+ // based on the zoom level. On all other browsers the `currentScale` appears to always be 1.
301
+ const scale = ( ( svg as unknown as SVGSVGElement ) . currentScale ?? 1 ) * 50 ;
302
+ return `${ scale } % ${ scale } %` ;
303
+ }
304
+
264
305
/** Dynamically generates a style tag containing the correct animation for this diameter. */
265
306
private _attachStyleNode ( ) : void {
266
307
const styleRoot = this . _styleRoot ;
@@ -333,8 +374,17 @@ export class MatSpinner extends MatProgressSpinner {
333
374
@Optional ( ) @Inject ( DOCUMENT ) document : any ,
334
375
@Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
335
376
@Inject ( MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS )
336
- defaults ?: MatProgressSpinnerDefaultOptions ) {
337
- super ( elementRef , platform , document , animationMode , defaults ) ;
377
+ defaults ?: MatProgressSpinnerDefaultOptions ,
378
+ /**
379
+ * @deprecated `changeDetectorRef`, `viewportRuler` and `ngZone`
380
+ * parameters to become required.
381
+ * @breaking -change 14.0.0
382
+ */
383
+ changeDetectorRef ?: ChangeDetectorRef ,
384
+ viewportRuler ?: ViewportRuler ,
385
+ ngZone ?: NgZone ) {
386
+ super ( elementRef , platform , document , animationMode , defaults , changeDetectorRef ,
387
+ viewportRuler , ngZone ) ;
338
388
this . mode = 'indeterminate' ;
339
389
}
340
390
}
0 commit comments