TD-666: 16.2 (#33)

This commit is contained in:
Rinat Arsaev 2023-09-19 15:49:31 +04:00 committed by GitHub
parent fd4e28bc23
commit 235e19fb69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 2686 additions and 2099 deletions

4372
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@
"@angular/cli": "~16.1.0",
"@angular/compiler-cli": "^16.1.1",
"@types/jasmine": "~4.3.0",
"cspell": "^6.31.1",
"cspell": "^7.0.0",
"eslint": "^8.39.0",
"jasmine-core": "~4.5.0",
"karma": "~6.4.0",
@ -47,7 +47,7 @@
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"ng-packagr": "^16.1.0",
"prettier": "^2.8.8",
"prettier": "^3.0.1",
"typescript": "~5.1.3"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@vality/cspell-config",
"version": "0.1.0",
"version": "7.0.0",
"publishConfig": {
"access": "public"
},
@ -9,7 +9,10 @@
"default": "./cspell.config.js"
}
},
"peerDependencies": {
"cspell": "^7.0.0"
},
"dependencies": {
"@cspell/dict-ru_ru": "^2.0.5"
"@cspell/dict-ru_ru": "^2.1.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@vality/eslint-config",
"version": "1.0.0",
"version": "8.0.0",
"publishConfig": {
"access": "public"
},
@ -12,13 +12,13 @@
"eslint": "^8.0.0"
},
"dependencies": {
"@angular-eslint/eslint-plugin": "^16.0.3",
"@angular-eslint/eslint-plugin-template": "^16.0.3",
"@angular-eslint/template-parser": "^16.0.3",
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.59.11",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"@angular-eslint/eslint-plugin": "^16.1.0",
"@angular-eslint/eslint-plugin-template": "^16.1.0",
"@angular-eslint/template-parser": "^16.1.0",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-unused-imports": "^2.0.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@vality/ng-core",
"version": "16.1.1",
"version": "16.2.0",
"sideEffects": false,
"exports": {
".": {

View File

@ -17,7 +17,7 @@ export class ConfirmDialogComponent extends DialogSuperclass<
confirm() {
this.closeWithSuccess(
this.dialogData?.hasReason ? { reason: this.control.value } : undefined
this.dialogData?.hasReason ? { reason: this.control.value } : undefined,
);
}
}

View File

@ -14,7 +14,7 @@ export class DialogService {
private dialog: MatDialog,
@Optional()
@Inject(DIALOG_CONFIG)
private readonly dialogConfig: DialogConfig
private readonly dialogConfig: DialogConfig,
) {
if (!dialogConfig) this.dialogConfig = DEFAULT_DIALOG_CONFIG;
}
@ -39,7 +39,7 @@ export class DialogService {
data: TDialogData,
configOrConfigName?:
| Omit<MatDialogConfig<TDialogData>, 'data'>
| keyof DialogConfig
| keyof DialogConfig,
]
): MatDialogRef<TDialogComponent, DialogResponse<TDialogResponseData, TDialogResponseStatus>> {
let config: Partial<MatDialogConfig<TDialogData>>;

View File

@ -10,7 +10,7 @@ export class DialogSuperclass<
TDialogComponent,
TDialogData = void,
TDialogResponseData = void,
TDialogResponseStatus = void
TDialogResponseStatus = void,
> {
static defaultDialogConfig = DEFAULT_DIALOG_CONFIG.medium;

View File

@ -54,13 +54,16 @@ export class FiltersComponent {
if (b.breakpoints[Breakpoints.Medium]) return 3;
if (b.breakpoints[Breakpoints.Small]) return 2;
return 1;
})
}),
);
displayedFiltersCount$ = this.repeat$.pipe(map((r) => r * 2));
filtersCount$ = new BehaviorSubject(0);
constructor(private dialog: DialogService, private breakpointObserver: BreakpointObserver) {}
constructor(
private dialog: DialogService,
private breakpointObserver: BreakpointObserver,
) {}
open() {
this.dialog.open(FiltersDialogComponent, { filters: this });

View File

@ -2,15 +2,60 @@
<mat-label>{{ label }}</mat-label>
<mtx-select
[closeOnSelect]="!multiple"
[formControl]="control"
[items]="options"
[loading]="progress"
[markFirst]="true"
[multiple]="multiple"
[notFoundText]="externalSearch && !searchStr ? 'Enter your search term' : 'No items found'"
[required]="required"
[searchable]="true"
[searchFn]="search"
[selectableGroup]="true"
[selectableGroupAsModel]="false"
bindLabel="label"
bindValue="value"
/>
groupBy="type"
(clear)="searchChange.emit(''); searchStr = ''"
(search)="searchChange.emit($event.term); searchStr = $event.term"
>
<ng-template let-index="index" let-item="item" let-item$="item$" ng-optgroup-tmp>
<mat-checkbox
*ngIf="multiple; else text"
[ngModel]="item$.selected"
class="checkbox-option"
id="item-{{ index }}"
>
{{ item.type | uppercase }}
</mat-checkbox>
<ng-template #text>
{{ item.type | uppercase }}
</ng-template>
</ng-template>
<ng-template let-index="index" let-item="item" let-item$="item$" ng-option-tmp>
<mat-checkbox
*ngIf="multiple; else text"
[ngModel]="item$.selected"
class="checkbox-option"
id="item-{{ index }}"
>
<ng-container *ngTemplateOutlet="text"></ng-container>
</mat-checkbox>
<ng-template #text>
<div class="label">{{ item.label }}</div>
<div *ngIf="item.description" class="description mat-caption mat-secondary-text">
{{ item.description }}
</div>
</ng-template>
</ng-template>
</mtx-select>
<mat-hint>{{ hint }}</mat-hint>
<mat-hint>{{
hint ||
(multiple && options && options.length > 1
? (control.value?.length || 0) + '/' + options.length
: '')
}}</mat-hint>
<mat-error>{{ error }}</mat-error>
</mat-form-field>

View File

@ -1,7 +1,26 @@
.mat-mdc-form-field {
::ng-deep .mat-mdc-form-field {
width: 100%;
& > * {
max-height: 56px;
}
.ng-value-container {
flex-wrap: nowrap !important;
overflow-x: hidden;
}
}
::ng-deep .ng-dropdown-panel.ng-select-bottom {
margin-top: -4px;
}
.checkbox-option {
margin: -12px 0 -12px -8px;
}
.label,
.description {
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -1,4 +1,4 @@
import { booleanAttribute, Component, Input } from '@angular/core';
import { booleanAttribute, Component, EventEmitter, Input, Output } from '@angular/core';
import { createControlProviders, FormControlSuperclass } from '../../utils';
@ -10,13 +10,27 @@ import { Option } from './types/option';
styleUrls: ['./select-field.component.scss'],
providers: createControlProviders(() => SelectFieldComponent),
})
export class SelectFieldComponent<T> extends FormControlSuperclass<T> {
export class SelectFieldComponent<T> extends FormControlSuperclass<T[]> {
@Input() options: Option<T>[] = [];
@Output() searchChange = new EventEmitter<string>();
@Input() label?: string;
@Input() hint?: string;
@Input() error?: string;
@Input() progress = false;
@Input({ transform: booleanAttribute }) externalSearch = false;
@Input({ transform: booleanAttribute }) multiple = false;
@Input({ transform: booleanAttribute }) required = false;
searchStr: string = '';
search = (term: string, item: Option<T>) => {
if (this.externalSearch) return true;
const termLowerCase = term.toLowerCase();
return (
item.label.toLowerCase().includes(termLowerCase) ||
!!item.description?.includes?.(termLowerCase)
);
};
}

View File

@ -1,6 +1,7 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatInputModule } from '@angular/material/input';
import { MtxSelectModule } from '@ng-matero/extensions/select';
@ -9,6 +10,13 @@ import { SelectFieldComponent } from './select-field.component';
@NgModule({
declarations: [SelectFieldComponent],
exports: [SelectFieldComponent],
imports: [CommonModule, MatInputModule, MtxSelectModule, FormsModule, ReactiveFormsModule],
imports: [
CommonModule,
MatInputModule,
MtxSelectModule,
FormsModule,
ReactiveFormsModule,
MatCheckboxModule,
],
})
export class SelectFieldModule {}

View File

@ -1,4 +1,6 @@
export interface Option<T> {
value: T;
label: string;
description?: string;
type?: unknown;
}

View File

@ -1,6 +1,6 @@
<div class="wrapper">
<ng-container
*ngIf="rowData | vSelect : colDef.tooltip : '' : [colDef] as tooltip; else linkTemplate"
*ngIf="rowData | vSelect: colDef.tooltip : '' : [colDef] as tooltip; else linkTemplate"
>
<div [matTooltip]="tooltip | json" class="tooltip" matTooltipPosition="right">
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
@ -21,17 +21,16 @@
<ng-template #contentTemplate>
<div
*ngLet="rowData | vSelect : colDef.formatter ?? colDef.field : '' : [colDef] as value"
*ngLet="rowData | vSelect: colDef.formatter ?? colDef.field : '' : [colDef] as value"
class="value"
>
<ng-template [ngIf]="colDef.type === 'datetime'">
{{ value | date : 'dd.MM.yyyy HH:mm:ss' : '+0000' }}
{{ value | date: 'dd.MM.yyyy HH:mm:ss' : '+0000' }}
</ng-template>
<ng-template [ngIf]="colDef.type === 'tag'">
<v-tag *ngIf="colDef.typeParameters.tags[value] ?? {} as tag" [color]="tag.color">
{{
tag.label ??
(rowData | vSelect : colDef.typeParameters.label : value : [colDef])
tag.label ?? (rowData | vSelect: colDef.typeParameters.label : value : [colDef])
}}
</v-tag>
</ng-template>
@ -47,7 +46,7 @@
{{
value
| amountCurrency
: (rowData | vSelect : colDef?.typeParameters?.currencyCode : '' : [colDef])
: (rowData | vSelect: colDef?.typeParameters?.currencyCode : '' : [colDef])
: 'long'
: (colDef.typeParameters.isMinor ? undefined : 0)
}}
@ -71,6 +70,6 @@
</ng-template>
</div>
<div class="description mat-caption mat-secondary-text">
{{ rowData | vSelect : colDef.description : '' : [colDef] }}
{{ rowData | vSelect: colDef.description : '' : [colDef] }}
</div>
</ng-template>

View File

@ -2,7 +2,7 @@ import { MenuColumn } from '../types/column';
export function createOperationColumn<T extends object>(
items: MenuColumn<T>['typeParameters']['items'],
other?: Partial<MenuColumn<T>>
other?: Partial<MenuColumn<T>>,
): MenuColumn<T> {
return {
typeParameters: { ...(other?.typeParameters || {}), items },

View File

@ -148,7 +148,7 @@ export class TableComponent<T extends object> implements Progressable, OnChanges
this.renderedColumns.map((c) => [
c.field,
this.cellTemplate?.[c.field as never] ?? c.cellTemplate ?? this.defaultCellTemplate,
])
]),
);
}
}

