Merge pull request #1222 from atmire/w2p-79730_Fix-search-sidebar-a11y-issues

Fix search sidebar a11y issues
This commit is contained in:
Tim Donohue
2021-06-25 16:56:09 -05:00
committed by GitHub
20 changed files with 296 additions and 61 deletions

View File

@@ -162,6 +162,7 @@ import { UsageReport } from './statistics/models/usage-report.model';
import { RootDataService } from './data/root-data.service'; import { RootDataService } from './data/root-data.service';
import { Root } from './data/root.model'; import { Root } from './data/root.model';
import { SearchConfig } from './shared/search/search-filters/search-config.model'; import { SearchConfig } from './shared/search/search-filters/search-config.model';
import { SequenceService } from './shared/sequence.service';
/** /**
* When not in production, endpoint responses can be mocked for testing purposes * When not in production, endpoint responses can be mocked for testing purposes
@@ -282,7 +283,8 @@ const PROVIDERS = [
FilteredDiscoveryPageResponseParsingService, FilteredDiscoveryPageResponseParsingService,
{ provide: NativeWindowService, useFactory: NativeWindowFactory }, { provide: NativeWindowService, useFactory: NativeWindowFactory },
VocabularyService, VocabularyService,
VocabularyTreeviewService VocabularyTreeviewService,
SequenceService,
]; ];
/** /**

View File

@@ -0,0 +1,22 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
import { SequenceService } from './sequence.service';
let service: SequenceService;
describe('SequenceService', () => {
beforeEach(() => {
service = new SequenceService();
});
it('should return sequential numbers on next(), starting with 1', () => {
const NUMBERS = [1,2,3,4,5];
const sequence = NUMBERS.map(() => service.next());
expect(sequence).toEqual(NUMBERS);
});
});

View File

@@ -0,0 +1,24 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
import { Injectable } from '@angular/core';
@Injectable()
/**
* Provides unique sequential numbers
*/
export class SequenceService {
private value: number;
constructor() {
this.value = 0;
}
public next(): number {
return ++this.value;
}
}

View File

@@ -3,13 +3,27 @@
(keydown.arrowdown)="shiftFocusDown($event)" (keydown.arrowdown)="shiftFocusDown($event)"
(keydown.arrowup)="shiftFocusUp($event)" (keydown.esc)="close()" (keydown.arrowup)="shiftFocusUp($event)" (keydown.esc)="close()"
(dsClickOutside)="close();"> (dsClickOutside)="close();">
<input #inputField type="text" [(ngModel)]="value" [name]="name" <div class="form-group mb-0">
class="form-control suggestion_input" <label *ngIf="label; else searchInput">
[ngClass]="{'is-invalid': !valid}" <span class="font-weight-bold">
[dsDebounce]="debounceTime" (onDebounce)="find($event)" {{label}}
[placeholder]="placeholder" </span>
[ngModelOptions]="{standalone: true}" autocomplete="off"/> <ng-container *ngTemplateOutlet="searchInput"></ng-container>
<input type="submit" class="d-none"/> </label>
</div>
<ng-template #searchInput>
<input #inputField type="text" [(ngModel)]="value" [name]="name"
class="form-control suggestion_input"
[ngClass]="{'is-invalid': !valid}"
[dsDebounce]="debounceTime" (onDebounce)="find($event)"
[placeholder]="placeholder"
[ngModelOptions]="{standalone: true}" autocomplete="off"
/>
</ng-template>
<label class="d-none">
<input type="submit"/>
<span>{{'search.filters.search.submit' | translate}}</span>
</label>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
<div class="dropdown-list"> <div class="dropdown-list">
<div *ngFor="let suggestionOption of suggestions"> <div *ngFor="let suggestionOption of suggestions">

View File

@@ -53,6 +53,11 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange
*/ */
@Input() valid = true; @Input() valid = true;
/**
* Label for the input field. Used for screen readers.
*/
@Input() label? = '';
/** /**
* Output for when the form is submitted * Output for when the form is submitted
*/ */

View File

