Skip to content

Commit fa249c6

Browse files
Merge pull request #14795 from IgniteUI/ganastasov/fix-14262-17.2.x
fix(slider): improve accessibility by correcting ARIA attributes and tab index - 17.2.x
2 parents 3307119 + b79e1b9 commit fa249c6

File tree

4 files changed

+272
-63
lines changed

4 files changed

+272
-63
lines changed

projects/igniteui-angular/src/lib/slider/slider.component.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@
3737
#thumbFrom
3838
type="from"
3939
[value]="lowerLabel"
40+
[min]="minValue"
41+
[max]="maxValue"
4042
[disabled]="disabled"
4143
[continuous]="continuous"
4244
[onPan]="onPan"
4345
[stepDistance]="stepDistance"
4446
[step]="step"
4547
[templateRef]="thumbFromTemplateRef"
4648
[context]="context"
49+
[labels]="labels"
4750
(thumbChange)="onThumbChange()"
4851
(hoverChange)="onHoverChange($event)"
4952
[deactiveState]="deactivateThumbLabel"
@@ -62,13 +65,16 @@
6265
#thumbTo
6366
type="to"
6467
[value]="upperLabel"
68+
[min]="minValue"
69+
[max]="maxValue"
6570
[disabled]="disabled"
6671
[continuous]="continuous"
6772
[onPan]="onPan"
6873
[stepDistance]="stepDistance"
6974
[step]="step"
7075
[templateRef]="thumbToTemplateRef"
7176
[context]="context"
77+
[labels]="labels"
7278
(thumbChange)="onThumbChange()"
7379
(hoverChange)="onHoverChange($event)"
7480
[deactiveState]="deactivateThumbLabel"

projects/igniteui-angular/src/lib/slider/slider.component.spec.ts

Lines changed: 217 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('IgxSlider', () => {
4343
SliderMinMaxComponent,
4444
SliderTestComponent,
4545
SliderWithLabelsComponent,
46+
RangeSliderTestComponent,
4647
RangeSliderWithLabelsComponent,
4748
RangeSliderWithCustomTemplateComponent,
4849
SliderTicksComponent,
@@ -897,22 +898,6 @@ describe('IgxSlider', () => {
897898
expect(slider.upperBound).toBe(100);
898899
expect(slider.lowerBound).toBe(0);
899900
});
900-
901-
it('aria properties should be successfully applied', () => {
902-
const sliderElement = fixture.nativeElement.querySelector('igx-slider');
903-
const sliderRole = fixture.nativeElement.querySelector('igx-slider[role="slider"]');
904-
905-
expect(sliderElement).toBeDefined();
906-
expect(sliderRole).toBeDefined();
907-
908-
const minValue = parseInt(sliderElement.getAttribute('aria-valuemin'), 10);
909-
const maxValue = parseInt(sliderElement.getAttribute('aria-valuemax'), 10);
910-
const readOnly = sliderElement.getAttribute('aria-readonly');
911-
912-
expect(minValue).toBe(slider.minValue);
913-
expect(maxValue).toBe(slider.maxValue);
914-
expect(readOnly).toBe('false');
915-
});
916901
});
917902

918903
describe('Slider type: Range - List View', () => {
@@ -1196,22 +1181,6 @@ describe('IgxSlider', () => {
11961181
expect(slider.upperBound).toBe(slider.maxValue);
11971182
expect(slider.lowerBound).toBe(slider.minValue);
11981183
});
1199-
1200-
it('aria properties should be successfully applied', () => {
1201-
const sliderElement = fixture.nativeElement.querySelector('igx-slider');
1202-
const sliderRole = fixture.nativeElement.querySelector('igx-slider[role="slider"]');
1203-
1204-
expect(sliderElement).toBeDefined();
1205-
expect(sliderRole).toBeDefined();
1206-
1207-
const minValue = parseInt(sliderElement.getAttribute('aria-valuemin'), 10);
1208-
const maxValue = parseInt(sliderElement.getAttribute('aria-valuemax'), 10);
1209-
const readOnly = sliderElement.getAttribute('aria-readonly');
1210-
1211-
expect(minValue).toBe(slider.minValue);
1212-
expect(maxValue).toBe(slider.maxValue);
1213-
expect(readOnly).toBe('false');
1214-
});
12151184
});
12161185

