update lint rules, fix lint errors

This commit is contained in:
Dan Percic 2021-08-16 21:19:47 +03:00
parent 0bb0e4454f
commit 908256aaca
26 changed files with 184 additions and 123 deletions

View File

@ -4,7 +4,18 @@
"overrides": [
{
"files": ["*.ts"],
"extends": ["plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates"],
"extends": [
"plugin:@nrwl/nx/angular",
"plugin:@angular-eslint/template/process-inline-templates",
"plugin:@angular-eslint/recommended",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:@angular-eslint/recommended--extra",
"airbnb-typescript/base",
"prettier",
"plugin:prettier/recommended"
],
"parserOptions": {
"project": ["libs/common-ui/tsconfig.*?.json"]
},
@ -25,13 +36,48 @@
"style": "kebab-case"
}
],
"no-param-reassign": "error"
"@angular-eslint/prefer-on-push-component-change-detection": "error",
"@angular-eslint/use-lifecycle-interface": "error",
"@angular-eslint/no-input-prefix": "error",
"@angular-eslint/no-input-rename": "error",
"@angular-eslint/no-output-on-prefix": "error",
"@angular-eslint/no-output-rename": "error",
"@angular-eslint/prefer-output-readonly": "error",
"@typescript-eslint/unbound-method": "error",
"@typescript-eslint/lines-between-class-members": "off",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "memberLike",
"modifiers": ["private"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": "memberLike",
"modifiers": ["protected"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": "memberLike",
"modifiers": ["private"],
"format": ["UPPER_CASE", "camelCase"],
"leadingUnderscore": "require"
}
],
"import/prefer-default-export": "off",
"no-underscore-dangle": "off",
"no-param-reassign": "error",
"consistent-return": "off",
"class-methods-use-this": "off"
},
"plugins": ["@angular-eslint/eslint-plugin", "@typescript-eslint"]
},
{
"files": ["*.html"],
"extends": ["plugin:@nrwl/nx/angular-template"],
"extends": ["plugin:@nrwl/nx/angular-template", "plugin:@angular-eslint/template/recommended"],
"plugins": ["prettier"],
"rules": {}
}
]

View File

