Skip to content

Commit a7d5561

Browse files
authored
fix(material/slider): not picking up static direction (#33006)
The slider was reading its direction once on init and one when it changes, however the initial read was too early which meant that the `dir` directive might not have received its value yet. Fixes #32970.
1 parent a7fe128 commit a7d5561

File tree

4 files changed

+28
-28
lines changed

4 files changed

+28
-28
lines changed

goldens/material/slider/index.api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { NgZone } from '@angular/core';
1616
import { OnDestroy } from '@angular/core';
1717
import { OnInit } from '@angular/core';
1818
import { QueryList } from '@angular/core';
19+
import { Signal } from '@angular/core';
1920
import { Subject } from 'rxjs';
2021
import { WritableSignal } from '@angular/core';
2122

@@ -56,7 +57,7 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
5657
_isCursorOnSliderThumb(event: PointerEvent, rect: DOMRect): boolean;
5758
// (undocumented)
5859
_isRange: boolean;
59-
_isRtl: boolean;
60+
_isRtl: i0.Signal<boolean>;
6061
_knobRadius: number;
6162
get max(): number;
6263
set max(v: number);

src/material/slider/slider-input.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,15 @@ export class MatSliderThumb implements _MatSliderThumb, OnDestroy, ControlValueA
201201
/** The percentage of the slider that coincides with the value. */
202202
get percentage(): number {
203203
if (this._slider.min >= this._slider.max) {
204-
return this._slider._isRtl ? 1 : 0;
204+
return this._slider._isRtl() ? 1 : 0;
205205
}
206206
return (this.value - this._slider.min) / (this._slider.max - this._slider.min);
207207
}
208208

209209
/** @docs-private */
210210
get fillPercentage(): number {
211211
if (!this._slider._cachedWidth) {
212-
return this._slider._isRtl ? 1 : 0;
212+
return this._slider._isRtl() ? 1 : 0;
213213
}
214214
if (this._translateX === 0) {
215215
return 0;
@@ -446,7 +446,7 @@ export class MatSliderThumb implements _MatSliderThumb, OnDestroy, ControlValueA
446446
const width = this._slider._cachedWidth;
447447
const step = this._slider.step === 0 ? 1 : this._slider.step;
448448
const numSteps = Math.floor((this._slider.max - this._slider.min) / step);
449-
const percentage = this._slider._isRtl ? 1 - xPos / width : xPos / width;
449+
const percentage = this._slider._isRtl() ? 1 - xPos / width : xPos / width;
450450

451451
// To ensure the percentage is rounded to the necessary number of decimals.
452452
const fixedPercentage = Math.round(percentage * numSteps) / numSteps;
@@ -507,7 +507,7 @@ export class MatSliderThumb implements _MatSliderThumb, OnDestroy, ControlValueA
507507
}
508508

509509
_calcTranslateXByValue(): number {
510-
if (this._slider._isRtl) {
510+
if (this._slider._isRtl()) {
511511
return (
512512
(1 - this.percentage) * (this._slider._cachedWidth - this._tickMarkOffset * 2) +
513513
this._tickMarkOffset
@@ -652,7 +652,7 @@ export class MatSliderRangeThumb extends MatSliderThumb implements _MatSliderRan
652652

653653
_setIsLeftThumb(): void {
654654
this._isLeftThumb =
655-
(this._isEndThumb && this._slider._isRtl) || (!this._isEndThumb && !this._slider._isRtl);
655+
(this._isEndThumb && this._slider._isRtl()) || (!this._isEndThumb && !this._slider._isRtl());
656656
}
657657

658658
/** Whether this slider corresponds to the input on the left hand side. */

src/material/slider/slider-interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {InjectionToken, ChangeDetectorRef, WritableSignal} from '@angular/core';
9+
import {InjectionToken, ChangeDetectorRef, WritableSignal, Signal} from '@angular/core';
1010
import {MatRipple, RippleGlobalOptions} from '../core';
1111

1212
/**
@@ -107,7 +107,7 @@ export interface _MatSlider {
107107
_isRange: boolean;
108108

109109
/** Whether the slider is rtl. */
110-
_isRtl: boolean;
110+
_isRtl: Signal<boolean>;
111111

112112
/** The stored width of the host element's bounding client rect. */
113113
_cachedWidth: number;

src/material/slider/slider.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
import {Directionality} from '@angular/cdk/bidi';
1010
import {Platform} from '@angular/cdk/platform';
1111
import {
12+
afterRenderEffect,
1213
AfterViewInit,
1314
booleanAttribute,
1415
ChangeDetectionStrategy,
1516
ChangeDetectorRef,
1617
Component,
18+
computed,
1719
ContentChild,
1820
ContentChildren,
1921
ElementRef,
@@ -34,7 +36,6 @@ import {
3436
RippleGlobalOptions,
3537
ThemePalette,
3638
} from '../core';
37-
import {Subscription} from 'rxjs';
3839
import {
3940
_MatThumb,
4041
_MatTickMark,
@@ -372,9 +373,6 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
372373
/** Whether animations have been disabled. */
373374
_noopAnimations = _animationsDisabled();
374375

375-
/** Subscription to changes to the directionality (LTR / RTL) context for the application. */
376-
private _dirChangeSubscription: Subscription | undefined;
377-
378376
/** Observer used to monitor size changes in the slider. */
379377
private _resizeObserver: ResizeObserver | null = null;
380378

@@ -401,7 +399,7 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
401399
_isRange: boolean = false;
402400

403401
/** Whether the slider is rtl. */
404-
_isRtl: boolean = false;
402+
_isRtl = computed(() => this._dir?.valueSignal() === 'rtl');
405403

406404
private _hasViewInitialized: boolean = false;
407405

@@ -422,10 +420,19 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
422420
constructor() {
423421
inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader);
424422

425-
if (this._dir) {
426-
this._dirChangeSubscription = this._dir.change.subscribe(() => this._onDirChange());
427-
this._isRtl = this._dir.value === 'rtl';
428-
}
423+
let prevIsRtl = this._isRtl();
424+
425+
afterRenderEffect(() => {
426+
const isRtl = this._isRtl();
427+
428+
// The diffing is normally handled by the signal, but we don't want to
429+
// fire on the first run, because it'll trigger unnecessary measurements.
430+
if (isRtl !== prevIsRtl) {
431+
prevIsRtl = isRtl;
432+
this._isRange ? this._onDirChangeRange() : this._onDirChangeNonRange();
433+
this._updateTickMarkUI();
434+
}
435+
});
429436
}
430437

431438
/** The radius of the native slider's knob. AFAIK there is no way to avoid hardcoding this. */
@@ -499,18 +506,10 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
499506
}
500507

501508
ngOnDestroy(): void {
502-
this._dirChangeSubscription?.unsubscribe();
503509
this._resizeObserver?.disconnect();
504510
this._resizeObserver = null;
505511
}
506512

507-
/** Handles updating the slider ui after a dir change. */
508-
private _onDirChange(): void {
509-
this._isRtl = this._dir?.value === 'rtl';
510-
this._isRange ? this._onDirChangeRange() : this._onDirChangeNonRange();
511-
this._updateTickMarkUI();
512-
}
513-
514513
private _onDirChangeRange(): void {
515514
const endInput = this._getInput(_MatThumb.END) as _MatSliderRangeThumb;
516515
const startInput = this._getInput(_MatThumb.START) as _MatSliderRangeThumb;
@@ -600,7 +599,7 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
600599
_calcTickMarkTransform(index: number): string {
601600
// TODO(wagnermaciel): See if we can avoid doing this and just using flex to position these.
602601
const offset = index * (this._tickMarkTrackWidth / (this._tickMarks.length - 1));
603-
const translateX = this._isRtl ? this._cachedWidth - 6 - offset : offset;
602+
const translateX = this._isRtl() ? this._cachedWidth - 6 - offset : offset;
604603
return `translateX(${translateX}px)`;
605604
}
606605

@@ -852,7 +851,7 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
852851
}
853852

854853
private _updateTrackUINonRange(source: _MatSliderThumb): void {
855-
this._isRtl
854+
this._isRtl()
856855
? this._setTrackActiveStyles({
857856
left: 'auto',
858857
right: '0px',
@@ -893,7 +892,7 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider {
893892
const value = this._getValue();
894893
let numActive = Math.max(Math.round((value - this.min) / step), 0) + 1;
895894
let numInactive = Math.max(Math.round((this.max - value) / step), 0) - 1;
896-
this._isRtl ? numActive++ : numInactive++;
895+
this._isRtl() ? numActive++ : numInactive++;
897896

898897
this._tickMarks = Array(numActive)
899898
.fill(_MatTickMark.ACTIVE)

0 commit comments

Comments
 (0)