IMP-81: New select autocomple for terminals, party and others (#256)

This commit is contained in:
Rinat Arsaev 2023-09-19 15:49:17 +04:00 committed by GitHub
parent 66e16e0af1
commit eba2e51541
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 115 additions and 358 deletions

25
package-lock.json generated
View File

@ -29,7 +29,7 @@
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
"@vality/fistful-proto": "2.0.0",
"@vality/magista-proto": "2.0.2-e46bba2.0",
"@vality/ng-core": "16.2.1-pr-33-0ae5287.0",
"@vality/ng-core": "16.2.1-pr-33-0e1ce8c.0",
"@vality/payout-manager-proto": "2.0.1-b079679.0",
"@vality/repairer-proto": "2.0.1-8f7973d.0",
"@vality/thrift-ts": "2.4.1-8ad5123.0",
@ -44,7 +44,6 @@
"lodash-es": "4.17.21",
"moment": "2.29.4",
"monaco-editor": "0.21.2",
"ngx-mat-select-search": "7.0.2",
"papaparse": "5.4.1",
"rxjs": "7.8.1",
"short-uuid": "4.2.2",
@ -6002,9 +6001,9 @@
"integrity": "sha512-2cPVToAJRdt1CFQ6G/C6ngw6hp94Jp7WFtPtNvtlcCSXRYRnppoi3KpK14tBH9SlLeqrpSy0IrIsCnAkj7tFfg=="
},
"node_modules/@vality/ng-core": {
"version": "16.2.1-pr-33-0ae5287.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-33-0ae5287.0.tgz",
"integrity": "sha512-utA80+v58QMxzgNcLZ1hFRAO5RGcCyDW6/TJl1FZmDhja/P1FInZVWZIonaZJI/a+yf+YsSPboqM+QeQGZ595g==",
"version": "16.2.1-pr-33-0e1ce8c.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-33-0e1ce8c.0.tgz",
"integrity": "sha512-nLPX5MtNjyBCeaqIngXjcPcp1tpD9GiLCOoCL0YukJT7wC9MQQg4npQl6SKGRR4rzGOotse71Fmk1EzVJEXP3A==",
"dependencies": {
"@ng-matero/extensions": "^16.0.0",
"@s-libs/js-core": "^16.0.0",
@ -16754,22 +16753,6 @@
"@angular/core": ">=16.0.0-0"
}
},
"node_modules/ngx-mat-select-search": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.2.tgz",
"integrity": "sha512-69vy/mWh7pnnR6rw1BWxO8E4HyylUvvBlmhrtWqXIYqfIx3mfvWuNj0tE1v+ERPg8a4vViFpfilRUUvvbrv1cA==",
"dependencies": {
"tslib": "^2.4.0"
},
"peerDependencies": {
"@angular/material": "^15.0.0 || ^16.0.0"
}
},
"node_modules/ngx-mat-select-search/node_modules/tslib": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
},
"node_modules/nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",

View File

@ -37,7 +37,7 @@
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
"@vality/fistful-proto": "2.0.0",
"@vality/magista-proto": "2.0.2-e46bba2.0",
"@vality/ng-core": "16.2.1-pr-33-0ae5287.0",
"@vality/ng-core": "16.2.1-pr-33-0e1ce8c.0",
"@vality/payout-manager-proto": "2.0.1-b079679.0",
"@vality/repairer-proto": "2.0.1-8f7973d.0",
"@vality/thrift-ts": "2.4.1-8ad5123.0",
@ -52,7 +52,6 @@
"lodash-es": "4.17.21",
"moment": "2.29.4",
"monaco-editor": "0.21.2",
"ngx-mat-select-search": "7.0.2",
"papaparse": "5.4.1",
"rxjs": "7.8.1",
"short-uuid": "4.2.2",

View File

