Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ jobs:
- uses: valitydev/action-frontend/setup-install@v3
with:
release: true
force: true
- name: Build
run: npm run build
- name: Deploy image
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: valitydev/action-frontend/setup-install@v3
with:
force: true
- name: Install Playwright
run: npx playwright install chromium --with-deps
- name: Check
Expand Down
2,141 changes: 209 additions & 1,932 deletions package-lock.json

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@
},
"private": true,
"dependencies": {
"@angular/cdk": "^22.0.1",
"@angular/common": "^22.0.1",
"@angular/compiler": "^22.0.1",
"@angular/core": "^22.0.1",
"@angular/forms": "^22.0.1",
"@angular/material": "^22.0.1",
"@angular/material-date-fns-adapter": "^22.0.1",
"@angular/platform-browser": "^22.0.1",
"@angular/router": "^22.0.1",
"@angular/cdk": "^22.0.5",
"@angular/common": "^22.0.7",
"@angular/compiler": "^22.0.7",
"@angular/core": "^22.0.7",
"@angular/forms": "^22.0.7",
"@angular/material": "^22.0.5",
"@angular/material-date-fns-adapter": "^22.0.5",
"@angular/platform-browser": "^22.0.7",
"@angular/router": "^22.0.7",
"@ngneat/input-mask": "^6.1.0",
"@tailwindcss/postcss": "^4.3.1",
"@vality/domain-proto": "^2.0.2-b07c077.0",
"@vality/domain-proto": "^2.0.2-239638b.0",
"@vality/fistful-proto": "^2.0.1-9864622.0",
"@vality/machinegun-proto": "^1.0.1-273f2f3.0",
"@vality/magista-proto": "^2.0.2-bd58cea.0",
Expand All @@ -62,9 +62,9 @@
"zone.js": "~0.16.2"
},
"devDependencies": {
"@angular/build": "^22.0.3",
"@angular/cli": "^22.0.2",
"@angular/compiler-cli": "^22.0.1",
"@angular/build": "^22.0.7",
"@angular/cli": "^22.0.7",
"@angular/compiler-cli": "^22.0.7",
"@cspell/dict-ru_ru": "^2.3.2",
"@eslint/js": "^10.0.1",
"@types/lodash-es": "^4.17.12",
Expand Down
22 changes: 11 additions & 11 deletions projects/matez/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@
"access": "public"
},
"peerDependencies": {
"@angular/cdk": "^22.0.1",
"@angular/common": "^22.0.1",
"@angular/core": "^22.0.1",
"@angular/forms": "^22.0.1",
"@angular/material": "^22.0.1",
"@angular/material-date-fns-adapter": "^22.0.1",
"@angular/router": "^22.0.1",
"@angular/cdk": "^22.0.5",
"@angular/common": "^22.0.7",
"@angular/core": "^22.0.7",
"@angular/forms": "^22.0.7",
"@angular/material": "^22.0.5",
"@angular/material-date-fns-adapter": "^22.0.5",
"@angular/router": "^22.0.7",
"date-fns": "^4.4.0",
"lodash-es": "^4.18.1",
"rxjs": "^7.8.2",
"utility-types": "^3.11.0"
},
"dependencies": {
"@ng-matero/extensions": "^22.0.0",
"@s-libs/js-core": "^21.0.0",
"@s-libs/micro-dash": "^21.0.0",
"@s-libs/ng-core": "^21.0.0",
"@s-libs/rxjs-core": "^21.0.0",
"@s-libs/js-core": "^22.0.0-next.2",
"@s-libs/micro-dash": "^22.0.0-next.2",
"@s-libs/ng-core": "^22.0.0-next.2",
"@s-libs/rxjs-core": "^22.0.0-next.2",
"auto-bind": "^5.0.1",
"dinero.js": "^2.0.2",
"papaparse": "^5.5.3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
[matAutocomplete]="auto"
[ngClass]="{ 'mat-mono': mono }"
[required]="required"
[type]="options?.length ? 'text' : type"
matInput
type="text"
(blur)="onBlur()"
/>

<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption>
@for (option of filteredOptions$ | async; track option) {
@for (option of externalSearch() ? options() : (filteredOptions$ | async); track option) {
<mat-option [value]="option.value">
<div [ngClass]="{ 'mat-mono': mono }" class="label">{{ option.label }}</div>
@if (option.description) {
Expand All @@ -33,7 +34,7 @@
<mat-icon>clear</mat-icon>
</button>
}
@if (options?.length) {
@if (options()?.length) {
<button
mat-icon-button
(click)="
Expand All @@ -45,9 +46,11 @@
</button>
}
</div>
<mat-hint>{{ hintText }}</mat-hint>
<mat-hint>{{ hintText$ | async }}</mat-hint>
<mat-error>{{
error ??
((selected$ | async) ? (selected$ | async)?.label + (hint ? ' | ' + hint : '') : hint)
((selected$ | async)
? (selected$ | async)?.label + (hint() ? ' | ' + hint() : '')
: hint())
}}</mat-error>
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { BehaviorSubject, merge } from 'rxjs';
import { isNil } from 'lodash-es';
import { combineLatest } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';

import {
ChangeDetectionStrategy,
Component,
DestroyRef,
Input,
OnChanges,
booleanAttribute,
inject,
input,
output,
} from '@angular/core';
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';

import { ComponentChanges, FormControlSuperclass, createControlProviders } from '../../../utils';
import {
FormControlSuperclass,
createControlProviders,
getValidValueChanges,
getValueChanges,
} from '../../../utils';
import { Option } from '../types';
import { searchOptions } from '../utils';
import { getHintText } from '../utils/get-hint-text';
Expand All @@ -22,36 +32,77 @@ import { getHintText } from '../utils/get-hint-text';
changeDetection: ChangeDetectionStrategy.Eager,
standalone: false,
})
export class AutocompleteFieldComponent<T> extends FormControlSuperclass<T> implements OnChanges {
@Input() options: Option<T>[] = [];
export class AutocompleteFieldComponent<T> extends FormControlSuperclass<T> {
private dr = inject(DestroyRef);

options = input<Option<T>[]>([]);
hint = input<string>(undefined);
externalSearch = input<boolean>(false, { transform: booleanAttribute });
searchChange = output<string>();
requireSelection = input<boolean>(false, { transform: booleanAttribute });

@Input() label?: string;
@Input() hint?: string;
@Input() error?: string;
@Input() type = 'text';
@Input() type: 'text' | 'number' = 'text';

@Input({ transform: booleanAttribute }) mono = false;
@Input({ transform: booleanAttribute }) required = false;

get hintText() {
return getHintText(this.options, [this.control.value], this.hint, { showLabel: true });
}

options$ = new BehaviorSubject<Option<T>[]>([]);
selected$ = merge(this.control.valueChanges, this.options$).pipe(
map(() => (this.options || []).find((o) => o.value === this.control.value)),
hintText$ = combineLatest([
getValidValueChanges(this.control),
toObservable(this.options),
toObservable(this.hint),
]).pipe(
map(([value, options, hint]) => getHintText(options, [value], hint, { showLabel: true })),
shareReplay({ refCount: true, bufferSize: 1 }),
);
filteredOptions$ = merge(this.control.valueChanges, this.options$).pipe(
map(() => String(this.control.value ?? '').toLowerCase()),
map((filter) => searchOptions(this.options, filter)),
selected$ = combineLatest([getValueChanges(this.control), toObservable(this.options)]).pipe(
map(([value, options]) => (options || []).find((o) => o.value === value)),
shareReplay({ refCount: true, bufferSize: 1 }),
);
filteredOptions$ = combineLatest([
getValueChanges(this.control).pipe(map((value) => String(value ?? '').toLowerCase())),
toObservable(this.options),
]).pipe(
map(([value, options]) => searchOptions(options, value)),
shareReplay({ refCount: true, bufferSize: 1 }),
);

override ngOnInit(): void {
super.ngOnInit();
getValueChanges(this.control)
.pipe(takeUntilDestroyed(this.dr))
.subscribe((value) => {
this.searchChange.emit(String(value ?? ''));
});
}

onBlur(): void {
const value = this.control.value;
const validValue = this.getValidValue(value);
if (value !== validValue) {
this.control.setValue(validValue);
}
}

override ngOnChanges(changes: ComponentChanges<AutocompleteFieldComponent<T>>) {
super.ngOnChanges(changes);
if (changes.options) {
this.options$.next(this.options || []);
protected override innerToOuterValue(inner: T): T {
return this.getValidValue(inner);
}

private getValidValue(value: T) {
if (isNil(value)) {
return value;
}
if (
value === '' ||
(this.requireSelection() &&
!this.options().some((o) => String(o.value) === String(value)))
) {
return undefined;
}
if (this.type === 'text') return value;
const res = Number(value);
if (isNaN(res)) return undefined;
return res as T;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@
<mat-label>{{ label }}</mat-label>

<mtx-select
#select
[closeOnSelect]="!multiple"
[formControl]="control"
[items]="options"
[loading]="progress"
[markFirst]="true"
[multiple]="multiple"
[notFoundText]="externalSearch && !searchStr ? 'Enter your search term' : 'No items found'"
[notFoundText]="externalSearch && !search() ? 'Enter your search term' : 'No items found'"
[required]="required"
[searchable]="true"
[searchFn]="search"
[searchFn]="searchFn"
[selectableGroup]="true"
[selectableGroupAsModel]="false"
bindLabel="label"
bindValue="value"
groupBy="type"
(clear)="searchChange.emit(''); searchStr = ''"
(search)="searchChange.emit($event.term); searchStr = $event.term"
(change)="close(select)"
(clear)="search.set('')"
(search)="search.set($event.term)"
>
<ng-template let-index="index" let-item="item" let-item$="item$" ng-optgroup-tmp>
@if (multiple) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { first, timer } from 'rxjs';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnInit,
Output,
booleanAttribute,
model,
} from '@angular/core';
import { MatFormFieldAppearance } from '@angular/material/form-field';
import { MtxSelect } from '@ng-matero/extensions/select';

import { FormControlSuperclass, createControlProviders } from '../../../utils';
import { Option } from '../types';
Expand All @@ -29,7 +29,7 @@ export class SelectFieldComponent<T = unknown>
implements OnInit
{
@Input() options: Option<T>[] = [];
@Output() searchChange = new EventEmitter<string>();
search = model<string>('');

@Input() appearance!: MatFormFieldAppearance;

Expand All @@ -44,15 +44,13 @@ export class SelectFieldComponent<T = unknown>

@Input() size?: 'small' | '';

searchStr: string = '';

override ngOnInit() {
super.ngOnInit();
if (this.externalSearch) {
timer(0)
.pipe(first())
.subscribe(() => {
this.searchChange.emit(String(this.control.value));
this.search.set(String(this.control.value));
});
}
}
Expand All @@ -68,7 +66,16 @@ export class SelectFieldComponent<T = unknown>
);
}

search = (term: string, item: Option<T>) => {
searchFn = (term: string, item: Option<T>) => {
return this.externalSearch || isSearchOption(item, term.toLowerCase());
};

// TODO: close not working for mouse click
close(select: MtxSelect) {
if (!this.multiple) {
timer(0)
.pipe(first())
.subscribe(() => select.close());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export interface Option<T> {
import { ThemePalette } from '../../../styles';
import { PossiblyAsync } from '../../../utils';

export interface Option<T = unknown> {
value: T;
label: string;
label?: string;
description?: string;
type?: unknown;
details?: PossiblyAsync<unknown>;
color?: ThemePalette;
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ export class SwitchButtonComponent<T = number>
this.control.setValue(this.getValue(this.nextIndex));
}

override ngOnChanges(changes: ComponentChanges<SwitchButtonComponent<T>>) {
super.ngOnChanges(changes);
ngOnChanges(changes: ComponentChanges<SwitchButtonComponent<T>>) {
if (changes.states && this.states.length) {
if (!isNil(this.states?.[0]?.value)) {
this.hasChangeValue = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ export class ObservableResource<
value: toSignal(value$, { injector: this.injector }),
map: (fn: (value: TResult) => PossiblyAsync<TNewResult>) =>
this.map(fn, value$ as never),
getFirstValue: () =>
combineLatest([value$, this.isLoading$]).pipe(
skipWhile(([_, isLoading]) => isLoading),
map(([value]) => value),
take(1),
),
}) as never;
}
}
Expand Down
2 changes: 1 addition & 1 deletion projects/matez/src/lib/utils/operators/tap-log.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MonoTypeOperatorFunction, tap } from 'rxjs';

export function tapLog<T>(message = `Log`): MonoTypeOperatorFunction<T> {
export function tapLog<T>(message: string | number = 'Log'): MonoTypeOperatorFunction<T> {
return (source) => {
return source.pipe(tap((value) => console.log(`${message}:`, value)));
};
Expand Down
Loading
Loading