Skip to content

Commit 0b3346e

Browse files
crisbetoannieyw
authored andcommitted
fix(material/slider): some screen readers announcing long decimal values (#20870)
It looks like some screen readers announce the value of a slider by calculating the percentage themselves using the `aria-valuemin`, `aria-valuemax` and `aria-valuenow`. The problem is that they don't round down the decimals so for a slider between 0 and 1 with a step of 0.1, they end up reading out values like 0.20000068. These changes work around the issue by setting `aria-valuetext` to the same value that we shown in the thumb which we truncate ourselves. Fixes #20719. (cherry picked from commit 9f4415e)
1 parent 2e39e63 commit 0b3346e

File tree

3 files changed

+45
-2
lines changed

3 files changed

+45
-2
lines changed

src/material/slider/slider.spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,37 @@ describe('MatSlider', () => {
507507
expect(sliderInstance.value).toBe(0.3);
508508
});
509509

510+
it('should set the truncated value to the aria-valuetext', () => {
511+
fixture.componentInstance.step = 0.1;
512+
fixture.detectChanges();
513+
514+
dispatchSlideEventSequence(sliderNativeElement, 0, 0.333333);
515+
fixture.detectChanges();
516+
517+
expect(sliderNativeElement.getAttribute('aria-valuetext')).toBe('33');
518+
});
519+
520+
it('should be able to override the aria-valuetext', () => {
521+
fixture.componentInstance.step = 0.1;
522+
fixture.componentInstance.valueText = 'custom';
523+
fixture.detectChanges();
524+
525+
dispatchSlideEventSequence(sliderNativeElement, 0, 0.333333);
526+
fixture.detectChanges();
527+
528+
expect(sliderNativeElement.getAttribute('aria-valuetext')).toBe('custom');
529+
});
530+
531+
it('should be able to clear aria-valuetext', () => {
532+
fixture.componentInstance.valueText = '';
533+
fixture.detectChanges();
534+
535+
dispatchSlideEventSequence(sliderNativeElement, 0, 0.333333);
536+
fixture.detectChanges();
537+
538+
expect(sliderNativeElement.getAttribute('aria-valuetext')).toBeFalsy();
539+
});
540+
510541
});
511542

512543
describe('slider with auto ticks', () => {
@@ -1494,11 +1525,12 @@ class SliderWithMinAndMax {
14941525
class SliderWithValue { }
14951526

14961527
@Component({
1497-
template: `<mat-slider [step]="step"></mat-slider>`,
1528+
template: `<mat-slider [step]="step" [valueText]="valueText"></mat-slider>`,
14981529
styles: [styles],
14991530
})
15001531
class SliderWithStep {
15011532
step = 25;
1533+
valueText: string;
15021534
}
15031535

15041536
@Component({

src/material/slider/slider.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ const _MatSliderMixinBase:
134134
'[attr.aria-valuemax]': 'max',
135135
'[attr.aria-valuemin]': 'min',
136136
'[attr.aria-valuenow]': 'value',
137+
138+
// NVDA and Jaws appear to announce the `aria-valuenow` by calculating its percentage based
139+
// on its value between `aria-valuemin` and `aria-valuemax`. Due to how decimals are handled,
140+
// it can cause the slider to read out a very long value like 0.20000068 if the current value
141+
// is 0.2 with a min of 0 and max of 1. We work around the issue by setting `aria-valuetext`
142+
// to the same value that we set on the slider's thumb which will be truncated.
143+
'[attr.aria-valuetext]': 'valueText == null ? displayValue : valueText',
137144
'[attr.aria-orientation]': 'vertical ? "vertical" : "horizontal"',
138145
'[class.mat-slider-disabled]': 'disabled',
139146
'[class.mat-slider-has-ticks]': 'tickInterval',
@@ -268,6 +275,9 @@ export class MatSlider extends _MatSliderMixinBase
268275
*/
269276
@Input() displayWith: (value: number) => string | number;
270277

278+
/** Text corresponding to the slider's value. Used primarily for improved accessibility. */
279+
@Input() valueText: string;
280+
271281
/** Whether the slider is vertical. */
272282
@Input()
273283
get vertical(): boolean { return this._vertical; }

tools/public_api_guard/material/slider.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export declare class MatSlider extends _MatSliderMixinBase implements ControlVal
2626
get value(): number | null;
2727
set value(v: number | null);
2828
readonly valueChange: EventEmitter<number | null>;
29+
valueText: string;
2930
get vertical(): boolean;
3031
set vertical(value: boolean);
3132
constructor(elementRef: ElementRef, _focusMonitor: FocusMonitor, _changeDetectorRef: ChangeDetectorRef, _dir: Directionality, tabIndex: string, _ngZone: NgZone, _document: any, _animationMode?: string | undefined);
@@ -71,7 +72,7 @@ export declare class MatSlider extends _MatSliderMixinBase implements ControlVal
7172
static ngAcceptInputType_tickInterval: NumberInput;
7273
static ngAcceptInputType_value: NumberInput;
7374
static ngAcceptInputType_vertical: BooleanInput;
74-
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatSlider, "mat-slider", ["matSlider"], { "disabled": "disabled"; "color": "color"; "tabIndex": "tabIndex"; "invert": "invert"; "max": "max"; "min": "min"; "step": "step"; "thumbLabel": "thumbLabel"; "tickInterval": "tickInterval"; "value": "value"; "displayWith": "displayWith"; "vertical": "vertical"; }, { "change": "change"; "input": "input"; "valueChange": "valueChange"; }, never, never>;
75+
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatSlider, "mat-slider", ["matSlider"], { "disabled": "disabled"; "color": "color"; "tabIndex": "tabIndex"; "invert": "invert"; "max": "max"; "min": "min"; "step": "step"; "thumbLabel": "thumbLabel"; "tickInterval": "tickInterval"; "value": "value"; "displayWith": "displayWith"; "valueText": "valueText"; "vertical": "vertical"; }, { "change": "change"; "input": "input"; "valueChange": "valueChange"; }, never, never>;
7576
static ɵfac: i0.ɵɵFactoryDef<MatSlider, [null, null, null, { optional: true; }, { attribute: "tabindex"; }, null, null, { optional: true; }]>;
7677
}
7778

0 commit comments

Comments
 (0)