View File

@ -14,7 +14,7 @@ type ColumnFn<TObject extends object, TResult = unknown> = (rowData: TObject) =>
export type BaseColumn<
T extends object,
TType extends string | undefined = undefined,
TTypeParameters extends object = never
TTypeParameters extends object = never,
> = Pick<
MtxGridColumn<T>,
| 'field'

View File

@ -9,14 +9,14 @@ import { formatCurrency } from '../utils';
export class AmountCurrencyPipe implements PipeTransform {
constructor(
@Inject(LOCALE_ID) private _locale: string,
@Inject(DEFAULT_CURRENCY_CODE) private _defaultCurrencyCode: string = 'USD'
@Inject(DEFAULT_CURRENCY_CODE) private _defaultCurrencyCode: string = 'USD',
) {}
transform(
amount: unknown,
currencyCode: string = this._defaultCurrencyCode,
format: 'short' | 'long' = 'long',
exponent?: number
exponent?: number,
): unknown {
if (typeof amount === 'number')
return formatCurrency(amount, currencyCode, format, this._locale, exponent);

View File

@ -5,3 +5,4 @@ export * from './enum-keys.pipe';
export * from './enum-key-values.pipe';
export * from './amount-currency.pipe';
export * from './select.pipe';
export * from './possibly-async.pipe';

View File

@ -6,6 +6,7 @@ import { EnumKeyValuesPipe } from './enum-key-values.pipe';
import { EnumKeyPipe } from './enum-key.pipe';
import { EnumKeysPipe } from './enum-keys.pipe';
import { InlineJsonPipe } from './inline-json.pipe';
import { VPossiblyAsyncPipe } from './possibly-async.pipe';
import { VSelectPipe } from './select.pipe';
@NgModule({
@ -17,6 +18,7 @@ import { VSelectPipe } from './select.pipe';
EnumKeyValuesPipe,
AmountCurrencyPipe,
VSelectPipe,
VPossiblyAsyncPipe,
],
imports: [
CommonModule,
@ -25,6 +27,7 @@ import { VSelectPipe } from './select.pipe';
EnumKeyValuesPipe,
AmountCurrencyPipe,
VSelectPipe,
VPossiblyAsyncPipe,
],
})
export class PipesModule {}

View File

@ -0,0 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core';
import { AsyncTransform } from '../utils';
@Pipe({
standalone: true,
name: 'vPossiblyAsync',
pure: false,
})
export class VPossiblyAsyncPipe<T> extends AsyncTransform<T> implements PipeTransform {}

View File

@ -20,7 +20,7 @@ export class VSelectPipe<TObject extends object, TResult, TParams = void>
obj: TObject,
selectFn: SelectFn<TObject, TResult, TParams>,
defaultValue?: TResult,
rest: TParams[] = []
rest: TParams[] = [],
): TResult | null {
const res = obj && selectFn ? select(obj, selectFn, defaultValue, rest) : null;
if (isObservable(res) && !this.asyncPipe) {

View File

@ -1,5 +1,5 @@
import { Observable, defer, mergeScan, map, BehaviorSubject, Subject } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { Observable, defer, mergeScan, map, BehaviorSubject, ReplaySubject, skipWhile } from 'rxjs';
import { shareReplay, startWith } from 'rxjs/operators';
import { inProgressFrom, progressTo } from '../../utils';
@ -30,17 +30,19 @@ export interface Accumulator<TResultItem, TParams, TContinuationToken> {
continuationToken?: TContinuationToken;
}
const DEFAULT_SIZE = 25;
export abstract class FetchSuperclass<TResultItem, TParams = void, TContinuationToken = string> {
result$ = defer(() => this.state$).pipe(map(({ result }) => result));
hasMore$ = defer(() => this.state$).pipe(map(({ continuationToken }) => !!continuationToken));
isLoading$ = inProgressFrom(
() => this.progress$,
() => this.state$
() => this.state$,
);
private fetch$ = new Subject<Action<TParams>>();
private fetch$ = new ReplaySubject<Action<TParams>>(1);
private progress$ = new BehaviorSubject(0);
private state$ = this.fetch$.pipe(
private state$ = defer(() => this.fetch$.pipe(skipWhile(({ type }) => type !== 'load'))).pipe(
mergeScan<Action<TParams>, Accumulator<TResultItem, TParams, TContinuationToken>>(
(acc, action) => {
const params = (action.type === 'load' ? action.params : acc.params) as TParams;
@ -48,6 +50,16 @@ export abstract class FetchSuperclass<TResultItem, TParams = void, TContinuation
const continuationToken =
action.type === 'more' ? acc.continuationToken : undefined;
return this.fetch(params, { size, continuationToken }).pipe(
...((action.type === 'load'
? ([
startWith({
params,
result: [],
size,
continuationToken: undefined,
}),
] as unknown)
: []) as []),
map(({ result, continuationToken }) => ({
params,
result:
@ -55,16 +67,16 @@ export abstract class FetchSuperclass<TResultItem, TParams = void, TContinuation
size,
continuationToken,
})),
progressTo(this.progress$)
progressTo(this.progress$),
);
},
{
size: 25,
size: DEFAULT_SIZE,
result: [],
},
1
1,
),
shareReplay({ bufferSize: 1, refCount: true })
shareReplay({ bufferSize: 1, refCount: true }),
);
load(params: TParams, options: LoadOptions = {}): void {
@ -77,6 +89,6 @@ export abstract class FetchSuperclass<TResultItem, TParams = void, TContinuation
protected abstract fetch(
params: TParams,
options: FetchOptions<TContinuationToken>
options: FetchOptions<TContinuationToken>,
): Observable<FetchResult<TResultItem, TContinuationToken>>;
}

View File

@ -19,7 +19,7 @@ export class LogError {
get details() {
return Object.fromEntries(
Object.entries(this.source).filter(([k, v]) => k !== 'name' && k !== 'message' && v)
Object.entries(this.source).filter(([k, v]) => k !== 'name' && k !== 'message' && v),
);
}
@ -27,7 +27,7 @@ export class LogError {
this.source = (isObject(error)
? error
: new Error(
error ? String(error) : DEFAULT_ERROR_NAME
error ? String(error) : DEFAULT_ERROR_NAME,
)) as unknown as LogError['source'];
}

View File

@ -1,27 +1,33 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { capitalize } from 'lodash-es';
import { first, of, timeout, Observer } from 'rxjs';
import { isAsync, PossiblyAsync } from '../../utils';
import { DEFAULT_ERROR_NAME, LogError } from './log-error';
import { Operation } from './types/operation';
const DEFAULT_DURATION_MS = 3000;
const DEFAULT_ERROR_DURATION_MS = 10000;
const DEFAULT_DURATION_MS = 3_000;
const DEFAULT_ERROR_DURATION_MS = 10_000;
const DEFAULT_TIMEOUT_MS = 10_000;
@Injectable({ providedIn: 'root' })
export class NotifyLogService {
constructor(private snackBar: MatSnackBar) {}
success = (message: string = 'Completed successfully'): void => {
success = (message: PossiblyAsync<string> = 'Completed successfully'): void => {
this.notify(message);
};
error = (errors: unknown | unknown[], message?: string): void => {
error = (errors: unknown | unknown[], message?: PossiblyAsync<string>): void => {
const logErrors = (Array.isArray(errors) ? errors : [errors]).map((e) => new LogError(e));
message = message || (logErrors.length === 1 ? logErrors[0].message : DEFAULT_ERROR_NAME);
console.warn(
[`Caught error: ${message}.`, ...logErrors.map((e) => e.getLogMessage())].join('\n')
);
this.subscribeWithTimeout(message, (msg) => {
console.warn(
[`Caught error: ${msg}.`, ...logErrors.map((e) => e.getLogMessage())].join('\n'),
);
});
this.notify(message, DEFAULT_ERROR_DURATION_MS);
};
@ -67,9 +73,25 @@ export class NotifyLogService {
this.error(errors, message);
}
private notify(message: string, duration = DEFAULT_DURATION_MS) {
return this.snackBar.open(message, 'OK', {
duration,
private notify(message: PossiblyAsync<string>, duration = DEFAULT_DURATION_MS) {
this.subscribeWithTimeout(message, {
next: (msg) => {
this.snackBar.open(msg, 'OK', {
duration,
});
},
error: () => {
// TODO: Default message
},
});
}
private subscribeWithTimeout<T>(
possiblyAsync: PossiblyAsync<T>,
subscribe: Partial<Observer<T>> | Observer<T>['next'],
) {
(isAsync(possiblyAsync) ? possiblyAsync : of(possiblyAsync))
.pipe(first(), timeout(DEFAULT_TIMEOUT_MS))
.subscribe(subscribe);
}
}

View File

@ -22,7 +22,7 @@ export class QueryParamsService<P extends object> {
startWith(this.route.snapshot.queryParams),
distinctUntilChanged(isEqual),
map((params) => this.deserialize(params)),
shareReplay({ refCount: true, bufferSize: 1 })
shareReplay({ refCount: true, bufferSize: 1 }),
);
get params(): P {
@ -32,7 +32,7 @@ export class QueryParamsService<P extends object> {
constructor(
private router: Router,
private route: ActivatedRoute,
@Optional() @Inject(QUERY_PARAMS_SERIALIZERS) private readonly serializers?: Serializer[]
@Optional() @Inject(QUERY_PARAMS_SERIALIZERS) private readonly serializers?: Serializer[],
) {
// Angular @Optional not support TS syntax: `serializers: Serializer[] = []`
if (!this.serializers) {
@ -54,12 +54,15 @@ export class QueryParamsService<P extends object> {
private serialize(
params: P,
{ filter = negate(isEmpty) }: Options = {}
{ filter = negate(isEmpty) }: Options = {},
): { [key: string]: string } {
return Object.entries(params).reduce((acc, [k, v]) => {
if (filter(v, k)) acc[k] = serializeQueryParam(v, this.serializers);
return acc;
}, {} as { [key: string]: string });
return Object.entries(params).reduce(
(acc, [k, v]) => {
if (filter(v, k)) acc[k] = serializeQueryParam(v, this.serializers);
return acc;
},
{} as { [key: string]: string },
);
}
private deserialize(params: Params): P {

View File

@ -3,5 +3,5 @@ import { InjectionToken } from '@angular/core';
import { Serializer } from '../types/serializer';
export const QUERY_PARAMS_SERIALIZERS = new InjectionToken<Serializer[]>(
'query params serializers'
'query params serializers',
);

View File

@ -1,17 +1,17 @@
import { ValuesType } from 'utility-types';
import { isEmpty } from './is-empty';
import { isEmptyPrimitive } from './is-empty-primitive';
import { isEmpty } from '../empty/is-empty';
import { isEmptyPrimitive } from '../empty/is-empty-primitive';
export function clean<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends ReadonlyArray<any> | ArrayLike<any> | Record<any, any>,
TAllowRootRemoval extends boolean = false
TAllowRootRemoval extends boolean = false,
>(
obj: T,
allowRootRemoval: TAllowRootRemoval = false as TAllowRootRemoval,
isNotDeep = false,
filterPredicate: (v: unknown, k?: PropertyKey) => boolean = (v) => !isEmpty(v)
filterPredicate: (v: unknown, k?: PropertyKey) => boolean = (v) => !isEmpty(v),
): TAllowRootRemoval extends true ? T | null : T {
if (allowRootRemoval && !filterPredicate(obj)) {
return null as never;
@ -27,7 +27,7 @@ export function clean<
return Object.fromEntries(
(Object.entries(obj) as [keyof T, ValuesType<T>][])
.map(([k, v]) => [k, cleanChild(v)] as const)
.filter(([k, v]) => filterPredicate(v, k))
.filter(([k, v]) => filterPredicate(v, k)),
) as never;
}
return obj;
@ -36,7 +36,7 @@ export function clean<
export function cleanPrimitiveProps<T extends object>(
obj: T,
allowRootRemoval = false,
isNotDeep = false
isNotDeep = false,
) {
return clean(obj, allowRootRemoval, isNotDeep, (v) => !isEmptyPrimitive(v));
}

View File

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

View File

@ -0,0 +1 @@
export * from './compare-different-types';

View File

@ -8,13 +8,13 @@ export function formatCurrency(
currencyCode: string = 'USD',
format: 'short' | 'long' = 'long',
locale = 'en-GB',
exponent?: number
exponent?: number,
): string {
return ngFormatCurrency(
isNil(exponent) ? toMajor(amount, currencyCode) : toMajorByExponent(amount, exponent),
locale,
getCurrencySymbol(currencyCode, 'narrow', locale),
currencyCode,
format === 'short' ? '0.0-2' : undefined
format === 'short' ? '0.0-2' : undefined,
);
}

View File

@ -0,0 +1,2 @@
export * from './is-empty';
export * from './is-empty-primitive';

View File

@ -1,7 +1,7 @@
import { ValuesType } from 'utility-types';
export function getEnumEntries<E extends Record<PropertyKey, unknown>>(
srcEnum: E
srcEnum: E,
): [key: keyof E, value: ValuesType<E>][] {
if (!srcEnum) return [];
const entries = Object.entries(srcEnum);
@ -12,7 +12,7 @@ export function getEnumEntries<E extends Record<PropertyKey, unknown>>(
}
export function getEnumKeyValues<E extends Record<PropertyKey, unknown>>(
srcEnum: E
srcEnum: E,
): { key: keyof E; value: ValuesType<E> }[] {
return getEnumEntries(srcEnum).map(([key, value]) => ({ key, value }));
}
@ -27,14 +27,14 @@ export function getEnumValues<E extends Record<PropertyKey, unknown>>(srcEnum: E
export function getEnumKey<E extends Record<PropertyKey, unknown>>(
srcEnum: E,
value?: ValuesType<E>
value?: ValuesType<E>,
): keyof E {
return getEnumKeyValues(srcEnum).find((e) => String(e.value) === String(value))?.key as keyof E;
}
export function enumHasValue<E extends Record<PropertyKey, unknown>>(
srcEnum: E,
value: ValuesType<E> | string
value: ValuesType<E> | string,
): value is ValuesType<E> {
return getEnumValues(srcEnum).includes(value as ValuesType<E>);
}

View File

@ -21,7 +21,7 @@ export abstract class AbstractControlSuperclass<OuterType, InnerType = OuterType
.pipe(
filter(() => this.control.invalid),
take(1),
takeUntilDestroyed(this.destroyRef)
takeUntilDestroyed(this.destroyRef),
)
.subscribe(() => {
this.onValidatorChange();

View File

@ -5,5 +5,5 @@ import { FormGroupSuperclass } from './form-group-superclass.directive';
@Directive()
export abstract class FormArraySuperclass<
OuterType,
InnerType = OuterType
InnerType = OuterType,
> extends FormGroupSuperclass<OuterType, InnerType> {}

View File

@ -6,7 +6,7 @@ import { AbstractControlSuperclass } from './abstract-control-superclass';
@Directive()
export class FormControlSuperclass<
OuterType,
InnerType = OuterType
InnerType = OuterType,
> extends AbstractControlSuperclass<OuterType, InnerType> {
control = new FormControl() as FormControl<InnerType>;
}

View File

@ -20,7 +20,7 @@ export function getErrorsTree(control: AbstractControl): ValidationErrors | null
errors['formGroupErrors'] = Object.fromEntries(
Array.from(Object.entries(control.controls))
.map(([k, c]) => [k, getErrorsTree(c)])
.filter(([, v]) => !!v)
.filter(([, v]) => !!v),
) as ValidationErrors;
}
}

View File

@ -6,10 +6,10 @@ import { getValue } from './get-value';
export function getFormValueChanges<T>(
form: AbstractControl<FormControlState<T> | T>,
hasStart = false
hasStart = false,
): Observable<T> {
return form.valueChanges.pipe(
...((hasStart ? [startWith(form.value)] : []) as []),
map(() => getValue(form))
map(() => getValue(form)),
) as Observable<T>;
}

View File

@ -2,13 +2,13 @@ import { AbstractControl } from '@angular/forms';
import omitBy from 'lodash-es/omitBy';
import { filter, map } from 'rxjs/operators';
import { isEmptyPrimitive } from '../is-empty-primitive';
import { isEmptyPrimitive } from '../empty/is-empty-primitive';
import { getFormValueChanges } from './get-form-value-changes';
export function getValidValueChanges(control: AbstractControl, predicate = isEmptyPrimitive) {
return getFormValueChanges(control, true).pipe(
filter(() => control.valid),
map((value) => omitBy(value, predicate))
map((value) => omitBy(value, predicate)),
);
}

View File

@ -3,7 +3,7 @@ import { AbstractControl } from '@angular/forms';
export function setDisabled(
control?: AbstractControl,
disabled: boolean = true,
options: Parameters<AbstractControl['disable']>[0] = {}
options: Parameters<AbstractControl['disable']>[0] = {},
) {
if (!control || control.disabled === disabled) return;
if (disabled) control.disable(options);

View File

@ -7,6 +7,6 @@ export function getImportValue<T>(imp: Promise<unknown>, prop: string = 'default
map((module) => {
if (!prop) return module as T;
return get(module, prop, null) as T;
})
}),
);
}

View File

@ -0,0 +1 @@
export * from './get-import-value';

View File

@ -7,10 +7,8 @@ export * from './date';
export * from './currency';
export * from './object';
export * from './enum';
export * from './pipe';
export * from './clean';
export * from './is-empty';
export * from './compare-different-types';
export * from './is-empty-primitive';
export * from './get-import-value';
export * from './empty';
export * from './compare';
export * from './import';

View File

@ -9,7 +9,7 @@ export function select<TObject extends object, TResult, TParams = void>(
obj: TObject,
selectFn: SelectFn<TObject, TResult, TParams>,
defaultValue?: TResult,
restParams: TParams[] = []
restParams: TParams[] = [],
): TResult | Observable<TResult> {
if (typeof selectFn === 'string') return _get(obj, selectFn, defaultValue) as TResult;
return selectFn(obj, ...restParams);
@ -19,7 +19,7 @@ export function selectAsObservable<TObject extends object, TResult, TParams = vo
obj: TObject,
selectFn: SelectFn<TObject, TResult, TParams>,
defaultValue?: TResult,
restParams: TParams[] = []
restParams: TParams[] = [],
): TResult | Observable<TResult> {
const res = select(obj, selectFn, defaultValue, restParams);
return isObservable(res) ? res : of(res);

View File

@ -14,7 +14,7 @@ export interface Result<T> {
export function forkJoinToResult<T>(
sources: Observable<T>[],
concurrency = 4,
progress$?: Subject<number>
progress$?: Subject<number>,
): Observable<Result<T>[]> {
let completed = 0;
if (progress$) progress$.next(getProgressByCount(sources.length));
@ -32,16 +32,16 @@ export function forkJoinToResult<T>(
finalize(() => {
completed += 1;
if (progress$) progress$.next(getProgressByCount(sources.length, completed));
})
)
}),
),
),
concurrency
concurrency,
).pipe(
scan((acc, value) => {
acc.push(value);
return acc;
}, [] as Result<T>[]),
takeLast(1),
map((r) => r.sort((a, b) => a.index - b.index))
map((r) => r.sort((a, b) => a.index - b.index)),
);
}

View File

@ -8,6 +8,6 @@ export function inProgressFrom(progress: ObservableOrFn<number>, main?: Observab
map(Boolean),
// make async to bypass angular detect changes
delay(0),
share()
share(),
);
}

View File

@ -2,7 +2,7 @@ import { catchError, EMPTY, MonoTypeOperatorFunction, of } from 'rxjs';
export function passError<T>(
handler: (err: unknown) => void,
value?: T
value?: T,
): MonoTypeOperatorFunction<T> {
return (source) =>
source.pipe(
@ -10,6 +10,6 @@ export function passError<T>(
handler(err);
if (arguments.length >= 2) return of(value as T);
return EMPTY;
})
}),
);
}

View File

@ -2,7 +2,7 @@ import { BehaviorSubject, defer, MonoTypeOperatorFunction, isObservable } from '
import { finalize } from 'rxjs/operators';
export function progressTo<T>(
subject$: BehaviorSubject<number> | (() => BehaviorSubject<number>)
subject$: BehaviorSubject<number> | (() => BehaviorSubject<number>),
): MonoTypeOperatorFunction<T> {
const getSub = () => (isObservable(subject$) ? subject$ : subject$());
return (src$) =>

View File

@ -0,0 +1,31 @@
import { AsyncPipe } from '@angular/common';
import { ChangeDetectorRef, Inject, Injectable, OnDestroy, PipeTransform } from '@angular/core';
import { Observable } from 'rxjs';
import { isAsync, PossiblyAsync } from './is-async';
@Injectable()
export abstract class AsyncTransform<T = unknown> implements OnDestroy, PipeTransform {
private asyncPipe?: AsyncPipe;
private cdr = Inject(ChangeDetectorRef);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transform(value: any, ...args: any[]): any {
return this.asyncTransform(this.getValue(value, ...args));
}
ngOnDestroy() {
this.asyncPipe?.ngOnDestroy?.();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected getValue(value: any, ..._args: any[]): Observable<T> {
return value;
}
protected asyncTransform(value: PossiblyAsync<T>) {
if (!isAsync(value)) return value;
if (!this.asyncPipe) this.asyncPipe = new AsyncPipe(this.cdr);
return this.asyncPipe.transform(value);
}
}

View File

@ -0,0 +1,2 @@
export * from './async-transform';
export * from './is-async';

View File

@ -0,0 +1,8 @@
import { isObservable, Observable } from 'rxjs';
export type Async<T> = Observable<T>;
export type PossiblyAsync<T> = Async<T> | T;
export function isAsync<T>(value: PossiblyAsync<T>): value is Async<T> {
return isObservable(value);
}

View File

@ -3,6 +3,7 @@
"printWidth": 100,
"singleQuote": true,
"tabWidth": 4,
"plugins": ["prettier-plugin-organize-attributes"],
"attributeSort": "ASC",
"attributeGroups": [
"$ANGULAR_ELEMENT_REF",
@ -20,6 +21,10 @@
"options": {
"tabWidth": 2
}
},
{
"files": "*.svg",
"options": { "parser": "html" }
}
]
}

View File

@ -1,14 +1,14 @@
{
"name": "@vality/prettier-config",
"version": "0.1.0",
"version": "3.0.0",
"publishConfig": {
"access": "public"
},
"main": "index.json",
"peerDependencies": {
"prettier": "^2.0.0"
"prettier": "^3.0.0"
},
"dependencies": {
"prettier-plugin-organize-attributes": "^0.0.5"
"prettier-plugin-organize-attributes": "^1.0.0"
}
}