@@ -8,15 +8,18 @@
</ng-container> </ng-container>
<div class="clearfix toggle-more-filters"> <div class="clearfix toggle-more-filters">
<a class="float-left" *ngIf="!(isLastPage$ | async)" <a class="float-left" *ngIf="!(isLastPage$ | async)"
(click)="showMore()">{{"search.filters.filter.show-more" (click)="showMore()" href="javascript:void(0);">
| translate}}</a> {{"search.filters.filter.show-more" | translate}}
</a>
<a class="float-right" *ngIf="(currentPage | async) > 1" <a class="float-right" *ngIf="(currentPage | async) > 1"
(click)="showFirstPageOnly()">{{"search.filters.filter.show-less" (click)="showFirstPageOnly()" href="javascript:void(0);">
| translate}}</a> {{"search.filters.filter.show-less" | translate}}
</a>
</div> </div>
</div> </div>
<ds-filter-input-suggestions [suggestions]="(filterSearchResults | async)" <ds-filter-input-suggestions [suggestions]="(filterSearchResults | async)"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder'| translate" [placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder' | translate"
[label]="'search.filters.filter.' + filterConfig.name + '.label' | translate"
[action]="currentUrl" [action]="currentUrl"
[name]="filterConfig.paramName" [name]="filterConfig.paramName"
[(ngModel)]="filter" [(ngModel)]="filter"

View File

@@ -8,11 +8,13 @@
</ng-container> </ng-container>
<div class="clearfix toggle-more-filters"> <div class="clearfix toggle-more-filters">
<a class="float-left" *ngIf="!(isLastPage$ | async)" <a class="float-left" *ngIf="!(isLastPage$ | async)"
(click)="showMore()">{{"search.filters.filter.show-more" (click)="showMore()" href="javascript:void(0);">
| translate}}</a> {{"search.filters.filter.show-more" | translate}}
</a>
<a class="float-right" *ngIf="(currentPage | async) > 1" <a class="float-right" *ngIf="(currentPage | async) > 1"
(click)="showFirstPageOnly()">{{"search.filters.filter.show-less" (click)="showFirstPageOnly()" href="javascript:void(0);">
| translate}}</a> {{"search.filters.filter.show-less" | translate}}
</a>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,13 @@
<a *ngIf="isVisible | async" class="d-flex flex-row" <a *ngIf="isVisible | async" class="d-flex flex-row"
[tabIndex]="-1"
[routerLink]="[searchLink]" [routerLink]="[searchLink]"
[queryParams]="addQueryParams" queryParamsHandling="merge"> [queryParams]="addQueryParams" queryParamsHandling="merge">
<input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/> <label class="mb-0">
<span class="filter-value px-1"> <input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/>
<span class="filter-value px-1">
{{ 'search.filters.' + filterConfig.name + '.' + filterValue.value | translate: {default: filterValue.value} }} {{ 'search.filters.' + filterConfig.name + '.' + filterValue.value | translate: {default: filterValue.value} }}
</span> </span>
</label>
<span class="float-right filter-value-count ml-auto"> <span class="float-right filter-value-count ml-auto">
<span class="badge badge-secondary badge-pill">{{filterValue.count}}</span> <span class="badge badge-secondary badge-pill">{{filterValue.count}}</span>
</span> </span>

View File

@@ -1,8 +1,11 @@
<a class="d-flex flex-row" <a class="d-flex flex-row"
[tabIndex]="-1"
[routerLink]="[searchLink]" [routerLink]="[searchLink]"
[queryParams]="removeQueryParams" queryParamsHandling="merge"> [queryParams]="removeQueryParams" queryParamsHandling="merge">
<label class="mb-0">
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/> <input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
<span class="filter-value pl-1 text-capitalize"> <span class="filter-value pl-1 text-capitalize">
{{ 'search.filters.' + filterConfig.name + '.' + selectedValue.value | translate: {default: selectedValue.label} }} {{ 'search.filters.' + filterConfig.name + '.' + selectedValue.value | translate: {default: selectedValue.label} }}
</span> </span>
</label>
</a> </a>

View File

