88
99import { coerceNumberProperty , NumberInput } from '@angular/cdk/coercion' ;
1010import { Platform , _getShadowRoot } from '@angular/cdk/platform' ;
11+ import { ViewportRuler } from '@angular/cdk/scrolling' ;
1112import { DOCUMENT } from '@angular/common' ;
1213import {
1314 ChangeDetectionStrategy ,
@@ -19,9 +20,13 @@ import {
1920 Optional ,
2021 ViewEncapsulation ,
2122 OnInit ,
23+ ChangeDetectorRef ,
24+ OnDestroy ,
25+ NgZone ,
2226} from '@angular/core' ;
2327import { CanColor , mixinColor } from '@angular/material/core' ;
2428import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations' ;
29+ import { Subscription } from 'rxjs' ;
2530
2631
2732/** Possible mode for a progress spinner. */
@@ -124,10 +129,12 @@ const INDETERMINATE_ANIMATION_TEMPLATE = `
124129 changeDetection : ChangeDetectionStrategy . OnPush ,
125130 encapsulation : ViewEncapsulation . None ,
126131} )
127- export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , CanColor {
132+ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , OnDestroy ,
133+ CanColor {
128134 private _diameter = BASE_SIZE ;
129135 private _value = 0 ;
130136 private _strokeWidth : number ;
137+ private _resizeSubscription = Subscription . EMPTY ;
131138
132139 /**
133140 * 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
185192 }
186193
187194 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 ,
193196 @Optional ( ) @Inject ( DOCUMENT ) private _document : any ,
194197 @Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
195198 @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 ) {
197208
198209 super ( elementRef ) ;
199210
@@ -218,6 +229,22 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
218229 this . strokeWidth = defaults . strokeWidth ;
219230 }
220231 }
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+ }
221248 }
222249
223250 ngOnInit ( ) {
@@ -231,6 +258,10 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
231258 element . classList . add ( 'mat-progress-spinner-indeterminate-animation' ) ;
232259 }
233260
261+ ngOnDestroy ( ) {
262+ this . _resizeSubscription . unsubscribe ( ) ;
263+ }
264+
234265 /** The radius of the spinner, adjusted for stroke width. */
235266 _getCircleRadius ( ) {
236267 return ( this . diameter - BASE_STROKE_WIDTH ) / 2 ;
@@ -261,6 +292,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
261292 return this . strokeWidth / this . diameter * 100 ;
262293 }
263294
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+
264305 /** Dynamically generates a style tag containing the correct animation for this diameter. */
265306 private _attachStyleNode ( ) : void {
266307 const styleRoot = this . _styleRoot ;
@@ -333,8 +374,17 @@ export class MatSpinner extends MatProgressSpinner {
333374 @Optional ( ) @Inject ( DOCUMENT ) document : any ,
334375 @Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
335376 @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 ) ;
338388 this . mode = 'indeterminate' ;
339389 }
340390}
0 commit comments