@@ -19,6 +19,8 @@ import {
19
19
Optional ,
20
20
ViewEncapsulation ,
21
21
OnInit ,
22
+ ChangeDetectorRef ,
23
+ OnDestroy ,
22
24
} from '@angular/core' ;
23
25
import { CanColor , mixinColor } from '@angular/material/core' ;
24
26
import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations' ;
@@ -124,7 +126,8 @@ const INDETERMINATE_ANIMATION_TEMPLATE = `
124
126
changeDetection : ChangeDetectionStrategy . OnPush ,
125
127
encapsulation : ViewEncapsulation . None ,
126
128
} )
127
- export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , CanColor {
129
+ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , OnDestroy ,
130
+ CanColor {
128
131
private _diameter = BASE_SIZE ;
129
132
private _value = 0 ;
130
133
private _strokeWidth : number ;
@@ -185,15 +188,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
185
188
}
186
189
187
190
constructor ( elementRef : ElementRef < HTMLElement > ,
188
- /**
189
- * @deprecated `_platform` parameter no longer being used.
190
- * @breaking -change 14.0.0
191
- */
192
- _platform : Platform ,
191
+ private _platform : Platform ,
193
192
@Optional ( ) @Inject ( DOCUMENT ) private _document : any ,
194
193
@Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
195
194
@Inject ( MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS )
196
- defaults ?: MatProgressSpinnerDefaultOptions ) {
195
+ defaults ?: MatProgressSpinnerDefaultOptions ,
196
+ /**
197
+ * @deprecated `changeDetectorRef` parameter to become required.
198
+ * @breaking -change 14.0.0
199
+ */
200
+ private _changeDetectorRef ?: ChangeDetectorRef ) {
197
201
198
202
super ( elementRef ) ;
199
203
@@ -218,6 +222,14 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
218
222
this . strokeWidth = defaults . strokeWidth ;
219
223
}
220
224
}
225
+
226
+ // Safari has an issue where the circle isn't positioned correctly when the page has a
227
+ // different zoom level from the default. This handler triggers a recalculation of the
228
+ // `transform-origin` when the page zoom level changes.
229
+ // See `_getCircleTransformOrigin` for more info.
230
+ if ( _platform . isBrowser && _platform . SAFARI ) {
231
+ window . addEventListener ( 'resize' , this . _resizeHandler ) ;
232
+ }
221
233
}
222
234
223
235
ngOnInit ( ) {
@@ -231,6 +243,12 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
231
243
element . classList . add ( 'mat-progress-spinner-indeterminate-animation' ) ;
232
244
}
233
245
246
+ ngOnDestroy ( ) {
247
+ if ( this . _platform . isBrowser ) {
248
+ window . removeEventListener ( 'resize' , this . _resizeHandler ) ;
249
+ }
250
+ }
251
+
234
252
/** The radius of the spinner, adjusted for stroke width. */
235
253
_getCircleRadius ( ) {
236
254
return ( this . diameter - BASE_STROKE_WIDTH ) / 2 ;
@@ -261,6 +279,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
261
279
return this . strokeWidth / this . diameter * 100 ;
262
280
}
263
281
282
+ /** Gets the `transform-origin` for the inner circle element. */
283
+ _getCircleTransformOrigin ( svg : HTMLElement ) : string {
284
+ // Safari has an issue where the `transform-origin` doesn't work as expected when the page
285
+ // has a different zoom level from the default. The problem appears to be that a zoom
286
+ // is applied on the `svg` node itself. We can work around it by calculating the origin
287
+ // based on the zoom level. On all other browsers the `currentScale` appears to always be 1.
288
+ const scale = ( ( svg as unknown as SVGSVGElement ) . currentScale ?? 1 ) * 50 ;
289
+ return `${ scale } % ${ scale } %` ;
290
+ }
291
+
264
292
/** Dynamically generates a style tag containing the correct animation for this diameter. */
265
293
private _attachStyleNode ( ) : void {
266
294
const styleRoot = this . _styleRoot ;
@@ -300,6 +328,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
300
328
return this . diameter . toString ( ) . replace ( '.' , '_' ) ;
301
329
}
302
330
331
+ /** Handles window `resize` events. */
332
+ private _resizeHandler = ( ) => {
333
+ // When the window is resize while the spinner is in `indeterminate` mode, we
334
+ // have to mark for check so the transform origin of the circle can be recomputed.
335
+ if ( this . mode === 'indeterminate' ) {
336
+ // @breaking -change 14.0.0 Remove null check for `_changeDetectorRef`.
337
+ this . _changeDetectorRef ?. markForCheck ( ) ;
338
+ }
339
+ }
340
+
303
341
static ngAcceptInputType_diameter : NumberInput ;
304
342
static ngAcceptInputType_strokeWidth : NumberInput ;
305
343
static ngAcceptInputType_value : NumberInput ;
@@ -333,8 +371,9 @@ export class MatSpinner extends MatProgressSpinner {
333
371
@Optional ( ) @Inject ( DOCUMENT ) document : any ,
334
372
@Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
335
373
@Inject ( MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS )
336
- defaults ?: MatProgressSpinnerDefaultOptions ) {
337
- super ( elementRef , platform , document , animationMode , defaults ) ;
374
+ defaults ?: MatProgressSpinnerDefaultOptions ,
375
+ changeDetectorRef ?: ChangeDetectorRef ) {
376
+ super ( elementRef , platform , document , animationMode , defaults , changeDetectorRef ) ;
338
377
this . mode = 'indeterminate' ;
339
378
}
340
379
}
0 commit comments