@@ -1,17 +1,21 @@
<div class="facet-filter d-block mb-3 p-3" *ngIf="active$ | async"> <div class="facet-filter d-block mb-3 p-3" *ngIf="active$ | async"
<div (click)="toggle()" class="filter-name"> [id]="regionId" [attr.aria-labelledby]="toggleId" [ngClass]="{ 'focus': focusBox }" role="region">
<button (click)="toggle()" (focusin)="focusBox = true" (focusout)="focusBox = false"
class="filter-name d-flex" [attr.aria-controls]="regionId" [id]="toggleId"
[attr.aria-expanded]="false"
[attr.aria-label]="((collapsed$ | async) ? 'search.filters.filter.expand' : 'search.filters.filter.collapse') | translate"
>
<h5 class="d-inline-block mb-0"> <h5 class="d-inline-block mb-0">
{{'search.filters.filter.' + filter.name + '.head'| translate}} {{'search.filters.filter.' + filter.name + '.head'| translate}}
</h5> </h5>
<span class="filter-toggle fas float-right" <span class="filter-toggle flex-grow-1 fas p-auto"
[ngClass]="(collapsed$ | async) ? 'fa-plus' : 'fa-minus'" [ngClass]="(collapsed$ | async) ? 'fa-plus' : 'fa-minus'"
[title]="((collapsed$ | async) ? 'search.filters.filter.expand' : 'search.filters.filter.collapse') | translate" [title]="((collapsed$ | async) ? 'search.filters.filter.expand' : 'search.filters.filter.collapse') | translate">
[attr.aria-label]="((collapsed$ | async) ? 'search.filters.filter.expand' : 'search.filters.filter.collapse') | translate">
</span> </span>
</div> </button>
<div [@slide]="(collapsed$ | async) ? 'collapsed' : 'expanded'" <div [@slide]="(collapsed$ | async) ? 'collapsed' : 'expanded'"
(@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)" (@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)"
class="search-filter-wrapper" [ngClass]="{'closed' : closed}"> class="search-filter-wrapper" [ngClass]="{ 'closed' : closed, 'notab': notab }">
<ds-search-facet-filter-wrapper <ds-search-facet-filter-wrapper
[filterConfig]="filter" [filterConfig]="filter"
[inPlaceSearch]="inPlaceSearch"> [inPlaceSearch]="inPlaceSearch">

View File

@@ -1,10 +1,36 @@
:host .facet-filter { :host .facet-filter {
border: 1px solid var(--bs-light); border: 1px solid var(--bs-light);
cursor: pointer; cursor: pointer;
.search-filter-wrapper.closed { line-height: 0;
overflow: hidden;
.search-filter-wrapper {
line-height: var(--bs-line-height-base);
&.closed {
overflow: hidden;
} }
.filter-toggle { &.notab {
line-height: var(--bs-line-height-base); visibility: hidden;
} }
}
.filter-toggle {
line-height: var(--bs-line-height-base);
text-align: right;
position: relative;
top: -0.125rem; // Fix weird outline shape in Chrome
}
> button {
appearance: none;
border: 0;
padding: 0;
background: transparent;
width: 100%;
outline: none !important;
}
&.focus {
outline: none;
box-shadow: var(--bs-input-btn-focus-box-shadow);
}
} }

View File

