Skip to content

Commit

Permalink
Slider: aria-label for the handles (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
fbasso authored Nov 15, 2023
1 parent 4e9153c commit 5a09508
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 45 deletions.
6 changes: 6 additions & 0 deletions angular/lib/src/lib/slider/slider.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {take} from 'rxjs';
[attr.aria-disabled]="state().disabled ? true : null"
[attr.aria-valuenow]="item.value"
[attr.aria-valuetext]="item.value"
[attr.aria-label]="item.ariaLabel"
[attr.aria-orientation]="state().vertical ? 'vertical' : null"
[disabled]="state().disabled"
[class]="state().vertical ? 'au-slider-handle-vertical' : 'au-slider-handle-horizontal'"
Expand Down Expand Up @@ -140,6 +141,11 @@ export class SliderComponent implements OnChanges {
@Input('auValues')
values: number[] | undefined;

/**
* Return the value for the 'aria-label' attribute for the handle
*/
@Input('auAriaLabelHandle') ariaLabelHandle: ((value: number, sortedIndex: number, index: number) => string) | undefined;

/**
* If `true` slider value cannot be changed but the slider is still focusable
*/
Expand Down
71 changes: 36 additions & 35 deletions core/lib/slider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const defaultStateValues = {
top: 0,
},
],
sortedHandles: [{id: 0, value: 0}],
sortedHandles: [{id: 0, value: 0, ariaLabel: '0'}],
className: '',
isInteractable: true,
};
Expand Down Expand Up @@ -96,7 +96,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 50,
};
expectedStateValue.sortedHandles = [{id: 0, value: 150}];
expectedStateValue.sortedHandles = [{id: 0, value: 150, ariaLabel: '150'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand Down Expand Up @@ -147,7 +147,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 50,
};
expectedStateValue.sortedHandles = [{id: 0, value: 50}];
expectedStateValue.sortedHandles = [{id: 0, value: 50, ariaLabel: '50'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand All @@ -171,7 +171,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 50,
};
expectedStateValue.sortedHandles = [{id: 0, value: 50}];
expectedStateValue.sortedHandles = [{id: 0, value: 50, ariaLabel: '50'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand All @@ -188,7 +188,7 @@ describe(`Slider basic`, () => {
expectedStateValue.handleDisplayOptions = [...expectedStateValue.handleDisplayOptions];
expectedStateValue.handleDisplayOptions = [...expectedStateValue.handleDisplayOptions];
expectedStateValue.handleDisplayOptions[0].left = 0;
expectedStateValue.sortedHandles = [{id: 0, value: 0}];
expectedStateValue.sortedHandles = [{id: 0, value: 0, ariaLabel: '0'}];
expectedStateValue.minValueLabelDisplay = false;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand All @@ -208,7 +208,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 100,
};
expectedStateValue.sortedHandles = [{id: 0, value: 100}];
expectedStateValue.sortedHandles = [{id: 0, value: 100, ariaLabel: '100'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.maxValueLabelDisplay = false;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
Expand All @@ -231,7 +231,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 70,
};
expectedStateValue.sortedHandles = [{id: 0, value: 70}];
expectedStateValue.sortedHandles = [{id: 0, value: 70, ariaLabel: '70'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand All @@ -253,7 +253,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 0,
};
expectedStateValue.sortedHandles = [{id: 0, value: 0}];
expectedStateValue.sortedHandles = [{id: 0, value: 0, ariaLabel: '0'}];
expectedStateValue.minValueLabelDisplay = false;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand Down Expand Up @@ -281,7 +281,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 50,
};
expectedStateValue.sortedHandles = [{id: 0, value: 50}];
expectedStateValue.sortedHandles = [{id: 0, value: 50, ariaLabel: '50'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand All @@ -300,7 +300,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 49,
};
expectedStateValue.sortedHandles = [{id: 0, value: 49}];
expectedStateValue.sortedHandles = [{id: 0, value: 49, ariaLabel: '49'}];
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
...expectedStateValue.progressDisplayOptions[0],
Expand All @@ -318,7 +318,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 48,
};
expectedStateValue.sortedHandles = [{id: 0, value: 48}];
expectedStateValue.sortedHandles = [{id: 0, value: 48, ariaLabel: '48'}];
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
...expectedStateValue.progressDisplayOptions[0],
Expand All @@ -343,7 +343,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 100,
};
expectedStateValue.sortedHandles = [{id: 0, value: 100}];
expectedStateValue.sortedHandles = [{id: 0, value: 100, ariaLabel: '100'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.maxValueLabelDisplay = false;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
Expand Down Expand Up @@ -372,7 +372,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 50,
};
expectedStateValue.sortedHandles = [{id: 0, value: 50}];
expectedStateValue.sortedHandles = [{id: 0, value: 50, ariaLabel: '50'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand All @@ -391,7 +391,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 51,
};
expectedStateValue.sortedHandles = [{id: 0, value: 51}];
expectedStateValue.sortedHandles = [{id: 0, value: 51, ariaLabel: '51'}];
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
...expectedStateValue.progressDisplayOptions[0],
Expand All @@ -409,7 +409,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 52,
};
expectedStateValue.sortedHandles = [{id: 0, value: 52}];
expectedStateValue.sortedHandles = [{id: 0, value: 52, ariaLabel: '52'}];
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
...expectedStateValue.progressDisplayOptions[0],
Expand All @@ -432,7 +432,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 50,
};
expectedStateValue.sortedHandles = [{id: 0, value: 50}];
expectedStateValue.sortedHandles = [{id: 0, value: 50, ariaLabel: '50'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand All @@ -451,7 +451,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 0,
};
expectedStateValue.sortedHandles = [{id: 0, value: 0}];
expectedStateValue.sortedHandles = [{id: 0, value: 0, ariaLabel: '0'}];
expectedStateValue.minValueLabelDisplay = false;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand All @@ -475,7 +475,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 50,
};
expectedStateValue.sortedHandles = [{id: 0, value: 50}];
expectedStateValue.sortedHandles = [{id: 0, value: 50, ariaLabel: '50'}];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand All @@ -494,7 +494,7 @@ describe(`Slider basic`, () => {
...expectedStateValue.handleDisplayOptions[0],
left: 100,
};
expectedStateValue.sortedHandles = [{id: 0, value: 100}];
expectedStateValue.sortedHandles = [{id: 0, value: 100, ariaLabel: '100'}];
expectedStateValue.maxValueLabelDisplay = false;
expectedStateValue.progressDisplayOptions = [...expectedStateValue.progressDisplayOptions];
expectedStateValue.progressDisplayOptions[0] = {
Expand Down Expand Up @@ -606,8 +606,8 @@ describe(`Slider range`, () => {
height: 100,
};
expectedStateValue.sortedHandles = [
{id: 0, value: 150},
{id: 1, value: 175},
{id: 0, value: 150, ariaLabel: '150'},
{id: 1, value: 175, ariaLabel: '175'},
];
expectedStateValue.minValueLabelDisplay = true;

Expand Down Expand Up @@ -640,8 +640,8 @@ describe(`Slider range`, () => {
};

expectedStateValue.sortedHandles = [
{id: 0, value: 10},
{id: 1, value: 50},
{id: 0, value: 10, ariaLabel: '10'},
{id: 1, value: 50, ariaLabel: '50'},
];
expectedStateValue.minValueLabelDisplay = true;

Expand Down Expand Up @@ -669,8 +669,8 @@ describe(`Slider range`, () => {
height: 100,
};
expectedStateValue.sortedHandles = [
{id: 0, value: 10},
{id: 1, value: 60},
{id: 0, value: 10, ariaLabel: '10'},
{id: 1, value: 60, ariaLabel: '60'},
];
expectedStateValue.minValueLabelDisplay = true;

Expand Down Expand Up @@ -702,8 +702,8 @@ describe(`Slider range`, () => {
height: 100,
};
expectedStateValue.sortedHandles = [
{id: 0, value: 10},
{id: 1, value: 50},
{id: 0, value: 10, ariaLabel: '10'},
{id: 1, value: 50, ariaLabel: '50'},
];
expectedStateValue.minValueLabelDisplay = true;

Expand Down Expand Up @@ -731,8 +731,8 @@ describe(`Slider range`, () => {
height: 100,
};
expectedStateValue.sortedHandles = [
{id: 1, value: 50},
{id: 0, value: 100},
{id: 1, value: 50, ariaLabel: '50'},
{id: 0, value: 100, ariaLabel: '100'},
];
expectedStateValue.maxValueLabelDisplay = false;

Expand Down Expand Up @@ -760,8 +760,8 @@ describe(`Slider range`, () => {
height: 100,
};
expectedStateValue.sortedHandles = [
{id: 1, value: 70},
{id: 0, value: 100},
{id: 1, value: 70, ariaLabel: '70'},
{id: 0, value: 100, ariaLabel: '100'},
];

expect(state).toStrictEqual(expectedStateValue);
Expand Down Expand Up @@ -792,8 +792,8 @@ describe(`Slider range`, () => {
height: 100,
};
expectedStateValue.sortedHandles = [
{id: 0, value: 45},
{id: 1, value: 50},
{id: 0, value: 45, ariaLabel: '45'},
{id: 1, value: 50, ariaLabel: '50'},
];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.combinedLabelDisplay = true;
Expand Down Expand Up @@ -822,8 +822,8 @@ describe(`Slider range`, () => {
height: 100,
};
expectedStateValue.sortedHandles = [
{id: 0, value: 45},
{id: 1, value: 70},
{id: 0, value: 45, ariaLabel: '45'},
{id: 1, value: 70, ariaLabel: '70'},
];
expectedStateValue.minValueLabelDisplay = true;
expectedStateValue.combinedLabelDisplay = false;
Expand Down Expand Up @@ -882,6 +882,7 @@ describe(`Slider vertical`, () => {
{
id: 0,
value: 30,
ariaLabel: '30',
},
];

Expand Down
29 changes: 23 additions & 6 deletions core/lib/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export interface SliderState extends SliderCommonPropsAndState {
/**
* Array of the sorted handles to display
*/
sortedHandles: {value: number; id: number}[];
sortedHandles: {value: number; id: number; ariaLabel: string}[];

/**
* Array of objects representing progress display options
Expand All @@ -122,6 +122,14 @@ export interface SliderState extends SliderCommonPropsAndState {
}

export interface SliderProps extends SliderCommonPropsAndState {
/**
* Return the value for the 'aria-label' attribute for the handle
* @param value - value of the handle
* @param sortedIndex - index of the handle in the sorted list
* @param index - index of the handle in the original list
*/
ariaLabelHandle: (value: number, sortedIndex: number, index: number) => string;

/**
* An event emitted when slider values are changed
*
Expand Down Expand Up @@ -180,6 +188,7 @@ const defaultSliderConfig: SliderProps = {
disabled: false,
vertical: false,
className: '',
ariaLabelHandle: (value, _index) => '' + value,
onValuesChange: noop,
values: [0],
};
Expand All @@ -199,6 +208,7 @@ const configValidator: ConfigValidator<SliderProps> = {
readonly: typeBoolean,
disabled: typeBoolean,
vertical: typeBoolean,
ariaLabelHandle: typeFunction,
onValuesChange: typeFunction,
values: typeArray,
};
Expand All @@ -217,6 +227,7 @@ export function createSlider(config?: PropsConfig<SliderProps>): SliderWidget {
stepSize$,
values$: _dirtyValues$,

ariaLabelHandle$,
onValuesChange$,

...stateProps
Expand All @@ -239,7 +250,7 @@ export function createSlider(config?: PropsConfig<SliderProps>): SliderWidget {
onValuesChange$,
[_dirtyValues$],
([dirtyValues]) => dirtyValues.map((dv) => computeCleanValue(dv)),
(a, b) => a.every((val, index) => val === b[index]),
typeArray.equal,
);

// computed
Expand Down Expand Up @@ -297,13 +308,19 @@ export function createSlider(config?: PropsConfig<SliderProps>): SliderWidget {
const sliderDomRectOffset$ = computed(() => (vertical$() ? sliderDomRect$().top : sliderDomRect$().left));
const sliderDomRectSize$ = computed(() => (vertical$() ? sliderDomRect$().height : sliderDomRect$().width));
const sortedValues$ = computed(() => [...values$()].sort((a, b) => a - b));
const sortedHandles$ = computed(() =>
values$()
const sortedHandlesValues$ = computed(() => {
return values$()
.map((val, index) => {
return {id: index, value: val};
})
.sort((a, b) => a.value - b.value),
);
.sort((a, b) => a.value - b.value);
});
const sortedHandles$ = computed(() => {
const ariaLabelHandle = ariaLabelHandle$();
return sortedHandlesValues$().map((sortedValue, index) => {
return {...sortedValue, ariaLabel: ariaLabelHandle(sortedValue.value, index, sortedValue.id)};
});
});
const valuesPercent$ = computed(() => values$().map((val) => percentCompute(val)));
const sortedValuesPercent$ = computed(() => [...valuesPercent$()].sort((a, b) => a - b));
const minLabelWidth$ = computed(() => (minLabelDomRect$().width / sliderDomRectSize$()) * 100);
Expand Down
11 changes: 7 additions & 4 deletions e2e/demo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,26 @@ const allRoutes = globSync(`${pathToFrameworkDir}/**/+page.svelte`).map((route)
normalizePath(route).replace(pathToFrameworkDir, '').replace('/+page.svelte', ''),
);

async function analyze(page: Page): Promise<AxeResults> {
return new AxeBuilder({page}).withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']).analyze();
async function analyze(page: Page, route: string): Promise<AxeResults> {
const analyser = new AxeBuilder({page}).withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']);
if (route.includes('slider')) {
analyser.disableRules('color-contrast');
}
return analyser.analyze();
}

test.describe.parallel('Demo Website', () => {
for (const route of allRoutes) {
const svelteRoute = route.replace('[framework]', 'svelte');
test(`Route ${svelteRoute || '/'} should be accessible`, async ({page}) => {
test.skip(svelteRoute.includes('slider'), 'Slider accessibility issues to be solved');
await page.goto(svelteRoute);
const frames = page.frames();
if (frames.length > 1) {
const iframes = frames.slice(1);
await Promise.all(iframes.map((frame) => expect.poll(() => frame.url()).not.toBe('about:blank')));
await Promise.all(iframes.map((frame) => frame.waitForURL(frame.url())));
}
expect((await analyze(page)).violations).toEqual([]);
expect((await analyze(page, svelteRoute)).violations).toEqual([]);
});
}
});
Loading

0 comments on commit 5a09508

Please sign in to comment.