mirror of https://github.com/ghostfolio/ghostfolio
4 changed files with 214 additions and 0 deletions
@ -0,0 +1,28 @@ |
|||
<input |
|||
autocapitalize="off" |
|||
autocomplete="off" |
|||
matInput |
|||
[formControl]="control" |
|||
[matAutocomplete]="currencyAutocomplete" |
|||
/> |
|||
|
|||
<mat-autocomplete |
|||
#currencyAutocomplete="matAutocomplete" |
|||
[displayWith]="displayFn" |
|||
(optionSelected)="onUpdateCurrency($event)" |
|||
> |
|||
<ng-container *ngIf="!isLoading"> |
|||
<mat-option |
|||
*ngFor="let currencyItem of filteredCurrencies" |
|||
class="line-height-1" |
|||
[value]="currencyItem.value" |
|||
> |
|||
{{ currencyItem.label }} |
|||
</mat-option> |
|||
</ng-container> |
|||
</mat-autocomplete> |
|||
<mat-spinner |
|||
*ngIf="isLoading" |
|||
class="position-absolute" |
|||
[diameter]="20" |
|||
></mat-spinner> |
@ -0,0 +1,8 @@ |
|||
:host { |
|||
display: block; |
|||
|
|||
.mat-mdc-progress-spinner { |
|||
right: 0; |
|||
top: calc(50% - 10px); |
|||
} |
|||
} |
@ -0,0 +1,154 @@ |
|||
import { FocusMonitor } from '@angular/cdk/a11y'; |
|||
import { |
|||
ChangeDetectionStrategy, |
|||
ChangeDetectorRef, |
|||
Component, |
|||
ElementRef, |
|||
Input, |
|||
OnDestroy, |
|||
OnInit, |
|||
ViewChild |
|||
} from '@angular/core'; |
|||
import { FormControl, NgControl } from '@angular/forms'; |
|||
import { |
|||
MatAutocomplete, |
|||
MatAutocompleteSelectedEvent |
|||
} from '@angular/material/autocomplete'; |
|||
import { MatFormFieldControl } from '@angular/material/form-field'; |
|||
import { MatInput } from '@angular/material/input'; |
|||
import { Subject, of, tap } from 'rxjs'; |
|||
import { |
|||
debounceTime, |
|||
distinctUntilChanged, |
|||
switchMap, |
|||
takeUntil |
|||
} from 'rxjs/operators'; |
|||
import { Currency } from '@ghostfolio/common/interfaces/currency.interface'; |
|||
import { AbstractMatFormField } from '../symbol-autocomplete/abstract-mat-form-field'; |
|||
|
|||
@Component({ |
|||
changeDetection: ChangeDetectionStrategy.OnPush, |
|||
host: { |
|||
'[attr.aria-describedBy]': 'describedBy', |
|||
'[id]': 'id' |
|||
}, |
|||
selector: 'gf-currency-autocomplete', |
|||
styleUrls: ['./currency-selector.component.scss'], |
|||
templateUrl: 'currency-selector.component.html', |
|||
providers: [ |
|||
{ |
|||
provide: MatFormFieldControl, |
|||
useExisting: CurrencySelectorComponent |
|||
} |
|||
] |
|||
}) |
|||
export class CurrencySelectorComponent |
|||
extends AbstractMatFormField<Currency> |
|||
implements OnInit, OnDestroy |
|||
{ |
|||
@Input() private currencies: Currency[] = []; |
|||
@Input() public isLoading = false; |
|||
|
|||
@ViewChild(MatInput, { static: false }) private input: MatInput; |
|||
|
|||
@ViewChild('currencyAutocomplete') |
|||
public currencyAutocomplete: MatAutocomplete; |
|||
|
|||
public control = new FormControl(); |
|||
public filteredCurrencies: Currency[] = []; |
|||
|
|||
private unsubscribeSubject = new Subject<void>(); |
|||
|
|||
public constructor( |
|||
public readonly _elementRef: ElementRef, |
|||
public readonly _focusMonitor: FocusMonitor, |
|||
public readonly changeDetectorRef: ChangeDetectorRef, |
|||
public readonly ngControl: NgControl |
|||
) { |
|||
super(_elementRef, _focusMonitor, ngControl); |
|||
|
|||
this.controlType = 'currency-autocomplete'; |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
if (this.disabled) { |
|||
this.control.disable(); |
|||
} |
|||
|
|||
this.control.valueChanges |
|||
.pipe( |
|||
debounceTime(400), |
|||
distinctUntilChanged(), |
|||
takeUntil(this.unsubscribeSubject), |
|||
tap(() => { |
|||
this.isLoading = true; |
|||
|
|||
this.changeDetectorRef.markForCheck(); |
|||
}), |
|||
switchMap((query) => { |
|||
return of( |
|||
this.currencies.filter((currency) => |
|||
currency.label.toLowerCase().includes(query?.toLowerCase() || '') |
|||
) || [] |
|||
); |
|||
}) |
|||
) |
|||
.subscribe((filteredCurrencies: Currency[]) => { |
|||
this.filteredCurrencies = filteredCurrencies; |
|||
|
|||
this.isLoading = false; |
|||
|
|||
this.changeDetectorRef.markForCheck(); |
|||
}); |
|||
} |
|||
|
|||
public displayFn(currency: string) { |
|||
return currency; |
|||
} |
|||
|
|||
public get empty() { |
|||
return this.input?.empty; |
|||
} |
|||
|
|||
public focus() { |
|||
this.input.focus(); |
|||
} |
|||
|
|||
public ngDoCheck() { |
|||
if (this.ngControl) { |
|||
this.validateRequired(); |
|||
this.errorState = this.ngControl.invalid && this.ngControl.touched; |
|||
this.stateChanges.next(); |
|||
} |
|||
} |
|||
|
|||
public onUpdateCurrency(event: MatAutocompleteSelectedEvent) { |
|||
super.value = { |
|||
value: event.option.value |
|||
} as Currency; |
|||
} |
|||
|
|||
public set value(value: Currency) { |
|||
this.control.setValue(value); |
|||
super.value = value; |
|||
} |
|||
|
|||
public ngOnDestroy() { |
|||
super.ngOnDestroy(); |
|||
|
|||
this.unsubscribeSubject.next(); |
|||
this.unsubscribeSubject.complete(); |
|||
} |
|||
|
|||
private validateRequired() { |
|||
const requiredCheck = super.required |
|||
? !super.value?.value && |
|||
!this.currencies |
|||
.map((currency) => currency.value) |
|||
.includes(super.value.value) |
|||
: false; |
|||
if (requiredCheck) { |
|||
this.ngControl.control.setErrors({ invalidData: true }); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
import { CommonModule } from '@angular/common'; |
|||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; |
|||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; |
|||
import { MatAutocompleteModule } from '@angular/material/autocomplete'; |
|||
import { MatFormFieldModule } from '@angular/material/form-field'; |
|||
import { MatInputModule } from '@angular/material/input'; |
|||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; |
|||
import { CurrencySelectorComponent } from './currency-selector.component'; |
|||
|
|||
@NgModule({ |
|||
declarations: [CurrencySelectorComponent], |
|||
exports: [CurrencySelectorComponent], |
|||
imports: [ |
|||
CommonModule, |
|||
FormsModule, |
|||
MatAutocompleteModule, |
|||
MatFormFieldModule, |
|||
MatInputModule, |
|||
MatProgressSpinnerModule, |
|||
ReactiveFormsModule |
|||
], |
|||
schemas: [CUSTOM_ELEMENTS_SCHEMA] |
|||
}) |
|||
export class GfCurrencyAutocompleteModule {} |
Loading…
Reference in new issue