diff --git a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html index 059d0acdd..005ae6e37 100644 --- a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html +++ b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html @@ -52,7 +52,8 @@ class="component-control" limit="10" [formControl]="search" - [matches]="ids" + [matches]="ids$ | async" + [count]="idCount$ | async" (keydown.enter)="onSelectValue(search.value)"> diff --git a/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts b/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts index 835e2f005..7eed2fca9 100644 --- a/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts +++ b/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts @@ -3,7 +3,7 @@ import { FormControl, Validators, AbstractControl, ValidatorFn } from '@angular/ import { ObjectWidget } from 'ngx-schema-form'; import { Store } from '@ngrx/store'; import { Observable, Subject } from 'rxjs'; -import { distinctUntilChanged, skipWhile, takeUntil, map } from 'rxjs/operators'; +import { distinctUntilChanged, skipWhile, takeUntil, map, withLatestFrom, filter, switchMap, startWith } from 'rxjs/operators'; import * as fromRoot from '../../../app.reducer'; import * as fromFilters from '../../../metadata/filter/reducer'; @@ -19,7 +19,7 @@ import { QueryEntityIds, ClearSearch } from '../../../metadata/filter/action/sea export class FilterTargetComponent extends ObjectWidget implements OnDestroy, AfterViewInit { private ngUnsubscribe: Subject = new Subject(); ids$: Observable; - ids: string[]; + idCount$: Observable; search: FormControl = new FormControl( '', @@ -40,17 +40,22 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af ) { super(); this.ids$ = this.store.select(fromFilters.getEntityCollection); - this.ids$.subscribe(ids => { - this.ids = [...ids]; - }); + + this.idCount$ = this.ids$.pipe(map(list => list.length)); this.search .valueChanges .pipe( takeUntil(this.ngUnsubscribe), - distinctUntilChanged() + distinctUntilChanged(), + withLatestFrom(this.ids$), + filter(([term, ids]) => term && term.length >= 4 && ids.indexOf(term) < 0), + map(([term]) => new QueryEntityIds({ + term, + limit: 10 + })) ) - .subscribe(query => this.searchEntityIds(query)); + .subscribe(action => this.store.dispatch(action)); this.script .valueChanges @@ -101,15 +106,6 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af }; } - searchEntityIds(term: string): void { - if (term && term.length >= 4 && this.ids.indexOf(term) < 0) { - this.store.dispatch(new QueryEntityIds({ - term, - limit: 10 - })); - } - } - getButtonConfig(id: string): any { let buttons = this.formProperty.getProperty('value').schema.buttons; return (buttons || []).map(btn => ({ diff --git a/ui/src/app/shared/autocomplete/autocomplete.component.html b/ui/src/app/shared/autocomplete/autocomplete.component.html index ff4fc99fd..7800382cd 100644 --- a/ui/src/app/shared/autocomplete/autocomplete.component.html +++ b/ui/src/app/shared/autocomplete/autocomplete.component.html @@ -6,32 +6,38 @@ [id]="fieldId" class="form-control" autocomplete="off" + autocorrect="off" + aria-autocomplete="list" role="combobox" type="text" [placeholder]="placeholder" [attr.aria-activedescendant]="activeDescendant" - aria-autocomplete="both" [attr.aria-owns]="fieldId + '__listbox'" [attr.aria-expanded]="(menuIsVisible$ | async) ? 'true' : 'false'" + [attr.aria-haspopup]="fieldId + '__listbox'" (focus)="handleInputFocus()" (blur)="handleInputBlur()" />
- \ No newline at end of file +

+ diff --git a/ui/src/app/shared/autocomplete/autocomplete.component.ts b/ui/src/app/shared/autocomplete/autocomplete.component.ts index a3d7d109a..cda5d6406 100644 --- a/ui/src/app/shared/autocomplete/autocomplete.component.ts +++ b/ui/src/app/shared/autocomplete/autocomplete.component.ts @@ -51,6 +51,7 @@ export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, Afte @Input() processing = false; @Input() dropdown = false; @Input() placeholder = ''; + @Input() count = null; @Output() more: EventEmitter = new EventEmitter(); @Output() onChange: EventEmitter = new EventEmitter(); @@ -118,14 +119,16 @@ export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, Afte } ngOnChanges(changes: SimpleChanges): void { - if (changes.matches && this.matches) { + if (changes.matches && this.matches && this.state.currentState.menuOpen) { this.announceResults(); } } announceResults(): void { const count = this.matches.length; - this.live.announce(count === 0 ? 'No results available' : `${count} result${count === 1 ? '' : 's'} available`); + this.live.announce(count === 0 ? + `${this.noneFoundText}` : + `${count} result${count === 1 ? '' : 's'} available`, 'polite', 5000); } writeValue(value: any): void { @@ -157,6 +160,12 @@ export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, Afte }); } + handleDropdown($event: MouseEvent | KeyboardEvent | Event): void { + const open = this.state.currentState.menuOpen; + this.state.setState({menuOpen: !open}); + this.handleOptionFocus(0); + } + handleViewMore($event: MouseEvent | KeyboardEvent | Event): void { $event.preventDefault(); $event.stopPropagation(); @@ -216,7 +225,6 @@ export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, Afte handleInputChange(query: string): void { query = query || ''; - const queryEmpty = query.length === 0; const autoselect = this.hasAutoselect; const optionsAvailable = this.matches.length > 0; @@ -227,8 +235,6 @@ export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, Afte selected: searchForOptions ? ((autoselect && optionsAvailable) ? 0 : -1) : null }); this.propagateChange(query); - - setTimeout(() => this.announceResults(), 250); } handleInputFocus(): void { @@ -284,7 +290,6 @@ export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, Afte } handleDownArrow(event: KeyboardEvent): void { - event.preventDefault(); let isNotAtBottom = this.state.currentState.selected !== this.matches.length - 1; if (this.showMoreAvailable) { isNotAtBottom = this.state.currentState.selected !== this.matches.length; @@ -293,6 +298,7 @@ export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, Afte if (allowMoveDown) { this.handleOptionFocus(this.state.currentState.selected + 1); } + event.preventDefault(); } handleSpace(event: KeyboardEvent): void { @@ -349,8 +355,8 @@ export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, Afte return !!(agent.match(/(iPod|iPhone|iPad)/g) && agent.match(/AppleWebKit/g)); } - getOptionId(index): string { - return `${this.fieldId}__option--${index}`; + getOptionId(index: string | number): string { + return `${this.fieldId}__option--${index}`.replace('/', ''); } get hasAutoselect(): boolean {