diff --git a/eslint.config.js b/eslint.config.js index e00d45c6..81e6b99e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -43,9 +43,14 @@ const baseTsLintConfig = { { "selector": "objectLiteralProperty", "format": null } ], '@typescript-eslint/no-deprecated': 'error', - '@typescript-eslint/no-empty-function': 'warn', + '@typescript-eslint/no-empty-function': ['warn', { + allow: ['arrowFunctions'], + }], '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/no-misused-promises': ['error', { + checksVoidReturn: { arguments: false }, + checksConditionals: true, + }], '@typescript-eslint/no-unsafe-argument': 'error', '@typescript-eslint/no-unsafe-assignment': 'error', '@typescript-eslint/no-unsafe-call': 'error', @@ -100,7 +105,10 @@ module.exports = tseslint.config( '@typescript-eslint/no-empty-function': 'off', // sometimes necessary, otherwise not that harmful '@typescript-eslint/no-explicit-any': 'off', // to much noise :( '@typescript-eslint/no-floating-promises': 'off', // to much noise :( - '@typescript-eslint/no-misused-promises': 'warn', + '@typescript-eslint/no-misused-promises': ['warn', { + checksVoidReturn: { arguments: false }, + checksConditionals: true, + }], '@typescript-eslint/no-unsafe-argument': 'off', // useful, but some false positives '@typescript-eslint/no-unsafe-assignment': 'off', // to many errors in existing code, some because @zvoove/components or @angular/components typing is bad '@typescript-eslint/no-unsafe-call': 'off', // useful, but some false positives diff --git a/projects/components/core/src/time/time-adapter.ts b/projects/components/core/src/time/time-adapter.ts index c03e8af1..a102fc88 100644 --- a/projects/components/core/src/time/time-adapter.ts +++ b/projects/components/core/src/time/time-adapter.ts @@ -71,14 +71,12 @@ export abstract class ZvTimeAdapter { * @returns The deserialized time object, either a valid time, null if the value can be * deserialized into a null time (e.g. the empty string), or an invalid time. */ - deserialize(value: any): TTime | null { + deserialize(value: unknown): TTime | null { if (!value) { return null; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - if (this.isTimeInstance(value) && this.isValid(value)) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return value; + if (this.isTimeInstance(value) && this.isValid(value as TTime)) { + return value as TTime; } if (typeof value === 'string') { const matches = value.match(/^(\d{2}):(\d{2})$/); diff --git a/projects/components/core/src/time/time-formats.ts b/projects/components/core/src/time/time-formats.ts index bee23169..589b97cd 100644 --- a/projects/components/core/src/time/time-formats.ts +++ b/projects/components/core/src/time/time-formats.ts @@ -2,10 +2,10 @@ import { InjectionToken } from '@angular/core'; export interface ZvTimeFormats { parse: { - timeInput: any; + timeInput: unknown; }; display: { - timeInput: any; + timeInput: unknown; }; } diff --git a/projects/components/date-time-input/src/date-time-input.component.html b/projects/components/date-time-input/src/date-time-input.component.html index 805c9368..08e372a8 100644 --- a/projects/components/date-time-input/src/date-time-input.component.html +++ b/projects/components/date-time-input/src/date-time-input.component.html @@ -25,7 +25,7 @@ formControlName="time" pattern="[0-9][0-9]?:?[0-9][0-9]?" (focus)="_onFocus()" - (blur)="_onBlur(true)" + (blur)="_onBlur()" (keydown)="_onTimeInputKeydown($event)" #time /> diff --git a/projects/components/date-time-input/src/date-time-input.component.ts b/projects/components/date-time-input/src/date-time-input.component.ts index 3eb3041e..c8ccf786 100644 --- a/projects/components/date-time-input/src/date-time-input.component.ts +++ b/projects/components/date-time-input/src/date-time-input.component.ts @@ -1,3 +1,9 @@ +/* eslint-disable @angular-eslint/no-conflicting-lifecycle -- + Both DoCheck and OnChanges are required: OnChanges notifies MatFormField + of input changes via stateChanges.next(), while DoCheck runs + updateErrorState() which depends on parent form submission state that + cannot be observed reactively. This follows Angular Material's own + MatInput implementation. */ import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { @@ -27,6 +33,7 @@ import { NgControl, NgForm, ReactiveFormsModule, + ValidationErrors, ValidatorFn, Validators, } from '@angular/forms'; @@ -167,7 +174,7 @@ export class ZvDateTimeInput timePlaceholder = this.dateTimeAdapter.timeAdapter.parseFormatExample(); /** `View -> model callback called when value changes` */ - _onChange: (value: any) => void = () => {}; + _onChange: (value: TDateTime | null) => void = () => {}; /** `View -> model callback called when input has been touched` */ _onTouched = () => {}; @@ -254,7 +261,7 @@ export class ZvDateTimeInput @ViewChild(MatDatepickerInput) public matDateInput!: MatDatepickerInput; @ViewChild(ZvTimeInput) public zvTimeInput!: ZvTimeInput; _childValidators: ValidatorFn[] = [(control) => this.matDateInput?.validate(control), (control) => this.zvTimeInput?.validate(control)]; - validate(control: AbstractControl): Record | null { + validate(control: AbstractControl): ValidationErrors | null { const errors = this._childValidators.map((v) => v(control)).filter((error) => error); if (!errors.length) { if (this._form.value.time && !this._form.value.date) { @@ -278,10 +285,8 @@ export class ZvDateTimeInput * * @param value New value to be written to the model. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - writeValue(value: any): void { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - this._assignValue(value, { assignForm: true, emitChange: false }); + writeValue(value: unknown): void { + this._assignValue(value as TDateTime | null, { assignForm: true, emitChange: false }); } /** @@ -291,8 +296,7 @@ export class ZvDateTimeInput * * @param fn Callback to be triggered when the value changes. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - registerOnChange(fn: (value: any) => void): void { + registerOnChange(fn: (value: TDateTime | null) => void): void { this._onChange = fn; } @@ -356,7 +360,7 @@ export class ZvDateTimeInput * Calls the touched callback only if the panel is closed. Otherwise, the trigger will * "blur" to the panel when it opens, causing a false positive. */ - _onBlur(_formatTime = false) { + _onBlur() { this._focused = false; if (!this.disabled) { diff --git a/projects/components/date-time-input/src/time-input.directive.ts b/projects/components/date-time-input/src/time-input.directive.ts index 9fb4ffe2..0227ef80 100644 --- a/projects/components/date-time-input/src/time-input.directive.ts +++ b/projects/components/date-time-input/src/time-input.directive.ts @@ -8,6 +8,7 @@ import { OnChanges, OnDestroy, Output, + Provider, SimpleChanges, forwardRef, inject, @@ -46,14 +47,14 @@ export class ZvTimeInputEvent { } /** @docs-private */ -export const ZV_TIME_VALUE_ACCESSOR: any = { +export const ZV_TIME_VALUE_ACCESSOR: Provider = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ZvTimeInput), multi: true, }; /** @docs-private */ -export const ZV_TIME_VALIDATORS: any = { +export const ZV_TIME_VALIDATORS: Provider = { provide: NG_VALIDATORS, useExisting: forwardRef(() => ZvTimeInput), multi: true, @@ -85,8 +86,7 @@ export class ZvTimeInput implements ControlValueAccessor, AfterViewInit, get value(): TTime | null { return this._value; } - set value(value: any) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + set value(value: TTime | null) { this._assignValueProgrammatically(value); } protected _value: TTime | null = null; @@ -130,7 +130,7 @@ export class ZvTimeInput implements ControlValueAccessor, AfterViewInit, _onTouched = () => {}; _validatorOnChange = () => {}; - private _cvaOnChange: (value: any) => void = () => {}; + private _cvaOnChange: (value: TTime | null) => void = () => {}; private _localeSubscription = Subscription.EMPTY; /** The form control validator for whether the input parses. */ @@ -179,12 +179,12 @@ export class ZvTimeInput implements ControlValueAccessor, AfterViewInit, } // Implemented as part of ControlValueAccessor. - writeValue(value: TTime): void { - this._assignValueProgrammatically(value); + writeValue(value: unknown): void { + this._assignValueProgrammatically(value as TTime | null); } // Implemented as part of ControlValueAccessor. - registerOnChange(fn: (value: any) => void): void { + registerOnChange(fn: (value: TTime | null) => void): void { this._cvaOnChange = fn; } diff --git a/projects/components/eslint.config.js b/projects/components/eslint.config.js index 3ac11143..9160fe93 100644 --- a/projects/components/eslint.config.js +++ b/projects/components/eslint.config.js @@ -8,7 +8,20 @@ module.exports = tseslint.config( files: ["**/*.ts"], rules: { "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-unused-vars": ["warn", { + args: "all", + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + ignoreRestSiblings: true, + }], + }, + }, + { + files: ["**/*.spec.ts"], + rules: { + "@typescript-eslint/no-explicit-any": "off", }, }, { diff --git a/projects/components/file-input/src/file-input.component.ts b/projects/components/file-input/src/file-input.component.ts index 9722b8cc..bbd44394 100644 --- a/projects/components/file-input/src/file-input.component.ts +++ b/projects/components/file-input/src/file-input.component.ts @@ -1,3 +1,9 @@ +/* eslint-disable @angular-eslint/no-conflicting-lifecycle -- + Both DoCheck and OnChanges are required: OnChanges notifies MatFormField + of input changes via stateChanges.next(), while DoCheck runs + updateErrorState() which depends on parent form submission state that + cannot be observed reactively. This follows Angular Material's own + MatInput implementation. */ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { diff --git a/projects/components/flip-container/src/flip-container.component.ts b/projects/components/flip-container/src/flip-container.component.ts index 94ea72ba..fd82f0a5 100644 --- a/projects/components/flip-container/src/flip-container.component.ts +++ b/projects/components/flip-container/src/flip-container.component.ts @@ -58,14 +58,13 @@ export class ZvFlipContainer implements AfterViewInit { this.show('back'); } - private _timerRef: any = 0; + private _timerRef: ReturnType | null = null; public show(show: 'back' | 'front') { if (this._active !== show) { this._active = show; this.cd.markForCheck(); this._flipStart(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - clearTimeout(this._timerRef); + clearTimeout(this._timerRef!); this._timerRef = setTimeout(() => { this._flipDone(); }, 300); diff --git a/projects/components/form-base/src/form.service.spec.ts b/projects/components/form-base/src/form.service.spec.ts index 6642700c..24b352b4 100644 --- a/projects/components/form-base/src/form.service.spec.ts +++ b/projects/components/form-base/src/form.service.spec.ts @@ -9,7 +9,6 @@ import { IZvFormError, IZvFormErrorData } from './models'; @Injectable({ providedIn: 'root' }) class TestZvFormService extends BaseZvFormService { - // eslint-disable-next-line @typescript-eslint/no-unused-vars public getLabel(_formControl: FormControl): Observable | null { return null; } diff --git a/projects/components/form-base/src/form.service.ts b/projects/components/form-base/src/form.service.ts index 42fcaa79..34468c93 100644 --- a/projects/components/form-base/src/form.service.ts +++ b/projects/components/form-base/src/form.service.ts @@ -46,9 +46,7 @@ export abstract class BaseZvFormService extends ZvFormService { */ public filterErrors( errorData: IZvFormErrorData[], - // eslint-disable-next-line @typescript-eslint/no-unused-vars _includeControls: boolean, - // eslint-disable-next-line @typescript-eslint/no-unused-vars _source: 'form' | 'control' ): Observable { return of(errorData); diff --git a/projects/components/form-base/src/helpers.ts b/projects/components/form-base/src/helpers.ts index c9115923..18cdf604 100644 --- a/projects/components/form-base/src/helpers.ts +++ b/projects/components/form-base/src/helpers.ts @@ -8,13 +8,10 @@ export function hasRequiredField(abstractControl: AbstractControl): boolean { } } if (abstractControl instanceof FormGroup || abstractControl instanceof FormArray) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const controls: any = abstractControl.controls; // any because of https://github.com/microsoft/TypeScript/issues/32552 - for (const controlName in controls) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (controls[controlName]) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access - if (hasRequiredField(controls[controlName])) { + const controls = abstractControl.controls; + for (const control of Object.values(controls)) { + if (control) { + if (hasRequiredField(control)) { return true; } } @@ -29,9 +26,10 @@ export function hasRequiredField(abstractControl: AbstractControl): boolean { * * @param control The control class (MatSlider, MatSelect, ...) */ +/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment -- + duck-typing against Angular Material internals with private fields (MatSlider._slider, _knobRadius, _step) */ export function getControlType(control: any): string | null { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const controlId: string = control.id /* MatFormFieldControl, z.B. checkbox */ || control.name; /* mat-radio-group */ + const controlId: string = control.id /* MatFormFieldControl, z.B. checkbox */ || control.name /* mat-radio-group */ || ''; if (controlId) { const parts = controlId.split('-'); if (parts[parts.length - 1].match(/[0-9]/)) { @@ -40,10 +38,10 @@ export function getControlType(control: any): string | null { return parts.join('-'); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (control._slider !== undefined || (control._knobRadius !== undefined && control._step !== undefined)) { return 'mat-slider'; } return null; } +/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */ diff --git a/projects/components/form-field/src/dummy-mat-form-field-control.ts b/projects/components/form-field/src/dummy-mat-form-field-control.ts index 8b1eb964..c45d58dc 100644 --- a/projects/components/form-field/src/dummy-mat-form-field-control.ts +++ b/projects/components/form-field/src/dummy-mat-form-field-control.ts @@ -76,8 +76,12 @@ export class DummyMatFormFieldControl implements MatFormFieldControl, On } } - public onContainerClick(): void {} - public setDescribedByIds(): void {} + public onContainerClick(): void { + /* noop - required by MatFormFieldControl */ + } + public setDescribedByIds(): void { + /* noop - required by MatFormFieldControl */ + } public onChange = () => {}; public onTouched = () => {}; @@ -92,11 +96,19 @@ export class DummyMatFormFieldControl implements MatFormFieldControl, On } } - public writeValue() {} + public writeValue() { + /* noop - required by ControlValueAccessor */ + } - public registerOnChange() {} + public registerOnChange() { + /* noop - required by ControlValueAccessor */ + } - public registerOnTouched(): void {} + public registerOnTouched(): void { + /* noop - required by ControlValueAccessor */ + } - public setDisabledState(): void {} + public setDisabledState(): void { + /* noop - required by ControlValueAccessor */ + } } diff --git a/projects/components/form-field/src/form-field.component.ts b/projects/components/form-field/src/form-field.component.ts index 0c1491bb..b53c4f7d 100644 --- a/projects/components/form-field/src/form-field.component.ts +++ b/projects/components/form-field/src/form-field.component.ts @@ -90,7 +90,7 @@ export class ZvFormField implements OnChanges, AfterContentChecked, OnDestroy { this._labelChild = value; this.updateLabel(); if (this._matFormField) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access -- accessing Angular Material internal _changeDetectorRef (this._matFormField as any)._changeDetectorRef.markForCheck(); } } @@ -184,9 +184,9 @@ export class ZvFormField implements OnChanges, AfterContentChecked, OnDestroy { this._matFormField._control = this.matFormFieldControl; this.emulated = this.matFormFieldControl instanceof DummyMatFormFieldControl; // This tells the mat-input that it is inside a mat-form-field - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access -- accessing Angular Material internal _isInFormField if ((this.matFormFieldControl as any)._isInFormField !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access -- accessing Angular Material internal _isInFormField (this.matFormFieldControl as any)._isInFormField = true; } this.realFormControl = getRealFormControl(this._ngControl, this.matFormFieldControl); @@ -201,7 +201,7 @@ export class ZvFormField implements OnChanges, AfterContentChecked, OnDestroy { if (this.formControl) { if (this.formsService.tryDetectRequired) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access -- dynamically setting required on MatFormFieldControl (this.matFormFieldControl as any).required = hasRequiredField(this.formControl); } @@ -264,7 +264,7 @@ export class ZvFormField implements OnChanges, AfterContentChecked, OnDestroy { // when only our own component is marked for check, then the label will not be shown // when labelText$ didn't run synchronously - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access -- accessing Angular Material internal _changeDetectorRef (this._matFormField as any)._changeDetectorRef.markForCheck(); }); } diff --git a/projects/components/form/src/form.component.spec.ts b/projects/components/form/src/form.component.spec.ts index 9d2a48a6..35483cc4 100644 --- a/projects/components/form/src/form.component.spec.ts +++ b/projects/components/form/src/form.component.spec.ts @@ -273,7 +273,6 @@ describe('ZvForm', () => { }, disconnect: () => {}, } as unknown as IntersectionObserver; - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const fixture = TestBed.createComponent(TestDataSourceComponent); diff --git a/projects/components/number-input/src/number-input.component.ts b/projects/components/number-input/src/number-input.component.ts index 636b9ca4..7fbdc03c 100644 --- a/projects/components/number-input/src/number-input.component.ts +++ b/projects/components/number-input/src/number-input.component.ts @@ -1,3 +1,9 @@ +/* eslint-disable @angular-eslint/no-conflicting-lifecycle -- + Both DoCheck and OnChanges are required: OnChanges notifies MatFormField + of input changes via stateChanges.next(), while DoCheck runs + updateErrorState() which depends on parent form submission state that + cannot be observed reactively. This follows Angular Material's own + MatInput implementation. */ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import type { ElementRef } from '@angular/core'; import { @@ -233,7 +239,7 @@ export class ZvNumberInput implements ControlValueAccessor, MatFormFieldControl< _ariaDescribedby = ''; _formattedValue = ''; - _timer: any; + _timer: ReturnType | null = null; _decimalSeparator!: string; _thousandSeparator!: string; _calculatedDecimals: number | null = null; @@ -242,7 +248,7 @@ export class ZvNumberInput implements ControlValueAccessor, MatFormFieldControl< @ViewChild('inputfield', { static: true }) _inputfieldViewChild!: ElementRef; - _onModelChange = (_val: any) => {}; + _onModelChange = (_val: number | null) => {}; _onModelTouched = () => {}; constructor() { @@ -322,12 +328,11 @@ export class ZvNumberInput implements ControlValueAccessor, MatFormFieldControl< this._inputfieldViewChild.nativeElement.focus(options); } - writeValue(value: any): void { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + writeValue(value: number | null): void { this.value = value; } - registerOnChange(fn: (val: any) => void): void { + registerOnChange(fn: (val: number | null) => void): void { this._onModelChange = fn; } @@ -352,8 +357,7 @@ export class ZvNumberInput implements ControlValueAccessor, MatFormFieldControl< _clearTimer() { if (this._timer) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - clearInterval(this._timer); + clearTimeout(this._timer); } } @@ -379,13 +383,12 @@ export class ZvNumberInput implements ControlValueAccessor, MatFormFieldControl< } _formatValue() { - const value: any = this.value; + const value = this.value; if (value == null) { this._formattedValue = ''; } else { const decimals = this._getDecimals(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - this._formattedValue = value.toLocaleString(this.localeId, { maximumFractionDigits: decimals }); + this._formattedValue = value.toLocaleString(this.localeId, { maximumFractionDigits: decimals ?? undefined }); } if (this._inputfieldViewChild && this._inputfieldViewChild.nativeElement) { diff --git a/projects/components/select/src/data/select-data-source.ts b/projects/components/select/src/data/select-data-source.ts index 06b8c9d0..2eb8b61c 100644 --- a/projects/components/select/src/data/select-data-source.ts +++ b/projects/components/select/src/data/select-data-source.ts @@ -1,16 +1,16 @@ import { Observable } from 'rxjs'; import { ZvSelectItem } from '../models'; -export const DEFAULT_COMPARER = (a: any, b: any) => a === b; +export const DEFAULT_COMPARER = (a: unknown, b: unknown) => a === b; -export abstract class ZvSelectDataSource { +export abstract class ZvSelectDataSource { /** The flag that indicates if the select is currently loading data. */ public loading = false; /** The error that occured in the last observable returned by _loadItems or null. */ - public error: any = null; + public error: unknown = null; - public compareWith: (value1: T, value2: T) => boolean = DEFAULT_COMPARER; + public compareWith: (value1: unknown, value2: unknown) => boolean = DEFAULT_COMPARER; /** * Connects a collection viewer (such as a data-table) to this data source. Note that @@ -35,10 +35,9 @@ export abstract class ZvSelectDataSource { } /** Checks whether an object is a data source. */ -export function isZvSelectDataSource(value: any): value is ZvSelectDataSource { +export function isZvSelectDataSource(value: unknown): value is ZvSelectDataSource { // Check if the value is a ZvSelectDataSource by observing if it has a connect function. Cannot // be checked as an `instanceof ZvSelectDataSource` since people could create their own sources // that match the interface, but don't extend ZvSelectDataSource. - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access - return value && typeof value.connect === 'function'; + return value != null && typeof (value as ZvSelectDataSource).connect === 'function'; } diff --git a/projects/components/select/src/defaults/default-select-data-source.spec.ts b/projects/components/select/src/defaults/default-select-data-source.spec.ts index 4023a52f..9d2de0da 100644 --- a/projects/components/select/src/defaults/default-select-data-source.spec.ts +++ b/projects/components/select/src/defaults/default-select-data-source.spec.ts @@ -394,7 +394,7 @@ describe('DefaultZvSelectDataSource', () => { expect(currentRenderOptions).toEqual([createMissingOption(10), createIdOption(item2), createIdOption(item)]); // Sollte auch mit custom compareWith function gehen (value 9 und value 11 identisch) - dataSource.compareWith = (a: number, b: number) => (a === 11 && b === 9) || (a === 9 && b === 11); + dataSource.compareWith = (a: unknown, b: unknown) => (a === 11 && b === 9) || (a === 9 && b === 11); items$.next([item, item3]); await vi.advanceTimersByTimeAsync(1); @@ -625,7 +625,7 @@ describe('DefaultZvSelectDataSource', () => { }); it('ZvSelectSort.Comparer with custom reverse sorting', async () => { - await initDataSource(ZvSelectSortBy.comparer, (a, b) => b.value - a.value); // reverse sort + await initDataSource(ZvSelectSortBy.comparer, (a, b) => (b.value as number) - (a.value as number)); // reverse sort const expectedOptions = [item5Label6Selected, item6Label5Selected, item2Label4, item4Label3, item3Label2Selected, item1Label1].map( (x) => createIdOption(x) @@ -649,7 +649,7 @@ describe('DefaultZvSelectDataSource', () => { }); it('ZvSelectSort.Both with custom reverse sorting', async () => { - await initDataSource(ZvSelectSortBy.both, (a, b) => b.value - a.value); // reverse sort + await initDataSource(ZvSelectSortBy.both, (a, b) => (b.value as number) - (a.value as number)); // reverse sort const expectedOptions = [item5Label6Selected, item6Label5Selected, item3Label2Selected, item2Label4, item4Label3, item1Label1].map( (x) => createIdOption(x) diff --git a/projects/components/select/src/defaults/default-select-data-source.ts b/projects/components/select/src/defaults/default-select-data-source.ts index ab969ecd..3fe6095b 100644 --- a/projects/components/select/src/defaults/default-select-data-source.ts +++ b/projects/components/select/src/defaults/default-select-data-source.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-redundant-type-constituents */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ @@ -345,7 +344,7 @@ function createEntityComparer(idKey: keyof any) { }; } -function normalizeLabel(option: ZvSelectItem) { +function normalizeLabel(option: ZvSelectItem) { if (!option.label) { option.label = ''; } else if (!(typeof option.label === 'string')) { diff --git a/projects/components/select/src/defaults/default-select-service.ts b/projects/components/select/src/defaults/default-select-service.ts index 65403ae9..2f44b985 100644 --- a/projects/components/select/src/defaults/default-select-service.ts +++ b/projects/components/select/src/defaults/default-select-service.ts @@ -6,24 +6,21 @@ import { getSelectUnknownDataSourceError } from '../helpers/errors'; import { ZvSelectService } from '../services/select.service'; import { DefaultZvSelectDataSource, isZvSelectOptionsData, ZvSelectDataSourceOptions } from './default-select-data-source'; -export declare type ZvSelectData = T[] | Observable | ZvSelectDataSource | ZvSelectDataSourceOptions; +export declare type ZvSelectData = T[] | Observable | ZvSelectDataSource | ZvSelectDataSourceOptions; @Injectable({ providedIn: 'root' }) export class DefaultZvSelectService extends ZvSelectService { public createDataSource(dataSource: ZvSelectData, _: AbstractControl | null): ZvSelectDataSource { if (isZvSelectDataSource(dataSource)) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return dataSource; + return dataSource as ZvSelectDataSource; } let options: ZvSelectDataSourceOptions; if (Array.isArray(dataSource) || isObservable(dataSource)) { options = { mode: 'id', - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - labelKey: 'label' as any, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - idKey: 'value' as any, + labelKey: 'label' as keyof T, + idKey: 'value' as keyof T, items: dataSource, }; } else if (isZvSelectOptionsData(dataSource)) { diff --git a/projects/components/select/src/models.ts b/projects/components/select/src/models.ts index 43e189a4..62a6e5f2 100644 --- a/projects/components/select/src/models.ts +++ b/projects/components/select/src/models.ts @@ -1,7 +1,7 @@ -export interface ZvSelectItem { +export interface ZvSelectItem { label: string; value: T; - entity?: any; + entity?: unknown; hidden?: boolean; disabled?: boolean; } diff --git a/projects/components/select/src/select.component.spec.ts b/projects/components/select/src/select.component.spec.ts index f2ecb057..aeb8ad09 100644 --- a/projects/components/select/src/select.component.spec.ts +++ b/projects/components/select/src/select.component.spec.ts @@ -97,7 +97,7 @@ function createFakeDataSource(items: ZvSelectItem[] = []): ZvSelectDataSource { return { connect: () => of(items), disconnect: () => {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-redundant-type-constituents + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents selectedValuesChanged: (_: any | any[]) => {}, panelOpenChanged: (_: boolean) => {}, searchTextChanged: (_: string) => {}, diff --git a/projects/components/select/src/select.component.ts b/projects/components/select/src/select.component.ts index 6feb9c8c..dba4912d 100644 --- a/projects/components/select/src/select.component.ts +++ b/projects/components/select/src/select.component.ts @@ -31,6 +31,7 @@ import { NgxMatSelectSearchModule } from 'ngx-mat-select-search'; import { BehaviorSubject, Subject, Subscription } from 'rxjs'; import { takeUntil, tap } from 'rxjs/operators'; import { DEFAULT_COMPARER, ZvSelectDataSource, isZvSelectDataSource } from './data/select-data-source'; +import { ZvSelectData } from './defaults/default-select-service'; import { ZvSelectOptionTemplate } from './directives/select-option-template.directive'; import { ZvSelectTriggerTemplate } from './directives/select-trigger-template.directive'; import { getSelectUnknownDataSourceError } from './helpers/errors'; @@ -116,12 +117,11 @@ export class ZvSelect implements ControlValueAccessor, MatFormField * - `DataSource` object that implements the connect/disconnect interface. */ @Input({ required: true }) - get dataSource(): any { + get dataSource(): ZvSelectDataSource { return this._dataSourceInstance; } - set dataSource(dataSource: any) { + set dataSource(dataSource: ZvSelectData | ZvSelectDataSource | string) { if (this._dataSourceInput !== dataSource) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this._dataSourceInput = dataSource; this._switchDataSource(dataSource); } @@ -141,7 +141,7 @@ export class ZvSelect implements ControlValueAccessor, MatFormField /** If true, then there will be a toggle all checkbox available (only multiple select mode) */ @Input() public showToggleAll = true; @Input() public multiple = false; - @Input() public panelClass: string | string[] | Set | Record = ''; + @Input() public panelClass: string | string[] | Set | Record = ''; @Input() public placeholder = ''; @Input() public required = false; @Input() public selectedLabel = true; @@ -196,6 +196,7 @@ export class ZvSelect implements ControlValueAccessor, MatFormField this._errorStateTracker.errorState = value; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- constrained by MatSelect.compareWith type public get compareWith(): (o1: any, o2: any) => boolean { return this._dataSourceInstance?.compareWith ?? DEFAULT_COMPARER; } @@ -223,7 +224,6 @@ export class ZvSelect implements ControlValueAccessor, MatFormField /** the error that occured while loading the options */ public get error() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return this._dataSourceInstance?.error; } @@ -244,12 +244,11 @@ export class ZvSelect implements ControlValueAccessor, MatFormField return ''; } - readonly $currentSelection = signal([] as MatOption[]); + readonly $currentSelection = signal([] as MatOption[]); readonly $customTriggerDataArray = computed(() => { - const selectedOptions = this.$currentSelection().map((option: MatOption): ZvSelectTriggerData => { + const selectedOptions = this.$currentSelection().map((option: MatOption): ZvSelectTriggerData => { return { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - value: option.value, + value: option.value as unknown as string, viewValue: option.viewValue, }; }); @@ -276,15 +275,15 @@ export class ZvSelect implements ControlValueAccessor, MatFormField /** The data source. */ private _dataSourceInstance!: ZvSelectDataSource; /** The value the [dataSource] input was called with. */ - private _dataSourceInput: any; + private _dataSourceInput: ZvSelectData | ZvSelectDataSource | string | undefined; private _matSelect!: MatSelect; - private _onModelTouched: any; + private _onModelTouched: (() => void) | undefined; private _focused = false; private _onInitCalled = false; _errorStateTracker: _ErrorStateTracker; /** View -> model callback called when value changes */ - private _onChange: (value: any) => void = () => {}; + private _onChange: (value: T | null) => void = () => {}; constructor() { const defaultErrorStateMatcher = inject(ErrorStateMatcher); @@ -321,7 +320,6 @@ export class ZvSelect implements ControlValueAccessor, MatFormField this.filterCtrl.valueChanges .pipe(takeUntil(this._ngUnsubscribe$)) - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access .subscribe((searchText) => this.dataSource.searchTextChanged(searchText)); let selectionSignalInitialized = false; @@ -329,8 +327,10 @@ export class ZvSelect implements ControlValueAccessor, MatFormField .pipe( tap(() => { if (!selectionSignalInitialized && this._matSelect._selectionModel) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- MatSelect._selectionModel.selected is MatOption[] this.$currentSelection.set(this._matSelect._selectionModel.selected); this._matSelect._selectionModel.changed.pipe(takeUntil(this._ngUnsubscribe$)).subscribe(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- MatSelect._selectionModel.selected is MatOption[] this.$currentSelection.set(this._matSelect._selectionModel.selected); }); selectionSignalInitialized = true; @@ -356,7 +356,7 @@ export class ZvSelect implements ControlValueAccessor, MatFormField this._matSelect.setDescribedByIds(ids); } - public writeValue(value: any) { + public writeValue(value: unknown) { this._propagateValueChange(value, ValueChangeSource.writeValue); } @@ -364,8 +364,7 @@ export class ZvSelect implements ControlValueAccessor, MatFormField this._onChange = fn; } - public registerOnTouched(fn: any): void { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + public registerOnTouched(fn: () => void): void { this._onModelTouched = fn; } @@ -384,7 +383,7 @@ export class ZvSelect implements ControlValueAccessor, MatFormField this._dataSourceInstance.panelOpenChanged(open); } - public onValueChange(value: any) { + public onValueChange(value: T | null) { this._propagateValueChange(value, ValueChangeSource.matSelect); } @@ -402,39 +401,35 @@ export class ZvSelect implements ControlValueAccessor, MatFormField this._dataSourceInstance.forceReload(); } - private _propagateValueChange(value: any, source: ValueChangeSource) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - this._value = value; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - this.empty = this.multiple ? !value?.length : value == null || value === ''; + private _propagateValueChange(value: unknown, source: ValueChangeSource) { + this._value = value as T | null; + this.empty = this.multiple ? !Array.isArray(value) || value.length === 0 : value == null || value === ''; this._updateToggleAllCheckbox(); - this._pushSelectedValuesToDataSource(value); + this._pushSelectedValuesToDataSource(this._value); if (source !== ValueChangeSource.valueInput) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - this.valueChange.emit(value); + this.valueChange.emit(this._value); } if (source !== ValueChangeSource.writeValue) { - this._onChange(value); + this._onChange(this._value); } this.cd.markForCheck(); } - private _pushSelectedValuesToDataSource(value: any): void { + private _pushSelectedValuesToDataSource(value: T | null): void { if (!this._dataSourceInstance) { return; } - let values: any[]; + let values: T[]; if (this.multiple) { values = Array.isArray(value) ? value : []; } else { values = value ? [value] : []; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument this._dataSourceInstance.selectedValuesChanged(values); } /** Set up a subscription for the data provided by the data source. */ - private _switchDataSource(dataSource: any) { + private _switchDataSource(dataSource: ZvSelectData | ZvSelectDataSource | string | undefined) { if (!this._onInitCalled) { // before oninit ngControl.control isn't set, but it is needed for datasource creation return; @@ -444,8 +439,8 @@ export class ZvSelect implements ControlValueAccessor, MatFormField this._dataSourceInstance?.disconnect(); this._renderChangeSubscription.unsubscribe(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - this._dataSourceInstance = this.selectService?.createDataSource(dataSource, this.ngControl?.control ?? null) ?? dataSource; + this._dataSourceInstance = (this.selectService?.createDataSource(dataSource, this.ngControl?.control ?? null) ?? + dataSource) as ZvSelectDataSource; if (!isZvSelectDataSource(this._dataSourceInstance)) { throw getSelectUnknownDataSourceError(); } @@ -476,7 +471,6 @@ export class ZvSelect implements ControlValueAccessor, MatFormField this.stateChanges.next(); } if (!isFocused && this._onModelTouched != null) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call this._onModelTouched(); } } diff --git a/projects/components/select/src/services/select.service.ts b/projects/components/select/src/services/select.service.ts index e0849f5e..8ce4ed50 100644 --- a/projects/components/select/src/services/select.service.ts +++ b/projects/components/select/src/services/select.service.ts @@ -4,5 +4,5 @@ import { ZvSelectDataSource } from '../data/select-data-source'; @Injectable({ providedIn: 'root' }) export abstract class ZvSelectService { - public abstract createDataSource(dataSource: any, _: AbstractControl | null): ZvSelectDataSource; + public abstract createDataSource(dataSource: unknown, _: AbstractControl | null): ZvSelectDataSource; } diff --git a/projects/components/table/src/data/table-data-source.spec.ts b/projects/components/table/src/data/table-data-source.spec.ts index 35325f73..37dc7fab 100644 --- a/projects/components/table/src/data/table-data-source.spec.ts +++ b/projects/components/table/src/data/table-data-source.spec.ts @@ -705,7 +705,7 @@ describe('ZvTableDataSource', () => { it('should pass last loadTrigger$ value to loadDataFn', () => { const loadTrigger$ = new Subject(); const loadTriggerValues: string[] = []; - const dataSource = new ZvTableDataSource({ + const dataSource = new ZvTableDataSource({ loadTrigger$: loadTrigger$, loadDataFn: (filter) => { loadTriggerValues.push(filter.triggerData); diff --git a/projects/components/table/src/data/table-data-source.ts b/projects/components/table/src/data/table-data-source.ts index 983bb902..352b4769 100644 --- a/projects/components/table/src/data/table-data-source.ts +++ b/projects/components/table/src/data/table-data-source.ts @@ -16,14 +16,14 @@ export interface IExtendedZvTableUpdateDataInfo extends IZvTableUpdate triggerData: TTrigger; } -export interface ZvTableDataSourceOptions { +export interface ZvTableDataSourceOptions { loadTrigger$?: Observable; loadDataFn: (updateInfo: IExtendedZvTableUpdateDataInfo) => Observable>; loadRowActionFn?: (data: TData, actions: IZvTableAction[]) => Observable[]>; openRowMenuActionFn?: ( data: TData, actions: IZvTableAction[] - ) => Observable[]> | IZvTableAction[] | null; + ) => Observable[]> | IZvTableAction[] | null; actions?: IZvTableAction[]; mode?: ZvTableMode; moreMenuThreshold?: number; @@ -36,7 +36,7 @@ export interface IZvTableFilterResult { export declare type ZvTableMode = 'client' | 'server'; -export class ZvTableDataSource extends DataSource implements ITableDataSource { +export class ZvTableDataSource extends DataSource implements ITableDataSource { /** Subject that emits, when the table should be checked by the change detection */ public _internalDetectChanges = new Subject(); @@ -64,7 +64,7 @@ export class ZvTableDataSource extends DataSource implemen public loading = true; /** The error that occured in the last observable returned by loadData or null. */ - public error: any = null; + public error: unknown = null; /** The locale for the table texts. */ public locale!: string; @@ -109,12 +109,12 @@ export class ZvTableDataSource extends DataSource implemen public readonly openMenuRowActionFn: ( data: T, actions: IZvTableAction[] - ) => Observable[]> | IZvTableAction[] | null; + ) => Observable[]> | IZvTableAction[] | null; public readonly moreMenuThreshold: number; /** Stream that emits when a new data array is set on the data source. */ - private readonly _updateDataTrigger$: Observable; + private readonly _updateDataTrigger$: Observable; private readonly _loadData: (updateInfo: IExtendedZvTableUpdateDataInfo) => Observable>; @@ -204,7 +204,7 @@ export class ZvTableDataSource extends DataSource implemen public filterPredicate = (row: Record, filter: string) => { // Transform the data into a lowercase string of all property values. const dataStr = this.filterValues(row) - .map((value) => (value instanceof Date ? value.toLocaleString(this.locale) : (value as any) + '').toLowerCase()) + .map((value) => (value instanceof Date ? value.toLocaleString(this.locale) : String(value)).toLowerCase()) // Use an obscure Unicode character to delimit the words in the concatenated string. // This avoids matches where the values of two columns combined will match the user's query // (e.g. `Flute` and `Stop` will match `Test`). The character is intended to be something @@ -260,10 +260,8 @@ export class ZvTableDataSource extends DataSource implemen } return data.sort((a: T, b: T) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - let valueA = this.sortingDataAccessor(a, sortProp) as any; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - let valueB = this.sortingDataAccessor(b, sortProp) as any; + let valueA: unknown = this.sortingDataAccessor(a, sortProp); + let valueB: unknown = this.sortingDataAccessor(b, sortProp); // If both valueA and valueB exist (truthy), then compare the two. Otherwise, check if // one value exists while the other doesn't. In this case, existing value should come first. @@ -272,8 +270,8 @@ export class ZvTableDataSource extends DataSource implemen let comparatorResult = 0; if (valueA != null && valueB != null) { if (valueA instanceof Date || valueB instanceof Date) { - valueA = new Date(valueA as unknown as string).toISOString(); - valueB = new Date(valueB as unknown as string).toISOString(); + valueA = new Date(valueA as string).toISOString(); + valueB = new Date(valueB as string).toISOString(); } const propertyType = typeof valueA; @@ -281,9 +279,9 @@ export class ZvTableDataSource extends DataSource implemen comparatorResult = (valueA as string).localeCompare(valueB as string); } // Check if one value is greater than the other; if equal, comparatorResult should remain 0. - else if (valueA > valueB) { + else if ((valueA as number) > (valueB as number)) { comparatorResult = 1; - } else if (valueA < valueB) { + } else if ((valueA as number) < (valueB as number)) { comparatorResult = -1; } } else if (valueA != null) { @@ -390,7 +388,6 @@ export class ZvTableDataSource extends DataSource implemen public connect() { this._initDataChangeSubscription(); this._updateDataTriggerSub = this._updateDataTrigger$.subscribe((data) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this._lastLoadTriggerData = data; this.updateData(); }); @@ -435,7 +432,7 @@ export class ZvTableDataSource extends DataSource implemen // If there is a filter string, filter out data that does not contain it. // Each data object is converted to a string using the function defined by filterTermAccessor. // May be overridden for customization. - const filteredData = !this.filter ? data : data.filter((obj) => this.filterPredicate(obj as Record, this.filter)); + const filteredData = !this.filter ? data : data.filter((obj) => this.filterPredicate(obj as Record, this.filter)); this.dataLength = filteredData.length; diff --git a/projects/components/table/src/directives/table.directives.ts b/projects/components/table/src/directives/table.directives.ts index 14e5b945..87f10ea1 100644 --- a/projects/components/table/src/directives/table.directives.ts +++ b/projects/components/table/src/directives/table.directives.ts @@ -26,9 +26,9 @@ export class ZvTableColumn { @Input() public headerStyles: Record = {}; @Input() public columnStyles: Record = {}; @ContentChild(ZvTableColumnTemplate, { read: TemplateRef }) - public columnTemplate: TemplateRef | null = null; + public columnTemplate: TemplateRef | null = null; @ContentChild(ZvTableColumnHeaderTemplate, { read: TemplateRef }) - public headerTemplate: TemplateRef | null = null; + public headerTemplate: TemplateRef | null = null; } @Directive({ @@ -66,7 +66,7 @@ export class ZvTableRowDetail { @Input() public showToggleColumn: boolean | ((row: object) => boolean) = true; @ContentChild(ZvTableRowDetailTemplate, { read: TemplateRef }) - public template: TemplateRef | null = null; + public template: TemplateRef | null = null; private expandedItems = new WeakSet(); private seenItems = new WeakSet(); diff --git a/projects/components/table/src/helper/state-manager.ts b/projects/components/table/src/helper/state-manager.ts index c739fc86..efaf3cf8 100644 --- a/projects/components/table/src/helper/state-manager.ts +++ b/projects/components/table/src/helper/state-manager.ts @@ -20,7 +20,7 @@ export class ZvTableUrlStateManager extends ZvTableStateManager { public remove(tableId: string) { const currentParams = this.route.snapshot.queryParamMap; - const newQueryParams: Record = {}; + const newQueryParams: Record = {}; for (const key of currentParams.keys) { if (key !== tableId) { newQueryParams[key] = currentParams.get(key); @@ -47,7 +47,7 @@ export class ZvTableUrlStateManager extends ZvTableStateManager { } public requestUpdate(tableId: string, updateInfo: IZvTableUpdateDataInfo) { - const newQueryParams: Record = {}; + const newQueryParams: Record = {}; const currentParams = this.route.snapshot.queryParamMap; for (const key of currentParams.keys) { diff --git a/projects/components/table/src/models.ts b/projects/components/table/src/models.ts index 4be6622a..a4330728 100644 --- a/projects/components/table/src/models.ts +++ b/projects/components/table/src/models.ts @@ -42,7 +42,7 @@ export interface IZvTableAction { export interface IZvTableActionRouterLink { path: string[]; - queryParams?: Record; + queryParams?: Record; } export interface ITableDataSource extends DataSource { @@ -63,7 +63,7 @@ export interface ITableDataSource extends DataSource { readonly dataLength: number; /** The error that occured in the last observable returned by loadData or null. */ - error: any; + error: unknown; /** The locale for the table texts. */ locale: string; @@ -99,7 +99,7 @@ export interface ITableDataSource extends DataSource { readonly loadRowActionFn: (data: T, actions: IZvTableAction[]) => Observable[]>; /** Component open callbacks of actions which can be executed for a selection of rows */ - readonly openMenuRowActionFn: (data: T, actions: IZvTableAction[]) => Observable[]> | IZvTableAction[] | null; + readonly openMenuRowActionFn: (data: T, actions: IZvTableAction[]) => Observable[]> | IZvTableAction[] | null; readonly moreMenuThreshold: number; diff --git a/projects/components/table/src/subcomponents/table-data.component.ts b/projects/components/table/src/subcomponents/table-data.component.ts index b0ae6348..e5a81600 100644 --- a/projects/components/table/src/subcomponents/table-data.component.ts +++ b/projects/components/table/src/subcomponents/table-data.component.ts @@ -124,7 +124,7 @@ export class ZvTableDataComponent implements OnChanges { this.sortChanged.emit({ sortColumn: sort.active, sortDirection: sort.direction || 'asc' }); } - public toggleRowDetail(item: Record) { + public toggleRowDetail(item: object) { this.rowDetail!.toggle(item); this.cd.markForCheck(); } @@ -153,9 +153,8 @@ export class ZvTableDataComponent implements OnChanges { return this.dataSource.selectionModel.selected; } - public showRowDetails(row: any) { + public showRowDetails(row: object) { if (typeof this.rowDetail!.showToggleColumn === 'function') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return this.rowDetail!.showToggleColumn(row); } diff --git a/projects/components/table/src/subcomponents/table-header.component.ts b/projects/components/table/src/subcomponents/table-header.component.ts index 3db726c7..a08a1c04 100644 --- a/projects/components/table/src/subcomponents/table-header.component.ts +++ b/projects/components/table/src/subcomponents/table-header.component.ts @@ -23,9 +23,9 @@ import { ZvTableSortComponent } from './table-sort.component'; }) export class ZvTableHeaderComponent { @Input() public caption!: string; - @Input() public topButtonSection!: TemplateRef | null; - @Input() public customHeader!: TemplateRef | null; - @Input() public selectedRows!: any[]; + @Input() public topButtonSection!: TemplateRef | null; + @Input() public customHeader!: TemplateRef | null; + @Input() public selectedRows!: unknown[]; @Input() public showSorting!: boolean; @Input() public sortColumn!: string | null; @Input() public sortDirection!: 'asc' | 'desc'; diff --git a/projects/components/table/src/subcomponents/table-settings.component.ts b/projects/components/table/src/subcomponents/table-settings.component.ts index 164f9dc5..b00c7f67 100644 --- a/projects/components/table/src/subcomponents/table-settings.component.ts +++ b/projects/components/table/src/subcomponents/table-settings.component.ts @@ -61,7 +61,7 @@ export class ZvTableSettingsComponent implements OnInit { @Input() public columnDefinitions: ZvTableColumn[] = []; @Input() public sortDefinitions: IZvTableSortDefinition[] = []; @Input() public pageSizeOptions!: number[]; - @Input() public customSettings: TemplateRef | null = null; + @Input() public customSettings: TemplateRef | null = null; @Output() public readonly settingsSaved = new EventEmitter(); @Output() public readonly settingsAborted = new EventEmitter(); diff --git a/projects/components/table/src/table.component.spec.ts b/projects/components/table/src/table.component.spec.ts index 5d628a99..6f4a97cc 100644 --- a/projects/components/table/src/table.component.spec.ts +++ b/projects/components/table/src/table.component.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { CommonModule } from '@angular/common'; diff --git a/projects/components/test-setup.ts b/projects/components/test-setup.ts index f6bd4886..73729ed6 100644 --- a/projects/components/test-setup.ts +++ b/projects/components/test-setup.ts @@ -2,17 +2,24 @@ import '@angular/localize/init'; // Polyfill IntersectionObserver for jsdom (not needed in browser mode) if (typeof globalThis.IntersectionObserver === 'undefined') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment globalThis.IntersectionObserver = class IntersectionObserver { - constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) {} - observe() {} - unobserve() {} - disconnect() {} + constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) { + /* noop */ + } + observe() { + /* noop */ + } + unobserve() { + /* noop */ + } + disconnect() { + /* noop */ + } takeRecords(): IntersectionObserverEntry[] { return []; } readonly root: Element | null = null; readonly rootMargin: string = ''; readonly thresholds: readonly number[] = []; - } as any; + } as unknown as typeof IntersectionObserver; } diff --git a/projects/zvoove-components-demo/src/app/app.config.ts b/projects/zvoove-components-demo/src/app/app.config.ts index 70a471fd..3b092e79 100644 --- a/projects/zvoove-components-demo/src/app/app.config.ts +++ b/projects/zvoove-components-demo/src/app/app.config.ts @@ -125,18 +125,17 @@ function getUsersLocale(allowedPrefixes: string[], defaultValue: string): string if (typeof window === 'undefined' || typeof window.navigator === 'undefined') { return defaultValue; } - const naviagtor = window.navigator; - const languages = [ + const nav = window.navigator as Navigator & { browserLanguage?: string; userLanguage?: string }; + const languages: (string | undefined)[] = [ document.cookie .split('; ') .find((row) => row.startsWith('LOCALE_ID=')) ?.split('=')[1], - ...naviagtor.languages, - naviagtor.language, - (naviagtor as any).browserLanguage, - (naviagtor as any).userLanguage, + ...nav.languages, + nav.language, + nav.browserLanguage, + nav.userLanguage, ]; - const allowedLanguages = languages.filter((lang) => lang && allowedPrefixes.some((prefix) => lang.startsWith(prefix))); - const lang = allowedLanguages.length ? allowedLanguages[0] : defaultValue; - return lang; + const allowedLanguages = languages.filter((lang): lang is string => !!lang && allowedPrefixes.some((prefix) => lang.startsWith(prefix))); + return allowedLanguages.length ? allowedLanguages[0] : defaultValue; } diff --git a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-input.component.ts b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-input.component.ts index 3efadf95..6bc78678 100644 --- a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-input.component.ts +++ b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-input.component.ts @@ -1,4 +1,4 @@ -import { Directive, Input } from '@angular/core'; +import { Directive, input } from '@angular/core'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector @@ -6,10 +6,10 @@ import { Directive, Input } from '@angular/core'; standalone: true, }) export class AppApiDocInput { - @Input({ required: true }) name: string; - @Input({ required: true }) type: string; - @Input() typeUrl: string; - @Input({ required: true }) desc: string; - @Input() required = false; - @Input() twoWay = false; + readonly name = input.required(); + readonly type = input.required(); + readonly typeUrl = input(); + readonly desc = input.required(); + readonly required = input(false); + readonly twoWay = input(false); } diff --git a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-method.component.ts b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-method.component.ts index 6f99986e..6eacb870 100644 --- a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-method.component.ts +++ b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-method.component.ts @@ -1,4 +1,4 @@ -import { Directive, Input } from '@angular/core'; +import { Directive, input } from '@angular/core'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector @@ -6,7 +6,7 @@ import { Directive, Input } from '@angular/core'; standalone: true, }) export class AppApiDocMethod { - @Input({ required: true }) signature: string; - @Input({ required: true }) returnType: string; - @Input({ required: true }) desc: string; + readonly signature = input.required(); + readonly returnType = input.required(); + readonly desc = input.required(); } diff --git a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-output.component.ts b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-output.component.ts index c13ccec6..059effdf 100644 --- a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-output.component.ts +++ b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-output.component.ts @@ -1,4 +1,4 @@ -import { Directive, Input } from '@angular/core'; +import { Directive, input } from '@angular/core'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector @@ -6,9 +6,9 @@ import { Directive, Input } from '@angular/core'; standalone: true, }) export class AppApiDocOutput { - @Input({ required: true }) name: string; - @Input({ required: true }) type: string; - @Input() typeUrl: string; - @Input({ required: true }) desc: string; - @Input() twoWay = false; + readonly name = input.required(); + readonly type = input.required(); + readonly typeUrl = input(); + readonly desc = input.required(); + readonly twoWay = input(false); } diff --git a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-property.component.ts b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-property.component.ts index bd4c9c8d..37e42e9c 100644 --- a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-property.component.ts +++ b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc-property.component.ts @@ -1,4 +1,4 @@ -import { Directive, Input } from '@angular/core'; +import { Directive, input } from '@angular/core'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector @@ -6,8 +6,8 @@ import { Directive, Input } from '@angular/core'; standalone: true, }) export class AppApiDocProperty { - @Input({ required: true }) name: string; - @Input({ required: true }) type: string; - @Input() typeUrl: string; - @Input({ required: true }) desc: string; + readonly name = input.required(); + readonly type = input.required(); + readonly typeUrl = input(); + readonly desc = input.required(); } diff --git a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc.component.html b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc.component.html index c1cd0f52..5594bb4d 100644 --- a/projects/zvoove-components-demo/src/app/common/api-doc/api-doc.component.html +++ b/projects/zvoove-components-demo/src/app/common/api-doc/api-doc.component.html @@ -13,24 +13,24 @@ - @for (input of inputs(); track input.name) { + @for (input of inputs(); track input.name()) { - [{{ input.name }}] - @if (input.required) { + [{{ input.name() }}] + @if (input.required()) { - required } - @if (!input.typeUrl) { - {{ input.type }} + @if (!input.typeUrl()) { + {{ input.type() }} } @else { - {{ input.type }} + {{ input.type() }} } - {{ input.desc }} - @if (input.twoWay) { + {{ input.desc() }} + @if (input.twoWay()) { Can be used for two-way binding. } @@ -47,19 +47,19 @@ - @for (output of outputs(); track output.name) { + @for (output of outputs(); track output.name()) { - ({{ output.name }}) + ({{ output.name() }}) - @if (!output.typeUrl) { - {{ output.type }} + @if (!output.typeUrl()) { + {{ output.type() }} } @else { - {{ output.type }} + {{ output.type() }} } - {{ output.desc }} - @if (output.twoWay) { + {{ output.desc() }} + @if (output.twoWay()) { Can be used for two-way binding. } @@ -76,17 +76,17 @@ - @for (property of properties(); track property.name) { + @for (property of properties(); track property.name()) { - {{ property.name }} + {{ property.name() }} - @if (!property.typeUrl) { - {{ property.type }} + @if (!property.typeUrl()) { + {{ property.type() }} } @else { - {{ property.type }} + {{ property.type() }} } - {{ property.desc }} + {{ property.desc() }} } @@ -100,11 +100,11 @@ - @for (method of methods(); track method.signature) { + @for (method of methods(); track method.signature()) { - {{ method.signature }} - {{ method.returnType }} - {{ method.desc }} + {{ method.signature() }} + {{ method.returnType() }} + {{ method.desc() }} } diff --git a/projects/zvoove-components-demo/src/app/common/code/code.component.html b/projects/zvoove-components-demo/src/app/common/code/code.component.html index 0b22bc22..1f3b4677 100644 --- a/projects/zvoove-components-demo/src/app/common/code/code.component.html +++ b/projects/zvoove-components-demo/src/app/common/code/code.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/projects/zvoove-components-demo/src/app/common/code/code.component.ts b/projects/zvoove-components-demo/src/app/common/code/code.component.ts index f28de7cb..09754b6b 100644 --- a/projects/zvoove-components-demo/src/app/common/code/code.component.ts +++ b/projects/zvoove-components-demo/src/app/common/code/code.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewEncapsulation, input, viewChild } from '@angular/core'; import { HighlightModule } from 'ngx-highlightjs'; import { HighlightLineNumbers } from 'ngx-highlightjs/line-numbers'; @@ -11,15 +11,15 @@ import { HighlightLineNumbers } from 'ngx-highlightjs/line-numbers'; encapsulation: ViewEncapsulation.None, }) export class AppCodeComponent implements OnInit { - @Input({ transform: cleanCode, required: true }) code = ''; - @Input({ required: true }) language = ''; + readonly code = input('', { transform: cleanCode }); + readonly language = input.required(); - @ViewChild('codeDiv', { static: true }) private _codeDiv!: ElementRef; + private readonly _codeDiv = viewChild.required>('codeDiv'); + + resolvedCode = ''; ngOnInit() { - if (!this.code) { - this.code = cleanCode(this._codeDiv.nativeElement.textContent); - } + this.resolvedCode = this.code() || cleanCode(this._codeDiv().nativeElement.textContent); } } diff --git a/projects/zvoove-components-demo/src/app/common/demo-zv-form-service.ts b/projects/zvoove-components-demo/src/app/common/demo-zv-form-service.ts index 7156f3d6..7d7b9d93 100644 --- a/projects/zvoove-components-demo/src/app/common/demo-zv-form-service.ts +++ b/projects/zvoove-components-demo/src/app/common/demo-zv-form-service.ts @@ -4,8 +4,9 @@ import { Observable, of } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class DemoZvFormsService extends BaseZvFormService { - public getLabel(formControl: any): Observable { - return formControl.zvLabel ? of(formControl.zvLabel) : null; + public getLabel(formControl: unknown): Observable { + const ctrl = formControl as { zvLabel?: string }; + return ctrl.zvLabel ? of(ctrl.zvLabel) : null; } protected mapDataToError(errorData: IZvFormErrorData[]): Observable { return of( diff --git a/projects/zvoove-components-demo/src/app/common/form-control-card/form-control-demo-card.component.html b/projects/zvoove-components-demo/src/app/common/form-control-card/form-control-demo-card.component.html index c96e2b8e..78b7c0d0 100644 --- a/projects/zvoove-components-demo/src/app/common/form-control-card/form-control-demo-card.component.html +++ b/projects/zvoove-components-demo/src/app/common/form-control-card/form-control-demo-card.component.html @@ -1,7 +1,7 @@ {{ - type === 'form' ? 'Reactive forms' : type === 'model' ? '[(ngModel)] binding' : '[(value)] binding' + type() === 'form' ? 'Reactive forms' : type() === 'model' ? '[(ngModel)] binding' : '[(value)] binding' }} @@ -11,11 +11,11 @@
Value
-
{{ formControl?.value ?? value }}
+
{{ formControl?.value ?? value() }}
Value Type
-
{{ getType(formControl?.value ?? value) }}
+
{{ getType(formControl?.value ?? value()) }}
@if (formControl) {
Touched
@@ -47,13 +47,13 @@
{{ formControl.errors | json }}
} - @for (data of additionalData | keyvalue; track data.key) { + @for (data of additionalData() | keyvalue; track data.key) {
{{ data.key }}
{{ data.value }}
} Code: - +
diff --git a/projects/zvoove-components-demo/src/app/common/form-control-card/form-control-demo-card.component.ts b/projects/zvoove-components-demo/src/app/common/form-control-card/form-control-demo-card.component.ts index e94f17d5..5b9324b3 100644 --- a/projects/zvoove-components-demo/src/app/common/form-control-card/form-control-demo-card.component.ts +++ b/projects/zvoove-components-demo/src/app/common/form-control-card/form-control-demo-card.component.ts @@ -1,5 +1,5 @@ import { JsonPipe, KeyValuePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, ContentChild, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, contentChild, input } from '@angular/core'; import { AbstractControl, NgModel } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; @@ -14,16 +14,16 @@ import { AppCodeFilesComponent, CodeFiles } from '../code-files/code-files.compo imports: [KeyValuePipe, MatCardModule, MatIconModule, JsonPipe, AppCodeFilesComponent], }) export class FormControlDemoCard { - @Input({ required: true }) type: 'form' | 'model' | 'value'; - @Input() control?: AbstractControl; - @Input() value?: unknown; - @Input({ required: true }) codeFiles: CodeFiles[]; - @Input() additionalData: Record; + readonly type = input.required<'form' | 'model' | 'value'>(); + readonly control = input(); + readonly value = input(); + readonly codeFiles = input.required(); + readonly additionalData = input>(); - @ContentChild(NgModel, { read: NgModel }) ngModel: NgModel; + readonly ngModel = contentChild(NgModel, { read: NgModel }); get formControl() { - return this.control ?? this.ngModel?.control; + return this.control() ?? this.ngModel()?.control; } getType(value: unknown) { diff --git a/projects/zvoove-components-demo/src/app/dialog-wrapper-demo/dialog-wrapper-demo.component.ts b/projects/zvoove-components-demo/src/app/dialog-wrapper-demo/dialog-wrapper-demo.component.ts index f0471456..a2a74c81 100644 --- a/projects/zvoove-components-demo/src/app/dialog-wrapper-demo/dialog-wrapper-demo.component.ts +++ b/projects/zvoove-components-demo/src/app/dialog-wrapper-demo/dialog-wrapper-demo.component.ts @@ -44,7 +44,9 @@ export class DemoDialogWrapperDataSource implements IZvDialogWrapperDataSource { return this.somethingChanged$; } - disconnect(): void {} + disconnect(): void { + /* noop */ + } confirm() { return this.options.actionFn(); diff --git a/projects/zvoove-components-demo/src/app/form-demo/form-data-source.ts b/projects/zvoove-components-demo/src/app/form-demo/form-data-source.ts index 75bf4d17..344dacf8 100644 --- a/projects/zvoove-components-demo/src/app/form-demo/form-data-source.ts +++ b/projects/zvoove-components-demo/src/app/form-demo/form-data-source.ts @@ -40,8 +40,7 @@ export class FormDataSource< TSaveResponse, TForm extends FormGroup = FormGroup, TFormValue = ReturnType, -> implements IZvFormDataSource -{ +> implements IZvFormDataSource { public buttons: IZvButton[] = []; public exception: IZvException | null = null; public progress: number | null = null; @@ -150,7 +149,7 @@ export class FormDataSource< this.updateButtons(); this.stateChanges$.next(); - const formValue: TFormValue = this.form.getRawValue(); + const formValue = this.form.getRawValue() as TFormValue; this.options .saveFn(formValue, { loadParams: this.loadParams, diff --git a/projects/zvoove-components-demo/src/app/form-demo/form-demo.component.ts b/projects/zvoove-components-demo/src/app/form-demo/form-demo.component.ts index f16a596e..b4e387f9 100644 --- a/projects/zvoove-components-demo/src/app/form-demo/form-demo.component.ts +++ b/projects/zvoove-components-demo/src/app/form-demo/form-demo.component.ts @@ -42,8 +42,10 @@ export class FormDemoComponent { input2: new FormControl('b'), }, [ - (formGroup: AbstractControl) => - formGroup.value.input1 === formGroup.value.input2 ? null : { equal: 'input1 and input2 must be equal' }, + (formGroup: AbstractControl) => { + const val = formGroup.value as { input1: string; input2: string }; + return val.input1 === val.input2 ? null : { equal: 'input1 and input2 must be equal' }; + }, ] ); public counter = 0; diff --git a/projects/zvoove-components-demo/src/app/form-errors-demo/form-errors-demo.component.ts b/projects/zvoove-components-demo/src/app/form-errors-demo/form-errors-demo.component.ts index 01877672..3cdce3b9 100644 --- a/projects/zvoove-components-demo/src/app/form-errors-demo/form-errors-demo.component.ts +++ b/projects/zvoove-components-demo/src/app/form-errors-demo/form-errors-demo.component.ts @@ -17,6 +17,9 @@ export class FormErrorsDemoComponent { input1: new FormControl('a', [Validators.required, Validators.minLength(3), Validators.maxLength(5)]), input2: new FormControl('', [Validators.required]), }, - (form: AbstractControl) => (form.value.input1 !== form.value.input2 ? { equal: 'must be equal' } : null) + (form: AbstractControl) => { + const val = form.value as { input1: string; input2: string }; + return val.input1 !== val.input2 ? { equal: 'must be equal' } : null; + } ); } diff --git a/projects/zvoove-components-demo/src/app/form-field-demo/form-field-demo.component.ts b/projects/zvoove-components-demo/src/app/form-field-demo/form-field-demo.component.ts index fb286344..f1223365 100644 --- a/projects/zvoove-components-demo/src/app/form-field-demo/form-field-demo.component.ts +++ b/projects/zvoove-components-demo/src/app/form-field-demo/form-field-demo.component.ts @@ -1,5 +1,5 @@ import { AsyncPipe } from '@angular/common'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, ViewEncapsulation, inject } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewEncapsulation, inject, input } from '@angular/core'; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -31,19 +31,19 @@ import { InvalidErrorStateMatcher } from '../common/invalid-error-state-matcher' @Component({ selector: 'app-reference-column', template: ` - + Referenz Column - + `, changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatFormFieldModule, MatInputModule, ReactiveFormsModule, FormsModule, ZvFormField], }) export class ReferenceColumnComponent { - @Input() public subscriptType: ZvFormFieldSubscriptType = 'single-line'; - @Input() public hintToggle = false; - @Input() public hint = 'hint text'; - @Input() public required = false; + readonly subscriptType = input('single-line'); + readonly hintToggle = input(false); + readonly hint = input('hint text'); + readonly required = input(false); public value = ''; } @@ -161,7 +161,7 @@ export class FormFieldDemoComponent { continue; } const ctrl = this.form.controls[ctrlName as keyof typeof this.form.controls]; - (ctrl as any).zvLabel = ctrlName; + (ctrl as unknown as { zvLabel: string }).zvLabel = ctrlName; } } } diff --git a/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-custom-select-service.component.ts b/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-custom-select-service.component.ts index c5ed9de7..77b51b18 100644 --- a/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-custom-select-service.component.ts +++ b/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-custom-select-service.component.ts @@ -12,9 +12,9 @@ export class CustomZvSelectService extends DefaultZvSelectService { super(); } - public override createDataSource(data: ZvSelectData | string, control: AbstractControl): ZvSelectDataSource { + public override createDataSource(data: ZvSelectData | string, control: AbstractControl | null): ZvSelectDataSource { if (typeof data === 'string') { - data = getLookupData(data); + return super.createDataSource(getLookupData(data) as ZvSelectData, control); } return super.createDataSource(data, control); } diff --git a/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-events-only.component.ts b/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-events-only.component.ts index 42ffd697..f835d377 100644 --- a/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-events-only.component.ts +++ b/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-events-only.component.ts @@ -18,6 +18,6 @@ export class SelectWithEventsOnlyComponent { public values: string[] = []; public onSelectionChange(event: MatSelectChange) { - this.values.push(event.value); + this.values.push(event.value as string); } } diff --git a/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-ng-model.component.ts b/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-ng-model.component.ts index 35710dbf..87866e7b 100644 --- a/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-ng-model.component.ts +++ b/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-ng-model.component.ts @@ -15,11 +15,11 @@ import { ZvSelectModule } from '@zvoove/components/select/src/select.module'; export class SelectWithNgModelComponent { private readonly cd = inject(ChangeDetectorRef); - public items: any[] = Array.from(Array(50).keys()).map((i) => ({ + public items: { value: string; label: string }[] = Array.from(Array(50).keys()).map((i) => ({ value: `id${i}`, label: `Item ${i}`, })); - public ngModelValue: any = 'id11'; + public ngModelValue = 'id11'; public random() { const idx = Math.floor(Math.random() * this.items.length); diff --git a/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-other-load-trigger.component.ts b/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-other-load-trigger.component.ts index 03f9c193..1fb1a007 100644 --- a/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-other-load-trigger.component.ts +++ b/projects/zvoove-components-demo/src/app/select-demo/demos/select-with-other-load-trigger.component.ts @@ -17,7 +17,7 @@ import { delay } from 'rxjs/operators'; export class SelectWithOtherLoadTriggerComponent { private readonly cd = inject(ChangeDetectorRef); - public dataSource: ZvSelectDataSource; + public dataSource!: ZvSelectDataSource; public currentLoadTrigger = 'initial'; public loadCount = 0; public form = new FormGroup({ diff --git a/projects/zvoove-components-demo/src/app/select-demo/select-demo.component.ts b/projects/zvoove-components-demo/src/app/select-demo/select-demo.component.ts index 2f265d50..19d332fb 100644 --- a/projects/zvoove-components-demo/src/app/select-demo/select-demo.component.ts +++ b/projects/zvoove-components-demo/src/app/select-demo/select-demo.component.ts @@ -16,7 +16,7 @@ import { ZvSelectService, ZvSelectSortBy, } from '@zvoove/components/select'; -import { iif, NEVER, of, throwError } from 'rxjs'; +import { iif, NEVER, Observable, of, throwError } from 'rxjs'; import { delay, finalize, tap } from 'rxjs/operators'; import { CodeFiles } from '../common/code-files/code-files.component'; import { DemoZvFormsService } from '../common/demo-zv-form-service'; @@ -38,6 +38,14 @@ interface DemoLogs extends Record { loadCount: number; } +interface DemoSelectItem { + id: number; + strId: string; + labelA: string; + labelB: string; + disabled?: boolean; +} + @Component({ selector: 'app-select-demo', templateUrl: './select-demo.component.html', @@ -76,27 +84,27 @@ export class SelectDemoComponent implements OnInit { private readonly cd = inject(ChangeDetectorRef); public visible = true; - public ngModel: any = null; - public value: any = null; + public ngModel: DemoSelectItem | DemoSelectItem[] | null = null; + public value: DemoSelectItem | DemoSelectItem[] | null = null; public form = new FormGroup({ ctrl: new FormControl(null), }); - public items = Array.from(Array(500).keys()).map((i) => ({ + public items: DemoSelectItem[] = Array.from(Array(500).keys()).map((i) => ({ id: i, strId: `id${i}`, labelA: `Label A ${i}`, labelB: `Label B ${i}`, disabled: i % 5 === 4, })); - public unknowIitem = { + public unknowIitem: DemoSelectItem = { id: -1, strId: `id-1`, labelA: `Label A -1`, labelB: `Label B -1`, }; - public ngModelDataSource: DefaultZvSelectDataSource; - public formDataSource: DefaultZvSelectDataSource; - public valueDataSource: DefaultZvSelectDataSource; + public ngModelDataSource: DefaultZvSelectDataSource; + public formDataSource: DefaultZvSelectDataSource; + public valueDataSource: DefaultZvSelectDataSource; public multiple = false; public clearable = true; public disabled = false; @@ -133,7 +141,7 @@ export class SelectDemoComponent implements OnInit { } public createDataSource(logs: DemoLogs) { - const ds = new DefaultZvSelectDataSource({ + const ds = new DefaultZvSelectDataSource({ mode: this.dataSourceMode, idKey: this.dataSourceIdKey, labelKey: this.dataSourceLabelKey, @@ -144,7 +152,7 @@ export class SelectDemoComponent implements OnInit { sortBy: this.getZvSelectSortBy(), }); if (this.reverseSort) { - ds.sortCompare = (a, b) => b.entity.id - a.entity.id; + ds.sortCompare = (a, b) => (b.entity as DemoSelectItem).id - (a.entity as DemoSelectItem).id; } return ds; } @@ -165,15 +173,17 @@ export class SelectDemoComponent implements OnInit { logs.loadCount = 0; switch (this.dataSourceItems) { case 'default': { - let items: any = this.items; + let items: typeof this.items | Observable | (() => typeof this.items | Observable) = + this.items; if (this.itemsAsObservable) { - items = of(items).pipe( + const obs = of(this.items).pipe( tap(() => { ++logs.loadCount; this.cd.markForCheck(); }), delay(1000) ); + items = obs; } if (this.itemsAsFunction) { const originalItems = items; diff --git a/projects/zvoove-components-demo/src/app/table-demo/table-demo.component.html b/projects/zvoove-components-demo/src/app/table-demo/table-demo.component.html index dcd24cb8..f50b0033 100644 --- a/projects/zvoove-components-demo/src/app/table-demo/table-demo.component.html +++ b/projects/zvoove-components-demo/src/app/table-demo/table-demo.component.html @@ -153,7 +153,7 @@ @if (showCustomToggleColumn && expandable) { - + } diff --git a/projects/zvoove-components-demo/src/app/table-demo/table-demo.component.ts b/projects/zvoove-components-demo/src/app/table-demo/table-demo.component.ts index 5a5813c9..663d44dd 100644 --- a/projects/zvoove-components-demo/src/app/table-demo/table-demo.component.ts +++ b/projects/zvoove-components-demo/src/app/table-demo/table-demo.component.ts @@ -1,5 +1,5 @@ import { DatePipe, JsonPipe } from '@angular/common'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewChild, inject } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, viewChild } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -109,7 +109,7 @@ export class TableDemoComponent { private readonly cd = inject(ChangeDetectorRef); public show = true; - @ViewChild(ZvTable) public table: ZvTable; + readonly tableRef = viewChild(ZvTable); public pageEvent: PageEvent;