@ -95,17 +95,12 @@
</div>
</div>
<div *ngIf="form.value.terminalType === terminalType.Existent" fxLayout>
<mat-form-field fxFlex>
<mat-label>Terminal</mat-label>
<mat-select formControlName="existentTerminalID" required>
<mat-option
*ngFor="let terminal of terminals$ | async"
[value]="terminal.ref.id"
>
{{ terminal.data.name }}
</mat-option>
</mat-select>
</mat-form-field>
<cc-domain-object-field
formControlName="existentTerminalID"
fxFlex
name="terminal"
required
></cc-domain-object-field>
</div>
</div>
</div>

View File

@ -5,8 +5,6 @@ import { domain } from '@vality/domain-proto';
import { Predicate } from '@vality/domain-proto/domain';
import { DialogSuperclass } from '@vality/ng-core';
import { DomainStoreService } from '@cc/app/api/deprecated-damsel';
import { AddRoutingRuleDialogService, TerminalType } from './add-routing-rule-dialog.service';
@UntilDestroy()
@ -25,12 +23,10 @@ export class AddRoutingRuleDialogComponent extends DialogSuperclass<
terminalType = TerminalType;
riskScore = domain.RiskScore;
terminals$ = this.domainStoreService.getObjects('terminal');
constructor(
injector: Injector,
private addShopRoutingRuleDialogService: AddRoutingRuleDialogService,
private domainStoreService: DomainStoreService,
private fb: FormBuilder,
) {
super(injector);

View File

@ -15,6 +15,8 @@ import { DialogModule } from '@vality/ng-core';
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
import { DomainObjectFieldComponent } from '../../../../shared';
import { AddRoutingRuleDialogComponent } from './add-routing-rule-dialog.component';
import { ExpanderComponent } from './expander';
import { PredicateComponent } from './predicate';
@ -35,6 +37,7 @@ import { PredicateComponent } from './predicate';
MatAutocompleteModule,
MetadataFormModule,
DialogModule,
DomainObjectFieldComponent,
],
declarations: [AddRoutingRuleDialogComponent, PredicateComponent, ExpanderComponent],
exports: [AddRoutingRuleDialogComponent],

View File

