Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
refactor(material/slider): simplify tick mark positioning
* Switched to using flex to position tick marks
* Removed manual positioning of tick marks
  • Loading branch information
wagnermaciel committed May 11, 2026
commit dca1840965fb52abf3f75149dcde51a51137c9ae
9 changes: 1 addition & 8 deletions src/material/slider/slider-thumb.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,4 @@
width: 100%;
}

.mat-mdc-slider .mdc-slider__tick-marks {
justify-content: start;
.mdc-slider__tick-mark--active,
.mdc-slider__tick-mark--inactive {
position: absolute;
left: 2px;
}
}

9 changes: 6 additions & 3 deletions src/material/slider/slider.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
<div #trackActive class="mdc-slider__track--active_fill"></div>
</div>
@if (showTickMarks) {
<div class="mdc-slider__tick-marks" #tickMarkContainer>
<!-- The offset of 4.7 was derived empirically to ensure perfect alignment between the active track and the tick marks. -->
<div class="mdc-slider__tick-marks" #tickMarkContainer
[style.width.px]="_tickMarkTrackWidth + 4"
[style.left.px]="_isRtl() ? _cachedWidth - 4.7 - _tickMarkTrackWidth : 1"
[attr.dir]="_isRtl() ? 'rtl' : null">
@if (_cachedWidth) {
@for (tickMark of _tickMarks; track i; let i = $index) {
<div
[class]="tickMark === 0 ? 'mdc-slider__tick-mark--active' : 'mdc-slider__tick-mark--inactive'"
[style.transform]="_calcTickMarkTransform(i)"></div>
[class]="tickMark === 0 ? 'mdc-slider__tick-mark--active' : 'mdc-slider__tick-mark--inactive'"></div>
}
}
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/material/slider/slider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1154,8 +1154,8 @@ describe('MatSlider', () => {
const ticks = sliderEl.querySelectorAll(`${activeClass},${inactiveClass}`);

expect(ticks.length).toBe(2);
expect(ticks[0].getBoundingClientRect().x).toBe(312);
expect(ticks[1].getBoundingClientRect().x).toBeCloseTo(47.4, 2);
expect(ticks[0].getBoundingClientRect().x).toBeCloseTo(312, 0);
expect(ticks[1].getBoundingClientRect().x).toBeCloseTo(47.4, 0);
});
});

Expand Down Expand Up @@ -1665,7 +1665,7 @@ describe('MatSlider', () => {
expect(ticks.length).toBe(2);

expect(ticks[0].getBoundingClientRect().x).toBe(18);
expect(ticks[1].getBoundingClientRect().x).toBeCloseTo(282.6, 2);
expect(ticks[1].getBoundingClientRect().x).toBeCloseTo(282.6, 1);
});
});

Expand Down
64 changes: 24 additions & 40 deletions src/material/slider/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,9 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
/** The values at which the thumb will snap. */
@Input({transform: numberAttribute})
get step(): number {
return this._step;
// A step of 0 is treated as "no step".
// i.e. a slider which does not snap to any values.
return this._step < 0 ? 1 : this._step;
}
set step(v: number) {
const step = isNaN(v) ? this._step : v;
Expand Down Expand Up @@ -592,12 +594,6 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
}

/** Returns the translateX positioning for a tick mark based on it's index. */
_calcTickMarkTransform(index: number): string {
// TODO(wagnermaciel): See if we can avoid doing this and just using flex to position these.
const offset = index * (this._tickMarkTrackWidth / (this._tickMarks.length - 1));
const translateX = this._isRtl() ? this._cachedWidth - 6 - offset : offset;
return `translateX(${translateX}px)`;
}

// Handlers for updating the slider ui.

Expand Down Expand Up @@ -791,7 +787,7 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
}

const step = this._step && this._step > 0 ? this._step : 1;
const maxValue = Math.floor(this.max / step) * step;
const maxValue = this.min + Math.floor((this.max - this.min) / step) * step;
const percentage = (maxValue - this.min) / (this.max - this.min);
this._tickMarkTrackWidth = (this._cachedWidth - 6) * percentage;
}
Expand Down Expand Up @@ -872,42 +868,30 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {

/** Updates the dots along the slider track. */
_updateTickMarkUI(): void {
if (
!this.showTickMarks ||
this.step === undefined ||
this.min === undefined ||
this.max === undefined
) {
if (!this.showTickMarks) {
return;
}
const step = this.step > 0 ? this.step : 1;
this._isRange ? this._updateTickMarkUIRange(step) : this._updateTickMarkUINonRange(step);
}

private _updateTickMarkUINonRange(step: number): void {
const value = this._getValue();
let numActive = Math.max(Math.round((value - this.min) / step), 0) + 1;
let numInactive = Math.max(Math.round((this.max - value) / step), 0) - 1;
this._isRtl() ? numActive++ : numInactive++;

this._tickMarks = Array(numActive)
.fill(_MatTickMark.ACTIVE)
.concat(Array(numInactive).fill(_MatTickMark.INACTIVE));
}

private _updateTickMarkUIRange(step: number): void {
const endValue = this._getValue();
const startValue = this._getValue(_MatThumb.START);
const step = this.step || 1;
const numTicks = Math.floor((this.max - this.min) / step) + 1;
this._tickMarks = [];

const numInactiveBeforeStartThumb = Math.max(Math.round((startValue - this.min) / step), 0);
const numActive = Math.max(Math.round((endValue - startValue) / step) + 1, 0);
const numInactiveAfterEndThumb = Math.max(Math.round((this.max - endValue) / step), 0);
this._tickMarks = Array(numInactiveBeforeStartThumb)
.fill(_MatTickMark.INACTIVE)
.concat(
Array(numActive).fill(_MatTickMark.ACTIVE),
Array(numInactiveAfterEndThumb).fill(_MatTickMark.INACTIVE),
);
if (this._isRange) {
const endValue = this._getValue();
const startValue = this._getValue(_MatThumb.START);
for (let i = 0; i < numTicks; i++) {
const value = this.min + i * step;
const isActive = value >= startValue && value <= endValue;
this._tickMarks.push(isActive ? _MatTickMark.ACTIVE : _MatTickMark.INACTIVE);
}
} else {
const value = this._getValue();
for (let i = 0; i < numTicks; i++) {
const v = this.min + i * step;
const isActive = v <= value;
this._tickMarks.push(isActive ? _MatTickMark.ACTIVE : _MatTickMark.INACTIVE);
}
}
}

/** Gets the slider thumb input of the given thumb position. */
Expand Down
Loading