12171186
describe('General Tests', () => {
@@ -1849,6 +1818,211 @@ describe('IgxSlider', () => {
18491818
}));
18501819
});
18511820

1821+
describe('Accessibility: ARIA Attributes', () => {
1822+
let fixture: ComponentFixture<RangeSliderTestComponent>;
1823+
let slider: IgxSliderComponent;
1824+
1825+
beforeEach(() => {
1826+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1827+
slider = fixture.componentInstance.slider;
1828+
fixture.detectChanges();
1829+
});
1830+
1831+
it('should apply all ARIA properties correctly to both thumbs', fakeAsync(() => {
1832+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1833+
slider = fixture.componentInstance.slider;
1834+
fixture.detectChanges();
1835+
tick();
1836+
1837+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
1838+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
1839+
1840+
expect(thumbFrom.getAttribute('role')).toBe('slider');
1841+
expect(thumbFrom.getAttribute('tabindex')).toBe('0');
1842+
expect(parseInt(thumbFrom.getAttribute('aria-valuenow'), 10)).toBe(slider.lowerValue);
1843+
expect(parseInt(thumbFrom.getAttribute('aria-valuemin'), 10)).toBe(slider.minValue);
1844+
expect(parseInt(thumbFrom.getAttribute('aria-valuemax'), 10)).toBe(slider.maxValue);
1845+
expect(thumbFrom.getAttribute('aria-label')).toBe('Slider thumb from');
1846+
expect(thumbFrom.getAttribute('aria-orientation')).toBe('horizontal');
1847+
expect(thumbFrom.getAttribute('aria-disabled')).toBe('false');
1848+
1849+
expect(thumbTo.getAttribute('role')).toBe('slider');
1850+
expect(thumbTo.getAttribute('tabindex')).toBe('0');
1851+
expect(parseInt(thumbTo.getAttribute('aria-valuenow'), 10)).toBe(slider.upperValue);
1852+
expect(parseInt(thumbTo.getAttribute('aria-valuemin'), 10)).toBe(slider.minValue);
1853+
expect(parseInt(thumbTo.getAttribute('aria-valuemax'), 10)).toBe(slider.maxValue);
1854+
expect(thumbTo.getAttribute('aria-label')).toBe('Slider thumb to');
1855+
expect(thumbTo.getAttribute('aria-orientation')).toBe('horizontal');
1856+
expect(thumbTo.getAttribute('aria-disabled')).toBe('false');
1857+
1858+
slider.labels = ['Low', 'Medium', 'High'];
1859+
fixture.detectChanges();
1860+
tick();
1861+
1862+
expect(thumbFrom.getAttribute('aria-valuetext')).toBe('Low');
1863+
expect(thumbTo.getAttribute('aria-valuetext')).toBe('High');
1864+
1865+
slider.disabled = true;
1866+
fixture.detectChanges();
1867+
tick();
1868+
1869+
expect(thumbFrom.getAttribute('aria-disabled')).toBe('true');
1870+
expect(thumbTo.getAttribute('aria-disabled')).toBe('true');
1871+
}));
1872+
1873+
it('should apply correct tabindex to thumbs', fakeAsync(() => {
1874+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1875+
slider = fixture.componentInstance.slider;
1876+
fixture.detectChanges();
1877+
tick();
1878+
1879+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
1880+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
1881+
1882+
expect(thumbFrom.getAttribute('tabindex')).toBe('0');
1883+
expect(thumbTo.getAttribute('tabindex')).toBe('0');
1884+
}));
1885+
1886+
it('should apply correct role to thumbs', fakeAsync(() => {
1887+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1888+
slider = fixture.componentInstance.slider;
1889+
fixture.detectChanges();
1890+
tick();
1891+
1892+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
1893+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
1894+
1895+
expect(thumbFrom.getAttribute('role')).toBe('slider');
1896+
expect(thumbTo.getAttribute('role')).toBe('slider');
1897+
}));
1898+
1899+
it('should apply aria-valuenow, aria-valuemin, and aria-valuemax to thumbs', fakeAsync(() => {
1900+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1901+
slider = fixture.componentInstance.slider;
1902+
fixture.detectChanges();
1903+
tick();
1904+
1905+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
1906+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
1907+
1908+
expect(thumbFrom.getAttribute('aria-valuenow')).toBe(String(slider.lowerValue));
1909+
expect(thumbFrom.getAttribute('aria-valuemin')).toBe(String(slider.minValue));
1910+
expect(thumbFrom.getAttribute('aria-valuemax')).toBe(String(slider.maxValue));
1911+
1912+
expect(thumbTo.getAttribute('aria-valuenow')).toBe(String(slider.upperValue));
1913+
expect(thumbTo.getAttribute('aria-valuemin')).toBe(String(slider.minValue));
1914+
expect(thumbTo.getAttribute('aria-valuemax')).toBe(String(slider.maxValue));
1915+
}));
1916+
1917+
it('should apply aria-valuenow to the thumbs', fakeAsync(() => {
1918+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1919+
slider = fixture.componentInstance.slider;
1920+
fixture.detectChanges();
1921+
tick();
1922+
1923+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
1924+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
1925+
1926+
expect(thumbFrom.getAttribute('aria-valuenow')).toBe(String(slider.lowerLabel));
1927+
expect(thumbTo.getAttribute('aria-valuenow')).toBe(String(slider.upperLabel));
1928+
}));
1929+
1930+
it('should update aria-valuenow when the slider value changes', fakeAsync(() => {
1931+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1932+
slider = fixture.componentInstance.slider;
1933+
fixture.detectChanges();
1934+
tick();
1935+
1936+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
1937+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
1938+
1939+
expect(thumbFrom.getAttribute('aria-valuenow')).toBe(String(slider.lowerLabel));
1940+
expect(thumbTo.getAttribute('aria-valuenow')).toBe(String(slider.upperLabel));
1941+
1942+
slider.value = {
1943+
lower: 30,
1944+
upper: 70
1945+
};
1946+
fixture.detectChanges();
1947+
tick();
1948+
1949+
expect(thumbFrom.getAttribute('aria-valuenow')).toBe('30');
1950+
expect(thumbTo.getAttribute('aria-valuenow')).toBe('70');
1951+
}));
1952+
1953+
it('should apply aria-valuetext when labels are provided', fakeAsync(() => {
1954+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1955+
slider = fixture.componentInstance.slider;
1956+
fixture.detectChanges();
1957+
tick();
1958+
1959+
slider.labels = ['Low', 'Medium', 'High'];
1960+
tick();
1961+
fixture.detectChanges();
1962+
1963+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
1964+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
1965+
1966+
expect(thumbFrom.getAttribute('aria-valuetext')).toBe('Low');
1967+
expect(thumbTo.getAttribute('aria-valuetext')).toBe('High');
1968+
1969+
slider.value = {
1970+
lower: 1,
1971+
upper: 1
1972+
};
1973+
fixture.detectChanges();
1974+
tick();
1975+
1976+
expect(thumbFrom.getAttribute('aria-valuetext')).toBe('Medium');
1977+
expect(thumbTo.getAttribute('aria-valuetext')).toBe('Medium');
1978+
}));
1979+
1980+
it('should apply correct aria-label to thumbs', fakeAsync(() => {
1981+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1982+
slider = fixture.componentInstance.slider;
1983+
fixture.detectChanges();
1984+
tick();
1985+
1986+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
1987+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
1988+
1989+
expect(thumbFrom.getAttribute('aria-label')).toBe('Slider thumb from');
1990+
expect(thumbTo.getAttribute('aria-label')).toBe('Slider thumb to');
1991+
}));
1992+
1993+
it('should apply correct aria-orientation to thumbs', fakeAsync(() => {
1994+
fixture = TestBed.createComponent(RangeSliderTestComponent);
1995+
slider = fixture.componentInstance.slider;
1996+
fixture.detectChanges();
1997+
tick();
1998+
1999+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
2000+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
2001+
2002+
expect(thumbFrom.getAttribute('aria-orientation')).toBe('horizontal');
2003+
expect(thumbTo.getAttribute('aria-orientation')).toBe('horizontal');
2004+
}));
2005+
2006+
it('should update aria-disabled when the slider is disabled', fakeAsync(() => {
2007+
fixture = TestBed.createComponent(RangeSliderTestComponent);
2008+
slider = fixture.componentInstance.slider;
2009+
fixture.detectChanges();
2010+
tick();
2011+
2012+
const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
2013+
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;
2014+
2015+
expect(thumbFrom.getAttribute('aria-disabled')).toBe('false');
2016+
expect(thumbTo.getAttribute('aria-disabled')).toBe('false');
2017+
2018+
slider.disabled = true;
2019+
fixture.detectChanges();
2020+
tick();
2021+
2022+
expect(thumbFrom.getAttribute('aria-disabled')).toBe('true');
2023+
expect(thumbTo.getAttribute('aria-disabled')).toBe('true');
2024+
}));
2025+
});
18522026

