@@ -19,6 +19,8 @@ import {
1919 Optional ,
2020 ViewEncapsulation ,
2121 OnInit ,
22+ ChangeDetectorRef ,
23+ OnDestroy ,
2224} from '@angular/core' ;
2325import { CanColor , mixinColor } from '@angular/material/core' ;
2426import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations' ;
@@ -124,7 +126,8 @@ const INDETERMINATE_ANIMATION_TEMPLATE = `
124126 changeDetection : ChangeDetectionStrategy . OnPush ,
125127 encapsulation : ViewEncapsulation . None ,
126128} )
127- export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , CanColor {
129+ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , OnDestroy ,
130+ CanColor {
128131 private _diameter = BASE_SIZE ;
129132 private _value = 0 ;
130133 private _strokeWidth : number ;
@@ -185,15 +188,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
185188 }
186189
187190 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 ,
193192 @Optional ( ) @Inject ( DOCUMENT ) private _document : any ,
194193 @Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
195194 @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 ) {
197201
198202 super ( elementRef ) ;
199203
@@ -218,6 +222,14 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
218222 this . strokeWidth = defaults . strokeWidth ;
219223 }
220224 }
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+ }
221233 }
222234
223235 ngOnInit ( ) {
@@ -231,6 +243,12 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
231243 element . classList . add ( 'mat-progress-spinner-indeterminate-animation' ) ;
232244 }
233245
246+ ngOnDestroy ( ) {
247+ if ( this . _platform . isBrowser ) {
248+ window . removeEventListener ( 'resize' , this . _resizeHandler ) ;
249+ }
250+ }
251+
234252 /** The radius of the spinner, adjusted for stroke width. */
235253 _getCircleRadius ( ) {
236254 return ( this . diameter - BASE_STROKE_WIDTH ) / 2 ;
@@ -261,6 +279,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
261279 return this . strokeWidth / this . diameter * 100 ;
262280 }
263281
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+
264292 /** Dynamically generates a style tag containing the correct animation for this diameter. */
265293 private _attachStyleNode ( ) : void {
266294 const styleRoot = this . _styleRoot ;
@@ -300,6 +328,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
300328 return this . diameter . toString ( ) . replace ( '.' , '_' ) ;
301329 }
302330
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+
303341 static ngAcceptInputType_diameter : NumberInput ;
304342 static ngAcceptInputType_strokeWidth : NumberInput ;
305343 static ngAcceptInputType_value : NumberInput ;
@@ -333,8 +371,9 @@ export class MatSpinner extends MatProgressSpinner {
333371 @Optional ( ) @Inject ( DOCUMENT ) document : any ,
334372 @Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
335373 @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 ) ;
338377 this . mode = 'indeterminate' ;
339378 }
340379}
0 commit comments