@ -4,7 +4,7 @@ import { MatDialogRef } from '@angular/material/dialog';
import { Predicate } from '@vality/domain-proto/domain';
import { DialogResponseStatus } from '@vality/ng-core';
import { of } from 'rxjs';
import { startWith, switchMap, take } from 'rxjs/operators';
import { startWith, take, switchMap } from 'rxjs/operators';
import { RoutingRulesService } from '../../services/routing-rules';
@ -22,7 +22,7 @@ export class AddRoutingRuleDialogService {
weight: '',
priority: 1000,
terminalType: [null, Validators.required],
existentTerminalID: ['', Validators.required],
existentTerminalID: [null, Validators.required],
newTerminal: this.fb.group({
name: ['', Validators.required],
description: ['', Validators.required],

View File

@ -62,7 +62,7 @@
></cc-pretty-json>
</div>
</div>
<mat-action-row flexLayout fxLayoutAlign="start">
<mat-action-row fxLayoutAlign="start">
<button color="warn" mat-button (click)="removeShopRule(idx)">Remove</button>
</mat-action-row>
</mat-expansion-panel>

View File

@ -1,6 +1,6 @@
<cc-select-search-field
<v-select-field
[formControl]="control"
[label]="name | titlecase"
[options]="options$ | async"
[progress]="isLoading$ | async"
></cc-select-search-field>
></v-select-field>

View File

@ -1,8 +1,14 @@
import { CommonModule } from '@angular/common';
import { Component, Input, OnChanges } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { DomainObject } from '@vality/domain-proto/internal/domain';
import { ComponentChanges } from '@vality/ng-core';
import {
ComponentChanges,
FormControlSuperclass,
createControlProviders,
SelectFieldModule,
Option,
} from '@vality/ng-core';
import { defer, switchMap, ReplaySubject } from 'rxjs';
import { shareReplay, map } from 'rxjs/operators';
@ -12,24 +18,22 @@ import {
OtherDomainObjects,
defaultDomainObjectToOption,
} from '@cc/app/shared/services/domain-metadata-form-extensions/utils/domains-objects-to-options';
import { SelectSearchFieldModule } from '@cc/components/select-search-field';
import { ValidatedFormControlSuperclass, provideValueAccessor } from '@cc/utils';
type DomainObjectID = unknown;
@Component({
standalone: true,
selector: 'cc-domain-object-field',
templateUrl: './domain-object-field.component.html',
providers: [provideValueAccessor(() => DomainObjectFieldComponent)],
imports: [CommonModule, SelectSearchFieldModule, ReactiveFormsModule],
providers: createControlProviders(() => DomainObjectFieldComponent),
imports: [CommonModule, ReactiveFormsModule, SelectFieldModule],
})
export class DomainObjectFieldComponent<T extends keyof DomainObject>
extends ValidatedFormControlSuperclass<DomainObject[T]>
extends FormControlSuperclass<DomainObjectID>
implements OnChanges
{
@Input() name: T;
control = new FormControl<DomainObject[T]>(null);
options$ = defer(() => this.name$).pipe(
switchMap((name) => this.domainStoreService.getObjects(name)),
map((objs) => {
@ -37,9 +41,13 @@ export class DomainObjectFieldComponent<T extends keyof DomainObject>
this.name in DOMAIN_OBJECTS_TO_OPTIONS
? DOMAIN_OBJECTS_TO_OPTIONS[this.name as keyof OtherDomainObjects]
: defaultDomainObjectToOption;
return objs
.map(domainObjectToOption)
.map((o) => ({ ...o, description: `#${String(o.value)}` }));
return objs.map(domainObjectToOption).map(
(o): Option<DomainObjectID> => ({
label: o.label,
value: o.value,
description: String(o.value),
}),
);
}),
shareReplay({ bufferSize: 1, refCount: true }),
);

View File

@ -1,9 +1,9 @@
<cc-select-search-field
<v-select-field
[formControl]="control"
[label]="label || 'Merchant'"
[options]="options$ | async"
[progress]="!!(progress$ | async)"
[required]="required"
isExternalSearch
externalSearch
(searchChange)="this.searchChange$.next($event)"
></cc-select-search-field>
></v-select-field>

View File

@ -1,24 +1,17 @@
import { Component, Input, AfterViewInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { PartyID } from '@vality/domain-proto/domain';
import { coerceBoolean } from 'coerce-property';
import { BehaviorSubject, Observable, of, ReplaySubject, Subject, merge } from 'rxjs';
import {
catchError,
debounceTime,
filter,
map,
switchMap,
first,
takeUntil,
startWith,
} from 'rxjs/operators';
Option,
NotifyLogService,
FormControlSuperclass,
createControlProviders,
} from '@vality/ng-core';
import { coerceBoolean } from 'coerce-property';
import { BehaviorSubject, Observable, of, ReplaySubject, Subject } from 'rxjs';
import { catchError, debounceTime, map, switchMap, tap, startWith } from 'rxjs/operators';
import { DeanonimusService } from '@cc/app/api/deanonimus';
import { Option } from '@cc/components/select-search-field';
import { createControlProviders, ValidatedFormControlSuperclass } from '@cc/utils';
import { progressTo } from '@cc/utils/operators';
@UntilDestroy()
@Component({
@ -27,7 +20,7 @@ import { progressTo } from '@cc/utils/operators';
providers: createControlProviders(() => MerchantFieldComponent),
})
export class MerchantFieldComponent
extends ValidatedFormControlSuperclass<PartyID>
extends FormControlSuperclass<PartyID>
implements AfterViewInit
{
@Input() label: string;
@ -35,41 +28,45 @@ export class MerchantFieldComponent
options$ = new ReplaySubject<Option<PartyID>[]>(1);
searchChange$ = new Subject<string>();
progress$ = new BehaviorSubject(0);
progress$ = new BehaviorSubject(false);
constructor(
private deanonimusService: DeanonimusService,
private snackBar: MatSnackBar,
private log: NotifyLogService,
) {
super();
}
ngAfterViewInit() {
merge(
this.searchChange$,
this.control.valueChanges.pipe(
startWith(this.control.value),
filter(Boolean),
first(),
takeUntil(this.searchChange$),
),
)
this.searchChange$
.pipe(
filter(Boolean),
startWith(this.control.value),
tap(() => {
this.options$.next([]);
this.progress$.next(true);
}),
debounceTime(600),
switchMap((str) => this.searchOptions(str)),
switchMap((term) => this.searchOptions(term)),
untilDestroyed(this),
)
.subscribe((options) => this.options$.next(options));
.subscribe((options) => {
this.options$.next(options);
this.progress$.next(false);
});
}
private searchOptions(str: string): Observable<Option<PartyID>[]> {
if (!str) return of([]);
return this.deanonimusService.searchParty(str).pipe(
map((parties) => parties.map((p) => ({ label: p.party.email, value: p.party.id }))),
progressTo(this.progress$),
map((parties) =>
parties.map((p) => ({
label: p.party.email,
value: p.party.id,
description: p.party.id,
})),
),
catchError((err) => {
this.snackBar.open('Search error', 'OK', { duration: 2000 });
console.error(err);
this.log.error(err, 'Search error');
return of([]);
}),
);

View File

@ -1,13 +1,12 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { SelectSearchFieldModule } from '@cc/components/select-search-field';
import { SelectFieldModule } from '@vality/ng-core';
import { MerchantFieldComponent } from './merchant-field.component';
@NgModule({
imports: [CommonModule, ReactiveFormsModule, SelectSearchFieldModule],
imports: [CommonModule, ReactiveFormsModule, SelectFieldModule],
declarations: [MerchantFieldComponent],
exports: [MerchantFieldComponent],
})

View File

@ -1,9 +1,7 @@
<cc-select-search-field
<v-select-field
[disabled]="!(partyId$ | async) || !(shopId$ | async)"
[formControl]="control"
[label]="label || 'Payout Tool'"
[options]="options$ | async"
[required]="required"
isExternalSearch
(searchChange)="searchChange$.next($event)"
></cc-select-search-field>
></v-select-field>

View File

@ -1,15 +1,13 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Component, Input, OnInit } from '@angular/core';
import { UntilDestroy } from '@ngneat/until-destroy';
import { PayoutTool } from '@vality/domain-proto/domain';
import { PartyID, ShopID } from '@vality/domain-proto/payment_processing';
import { createControlProviders, FormControlSuperclass, Option } from '@vality/ng-core';
import { coerceBoolean } from 'coerce-property';
import { BehaviorSubject, combineLatest, defer, Observable, of, Subject, switchMap } from 'rxjs';
import { map, pluck, shareReplay, startWith } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, defer, Observable, of, switchMap } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { PartyManagementService } from '@cc/app/api/payment-processing';
import { Option } from '@cc/components/select-search-field';
import { createControlProviders, ValidatedControlSuperclass } from '@cc/utils/forms';
import { handleError, NotificationErrorService } from '../../services/notification-error';
@ -17,11 +15,10 @@ import { handleError, NotificationErrorService } from '../../services/notificati
@Component({
selector: 'cc-payout-tool-field',
templateUrl: 'payout-tool-field.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: createControlProviders(() => PayoutToolFieldComponent),
})
export class PayoutToolFieldComponent
extends ValidatedControlSuperclass<PartyID>
extends FormControlSuperclass<PayoutTool['id']>
implements OnInit
{
@Input() label: string;
@ -33,18 +30,12 @@ export class PayoutToolFieldComponent
this.shopId$.next(shopId);
}
control = new FormControl() as FormControl<PartyID>;
partyId$ = new BehaviorSubject<PartyID>(null);
shopId$ = new BehaviorSubject<ShopID>(null);
searchChange$ = new Subject<string>();
options$: Observable<Option<PayoutTool['id']>[]> = combineLatest([
this.searchChange$.pipe(map((str) => str?.trim()?.toLowerCase())).pipe(startWith('')),
defer(() => this.payoutTools$),
]).pipe(
map(([str, payoutTools]) => payoutTools.filter((t) => t.id.includes(str))),
map((payoutTools) => payoutTools.map((t) => ({ label: t.id, value: t.id }))),
options$: Observable<Option<PayoutTool['id']>[]> = defer(() => this.payoutTools$).pipe(
map((payoutTools) =>
payoutTools.map((t) => ({ label: t.id, value: t.id, description: t.id })),
),
shareReplay({ refCount: true, bufferSize: 1 }),
);
@ -57,7 +48,7 @@ export class PayoutToolFieldComponent
switchMap(({ contract_id }) =>
this.partyManagementService.GetContract(partyId, contract_id),
),
pluck('payout_tools'),
map((contract) => contract.payout_tools),
)
.pipe(
handleError(

View File

@ -4,8 +4,7 @@ import { ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { SelectSearchFieldModule } from '@cc/components/select-search-field';
import { SelectFieldModule } from '@vality/ng-core';
import { PayoutToolFieldComponent } from './payout-tool-field.component';
@ -16,10 +15,9 @@ import { PayoutToolFieldComponent } from './payout-tool-field.component';
MatAutocompleteModule,
MatInputModule,
CommonModule,
SelectSearchFieldModule,
SelectFieldModule,
],
declarations: [PayoutToolFieldComponent],
exports: [PayoutToolFieldComponent],
providers: [],
})
export class PayoutToolFieldModule {}

View File

@ -12,11 +12,13 @@ import { handleError, NotificationErrorService } from './notification-error';
export class FetchPartiesService {
parties$: Observable<Party[]> = defer(() => this.searchParties$).pipe(
switchMap((text) =>
this.deanonimusService.searchParty(text).pipe(
map((hits) => hits.map((hit) => hit.party)),
handleError(this.notificationErrorService.error, null, of<Party[]>([])),
progressTo(this.progress$),
),
text
? this.deanonimusService.searchParty(text).pipe(
map((hits) => hits.map((hit) => hit.party)),
handleError(this.notificationErrorService.error, null, of<Party[]>([])),
progressTo(this.progress$),
)
: of([]),
),
shareReplay(1),
);

View File

@ -1,4 +0,0 @@
export * from './select-search-field.component';
export * from './select-search-field.module';
export * from './types';
export * from './tokens';

View File

@ -1,59 +0,0 @@
<mat-form-field>
<mat-label>{{ label }}</mat-label>
<mat-hint *ngIf="hint">{{ hint }}</mat-hint>
<mat-select
[disabled]="disabled"
[required]="required"
[value]="selected$ | async"
(selectionChange)="select($event.value)"
>
<mat-option>
<ngx-mat-select-search
[formControl]="selectSearchControl"
noEntriesFoundLabel="No entries found"
placeholderLabel="Search..."
>
<mat-icon *ngIf="svgIcon" [svgIcon]="svgIcon" ngxMatSelectSearchClear></mat-icon>
</ngx-mat-select-search>
</mat-option>
<ng-container *ngIf="!progress; else progressBar">
<mat-option
*ngFor="let option of isExternalSearch ? options : (filteredOptions$ | async)"
[value]="option.value"
>
<div class="label">{{ option.label }}</div>
<div *ngIf="option.description" class="mat-caption mat-secondary-text">
{{ option.description }}
</div>
</mat-option>
<mat-option
*ngIf="
!(isExternalSearch ? options : (filteredOptions$ | async))?.length &&
!selectSearchControl.value &&
cachedOption?.value &&
(selected$ | async) === cachedOption?.value
"
[value]="cachedOption.value"
>
<div class="label">{{ cachedOption.label }}</div>
<div *ngIf="cachedOption.description" class="mat-caption mat-secondary-text">
{{ cachedOption.description }}
</div>
</mat-option>
</ng-container>
<ng-template #progressBar>
<mat-option disabled fxLayout="column" fxLayoutAlign="center none">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</mat-option>
</ng-template>
</mat-select>
<button *ngIf="selected$ | async" mat-icon-button matSuffix (click)="clear($event)">
<mat-icon *ngIf="!options?.length; else clearIcon">sync</mat-icon>
<ng-template #clearIcon>
<ng-container *ngIf="svgIcon; else defaultIcon">
<mat-icon [svgIcon]="svgIcon"></mat-icon>
</ng-container>
<ng-template #defaultIcon><mat-icon>close</mat-icon></ng-template>
</ng-template>
</button>
</mat-form-field>

View File

@ -1,9 +0,0 @@
mat-form-field {
width: 100%;
}
.label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -1,99 +0,0 @@
import {
Component,
Inject,
Injector,
Input,
OnChanges,
Optional,
Output,
EventEmitter,
OnInit,
} from '@angular/core';
import { FormControl } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { FormComponentSuperclass } from '@s-libs/ng-core';
import { ComponentChanges } from '@vality/ng-core';
import { coerceBoolean } from 'coerce-property';
import { BehaviorSubject, combineLatest, defer, Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { getFormValueChanges, provideValueAccessor } from '@cc/utils/forms';
import { SelectSearchFieldOptions, SELECT_SEARCH_FIELD_OPTIONS } from './tokens';
import { Option } from './types';
import { filterOptions } from './utils';
@UntilDestroy()
@Component({
selector: 'cc-select-search-field',
templateUrl: 'select-search-field.component.html',
styleUrls: ['select-search-field.component.scss'],
providers: [provideValueAccessor(() => SelectSearchFieldComponent)],
})
export class SelectSearchFieldComponent<Value>
extends FormComponentSuperclass<Value>
implements OnInit, OnChanges
{
@Input() label: string;
@Input() @coerceBoolean required = false;
@Input() @coerceBoolean disabled = false;
@Input() options: Option<Value>[];
@Input() svgIcon: string | null = this.fieldOptions?.svgIcon;
@Input() hint: string | null;
@Input() @coerceBoolean isExternalSearch: boolean | '' = false;
@Input() @coerceBoolean progress: boolean = false;
@Output() searchChange = new EventEmitter<string>();
selectSearchControl = new FormControl<string>('');
filteredOptions$: Observable<Option<Value>[]> = combineLatest([
getFormValueChanges(this.selectSearchControl),
defer(() => this.options$),
]).pipe(map(([value, options]) => filterOptions(options, value)));
selected$ = new BehaviorSubject<Value>(null);
cachedOption: Option<Value> = null;
private options$ = new BehaviorSubject<Option<Value>[]>([]);
constructor(
injector: Injector,
@Optional()
@Inject(SELECT_SEARCH_FIELD_OPTIONS)
private fieldOptions: SelectSearchFieldOptions,
) {
super(injector);
}
ngOnInit(): void {
this.selectSearchControl.valueChanges
.pipe(distinctUntilChanged(), untilDestroyed(this))
.subscribe((v) => this.searchChange.emit(v));
}
handleIncomingValue(value: Value): void {
this.select(value);
}
ngOnChanges({ options }: ComponentChanges<SelectSearchFieldComponent<Value>>): void {
if (options) {
this.options$.next(options.currentValue);
this.cacheOption();
}
}
clear(event: MouseEvent): void {
this.select(null);
event.stopPropagation();
}
select(value: Value): void {
this.selected$.next(value);
this.emitOutgoingValue(value);
this.cacheOption();
}
private cacheOption(): void {
const option = this.options?.find((o) => o.value === this.selected$.value);
if (option) this.cachedOption = option;
}
}

View File

@ -1,31 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectModule } from '@angular/material/select';
import { NgxMatSelectSearchModule } from 'ngx-mat-select-search';
import { SelectSearchFieldComponent } from './select-search-field.component';
@NgModule({
imports: [
CommonModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
MatButtonModule,
MatIconModule,
MatSelectModule,
NgxMatSelectSearchModule,
MatProgressBarModule,
FlexModule,
],
declarations: [SelectSearchFieldComponent],
exports: [SelectSearchFieldComponent],
})
export class SelectSearchFieldModule {}

View File

@ -1,8 +0,0 @@
import { InjectionToken } from '@angular/core';
export interface SelectSearchFieldOptions {
svgIcon?: string;
}
export const SELECT_SEARCH_FIELD_OPTIONS: InjectionToken<SelectSearchFieldOptions> =
new InjectionToken('SelectSearchFieldOptions');

View File

@ -1 +0,0 @@
export * from './option';

View File

@ -1,5 +0,0 @@
export interface Option<T> {
value: T;
label: string;
description?: string;
}

View File

@ -1,14 +0,0 @@
import { Option } from '../types';
const filterPredicate =
<T>(searchStr: string) =>
(option: Option<T>) =>
option.label.toLowerCase().includes(searchStr) ||
(option.description && option.description.toLowerCase().includes(searchStr)) ||
(typeof option.value !== 'object' &&
String(option.value).toLowerCase().includes(searchStr));
export const filterOptions = <T>(options: Option<T>[], controlValue: unknown): Option<T>[] =>
controlValue && typeof controlValue === 'string'
? options?.filter(filterPredicate(controlValue.toLowerCase()))
: options;

View File

@ -1 +0,0 @@
export * from './filter-options';

View File

@ -3,6 +3,10 @@ import { Provider, Type } from '@angular/core';
import { provideValidators } from './provide-validators';
import { provideValueAccessor } from './provide-value-accessor';
/**
* @deprecated
* @param component
*/
export const createControlProviders = (component: () => Type<unknown>): Provider[] => [
provideValueAccessor(component),
provideValidators(component),

View File

@ -1,6 +1,10 @@
import { Provider, forwardRef, Type } from '@angular/core';
import { NG_VALIDATORS } from '@angular/forms';
/**
* @deprecated
* @param component
*/
export const provideValidators = (component: () => Type<unknown>): Provider => ({
provide: NG_VALIDATORS,
useExisting: forwardRef(component),

View File

@ -1,6 +1,10 @@
import { Provider, forwardRef, Type } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
/**
* @deprecated
* @param component
*/
export const provideValueAccessor = (component: () => Type<unknown>): Provider => ({
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(component),

View File

@ -3,6 +3,7 @@ import { AbstractControl, ValidationErrors } from '@angular/forms';
import { hasControls } from '../../has-controls';
/**
* @deprecated
* FormGroup/FormArray don't return internal control errors,
* so you need to get internal errors manually
*/

View File

@ -7,6 +7,9 @@ import { getValue } from '../get-value';
import { getErrorsTree } from './utils/get-errors-tree';
/**
* @deprecated
*/
@Directive()
export abstract class ValidatedControlSuperclass<OuterType, InnerType = OuterType>
extends WrappedControlSuperclass<OuterType, InnerType>

View File

@ -3,6 +3,9 @@ import { ValidationErrors, FormControl } from '@angular/forms';
import { WrappedControlSuperclass } from '@s-libs/ng-core';
import { EMPTY, Observable } from 'rxjs';
/**
* @deprecated
*/
@Directive()
export class ValidatedFormControlSuperclass<
OuterType,