From 11e21027cfae1c68cc387853783265a4f077f493 Mon Sep 17 00:00:00 2001 From: Dmytro Mezhenskyi Date: Mon, 17 Jan 2022 14:29:56 +0100 Subject: [PATCH 1/2] fix(material/slider): Ticks updated wrongly if the max property changed to 0 When we get equal min & max value for the slider then we can get division by zero which leads to an improper style value for the background-size (Infinity%). This fix introduces a tiny function that checks if we deal with finite numbers during calculations where potentially division by zero may occur. Fixes #23913 --- src/material/slider/slider.spec.ts | 20 ++++++++++++++++++++ src/material/slider/slider.ts | 16 +++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/material/slider/slider.spec.ts b/src/material/slider/slider.spec.ts index a5c044ba8b3f..04e92cd4fbb8 100644 --- a/src/material/slider/slider.spec.ts +++ b/src/material/slider/slider.spec.ts @@ -450,6 +450,26 @@ describe('MatSlider', () => { expect(sliderInstance.percent).toBe(1); }, ); + it('should properly update ticks when max value changed to 0', () => { + testComponent.min = 0; + testComponent.max = 100; + fixture.detectChanges(); + + dispatchMouseenterEvent(sliderNativeElement); + fixture.detectChanges(); + + expect(ticksElement.style.backgroundSize).toBe('6% 2px'); + expect(ticksElement.style.transform).toContain('translateX(3%)'); + + testComponent.max = 0; + fixture.detectChanges(); + + dispatchMouseenterEvent(sliderNativeElement); + fixture.detectChanges(); + + expect(ticksElement.style.backgroundSize).toBe('0% 2px'); + expect(ticksElement.style.transform).toContain('translateX(0%)'); + }); }); describe('slider with set value', () => { diff --git a/src/material/slider/slider.ts b/src/material/slider/slider.ts index 837c27f5b163..8eee6536306e 100644 --- a/src/material/slider/slider.ts +++ b/src/material/slider/slider.ts @@ -424,7 +424,7 @@ export class MatSlider // For a horizontal slider in RTL languages we push the ticks container off the left edge // instead of the right edge to avoid causing a horizontal scrollbar to appear. let sign = !this.vertical && this._getDirection() == 'rtl' ? '' : '-'; - let offset = (this._tickIntervalPercent / 2) * 100; + let offset = (this._getTickIntervalPercent() / 2) * 100; return { 'transform': `translate${axis}(${sign}${offset}%)`, }; @@ -432,7 +432,7 @@ export class MatSlider /** CSS styles for the ticks element. */ _getTicksStyles(): {[key: string]: string} { - let tickSize = this._tickIntervalPercent * 100; + let tickSize = this._getTickIntervalPercent() * 100; let backgroundSize = this.vertical ? `2px ${tickSize}%` : `${tickSize}% 2px`; let axis = this.vertical ? 'Y' : 'X'; // Depending on the direction we pushed the ticks container, push the ticks the opposite @@ -503,6 +503,10 @@ export class MatSlider const shouldInvertAxis = this._shouldInvertAxis(); return this._getDirection() == 'rtl' && !this.vertical ? !shouldInvertAxis : shouldInvertAxis; } + /** It returns safe tick interval percent coercing NaN and Infinity to fallback */ + private _getTickIntervalPercent(fallback = 0) { + return isSafeNumber(this._tickIntervalPercent) ? this._tickIntervalPercent : fallback; + } /** The language direction for this slider element. */ private _getDirection() { @@ -883,7 +887,8 @@ export class MatSlider /** Calculates the percentage of the slider that a value is. */ private _calculatePercentage(value: number | null) { - return ((value || 0) - this.min) / (this.max - this.min); + const percentage = ((value || 0) - this.min) / (this.max - this.min); + return isSafeNumber(percentage) ? percentage : 0; } /** Calculates the value a percentage of the slider corresponds to. */ @@ -954,6 +959,11 @@ export class MatSlider } } +/** Checks if number is safe for calculation */ +function isSafeNumber(value: number) { + return !isNaN(value) && isFinite(value); +} + /** Returns whether an event is a touch event. */ function isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent { // This function is called for every pixel that the user has dragged so we need it to be From 89b7c3212c4763062ba75d1febd04b4c03e6563a Mon Sep 17 00:00:00 2001 From: Dmytro Mezhenskyi Date: Wed, 26 Jan 2022 15:58:17 +0100 Subject: [PATCH 2/2] fix(material/slider): Check if interval persentage a valid number Fixes #23913 --- src/material/slider/slider.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/material/slider/slider.ts b/src/material/slider/slider.ts index 8eee6536306e..019043801915 100644 --- a/src/material/slider/slider.ts +++ b/src/material/slider/slider.ts @@ -424,7 +424,7 @@ export class MatSlider // For a horizontal slider in RTL languages we push the ticks container off the left edge // instead of the right edge to avoid causing a horizontal scrollbar to appear. let sign = !this.vertical && this._getDirection() == 'rtl' ? '' : '-'; - let offset = (this._getTickIntervalPercent() / 2) * 100; + let offset = (this._tickIntervalPercent / 2) * 100; return { 'transform': `translate${axis}(${sign}${offset}%)`, }; @@ -432,7 +432,7 @@ export class MatSlider /** CSS styles for the ticks element. */ _getTicksStyles(): {[key: string]: string} { - let tickSize = this._getTickIntervalPercent() * 100; + let tickSize = this._tickIntervalPercent * 100; let backgroundSize = this.vertical ? `2px ${tickSize}%` : `${tickSize}% 2px`; let axis = this.vertical ? 'Y' : 'X'; // Depending on the direction we pushed the ticks container, push the ticks the opposite @@ -503,10 +503,6 @@ export class MatSlider const shouldInvertAxis = this._shouldInvertAxis(); return this._getDirection() == 'rtl' && !this.vertical ? !shouldInvertAxis : shouldInvertAxis; } - /** It returns safe tick interval percent coercing NaN and Infinity to fallback */ - private _getTickIntervalPercent(fallback = 0) { - return isSafeNumber(this._tickIntervalPercent) ? this._tickIntervalPercent : fallback; - } /** The language direction for this slider element. */ private _getDirection() { @@ -864,15 +860,17 @@ export class MatSlider return; } + let tickIntervalPercent: number; if (this.tickInterval == 'auto') { let trackSize = this.vertical ? this._sliderDimensions.height : this._sliderDimensions.width; let pixelsPerStep = (trackSize * this.step) / (this.max - this.min); let stepsPerTick = Math.ceil(MIN_AUTO_TICK_SEPARATION / pixelsPerStep); let pixelsPerTick = stepsPerTick * this.step; - this._tickIntervalPercent = pixelsPerTick / trackSize; + tickIntervalPercent = pixelsPerTick / trackSize; } else { - this._tickIntervalPercent = (this.tickInterval * this.step) / (this.max - this.min); + tickIntervalPercent = (this.tickInterval * this.step) / (this.max - this.min); } + this._tickIntervalPercent = isSafeNumber(tickIntervalPercent) ? tickIntervalPercent : 0; } /** Creates a slider change object from the specified value. */