Fix accessibility of date sliders by adding aria-labels

(cherry picked from commit 2a881791ba)
This commit is contained in:
Tim Donohue
2023-08-29 15:01:21 -05:00
committed by github-actions[bot]
parent b90d102e5e
commit 3cdcdaf475
4 changed files with 36 additions and 15 deletions

View File

@@ -22,15 +22,11 @@ describe('My DSpace page', () => {
testA11y( testA11y(
{ {
include: ['ds-my-dspace-page'], include: ['ds-my-dspace-page'],
exclude: [
['nouislider'] // Date filter slider is missing ARIA labels. Will be fixed by #1175
],
}, },
{ {
rules: { rules: {
// Search filters fail these two "moderate" impact rules // Search filters fail this "moderate" impact rules
'heading-order': { enabled: false }, 'heading-order': { enabled: false },
'landmark-unique': { enabled: false }
} }
} as Options } as Options
); );

View File

@@ -30,15 +30,11 @@ describe('Search Page', () => {
testA11y( testA11y(
{ {
include: ['ds-search-page'], include: ['ds-search-page'],
exclude: [
['nouislider'] // Date filter slider is missing ARIA labels. Will be fixed by #1175
],
}, },
{ {
rules: { rules: {
// Search filters fail these two "moderate" impact rules // Search filters fail this "moderate" impact rule
'heading-order': { enabled: false }, 'heading-order': { enabled: false },
'landmark-unique': { enabled: false }
} }
} as Options } as Options
); );

View File

@@ -9,8 +9,8 @@
</span> </span>
<input type="text" [(ngModel)]="range[0]" [name]="filterConfig.paramName + '.min'" <input type="text" [(ngModel)]="range[0]" [name]="filterConfig.paramName + '.min'"
class="form-control" (blur)="onSubmit()" class="form-control" (blur)="onSubmit()"
aria-label="Mininum value" [attr.aria-label]="minLabel"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.min.placeholder' | translate" [placeholder]="minLabel"
/> />
</label> </label>
</div> </div>
@@ -21,8 +21,8 @@
</span> </span>
<input type="text" [(ngModel)]="range[1]" [name]="filterConfig.paramName + '.max'" <input type="text" [(ngModel)]="range[1]" [name]="filterConfig.paramName + '.max'"
class="form-control" (blur)="onSubmit()" class="form-control" (blur)="onSubmit()"
aria-label="Maximum value" [attr.aria-label]="maxLabel"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.max.placeholder' | translate" [placeholder]="maxLabel"
/> />
</label> </label>
</div> </div>
@@ -33,7 +33,7 @@
</form> </form>
<ng-container *ngIf="shouldShowSlider()"> <ng-container *ngIf="shouldShowSlider()">
<nouislider [connect]="true" [min]="min" [max]="max" [step]="1" <nouislider [connect]="true" [config]="config" [min]="min" [max]="max" [step]="1"
[dsDebounce]="250" (onDebounce)="onSubmit()" [dsDebounce]="250" (onDebounce)="onSubmit()"
(keydown)="startKeyboardControl()" (keyup)="stopKeyboardControl()" (keydown)="startKeyboardControl()" (keyup)="stopKeyboardControl()"
[(ngModel)]="range" ngDefaultControl> [(ngModel)]="range" ngDefaultControl>

View File

@@ -2,6 +2,7 @@ import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription
import { map, startWith } from 'rxjs/operators'; import { map, startWith } from 'rxjs/operators';
import { isPlatformBrowser } from '@angular/common'; import { isPlatformBrowser } from '@angular/common';
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
import { FilterType } from '../../../models/filter-type.model'; import { FilterType } from '../../../models/filter-type.model';
import { renderFacetFor } from '../search-filter-type-decorator'; import { renderFacetFor } from '../search-filter-type-decorator';
@@ -53,11 +54,27 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
*/ */
min = 1950; min = 1950;
/**
* i18n Label to use for minimum field
*/
minLabel: string;
/** /**
* Fallback maximum for the range * Fallback maximum for the range
*/ */
max = new Date().getUTCFullYear(); max = new Date().getUTCFullYear();
/**
* i18n Label to use for maximum field
*/
maxLabel: string;
/**
* Base configuration for nouislider
* https://refreshless.com/nouislider/slider-options/
*/
config = {};
/** /**
* The current range of the filter * The current range of the filter
*/ */
@@ -78,6 +95,7 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
protected filterService: SearchFilterService, protected filterService: SearchFilterService,
protected router: Router, protected router: Router,
protected rdbs: RemoteDataBuildService, protected rdbs: RemoteDataBuildService,
private translateService: TranslateService,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
@Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean, @Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean,
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
@@ -96,6 +114,8 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
super.ngOnInit(); super.ngOnInit();
this.min = yearFromString(this.filterConfig.minValue) || this.min; this.min = yearFromString(this.filterConfig.minValue) || this.min;
this.max = yearFromString(this.filterConfig.maxValue) || this.max; this.max = yearFromString(this.filterConfig.maxValue) || this.max;
this.minLabel = this.translateService.instant('search.filters.filter.' + this.filterConfig.name + '.min.placeholder');
this.maxLabel = this.translateService.instant('search.filters.filter.' + this.filterConfig.name + '.max.placeholder');
const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX).pipe(startWith(undefined)); const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX).pipe(startWith(undefined));
const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX).pipe(startWith(undefined)); const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX).pipe(startWith(undefined));
this.sub = observableCombineLatest(iniMin, iniMax).pipe( this.sub = observableCombineLatest(iniMin, iniMax).pipe(
@@ -105,6 +125,15 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
return [minimum, maximum]; return [minimum, maximum];
}) })
).subscribe((minmax) => this.range = minmax); ).subscribe((minmax) => this.range = minmax);
// Default/base config for nouislider
this.config = {
// Ensure draggable handles have labels
handleAttributes: [
{ 'aria-label': this.minLabel },
{ 'aria-label': this.maxLabel },
],
};
} }
/** /**