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/** Possible mode for a progress spinner. */
2732export type ProgressSpinnerMode = 'determinate' | 'indeterminate' ;
@@ -126,10 +131,14 @@ const INDETERMINATE_ANIMATION_TEMPLATE = `
126131 changeDetection : ChangeDetectionStrategy . OnPush ,
127132 encapsulation : ViewEncapsulation . None ,
128133} )
129- export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnInit , CanColor {
134+ export class MatProgressSpinner
135+ extends _MatProgressSpinnerBase
136+ implements OnInit , OnDestroy , CanColor
137+ {
130138 private _diameter = BASE_SIZE ;
131139 private _value = 0 ;
132140 private _strokeWidth : number ;
141+ private _resizeSubscription = Subscription . EMPTY ;
133142
134143 /**
135144 * 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
190199
191200 constructor (
192201 elementRef : ElementRef < HTMLElement > ,
193- /**
194- * @deprecated `_platform` parameter no longer being used.
195- * @breaking -change 14.0.0
196- */
197202 _platform : Platform ,
198203 @Optional ( ) @Inject ( DOCUMENT ) private _document : any ,
199204 @Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
200205 @Inject ( MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS )
201206 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 ,
202215 ) {
203216 super ( elementRef ) ;
204217
@@ -223,6 +236,22 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
223236 this . strokeWidth = defaults . strokeWidth ;
224237 }
225238 }
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+ }
226255 }
227256
228257 ngOnInit ( ) {
@@ -236,6 +265,10 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
236265 element . classList . add ( 'mat-progress-spinner-indeterminate-animation' ) ;
237266 }
238267
268+ ngOnDestroy ( ) {
269+ this . _resizeSubscription . unsubscribe ( ) ;
270+ }
271+
239272 /** The radius of the spinner, adjusted for stroke width. */
240273 _getCircleRadius ( ) {
241274 return ( this . diameter - BASE_STROKE_WIDTH ) / 2 ;
@@ -266,6 +299,16 @@ export class MatProgressSpinner extends _MatProgressSpinnerBase implements OnIni
266299 return ( this . strokeWidth / this . diameter ) * 100 ;
267300 }
268301
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+
269312 /** Dynamically generates a style tag containing the correct animation for this diameter. */
270313 private _attachStyleNode ( ) : void {
271314 const styleRoot = this . _styleRoot ;
@@ -338,8 +381,25 @@ export class MatSpinner extends MatProgressSpinner {
338381 @Optional ( ) @Inject ( ANIMATION_MODULE_TYPE ) animationMode : string ,
339382 @Inject ( MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS )
340383 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 ,
341392 ) {
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+ ) ;
343403 this . mode = 'indeterminate' ;
344404 }
345405}
0 commit comments