18532027
const verifySecondaryTicsLabelsAreHidden = (ticks, hidden) => {
18542028
const allTicks = Array.from(ticks.nativeElement.querySelectorAll(`${SLIDER_GROUP_TICKS_CLASS}`));
@@ -1983,6 +2157,17 @@ class SliderWithLabelsComponent {
19832157
@ViewChild(IgxSliderComponent, { read: IgxSliderComponent, static: true }) public slider: IgxSliderComponent;
19842158
}
19852159

2160+
@Component({
2161+
template: `<igx-slider #slider [type]="type">
2162+
</igx-slider>`,
2163+
standalone: true,
2164+
imports: [IgxSliderComponent]
2165+
})
2166+
class RangeSliderTestComponent {
2167+
@ViewChild(IgxSliderComponent, { static: true }) public slider: IgxSliderComponent;
2168+
public type = IgxSliderType.RANGE;
2169+
}
2170+
19862171
@Component({
19872172
template: `
19882173
<igx-slider

projects/igniteui-angular/src/lib/slider/slider.component.ts

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,6 @@ export class IgxSliderComponent implements
9696
@ContentChild(IgxTickLabelTemplateDirective, { read: TemplateRef, static: false })
9797
public tickLabelTemplateRef: TemplateRef<any>;
9898

99-
/**
100-
* @hidden
101-
*/
102-
@HostBinding(`attr.role`)
103-
public role = 'slider';
104-
10599
/**
106100
* @hidden
107101
*/
@@ -128,30 +122,6 @@ export class IgxSliderComponent implements
128122
@Input()
129123
public thumbLabelVisibilityDuration = 750;
130124

131-
/**
132-
* @hidden
133-
*/
134-
@HostBinding(`attr.aria-valuemin`)
135-
public get valuemin() {
136-
return this.minValue;
137-
}
138-
139-
/**
140-
* @hidden
141-
*/
142-
@HostBinding(`attr.aria-valuemax`)
143-
public get valuemax() {
144-
return this.maxValue;
145-
}
146-
147-
/**
148-
* @hidden
149-
*/
150-
@HostBinding(`attr.aria-readonly`)
151-
public get readonly() {
152-
return this.disabled;
153-
}
154-
155125
/**
156126
* @hidden
157127
*/
@@ -1353,7 +1323,7 @@ export class IgxSliderComponent implements
13531323
}
13541324

13551325
private changeThumbFocusableState(state: boolean) {
1356-
const value = state ? -1 : 1;
1326+
const value = state ? -1 : 0;
13571327

13581328
if (this.isRange) {
13591329
this.thumbFrom.tabindex = value;

0 commit comments

Comments
 (0)