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
/** Possible mode for a progress spinner. */
27
32
export type ProgressSpinnerMode = 'determinate' | 'indeterminate' ;
@@ -126,10 +131,14 @@ const INDETERMINATE_ANIMATION_TEMPLATE = `
126
131
changeDetection : ChangeDetectionStrategy . OnPush ,
127
132
encapsulation : ViewEncapsulation . None ,
128
133
} )
129
- export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , CanColor {
134
+ export class MatProgressSpinner
135
+ extends _MatProgressSpinnerBase
136
+ implements OnInit , OnDestroy , CanColor
137
+ {
130
138
private _diameter = BASE_SIZE ;
131
139
private _value = 0 ;
132
140
private _strokeWidth : number ;
141
+ private _resizeSubscription = Subscription . EMPTY ;
133
142
134
143
/**
135
144
* Element to which we should add the generated style tags for the indeterminate animation.
@@ -190,15 +199,19 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
190
199
191
200
constructor (
192
201
elementRef : ElementRef < HTMLElement > ,
193
- /**
194
- * @deprecated `_platform` parameter no longer being used.
195
- * @breaking -change 14.0.0
196
- */
197
202
_platform : Platform ,
198
203
@Optional ( ) @Inject ( DOCUMENT ) private _document : any ,
199
204
@Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
200
205
@Inject ( MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS )
201
206
defaults ?: MatProgressSpinnerDefaultOptions ,
207
+ /**
208
+ * @deprecated `changeDetectorRef`, `viewportRuler` and `ngZone`
209
+ * parameters to become required.
210
+ * @breaking -change 14.0.0
211
+ */
212
+ changeDetectorRef ?: ChangeDetectorRef ,
213
+ viewportRuler ?: ViewportRuler ,
214
+ ngZone ?: NgZone ,
202
215
) {
203
216
super ( elementRef ) ;
204
217
@@ -223,6 +236,22 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
223
236
this . strokeWidth = defaults . strokeWidth ;
224
237
}
225
238
}
239
+
240
+ // Safari has an issue where the circle isn't positioned correctly when the page has a
241
+ // different zoom level from the default. This handler triggers a recalculation of the
242
+ // `transform-origin` when the page zoom level changes.
243
+ // See `_getCircleTransformOrigin` for more info.
244
+ // @breaking -change 14.0.0 Remove null checks for `_changeDetectorRef`,
245
+ // `viewportRuler` and `ngZone`.
246
+ if ( _platform . isBrowser && _platform . SAFARI && viewportRuler && changeDetectorRef && ngZone ) {
247
+ this . _resizeSubscription = viewportRuler . change ( 150 ) . subscribe ( ( ) => {
248
+ // When the window is resize while the spinner is in `indeterminate` mode, we
249
+ // have to mark for check so the transform origin of the circle can be recomputed.
250
+ if ( this . mode === 'indeterminate' ) {
251
+ ngZone . run ( ( ) => changeDetectorRef . markForCheck ( ) ) ;
252
+ }
253
+ } ) ;
254
+ }
226
255
}
227
256
228
257
ngOnInit ( ) {
@@ -236,6 +265,10 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
236
265
element . classList . add ( 'mat-progress-spinner-indeterminate-animation' ) ;
237
266
}
238
267
268
+ ngOnDestroy ( ) {
269
+ this . _resizeSubscription . unsubscribe ( ) ;
270
+ }
271
+
239
272
/** The radius of the spinner, adjusted for stroke width. */
240
273
_getCircleRadius ( ) {
241
274
return ( this . diameter - BASE_STROKE_WIDTH ) / 2 ;
@@ -266,6 +299,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
266
299
return ( this . strokeWidth / this . diameter ) * 100 ;
267
300
}
268
301
302
+ /** Gets the `transform-origin` for the inner circle element. */
303
+ _getCircleTransformOrigin ( svg : HTMLElement ) : string {
304
+ // Safari has an issue where the `transform-origin` doesn't work as expected when the page
305
+ // has a different zoom level from the default. The problem appears to be that a zoom
306
+ // is applied on the `svg` node itself. We can work around it by calculating the origin
307
+ // based on the zoom level. On all other browsers the `currentScale` appears to always be 1.
308
+ const scale = ( ( svg as unknown as SVGSVGElement ) . currentScale ?? 1 ) * 50 ;
309
+ return `${ scale } % ${ scale } %` ;
310
+ }
311
+
269
312
/** Dynamically generates a style tag containing the correct animation for this diameter. */
270
313
private _attachStyleNode ( ) : void {
271
314
const styleRoot = this . _styleRoot ;
@@ -338,8 +381,25 @@ export class MatSpinner extends MatProgressSpinner {
338
381
@Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
339
382
@Inject ( MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS )
340
383
defaults ?: MatProgressSpinnerDefaultOptions ,
384
+ /**
385
+ * @deprecated `changeDetectorRef`, `viewportRuler` and `ngZone`
386
+ * parameters to become required.
387
+ * @breaking -change 14.0.0
388
+ */
389
+ changeDetectorRef ?: ChangeDetectorRef ,
390
+ viewportRuler ?: ViewportRuler ,
391
+ ngZone ?: NgZone ,
341
392
) {
342
- super ( elementRef , platform , document , animationMode , defaults ) ;
393
+ super (
394
+ elementRef ,
395
+ platform ,
396
+ document ,
397
+ animationMode ,
398
+ defaults ,
399
+ changeDetectorRef ,
400
+ viewportRuler ,
401
+ ngZone ,
402
+ ) ;
343
403
this . mode = 'indeterminate' ;
344
404
}
345
405
}
0 commit comments