@ -24,18 +24,18 @@ export class CircleButtonComponent implements OnInit {
@Input() isSubmit = false;
@Input() size = 34;
@Input() iconSize = 14;
@Output() action = new EventEmitter<MouseEvent>();
@Output() readonly action = new EventEmitter<MouseEvent>();
@ViewChild(MatTooltip) private readonly _matTooltip!: MatTooltip;
constructor(private readonly _elementRef: ElementRef) {}
ngOnInit() {
this._elementRef.nativeElement.style.setProperty('--size', this.size + 'px');
this._elementRef.nativeElement.style.setProperty('--iconSize', this.iconSize + 'px');
ngOnInit(): void {
(this._elementRef.nativeElement as HTMLElement).style.setProperty('--size', `${this.size}px`);
(this._elementRef.nativeElement as HTMLElement).style.setProperty('--iconSize', `${this.iconSize}px`);
}
performAction($event: MouseEvent) {
performAction($event: MouseEvent): void {
if (this.disabled) return;
if (this.removeTooltip) {

View File

@ -16,5 +16,5 @@ export class IconButtonComponent {
@Input() showDot = false;
@Input() disabled = false;
@Input() type: IconButtonType = IconButtonTypes.default;
@Output() action = new EventEmitter<MouseEvent>();
@Output() readonly action = new EventEmitter<MouseEvent>();
}

View File

@ -1,23 +1,23 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IconButtonComponent } from './buttons/icon-button/icon-button.component';
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component';
import { DomSanitizer } from '@angular/platform-browser';
import { CircleButtonComponent } from './buttons/circle-button/circle-button.component';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { FormsModule } from '@angular/forms';
import { IconButtonComponent } from './buttons/icon-button/icon-button.component';
import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component';
import { CircleButtonComponent } from './buttons/circle-button/circle-button.component';
import { RoundCheckboxComponent } from './inputs/round-checkbox/round-checkbox.component';
import { SortByPipe } from './sorting/sort-by.pipe';
import { HumanizePipe } from './utils/pipes/humanize.pipe';
import { TableColumnNameComponent } from './tables/table-column-name/table-column-name.component';
import { QuickFiltersComponent } from './filtering/quick-filters/quick-filters.component';
import { TableHeaderComponent } from './tables/table-header/table-header.component';
import { TranslateModule } from '@ngx-translate/core';
import { SyncWidthDirective } from './tables/sync-width.directive';
import { StatusBarComponent } from './misc/status-bar/status-bar.component';
import { EditableInputComponent } from './inputs/editable-input/editable-input.component';
import { FormsModule } from '@angular/forms';
const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent];
@ -40,8 +40,8 @@ export class CommonUiModule {
constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) {
const icons = ['arrow-down', 'check', 'close', 'edit', 'sort-asc', 'sort-desc'];
for (const icon of icons) {
icons.forEach(icon => {
_iconRegistry.addSvgIconInNamespace('iqser', icon, _sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/${icon}.svg`));
}
});
}
}

View File

@ -1,31 +1,22 @@
/* eslint-disable no-param-reassign */
import { NestedFilter } from './models/nested-filter.model';
import { FilterGroup } from './models/filter-group.model';
import { Filter } from './models/filter.model';
export function processFilters(oldFilters: NestedFilter[], newFilters: NestedFilter[]) {
copySettings(oldFilters, newFilters);
if (newFilters) {
newFilters.forEach(filter => {
handleCheckedValue(filter);
});
}
return newFilters;
}
function copySettings(oldFilters: NestedFilter[], newFilters: NestedFilter[]) {
if (oldFilters && newFilters) {
for (const oldFilter of oldFilters) {
const newFilter = newFilters.find(f => f.key === oldFilter.key);
if (newFilter) {
newFilter.checked = oldFilter.checked;
newFilter.indeterminate = oldFilter.indeterminate;
if (oldFilter.children && newFilter.children) copySettings(oldFilter.children, newFilter.children);
}
if (!oldFilters || !newFilters) return;
oldFilters.forEach(filter => {
const newFilter = newFilters.find(f => f.key === filter.key);
if (newFilter) {
newFilter.checked = filter.checked;
newFilter.indeterminate = filter.indeterminate;
if (filter.children && newFilter.children) copySettings(filter.children, newFilter.children);
}
}
});
}
export function handleCheckedValue(filter: NestedFilter) {
export function handleCheckedValue(filter: NestedFilter): void {
if (filter.children && filter.children.length) {
filter.checked = filter.children.reduce<boolean>((acc, next) => acc && !!next.checked, true);
if (filter.checked) {
@ -38,38 +29,54 @@ export function handleCheckedValue(filter: NestedFilter) {
}
}
export function checkFilter(entity: any, filters: NestedFilter[], validate: Function, validateArgs: any = [], matchAll: boolean = false) {
export function processFilters(oldFilters: NestedFilter[], newFilters: NestedFilter[]): NestedFilter[] {
copySettings(oldFilters, newFilters);
if (newFilters) {
newFilters.forEach(filter => {
handleCheckedValue(filter);
});
}
return newFilters;
}
export function checkFilter(
entity: unknown,
filters: NestedFilter[],
validate?: (...args: unknown[]) => boolean,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
validateArgs: any = [],
matchAll = false
): boolean {
const hasChecked = filters.find(f => f.checked);
if (!hasChecked) return true;
let filterMatched = matchAll;
for (const filter of filters) {
filters.forEach(filter => {
if (filter.checked) {
if (matchAll) {
filterMatched = filterMatched && validate(entity, filter, ...validateArgs);
filterMatched = filterMatched && !!validate?.(entity, filter, ...validateArgs);
} else {
filterMatched = filterMatched || validate(entity, filter, ...validateArgs);
filterMatched = filterMatched || !!validate?.(entity, filter, ...validateArgs);
}
}
}
});
return filterMatched;
}
export const keyChecker = (key: string) => (entity: any, filter: NestedFilter) => entity[key] === filter.key;
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const keyChecker = (key: string) => (entity: Record<string, string>, filter: NestedFilter) => entity[key] === filter.key;
export function getFilteredEntities<T>(entities: T[], filters: FilterGroup[]) {
export function getFilteredEntities<T>(entities: T[], filters: FilterGroup[]): T[] {
const filteredEntities: T[] = [];
for (const entity of entities) {
entities.forEach(entity => {
let add = true;
for (const filter of filters) {
filters.forEach(filter => {
add = add && checkFilter(entity, filter.filters, filter.checker, filter.checkerArgs, filter.matchAll);
}
if (add) {
filteredEntities.push(entity);
}
}
});
if (add) filteredEntities.push(entity);
});
return filteredEntities;
}

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { processFilters, toFlatFilters } from './filter-utils';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
import { processFilters, toFlatFilters } from './filter-utils';
import { FilterGroup } from './models/filter-group.model';
import { NestedFilter } from './models/nested-filter.model';
@ -36,7 +36,7 @@ export class FilterService {
if (!filters) return console.error(`Cannot find filter group "${filterGroupSlug}"`);
let found = filters.find(f => f.key === key);
if (!found) found = filters.map(f => f.children?.find(ff => ff.key === key))[0];
if (!found) [found] = filters.map(f => f.children?.find(ff => ff.key === key));
if (!found) return console.error(`Cannot find filter with key "${key}" in group "${filterGroupSlug}"`);
found.checked = !found.checked;
@ -48,8 +48,8 @@ export class FilterService {
const oldFilters = this.getGroup(value.slug)?.filters;
if (!oldFilters) return this._filterGroups$.next([...this.filterGroups, value]);
value.filters = processFilters(oldFilters, value.filters);
this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== value.slug), value]);
const newGroup = { ...value, filters: processFilters(oldFilters, value.filters) };
this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== newGroup.slug), newGroup]);
}
getGroup(slug: string): FilterGroup | undefined {
@ -67,9 +67,14 @@ export class FilterService {
reset(): void {
this.filterGroups.forEach(group => {
group.filters.forEach(filter => {
// eslint-disable-next-line no-param-reassign
filter.checked = false;
// eslint-disable-next-line no-param-reassign
filter.indeterminate = false;
filter.children?.forEach(f => (f.checked = false));
filter.children?.forEach(f => {
// eslint-disable-next-line no-param-reassign
f.checked = false;
});
});
});

View File

@ -1,14 +1,14 @@
import { NestedFilter } from './nested-filter.model';
import { TemplateRef } from '@angular/core';
import { NestedFilter } from './nested-filter.model';
export interface FilterGroup {
filters: NestedFilter[];
readonly slug: string;
readonly label?: string;
readonly icon?: string;
readonly filterTemplate?: TemplateRef<any>;
readonly filterTemplate?: TemplateRef<unknown>;
readonly hide?: boolean;
readonly checker?: Function;
readonly checker?: (...args: unknown[]) => boolean;
readonly matchAll?: boolean;
readonly checkerArgs?: any[];
readonly checkerArgs?: unknown[];
}

View File

@ -17,7 +17,7 @@ export class EditableInputComponent {
@Input() class?: string;
@Input() showPreview = true;
@Input() buttonsType?: CircleButtonType;
@Output() save = new EventEmitter<string>();
@Output() readonly save = new EventEmitter<string>();
newValue = '';
private _editing = false;
@ -32,7 +32,7 @@ export class EditableInputComponent {
this.newValue = this.value;
}
saveValue() {
saveValue(): void {
this.save.emit(this.newValue);
this.editing = false;
}

View File

@ -15,6 +15,6 @@ export class RoundCheckboxComponent implements OnInit {
@ViewChild('wrapper', { static: true }) private readonly _wrapper!: ElementRef;
ngOnInit(): void {
this._wrapper.nativeElement.style.setProperty('--size', this.size + 'px');
(this._wrapper.nativeElement as HTMLElement).style.setProperty('--size', `${this.size}px`);
}
}

View File

@ -10,7 +10,7 @@ const controlsConfig = {
type FormControls = { [key in KeysOf<typeof controlsConfig>]: string };
@Injectable()
export class SearchService<T extends object> {
export class SearchService<T> {
readonly searchForm = this._formBuilder.group(controlsConfig);
readonly valueChanges$ = this.searchForm.valueChanges.pipe(
startWith(''),
@ -21,14 +21,14 @@ export class SearchService<T extends object> {
constructor(private readonly _formBuilder: FormBuilder) {}
get searchValue(): string {
return this.searchForm.get('query')?.value;
return this.searchForm.get('query')?.value as string;
}
set searchValue(value: string) {
this.searchForm.patchValue({ query: value });
}
searchIn(entities: T[]) {
searchIn(entities: T[]): T[] {
if (!this._searchKey) return entities;
const searchValue = this.searchValue.toLowerCase();

View File

@ -1,7 +1,7 @@
import { SortingOrder } from './sorting-order.type';
import { KeysOf } from '../../utils/types/utility-types';
export interface SortingOption<T extends object> {
export interface SortingOption<T> {
readonly order: SortingOrder;
readonly column: KeysOf<T>;
}

View File

@ -1,14 +1,12 @@
import { NonFunctionKeys } from '../../utils/types/utility-types';
function inverseOf(order?: SortingOrder) {
if (order === undefined) return SortingOrders.asc;
return order === SortingOrders.asc ? SortingOrders.desc : SortingOrders.asc;
}
export const SortingOrders = {
asc: 'asc',
desc: 'desc',
inverseOf: inverseOf
inverseOf: (order?: 'asc' | 'desc') => {
if (order === undefined) return 'asc';
return order === 'asc' ? 'desc' : 'asc';
}
} as const;
export type SortingOrder = NonFunctionKeys<typeof SortingOrders>;

View File

@ -5,7 +5,7 @@ import { KeysOf } from '../utils/types/utility-types';
@Pipe({ name: 'sortBy' })
export class SortByPipe implements PipeTransform {
transform<T extends object>(values: T[], order: SortingOrder, column: KeysOf<T>): T[] {
transform<T>(values: T[], order: SortingOrder, column: KeysOf<T>): T[] {
return SortingService.sort(values, order, column);
}
}

View File

@ -6,7 +6,7 @@ import { SortingOrder, SortingOrders } from './models/sorting-order.type';
import { KeysOf } from '../utils/types/utility-types';
@Injectable()
export class SortingService<T extends object> {
export class SortingService<T> {
private readonly _sortingOption$ = new BehaviorSubject<SortingOption<T> | undefined>(undefined);
readonly sortingOption$ = this._sortingOption$.asObservable();
@ -14,7 +14,7 @@ export class SortingService<T extends object> {
return this._sortingOption$.getValue();
}
static sort<T extends object>(values: T[], order?: SortingOrder, column?: KeysOf<T>): T[] {
static sort<T>(values: T[], order?: SortingOrder, column?: KeysOf<T>): T[] {
if (!values || values.length <= 1 || !order) return values;
if (!column) {

View File

@ -9,7 +9,7 @@ const toLengthValue = (entities: unknown[]) => entities?.length ?? 0;
const getLength = pipe(map(toLengthValue), distinctUntilChanged());
@Injectable()
export class EntitiesService<T extends object> {
export class EntitiesService<T> {
private readonly _all$ = new BehaviorSubject<T[]>([]);
readonly all$ = this._all$.asObservable();
readonly allLength$ = this._all$.pipe(getLength);
@ -38,13 +38,15 @@ export class EntitiesService<T extends object> {
}
private get _getDisplayed$(): Observable<T[]> {
const filterGroups$ = this._filterService.filterGroups$;
const searchValue$ = this._searchService.valueChanges$;
const { filterGroups$ } = this._filterService;
const { valueChanges$ } = this._searchService;
return combineLatest([this.all$, filterGroups$, searchValue$]).pipe(
return combineLatest([this.all$, filterGroups$, valueChanges$]).pipe(
map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)),
map(entities => this._searchService.searchIn(entities)),
tap(displayed => (this._displayed = displayed))
tap(displayed => {
this._displayed = displayed;
})
);
}

View File

@ -14,7 +14,7 @@ import { EntitiesService } from './entities.service';
export const DefaultListingServices = [FilterService, SearchService, EntitiesService, SortingService] as const;
@Directive()
export abstract class ListingComponent<T extends object> extends AutoUnsubscribe implements OnDestroy {
export abstract class ListingComponent<T> extends AutoUnsubscribe implements OnDestroy {
readonly filterService = this._injector.get(FilterService);
readonly searchService = this._injector.get<SearchService<T>>(SearchService);
readonly sortingService = this._injector.get<SortingService<T>>(SortingService);
@ -53,7 +53,7 @@ export abstract class ListingComponent<T extends object> extends AutoUnsubscribe
);
}
setInitialConfig() {
setInitialConfig(): void {
this.sortingService.setSortingOption({
column: this._primaryKey,
order: SortingOrders.asc
@ -61,9 +61,9 @@ export abstract class ListingComponent<T extends object> extends AutoUnsubscribe
this.searchService.setSearchKey(this._primaryKey);
}
toggleEntitySelected(event: MouseEvent, entity: T) {
toggleEntitySelected(event: MouseEvent, entity: T): void {
event.stopPropagation();
return this.entitiesService.select(entity);
this.entitiesService.select(entity);
}
isSelected(entity: T): boolean {
@ -71,7 +71,7 @@ export abstract class ListingComponent<T extends object> extends AutoUnsubscribe
}
@Bind()
trackByPrimaryKey(index: number, item: T) {
trackByPrimaryKey(index: number, item: T): unknown {
return item[this._primaryKey];
}
}

View File

@ -1,6 +1,6 @@
import { KeysOf } from '../../utils/types/utility-types';
export interface TableColumnConfig<T extends object> {
export interface TableColumnConfig<T> {
readonly label: string;
readonly sortByKey?: KeysOf<T>;
readonly class?: string;

View File

@ -18,36 +18,40 @@ export class SyncWidthDirective implements OnDestroy {
}
private _matchWidth() {
const headerItems = this._elementRef.nativeElement.children;
const tableRows = this._elementRef.nativeElement.parentElement.parentElement.getElementsByClassName(this.iqserSyncWidth);
const headerItems = (this._elementRef.nativeElement as HTMLElement).children;
const tableRows = (this._elementRef.nativeElement as HTMLElement).parentElement?.parentElement?.getElementsByClassName(
this.iqserSyncWidth
);
if (!tableRows || !tableRows.length) {
return;
}
this._elementRef.nativeElement.setAttribute('synced', true);
(this._elementRef.nativeElement as HTMLElement).setAttribute('synced', 'true');
const { tableRow, length } = this._sampleRow(tableRows);
if (!tableRow) return;
const hasExtraColumns = headerItems.length !== length ? 1 : 0;
for (let idx = 0; idx < length - hasExtraColumns - 1; ++idx) {
if (headerItems[idx]) {
headerItems[idx].style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`;
headerItems[idx].style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`;
for (let idx = 0; idx < length - hasExtraColumns - 1; idx += 1) {
const item = headerItems[idx] as HTMLElement;
if (item) {
item.style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`;
item.style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`;
}
}
for (let idx = length - hasExtraColumns - 1; idx < headerItems.length; ++idx) {
if (headerItems[idx]) {
headerItems[idx].style.minWidth = `0`;
for (let idx = length - hasExtraColumns - 1; idx < headerItems.length; idx += 1) {
const item = headerItems[idx] as HTMLElement;
if (item) {
item.style.minWidth = `0`;
}
}
}
@HostListener('window:resize')
onResize() {
onResize(): void {
this._matchWidth();
}
@ -58,7 +62,7 @@ export class SyncWidthDirective implements OnDestroy {
let length = 0;
let tableRow: Element | undefined;
for (let idx = 0; idx < tableRows.length; ++idx) {
for (let idx = 0; idx < tableRows.length; idx += 1) {
const row = tableRows.item(idx);
if (row && row.children.length > length) {
length = row.children.length;

View File

@ -4,7 +4,7 @@ import { Required } from '../../utils/decorators/required.decorator';
import { KeysOf } from '../../utils/types/utility-types';
import { SortingService } from '../../sorting/sorting.service';
const ifHasRightIcon = <T extends object>(thisArg: TableColumnNameComponent<T>) => !!thisArg.rightIcon;
const ifHasRightIcon = <T>(thisArg: TableColumnNameComponent<T>) => !!thisArg.rightIcon;
@Component({
selector: 'iqser-table-column-name',
@ -12,7 +12,7 @@ const ifHasRightIcon = <T extends object>(thisArg: TableColumnNameComponent<T>)
styleUrls: ['./table-column-name.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TableColumnNameComponent<T extends object> {
export class TableColumnNameComponent<T> {
readonly sortingOrders = SortingOrders;
@Input() @Required() label!: string;

View File

@ -10,12 +10,12 @@ import { EntitiesService } from '../entities.service';
styleUrls: ['./table-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TableHeaderComponent<T extends object> {
export class TableHeaderComponent<T> {
@Input() @Required() tableHeaderLabel!: string;
@Input() @Required() tableColumnConfigs!: readonly TableColumnConfig<T>[];
@Input() hasEmptyColumn = false;
@Input() selectionEnabled = false;
@Input() bulkActions?: TemplateRef<any>;
@Input() bulkActions?: TemplateRef<unknown>;
readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters');

View File

@ -1,5 +1,5 @@
import { Directive, OnDestroy } from "@angular/core";
import { Subscription } from "rxjs";
import { Directive, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
/**
* Inherit this class when you need to subscribe to observables in your components

View File

@ -6,20 +6,16 @@
*
*/
export function Bind() {
return function _bind<T extends Function>(
_target: Object,
propertyKey: PropertyKey,
descriptor: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | never {
export function Bind(): MethodDecorator {
return function _bind(_target: unknown, propertyKey: PropertyKey, descriptor: PropertyDescriptor): PropertyDescriptor | never {
if (typeof descriptor.value !== 'function') {
throw new TypeError(`@Bind() decorator can only be applied to methods, not to ${typeof descriptor.value}`);
}
return {
configurable: true,
get(): T {
const boundMethod: T = descriptor.value?.bind(this);
get() {
const boundMethod = (descriptor.value as (...args: unknown[]) => unknown).bind(this);
Object.defineProperty(this, propertyKey, {
value: boundMethod,
configurable: true,

View File

@ -1,14 +1,14 @@
export function Debounce(delay = 300): MethodDecorator {
return function (target: Object, propertyKey: PropertyKey, descriptor: PropertyDescriptor) {
return function _debounce(_target: unknown, propertyKey: PropertyKey, descriptor: PropertyDescriptor) {
let timeout: number | undefined;
const original = descriptor.value;
const descriptorCopy = { ...descriptor };
descriptor.value = function (...args: unknown[]) {
descriptorCopy.value = function _new(...args: unknown[]) {
clearTimeout(timeout);
timeout = setTimeout(() => original.apply(this, args), delay);
timeout = setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay);
};
return descriptor;
return descriptorCopy;
};
}

View File

@ -1,12 +1,12 @@
export type Condition<T> = (thisArg: T) => boolean;
export function Required<T>(condition: Condition<T> = () => true): PropertyDecorator {
return function (target: Object, propertyKey: PropertyKey) {
return function _required(target: unknown, propertyKey: PropertyKey) {
Object.defineProperty(target, propertyKey, {
get() {
if (condition(this)) throw new Error(`Attribute ${String(propertyKey)} is required`);
},
set(value) {
set(value: unknown) {
Object.defineProperty(this, propertyKey, {
value,
writable: true

View File

@ -7,7 +7,7 @@
* // Expect: "name | setName | someKeys | someFn"
* type Keys = KeysOf<Object>;
*/
export type KeysOf<T extends object> = {
export type KeysOf<T> = {
[K in keyof T]: K;
}[keyof T];
@ -20,7 +20,7 @@ export type KeysOf<T extends object> = {
* // Expect: "some bar | some foo"
* type Values = ValuesOf<Object>;
*/
export type ValuesOf<T extends object> = T[KeysOf<T>];
export type ValuesOf<T> = T[KeysOf<T>];
/**
* NonUndefined
@ -40,7 +40,8 @@ export type NonUndefined<T> = T extends undefined ? never : T;
* // Expect: "setName | someFn"
* type Keys = FunctionKeys<MixedProps>;
*/
export type FunctionKeys<T extends object> = {
export type FunctionKeys<T> = {
// eslint-disable-next-line @typescript-eslint/ban-types
[K in keyof T]-?: NonUndefined<T[K]> extends Function ? K : never;
}[keyof T];
@ -53,6 +54,7 @@ export type FunctionKeys<T extends object> = {
* // Expect: "name | someKey"
* type Keys = NonFunctionKeys<MixedProps>;
*/
export type NonFunctionKeys<T extends object> = {
export type NonFunctionKeys<T> = {
// eslint-disable-next-line @typescript-eslint/ban-types
[K in keyof T]-?: NonUndefined<T[K]> extends Function ? never : K;
}[keyof T];

View File

@ -1 +1,2 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import 'jest-preset-angular/setup-jest';