mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge pull request #1222 from atmire/w2p-79730_Fix-search-sidebar-a11y-issues
Fix search sidebar a11y issues
This commit is contained in:
@@ -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,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
22
src/app/core/shared/sequence.service.spec.ts
Normal file
22
src/app/core/shared/sequence.service.spec.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
24
src/app/core/shared/sequence.service.ts
Normal file
24
src/app/core/shared/sequence.service.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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">
|
||||||
|
@@ -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
|
||||||
*/
|
*/
|
||||||
|
@@ -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"
|
||||||
|
@@ -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>
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
|
@@ -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>
|
||||||
|
@@ -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">
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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');
|
||||||
|
@@ -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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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"
|
||||||
|
@@ -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">
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
}
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
box-shadow: var(--bs-input-btn-focus-box-shadow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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"
|
||||||
|
@@ -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",
|
||||||
|
Reference in New Issue
Block a user