diff --git a/angular/lib/src/lib/slider/slider.component.ts b/angular/lib/src/lib/slider/slider.component.ts index 95152c1263..2171b2682f 100644 --- a/angular/lib/src/lib/slider/slider.component.ts +++ b/angular/lib/src/lib/slider/slider.component.ts @@ -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'" @@ -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 */ diff --git a/core/lib/slider.spec.ts b/core/lib/slider.spec.ts index b2483281f9..da08c307d0 100644 --- a/core/lib/slider.spec.ts +++ b/core/lib/slider.spec.ts @@ -38,7 +38,7 @@ const defaultStateValues = { top: 0, }, ], - sortedHandles: [{id: 0, value: 0}], + sortedHandles: [{id: 0, value: 0, ariaLabel: '0'}], className: '', isInteractable: true, }; @@ -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] = { @@ -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] = { @@ -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] = { @@ -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] = { @@ -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]; @@ -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] = { @@ -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] = { @@ -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] = { @@ -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], @@ -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], @@ -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]; @@ -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] = { @@ -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], @@ -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], @@ -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] = { @@ -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] = { @@ -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] = { @@ -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] = { @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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); @@ -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; @@ -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; @@ -882,6 +882,7 @@ describe(`Slider vertical`, () => { { id: 0, value: 30, + ariaLabel: '30', }, ]; diff --git a/core/lib/slider.ts b/core/lib/slider.ts index 36e29538a8..ad415c1327 100644 --- a/core/lib/slider.ts +++ b/core/lib/slider.ts @@ -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 @@ -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 * @@ -180,6 +188,7 @@ const defaultSliderConfig: SliderProps = { disabled: false, vertical: false, className: '', + ariaLabelHandle: (value, _index) => '' + value, onValuesChange: noop, values: [0], }; @@ -199,6 +208,7 @@ const configValidator: ConfigValidator = { readonly: typeBoolean, disabled: typeBoolean, vertical: typeBoolean, + ariaLabelHandle: typeFunction, onValuesChange: typeFunction, values: typeArray, }; @@ -217,6 +227,7 @@ export function createSlider(config?: PropsConfig): SliderWidget { stepSize$, values$: _dirtyValues$, + ariaLabelHandle$, onValuesChange$, ...stateProps @@ -239,7 +250,7 @@ export function createSlider(config?: PropsConfig): SliderWidget { onValuesChange$, [_dirtyValues$], ([dirtyValues]) => dirtyValues.map((dv) => computeCleanValue(dv)), - (a, b) => a.every((val, index) => val === b[index]), + typeArray.equal, ); // computed @@ -297,13 +308,19 @@ export function createSlider(config?: PropsConfig): 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); diff --git a/e2e/demo.spec.ts b/e2e/demo.spec.ts index f47b2cdc0f..890bbd97da 100644 --- a/e2e/demo.spec.ts +++ b/e2e/demo.spec.ts @@ -11,15 +11,18 @@ const allRoutes = globSync(`${pathToFrameworkDir}/**/+page.svelte`).map((route) normalizePath(route).replace(pathToFrameworkDir, '').replace('/+page.svelte', ''), ); -async function analyze(page: Page): Promise { - return new AxeBuilder({page}).withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']).analyze(); +async function analyze(page: Page, route: string): Promise { + 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) { @@ -27,7 +30,7 @@ test.describe.parallel('Demo Website', () => { 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([]); }); } }); diff --git a/e2e/samplesMarkup.e2e-spec.ts-snapshots/slider-default.html b/e2e/samplesMarkup.e2e-spec.ts-snapshots/slider-default.html index af2a5f1884..128e2291a8 100644 --- a/e2e/samplesMarkup.e2e-spec.ts-snapshots/slider-default.html +++ b/e2e/samplesMarkup.e2e-spec.ts-snapshots/slider-default.html @@ -37,6 +37,7 @@

"70 -"