remove focus, use custom highlight

This commit is contained in:
Dan Percic 2021-07-27 18:00:58 +03:00
parent a65daaf90c
commit e50e5cbfb2
5 changed files with 67 additions and 91 deletions

View File

@ -1,6 +1,6 @@
export interface SpotlightSearchAction {
text: string;
action: (query: string) => void;
icon?: string;
hide?: boolean;
readonly text: string;
readonly action: (query: string) => void;
readonly icon?: string;
readonly hide?: boolean;
}

View File

@ -1,6 +1,6 @@
import { SpotlightSearchAction } from './spotlight-search-action';
export interface SpotlightSearchDialogData {
actionsConfig: SpotlightSearchAction[];
placeholder: string;
readonly actionsConfig: SpotlightSearchAction[];
readonly placeholder: string;
}

View File

@ -1,43 +1,30 @@
<div class="spotlight-wrapper">
<form [formGroup]="formGroup">
<div class="search d-flex">
<input
tabindex="-1"
id="query"
type="text"
formControlName="query"
autocomplete="off"
class="spotlight-row"
[placeholder]="data.placeholder"
/>
<form class="spotlight-wrapper" [formGroup]="formGroup">
<div class="search d-flex">
<input id="query" type="text" formControlName="query" autocomplete="off" class="spotlight-row" [placeholder]="data.placeholder" />
<mat-icon class="mr-34" *ngIf="(showActions$ | async) === false" [svgIcon]="'red:search'"></mat-icon>
<ng-container *ngIf="showActions$ | async as showActions">
<mat-icon class="mr-34" *ngIf="!showActions" [svgIcon]="'red:search'"></mat-icon>
<redaction-circle-button
*ngIf="showActions$ | async"
*ngIf="showActions"
class="mr-24"
(action)="close()"
(action)="dialogRef.close()"
icon="red:close"
></redaction-circle-button>
</div>
<div class="divider"></div>
<ng-container *ngIf="showActions$ | async">
<ng-container *ngFor="let item of data.actionsConfig">
<button
*ngIf="!item.hide"
#actions
(keydown.space)="$event.preventDefault()"
(keyup.space)="$event.preventDefault()"
tabindex="0"
class="spotlight-row focus pointer"
(click)="item.action(formGroup.get('query').value); close()"
>
<mat-icon class="mr-16" [svgIcon]="item.icon"></mat-icon>
<span>{{ item.text }}</span>
</button>
</ng-container>
</ng-container>
</form>
</div>
</div>
<div class="divider"></div>
<ng-container *ngIf="showActions$ | async">
<button
*ngFor="let item of shownActions; let index = index"
class="spotlight-row pointer"
[class.highlight]="(currentActionIdx$ | async) === index"
(click)="item.action(formGroup.get('query').value); dialogRef.close()"
>
<mat-icon class="mr-16" [svgIcon]="item.icon"></mat-icon>
<span>{{ item.text }}</span>
</button>
</ng-container>
</form>

View File

@ -2,8 +2,6 @@
.spotlight-wrapper {
overflow: hidden;
width: 750px;
margin: auto;
position: absolute;
top: 15%;
left: 0;
@ -13,10 +11,9 @@
}
.spotlight-row {
display: block;
width: 750px;
display: flex;
align-items: center;
height: 60px;
margin: auto;
text-align: left;
font-size: 16px;
font-weight: 500;
@ -27,7 +24,13 @@
background-color: $white;
}
.focus:focus {
.spotlight-row,
.spotlight-wrapper {
width: 750px;
margin: auto;
}
.highlight {
background-color: $grey-2;
}
@ -41,10 +44,6 @@
background-color: rgba(226, 228, 233, 0.9);
}
input {
width: 668px !important;
}
mat-icon {
width: 14px;
height: 14px;

View File

@ -1,9 +1,9 @@
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Inject, QueryList, ViewChildren } from '@angular/core';
import { ChangeDetectionStrategy, Component, HostListener, Inject } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { debounceTime, map, startWith, tap } from 'rxjs/operators';
import { debounce } from '@utils/debounce';
import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operators';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { SpotlightSearchDialogData } from '@components/spotlight-search/spotlight-search-dialog-data';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'redaction-spotlight-search',
@ -12,58 +12,48 @@ import { SpotlightSearchDialogData } from '@components/spotlight-search/spotligh
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpotlightSearchComponent {
@ViewChildren('actions')
private readonly _actions: QueryList<ElementRef>;
private _currentActionIdx = 0;
private readonly _currentActionIdx$ = new BehaviorSubject(0);
formGroup = this._formBuilder.group({ query: [''] });
query$ = this.formGroup.get('query').valueChanges.pipe(startWith(''));
showActions$ = this.query$.pipe(
readonly currentActionIdx$ = this._currentActionIdx$.asObservable().pipe(distinctUntilChanged());
readonly shownActions = this.data.actionsConfig.filter(a => !a.hide);
readonly formGroup = this._formBuilder.group({ query: [''] });
readonly showActions$ = this.formGroup.get('query').valueChanges.pipe(
startWith(''),
debounceTime(300),
tap(value => this._restoreFocusOnAction(value === '')),
map(value => value !== '')
);
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _dialogRef: MatDialogRef<SpotlightSearchComponent>,
readonly dialogRef: MatDialogRef<SpotlightSearchComponent>,
@Inject(MAT_DIALOG_DATA)
readonly data: SpotlightSearchDialogData
) {}
close() {
this._dialogRef.close();
@HostListener('document:keydown', ['$event'])
handleKeyDown(event: KeyboardEvent): void {
if (['ArrowDown', 'ArrowUp'].includes(event.code)) {
event.preventDefault();
return event.stopPropagation();
}
}
@HostListener('document:keyup', ['$event'])
handleKeyDown(event: KeyboardEvent) {
if (event.code === 'ArrowDown' && this._actions) {
this._currentActionIdx++;
return this._restoreFocusOnAction(this._currentActionIdx === this._actions.length);
handleKeyUp(event: KeyboardEvent): void {
if (['ArrowDown', 'ArrowUp'].includes(event.code)) {
event.preventDefault();
event.stopPropagation();
const index = this._currentActionIdx + event.code === 'ArrowDown' ? 1 : -1;
return this._currentActionIdx$.next(this._fixIndexOutOfBound(index));
}
if (event.code === 'ArrowUp' && this._actions) {
if (this._currentActionIdx === 0) this._currentActionIdx = this._actions.length - 1;
else this._currentActionIdx--;
return this._restoreFocusOnAction();
}
if (['Tab'].includes(event.code)) {
return;
}
if (this._actions.find((_, index) => index === this._currentActionIdx)?.nativeElement === document.activeElement) {
let query = this.formGroup.get('query').value as string;
if (event.code === 'Backspace') query = query.substring(0, query.length - 1);
else if (event.key.length === 1) query = query + event.key;
this.formGroup.patchValue({ query: query });
}
document.getElementById('query').focus();
}
@debounce(50)
private _restoreFocusOnAction(resetToFirst = false) {
if (resetToFirst) this._currentActionIdx = 0;
this._actions.find((_, index) => index === this._currentActionIdx)?.nativeElement.focus();
private get _currentActionIdx(): number {
return this._currentActionIdx$.getValue();
}
private _fixIndexOutOfBound(index: number): number {
const indexOutOfBound = index < 0 || index >= this.shownActions.length;
return indexOutOfBound ? Math.abs(this.shownActions.length - Math.abs(index)) : index;
}
}