@@ -12,6 +12,7 @@ import { SearchFilterConfig } from '../../search-filter-config.model';
import { FilterType } from '../../filter-type.model'; import { FilterType } from '../../filter-type.model';
import { SearchConfigurationServiceStub } from '../../../testing/search-configuration-service.stub'; import { SearchConfigurationServiceStub } from '../../../testing/search-configuration-service.stub';
import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component';
import { SequenceService } from '../../../../core/shared/sequence.service';
describe('SearchFilterComponent', () => { describe('SearchFilterComponent', () => {
let comp: SearchFilterComponent; let comp: SearchFilterComponent;
@@ -50,12 +51,15 @@ describe('SearchFilterComponent', () => {
}; };
let filterService; let filterService;
let sequenceService;
const mockResults = observableOf(['test', 'data']); const mockResults = observableOf(['test', 'data']);
const searchServiceStub = { const searchServiceStub = {
getFacetValuesFor: (filter) => mockResults getFacetValuesFor: (filter) => mockResults
}; };
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
sequenceService = jasmine.createSpyObj('sequenceService', { next: 17 });
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NoopAnimationsModule], imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NoopAnimationsModule],
declarations: [SearchFilterComponent], declarations: [SearchFilterComponent],
@@ -65,7 +69,8 @@ describe('SearchFilterComponent', () => {
provide: SearchFilterService, provide: SearchFilterService,
useValue: mockFilterService useValue: mockFilterService
}, },
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() } { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
{ provide: SequenceService, useValue: sequenceService },
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(SearchFilterComponent, { }).overrideComponent(SearchFilterComponent, {
@@ -81,6 +86,12 @@ describe('SearchFilterComponent', () => {
filterService = (comp as any).filterService; filterService = (comp as any).filterService;
}); });
it('should generate unique IDs', () => {
expect(sequenceService.next).toHaveBeenCalled();
expect(comp.toggleId).toContain('17');
expect(comp.regionId).toContain('17');
});
describe('when the toggle method is triggered', () => { describe('when the toggle method is triggered', () => {
beforeEach(() => { beforeEach(() => {
spyOn(filterService, 'toggle'); spyOn(filterService, 'toggle');

View File

@@ -10,6 +10,7 @@ import { isNotEmpty } from '../../../empty.util';
import { SearchService } from '../../../../core/shared/search/search.service'; import { SearchService } from '../../../../core/shared/search/search.service';
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component';
import { SequenceService } from '../../../../core/shared/sequence.service';
@Component({ @Component({
selector: 'ds-search-filter', selector: 'ds-search-filter',
@@ -37,6 +38,16 @@ export class SearchFilterComponent implements OnInit {
*/ */
closed: boolean; closed: boolean;
/**
* True when the filter controls should be hidden & removed from the tablist
*/
notab: boolean;
/**
* True when the filter toggle button is focused
*/
focusBox = false;
/** /**
* Emits true when the filter is currently collapsed in the store * Emits true when the filter is currently collapsed in the store
*/ */
@@ -52,10 +63,15 @@ export class SearchFilterComponent implements OnInit {
*/ */
active$: Observable<boolean>; active$: Observable<boolean>;
private readonly sequenceId: number;
constructor( constructor(
private filterService: SearchFilterService, private filterService: SearchFilterService,
private searchService: SearchService, private searchService: SearchService,
@Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService) { @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService,
private sequenceService: SequenceService,
) {
this.sequenceId = this.sequenceService.next();
} }
/** /**
@@ -112,6 +128,9 @@ export class SearchFilterComponent implements OnInit {
if (event.fromState === 'collapsed') { if (event.fromState === 'collapsed') {
this.closed = false; this.closed = false;
} }
if (event.toState === 'collapsed') {
this.notab = true;
}
} }
/** /**
@@ -122,6 +141,17 @@ export class SearchFilterComponent implements OnInit {
if (event.toState === 'collapsed') { if (event.toState === 'collapsed') {
this.closed = true; this.closed = true;
} }
if (event.fromState === 'collapsed') {
this.notab = false;
}
}
get regionId(): string {
return `search-filter-region-${this.sequenceId}`;
}
get toggleId(): string {
return `search-filter-toggle-${this.sequenceId}`;
} }
/** /**

View File

@@ -8,15 +8,18 @@
</ng-container> </ng-container>
<div class="clearfix toggle-more-filters"> <div class="clearfix toggle-more-filters">
<a class="float-left" *ngIf="!(isLastPage$ | async)" <a class="float-left" *ngIf="!(isLastPage$ | async)"
(click)="showMore()">{{"search.filters.filter.show-more" (click)="showMore()" href="javascript:void(0);">
| translate}}</a> {{"search.filters.filter.show-more" | translate}}
</a>
<a class="float-right" *ngIf="(currentPage | async) > 1" <a class="float-right" *ngIf="(currentPage | async) > 1"
(click)="showFirstPageOnly()">{{"search.filters.filter.show-less" (click)="showFirstPageOnly()" href="javascript:void(0);">
| translate}}</a> {{"search.filters.filter.show-less" | translate}}
</a>
</div> </div>
</div> </div>
<ds-filter-input-suggestions [suggestions]="(filterSearchResults | async)" <ds-filter-input-suggestions [suggestions]="(filterSearchResults | async)"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder'| translate" [placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder' | translate"
[label]="'search.filters.filter.' + filterConfig.name + '.label' | translate"
[action]="currentUrl" [action]="currentUrl"
[name]="filterConfig.paramName" [name]="filterConfig.paramName"
[(ngModel)]="filter" [(ngModel)]="filter"

View File

@@ -2,25 +2,42 @@
<div class="filters py-2"> <div class="filters py-2">
<form #form="ngForm" (ngSubmit)="onSubmit()" class="add-filter row" <form #form="ngForm" (ngSubmit)="onSubmit()" class="add-filter row"
[action]="currentUrl"> [action]="currentUrl">
<div class="col-6"> <div class="col-6 form-group mb-0">
<input type="text" [(ngModel)]="range[0]" [name]="filterConfig.paramName + '.min'" <label>
class="form-control" (blur)="onSubmit()" <span class="font-weight-bold">
aria-label="Mininum value" {{'search.filters.filter.' + filterConfig.name + '.min.label' | translate}}
[placeholder]="'search.filters.filter.' + filterConfig.name + '.min.placeholder'| translate"/> </span>
<input type="text" [(ngModel)]="range[0]" [name]="filterConfig.paramName + '.min'"
class="form-control" (blur)="onSubmit()"
aria-label="Mininum value"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.min.placeholder' | translate"
/>
</label>
</div> </div>
<div class="col-6"> <div class="col-6">
<input type="text" [(ngModel)]="range[1]" [name]="filterConfig.paramName + '.max'" <label>
class="form-control" (blur)="onSubmit()" <span class="font-weight-bold">
aria-label="Maximum value" {{'search.filters.filter.' + filterConfig.name + '.max.label' | translate}}
[placeholder]="'search.filters.filter.' + filterConfig.name + '.max.placeholder'| translate"/> </span>
<input type="text" [(ngModel)]="range[1]" [name]="filterConfig.paramName + '.max'"
class="form-control" (blur)="onSubmit()"
aria-label="Maximum value"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.max.placeholder' | translate"
/>
</label>
</div> </div>
<input type="submit" class="d-none"/> <label class="d-none">
<input type="submit" class="d-none"/>
<span>{{'search.filters.search.submit' | translate}}</span>
</label>
</form> </form>
<ng-container *ngIf="shouldShowSlider()"> <ng-container *ngIf="shouldShowSlider()">
<nouislider [connect]="true" [min]="min" [max]="max" [step]="1" <nouislider [connect]="true" [min]="min" [max]="max" [step]="1"
[(ngModel)]="range" (change)="onSubmit()" ngDefaultControl></nouislider> [dsDebounce]="250" (onDebounce)="onSubmit()"
(keydown)="startKeyboardControl()" (keyup)="stopKeyboardControl()"
[(ngModel)]="range" ngDefaultControl>
</nouislider>
</ng-container> </ng-container>
<ng-container *ngFor="let page of (filterValues$ | async)?.payload"> <ng-container *ngFor="let page of (filterValues$ | async)?.payload">
<div [@facetLoad]="animationState"> <div [@facetLoad]="animationState">

View File

@@ -21,6 +21,7 @@
} }
&:focus { &:focus {
outline: none; outline: none;
box-shadow: var(--bs-input-btn-focus-box-shadow);
} }
} }

View File

@@ -68,6 +68,12 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
*/ */
sub: Subscription; sub: Subscription;
/**
* Whether the sider is being controlled by the keyboard.
* Supresses any changes until the key is released.
*/
keyboardControl: boolean;
constructor(protected searchService: SearchService, constructor(protected searchService: SearchService,
protected filterService: SearchFilterService, protected filterService: SearchFilterService,
protected router: Router, protected router: Router,
@@ -104,6 +110,10 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
* Submits new custom range values to the range filter from the widget * Submits new custom range values to the range filter from the widget
*/ */
onSubmit() { onSubmit() {
if (this.keyboardControl) {
return; // don't submit if a key is being held down
}
const newMin = this.range[0] !== this.min ? [this.range[0]] : null; const newMin = this.range[0] !== this.min ? [this.range[0]] : null;
const newMax = this.range[1] !== this.max ? [this.range[1]] : null; const newMax = this.range[1] !== this.max ? [this.range[1]] : null;
this.router.navigate(this.getSearchLinkParts(), { this.router.navigate(this.getSearchLinkParts(), {
@@ -117,6 +127,14 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
this.filter = ''; this.filter = '';
} }
startKeyboardControl(): void {
this.keyboardControl = true;
}
stopKeyboardControl(): void {
this.keyboardControl = false;
}
/** /**
* TODO when upgrading nouislider, verify that this check is still needed. * TODO when upgrading nouislider, verify that this check is still needed.
* Prevents AoT bug * Prevents AoT bug

View File

@@ -8,15 +8,18 @@
</ng-container> </ng-container>
<div class="clearfix toggle-more-filters"> <div class="clearfix toggle-more-filters">
<a class="float-left" *ngIf="!(isLastPage$ | async)" <a class="float-left" *ngIf="!(isLastPage$ | async)"
(click)="showMore()">{{"search.filters.filter.show-more" (click)="showMore()" href="javascript:void(0);">
| translate}}</a> {{"search.filters.filter.show-more" | translate}}
</a>
<a class="float-right" *ngIf="(currentPage | async) > 1" <a class="float-right" *ngIf="(currentPage | async) > 1"
(click)="showFirstPageOnly()">{{"search.filters.filter.show-less" (click)="showFirstPageOnly()" href="javascript:void(0);">
| translate}}</a> {{"search.filters.filter.show-less" | translate}}
</a>
</div> </div>
</div> </div>
<ds-filter-input-suggestions [suggestions]="(filterSearchResults | async)" <ds-filter-input-suggestions [suggestions]="(filterSearchResults | async)"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder'| translate" [placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder' | translate"
[label]="'search.filters.filter.' + filterConfig.name + '.label' | translate"
[action]="currentUrl" [action]="currentUrl"
[name]="filterConfig.paramName" [name]="filterConfig.paramName"
[(ngModel)]="filter" [(ngModel)]="filter"

View File

@@ -2882,38 +2882,56 @@
"search.filters.filter.author.placeholder": "Author name", "search.filters.filter.author.placeholder": "Author name",
"search.filters.filter.author.label": "Search author name",
"search.filters.filter.birthDate.head": "Birth Date", "search.filters.filter.birthDate.head": "Birth Date",
"search.filters.filter.birthDate.placeholder": "Birth Date", "search.filters.filter.birthDate.placeholder": "Birth Date",
"search.filters.filter.birthDate.label": "Search birth date",
"search.filters.filter.collapse": "Collapse filter", "search.filters.filter.collapse": "Collapse filter",
"search.filters.filter.creativeDatePublished.head": "Date Published", "search.filters.filter.creativeDatePublished.head": "Date Published",
"search.filters.filter.creativeDatePublished.placeholder": "Date Published", "search.filters.filter.creativeDatePublished.placeholder": "Date Published",
"search.filters.filter.creativeDatePublished.label": "Search date published",
"search.filters.filter.creativeWorkEditor.head": "Editor", "search.filters.filter.creativeWorkEditor.head": "Editor",
"search.filters.filter.creativeWorkEditor.placeholder": "Editor", "search.filters.filter.creativeWorkEditor.placeholder": "Editor",
"search.filters.filter.creativeWorkEditor.label": "Search editor",
"search.filters.filter.creativeWorkKeywords.head": "Subject", "search.filters.filter.creativeWorkKeywords.head": "Subject",
"search.filters.filter.creativeWorkKeywords.placeholder": "Subject", "search.filters.filter.creativeWorkKeywords.placeholder": "Subject",
"search.filters.filter.creativeWorkKeywords.label": "Search subject",
"search.filters.filter.creativeWorkPublisher.head": "Publisher", "search.filters.filter.creativeWorkPublisher.head": "Publisher",
"search.filters.filter.creativeWorkPublisher.placeholder": "Publisher", "search.filters.filter.creativeWorkPublisher.placeholder": "Publisher",
"search.filters.filter.creativeWorkPublisher.label": "Search publisher",
"search.filters.filter.dateIssued.head": "Date", "search.filters.filter.dateIssued.head": "Date",
"search.filters.filter.dateIssued.max.placeholder": "Minimum Date", "search.filters.filter.dateIssued.max.placeholder": "Maximum Date",
"search.filters.filter.dateIssued.min.placeholder": "Maximum Date", "search.filters.filter.dateIssued.max.label": "End",
"search.filters.filter.dateIssued.min.placeholder": "Minimum Date",
"search.filters.filter.dateIssued.min.label": "Start",
"search.filters.filter.dateSubmitted.head": "Date submitted", "search.filters.filter.dateSubmitted.head": "Date submitted",
"search.filters.filter.dateSubmitted.placeholder": "Date submitted", "search.filters.filter.dateSubmitted.placeholder": "Date submitted",
"search.filters.filter.dateSubmitted.label": "Search date submitted",
"search.filters.filter.discoverable.head": "Private", "search.filters.filter.discoverable.head": "Private",
"search.filters.filter.withdrawn.head": "Withdrawn", "search.filters.filter.withdrawn.head": "Withdrawn",
@@ -2922,6 +2940,8 @@
"search.filters.filter.entityType.placeholder": "Item Type", "search.filters.filter.entityType.placeholder": "Item Type",
"search.filters.filter.entityType.label": "Search item type",
"search.filters.filter.expand": "Expand filter", "search.filters.filter.expand": "Expand filter",
"search.filters.filter.has_content_in_original_bundle.head": "Has files", "search.filters.filter.has_content_in_original_bundle.head": "Has files",
@@ -2930,38 +2950,56 @@
"search.filters.filter.itemtype.placeholder": "Type", "search.filters.filter.itemtype.placeholder": "Type",
"search.filters.filter.itemtype.label": "Search type",
"search.filters.filter.jobTitle.head": "Job Title", "search.filters.filter.jobTitle.head": "Job Title",
"search.filters.filter.jobTitle.placeholder": "Job Title", "search.filters.filter.jobTitle.placeholder": "Job Title",
"search.filters.filter.jobTitle.label": "Search job title",
"search.filters.filter.knowsLanguage.head": "Known language", "search.filters.filter.knowsLanguage.head": "Known language",
"search.filters.filter.knowsLanguage.placeholder": "Known language", "search.filters.filter.knowsLanguage.placeholder": "Known language",
"search.filters.filter.knowsLanguage.label": "Search known language",
"search.filters.filter.namedresourcetype.head": "Status", "search.filters.filter.namedresourcetype.head": "Status",
"search.filters.filter.namedresourcetype.placeholder": "Status", "search.filters.filter.namedresourcetype.placeholder": "Status",
"search.filters.filter.namedresourcetype.label": "Search status",
"search.filters.filter.objectpeople.head": "People", "search.filters.filter.objectpeople.head": "People",
"search.filters.filter.objectpeople.placeholder": "People", "search.filters.filter.objectpeople.placeholder": "People",
"search.filters.filter.objectpeople.label": "Search people",
"search.filters.filter.organizationAddressCountry.head": "Country", "search.filters.filter.organizationAddressCountry.head": "Country",
"search.filters.filter.organizationAddressCountry.placeholder": "Country", "search.filters.filter.organizationAddressCountry.placeholder": "Country",
"search.filters.filter.organizationAddressCountry.label": "Search country",
"search.filters.filter.organizationAddressLocality.head": "City", "search.filters.filter.organizationAddressLocality.head": "City",
"search.filters.filter.organizationAddressLocality.placeholder": "City", "search.filters.filter.organizationAddressLocality.placeholder": "City",
"search.filters.filter.organizationAddressLocality.label": "Search city",
"search.filters.filter.organizationFoundingDate.head": "Date Founded", "search.filters.filter.organizationFoundingDate.head": "Date Founded",
"search.filters.filter.organizationFoundingDate.placeholder": "Date Founded", "search.filters.filter.organizationFoundingDate.placeholder": "Date Founded",
"search.filters.filter.organizationFoundingDate.label": "Search date founded",
"search.filters.filter.scope.head": "Scope", "search.filters.filter.scope.head": "Scope",
"search.filters.filter.scope.placeholder": "Scope filter", "search.filters.filter.scope.placeholder": "Scope filter",
"search.filters.filter.scope.label": "Search scope filter",
"search.filters.filter.show-less": "Collapse", "search.filters.filter.show-less": "Collapse",
"search.filters.filter.show-more": "Show more", "search.filters.filter.show-more": "Show more",
@@ -2970,10 +3008,14 @@
"search.filters.filter.subject.placeholder": "Subject", "search.filters.filter.subject.placeholder": "Subject",
"search.filters.filter.subject.label": "Search subject",
"search.filters.filter.submitter.head": "Submitter", "search.filters.filter.submitter.head": "Submitter",
"search.filters.filter.submitter.placeholder": "Submitter", "search.filters.filter.submitter.placeholder": "Submitter",
"search.filters.filter.submitter.label": "Search submitter",
"search.filters.entityType.JournalIssue": "Journal Issue", "search.filters.entityType.JournalIssue": "Journal Issue",
@@ -2999,6 +3041,8 @@
"search.filters.reset": "Reset filters", "search.filters.reset": "Reset filters",
"search.filters.search.submit": "Submit",
"search.form.search": "Search", "search.form.search": "Search",