diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index f7141d37..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "root": true, - "ignorePatterns": [ - "libs/**/*" - ], - "overrides": [ - { - "files": [ - "*.ts" - ], - "parserOptions": { - "project": [ - "tsconfig.json", - "e2e/tsconfig.json" - ], - "createDefaultProgram": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@angular-eslint/recommended", - "plugin:@angular-eslint/template/process-inline-templates" - ], - "rules": { - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": ["app", "enigmatry", "entry"], - "style": "kebab-case" - } - ], - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": ["app", "enigmatry", "entry"], - "style": "camelCase" - } - ], - "prefer-arrow/prefer-arrow-functions": "off", - "@typescript-eslint/no-unused-vars": [ - "error", - { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_" - } - ], - "@angular-eslint/prefer-standalone": [ - "warn" - ] - } - }, - { - "files": [ - "*.html" - ], - "extends": [ - "plugin:@angular-eslint/template/recommended" - ], - "rules": {} - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index 53b24976..392e1b02 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,20 @@ { "cSpell.words": [ + "Formly", "stylelint" ], + "css.validate": false, + "less.validate": false, + "scss.validate": false, + "stylelint.enable": true, + "stylelint.validate": [ + "css", + "scss" + ], + "editor.formatOnSave": false, "editor.codeActionsOnSave": { + "source.fixAll.stylelint": "explicit", "source.fixAll.eslint": "explicit" - } + }, + "files.autoSaveDelay": 500 } \ No newline at end of file diff --git a/README.md b/README.md index 31676528..9c50c587 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Visit components demo application. [Website](https://entry-demo.enigmatry.com/) |17.x| = 17 |18.x| = 18 |19.x| = 19 +|20.x| = 20 ## Migrate project to entry diff --git a/angular.json b/angular.json index f10623da..613c9461 100644 --- a/angular.json +++ b/angular.json @@ -23,15 +23,6 @@ } }, "defaultConfiguration": "production" - }, - "lint": { - "builder": "@angular-eslint/builder:lint", - "options": { - "lintFilePatterns": [ - "libs/entry-form/**/*.ts", - "libs/entry-form/**/*.html" - ] - } } } }, @@ -55,15 +46,6 @@ } }, "defaultConfiguration": "production" - }, - "lint": { - "builder": "@angular-eslint/builder:lint", - "options": { - "lintFilePatterns": [ - "libs/entry-components/**/*.ts", - "libs/entry-components/**/*.html" - ] - } } } }, @@ -169,23 +151,8 @@ "options": { "buildTarget": "@enigmatry/demo-app:build" } - }, - "lint": { - "builder": "@angular-eslint/builder:lint", - "options": { - "lintFilePatterns": [ - "apps/demo-app/**/*.ts", - "apps/demo-app/**/*.html" - ] - } } } } - }, - "cli": { - "schematicCollections": [ - "@angular-eslint/schematics" - ], - "analytics": false } } diff --git a/apps/demo-app/.eslintrc.json b/apps/demo-app/.eslintrc.json deleted file mode 100644 index d0682b27..00000000 --- a/apps/demo-app/.eslintrc.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "extends": "../../.eslintrc.json", - "ignorePatterns": [ - "!**/*" - ], - "overrides": [ - { - "files": [ - "*.ts" - ], - "parserOptions": { - "project": [ - "tsconfig.json", - "apps/demo-app/tsconfig.app.json", - "apps/demo-app/tsconfig.spec.json" - ], - "createDefaultProgram": true - }, - "rules": { - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": "app", - "style": "camelCase" - } - ], - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": "app", - "style": "kebab-case" - } - ] - } - }, - { - "files": [ - "*.html" - ], - "rules": {} - } - ] -} diff --git a/apps/demo-app/eslint.config.js b/apps/demo-app/eslint.config.js new file mode 100644 index 00000000..f96fb56b --- /dev/null +++ b/apps/demo-app/eslint.config.js @@ -0,0 +1,11 @@ +import defaultEnigmatryConfiguration from '../../libs/eslint-config/index.js'; + +export default [ + ...defaultEnigmatryConfiguration, + { + files: ['src/**/*.ts'], + rules: { + '@angular-eslint/prefer-standalone': 'off' // TODO: Remove when we get rid of Formly + } + } +]; \ No newline at end of file diff --git a/apps/demo-app/src/app/app-routing.module.ts b/apps/demo-app/src/app/app-routing.module.ts index 13bc6b09..898fed8f 100644 --- a/apps/demo-app/src/app/app-routing.module.ts +++ b/apps/demo-app/src/app/app-routing.module.ts @@ -1,4 +1,4 @@ -/* eslint-disable max-len */ + import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { RouteSegments } from './features/route-segments'; @@ -51,7 +51,8 @@ const routes: Routes = [ }, { path: RouteSegments.dateTimePicker, - loadComponent: () => import('./features/date-time-picker/date-time-picker-documentation.component').then(m => m.DateTimePickerDocumentationComponent) + loadComponent: () => import('./features/date-time-picker/date-time-picker-documentation.component') + .then(m => m.DateTimePickerDocumentationComponent) } ]; diff --git a/apps/demo-app/src/app/app.component.ts b/apps/demo-app/src/app/app.component.ts index 7e9d44a9..a268cd07 100644 --- a/apps/demo-app/src/app/app.component.ts +++ b/apps/demo-app/src/app/app.component.ts @@ -1,7 +1,7 @@ -import { Component, OnInit } from '@angular/core'; -import { IComponentDefinition, COMPONENT_DEFINITIONS } from './features/component-definitions'; +import { Component, inject, OnInit } from '@angular/core'; import { NavigationEnd, Router } from '@angular/router'; import { filter } from 'rxjs/operators'; +import { IComponentDefinition, COMPONENT_DEFINITIONS } from './features/component-definitions'; @Component({ selector: 'app-root', @@ -12,8 +12,7 @@ import { filter } from 'rxjs/operators'; export class AppComponent implements OnInit { menuItems = COMPONENT_DEFINITIONS; selectedMenuItem: IComponentDefinition | undefined = undefined; - - constructor(private router: Router) { } + private readonly router: Router = inject(Router); ngOnInit(): void { this.router.events diff --git a/apps/demo-app/src/app/app.module.ts b/apps/demo-app/src/app/app.module.ts index bbe8c641..45ce14a4 100644 --- a/apps/demo-app/src/app/app.module.ts +++ b/apps/demo-app/src/app/app.module.ts @@ -1,12 +1,12 @@ +import { provideHttpClient } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { provideHttpClient } from '@angular/common/http'; +import { EntryCommonModule } from '@enigmatry/entry-components/common'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { SharedModule } from './shared/shared.module'; -import { EntryCommonModule } from '@enigmatry/entry-components/common'; @NgModule({ diff --git a/apps/demo-app/src/app/examples/button/button-example.module.ts b/apps/demo-app/src/app/examples/button/button-example.module.ts index 1f3ade20..7358baf7 100644 --- a/apps/demo-app/src/app/examples/button/button-example.module.ts +++ b/apps/demo-app/src/app/examples/button/button-example.module.ts @@ -1,8 +1,8 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ButtonExampleComponent } from './button-example/button-example.component'; -import { SharedModule } from '../../shared/shared.module'; +import { NgModule } from '@angular/core'; import { EntryButtonModule, provideEntryButtonConfig } from '@enigmatry/entry-components/button'; +import { SharedModule } from '../../shared/shared.module'; +import { ButtonExampleComponent } from './button-example/button-example.component'; @NgModule({ declarations: [ diff --git a/apps/demo-app/src/app/examples/common/common-example.module.ts b/apps/demo-app/src/app/examples/common/common-example.module.ts index 8632323c..b31a7d90 100644 --- a/apps/demo-app/src/app/examples/common/common-example.module.ts +++ b/apps/demo-app/src/app/examples/common/common-example.module.ts @@ -1,12 +1,12 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { EventPluginExampleComponent } from './event-plugin-example/event-plugin-example.component'; -import { MatInputModule } from '@angular/material/input'; +import { NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; -import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; import { SharedModule } from '../../shared/shared.module'; import { AutoDisableButtonExampleComponent } from './directives/auto-disable-button-example/auto-disable-button-example.component'; +import { EventPluginExampleComponent } from './event-plugin-example/event-plugin-example.component'; @NgModule({ declarations: [ diff --git a/apps/demo-app/src/app/examples/common/event-plugin-example/event-plugin-example.component.ts b/apps/demo-app/src/app/examples/common/event-plugin-example/event-plugin-example.component.ts index b1978050..9ec4fb53 100644 --- a/apps/demo-app/src/app/examples/common/event-plugin-example/event-plugin-example.component.ts +++ b/apps/demo-app/src/app/examples/common/event-plugin-example/event-plugin-example.component.ts @@ -6,7 +6,6 @@ import { Component, ElementRef, ViewChild } from '@angular/core'; standalone: false }) export class EventPluginExampleComponent { - @ViewChild('events', { static: true }) events: ElementRef; log(eventName: string) { diff --git a/apps/demo-app/src/app/examples/date-time-picker/date-time-picker-example.module.ts b/apps/demo-app/src/app/examples/date-time-picker/date-time-picker-example.module.ts index bfbdcb24..dbce1258 100644 --- a/apps/demo-app/src/app/examples/date-time-picker/date-time-picker-example.module.ts +++ b/apps/demo-app/src/app/examples/date-time-picker/date-time-picker-example.module.ts @@ -1,22 +1,22 @@ -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; -import { EntryDateTimePickerModule } from "@enigmatry/entry-components/date-time-picker"; -import { MinMaxComponent } from './min-max/min-max.component'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { DateFnsAdapter, MAT_DATE_FNS_FORMATS } from '@angular/material-date-fns-adapter'; +import { provideEntryNativeTimeAdapter } from '@enigmatry/entry-components/common'; +import { EntryDateTimePickerModule } from '@enigmatry/entry-components/date-time-picker'; +import { provideEntryValidationConfig } from '@enigmatry/entry-components/validation'; import { BasicComponent } from './basic/basic.component'; +import { DefaultTimeComponent } from './default-time/default-time.component'; import { DisableComponent } from './disable/disable.component'; import { MeridiemComponent } from './meridiem/meridiem.component'; -import { provideEntryNativeTimeAdapter } from "@enigmatry/entry-components/common"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { DateFnsAdapter, MAT_DATE_FNS_FORMATS } from "@angular/material-date-fns-adapter"; -import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from "@angular/material/core"; -import { MatCheckboxModule } from "@angular/material/checkbox"; -import { MatDatepickerModule } from "@angular/material/datepicker"; -import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatInputModule } from "@angular/material/input"; -import { provideEntryValidationConfig } from "@enigmatry/entry-components/validation"; +import { MinMaxComponent } from './min-max/min-max.component'; import { SecondsComponent } from './seconds/seconds.component'; -import { DefaultTimeComponent } from './default-time/default-time.component'; -import { WithValidationComponent } from "./with-validation/with-validation.component"; +import { WithValidationComponent } from './with-validation/with-validation.component'; @NgModule({ declarations: [ @@ -55,17 +55,17 @@ import { WithValidationComponent } from "./with-validation/with-validation.compo { provide: DateAdapter, useClass: DateFnsAdapter, - deps: [MAT_DATE_LOCALE], + deps: [MAT_DATE_LOCALE] }, provideEntryNativeTimeAdapter({ parse: { - dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy HH:mm'], + dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy HH:mm'] }, display: { dateInput: 'dd-MM-yyyy HH:mm', monthYearLabel: 'LLL uuuu', dateA11yLabel: 'PP', - monthYearA11yLabel: 'LLLL uuuu', + monthYearA11yLabel: 'LLLL uuuu' } }), provideEntryValidationConfig({ diff --git a/apps/demo-app/src/app/examples/date-time-picker/default-time/default-time.component.ts b/apps/demo-app/src/app/examples/date-time-picker/default-time/default-time.component.ts index cc5474a9..c4c30747 100644 --- a/apps/demo-app/src/app/examples/date-time-picker/default-time/default-time.component.ts +++ b/apps/demo-app/src/app/examples/date-time-picker/default-time/default-time.component.ts @@ -8,5 +8,6 @@ import { FormControl } from '@angular/forms'; }) export class DefaultTimeComponent { dateTime = new FormControl(); + // eslint-disable-next-line @typescript-eslint/no-magic-numbers defaultTime = new Date(0, 0, 0, 12, 0, 0); } diff --git a/apps/demo-app/src/app/examples/date-time-picker/meridiem/meridiem.component.ts b/apps/demo-app/src/app/examples/date-time-picker/meridiem/meridiem.component.ts index 216a879e..e66e5ae5 100644 --- a/apps/demo-app/src/app/examples/date-time-picker/meridiem/meridiem.component.ts +++ b/apps/demo-app/src/app/examples/date-time-picker/meridiem/meridiem.component.ts @@ -8,13 +8,13 @@ import { provideEntryNativeTimeAdapter } from '@enigmatry/entry-components'; providers: [ provideEntryNativeTimeAdapter({ parse: { - dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy hh:mm aaa'], + dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy hh:mm aaa'] }, display: { dateInput: 'dd-MM-yyyy hh:mm aaa', monthYearLabel: 'LLL uuuu', dateA11yLabel: 'PP', - monthYearA11yLabel: 'LLLL uuuu', + monthYearA11yLabel: 'LLLL uuuu' } }) ], diff --git a/apps/demo-app/src/app/examples/date-time-picker/min-max/min-max.component.ts b/apps/demo-app/src/app/examples/date-time-picker/min-max/min-max.component.ts index 7418d211..8494e286 100644 --- a/apps/demo-app/src/app/examples/date-time-picker/min-max/min-max.component.ts +++ b/apps/demo-app/src/app/examples/date-time-picker/min-max/min-max.component.ts @@ -7,6 +7,7 @@ import { FormControl } from '@angular/forms'; standalone: false }) export class MinMaxComponent { + // eslint-disable-next-line @typescript-eslint/no-magic-numbers tenDays = 10 * 24 * 60 * 60 * 1000; minDate = new Date(new Date().getTime() - this.tenDays); maxDate = new Date(new Date().getTime() + this.tenDays); diff --git a/apps/demo-app/src/app/examples/date-time-picker/seconds/seconds.component.ts b/apps/demo-app/src/app/examples/date-time-picker/seconds/seconds.component.ts index 9a97dab1..f9a6cbdc 100644 --- a/apps/demo-app/src/app/examples/date-time-picker/seconds/seconds.component.ts +++ b/apps/demo-app/src/app/examples/date-time-picker/seconds/seconds.component.ts @@ -8,13 +8,13 @@ import { provideEntryNativeTimeAdapter } from '@enigmatry/entry-components/commo providers: [ provideEntryNativeTimeAdapter({ parse: { - dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy HH:mm:ss'], + dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy HH:mm:ss'] }, display: { dateInput: 'dd-MM-yyyy HH:mm:ss', monthYearLabel: 'LLL uuuu', dateA11yLabel: 'PP', - monthYearA11yLabel: 'LLLL uuuu', + monthYearA11yLabel: 'LLLL uuuu' } }) ], diff --git a/apps/demo-app/src/app/examples/dialog/alert/alert-example.component.ts b/apps/demo-app/src/app/examples/dialog/alert/alert-example.component.ts index 8b9f99a2..9504ede2 100644 --- a/apps/demo-app/src/app/examples/dialog/alert/alert-example.component.ts +++ b/apps/demo-app/src/app/examples/dialog/alert/alert-example.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { EntryDialogService } from '@enigmatry/entry-components/dialog'; @Component({ @@ -7,12 +7,11 @@ import { EntryDialogService } from '@enigmatry/entry-components/dialog'; standalone: false }) export class AlertExampleComponent { - - constructor(private _entryDialog: EntryDialogService) { } + private readonly _entryDialog: EntryDialogService = inject(EntryDialogService); openAlert = () => this._entryDialog.openAlert({ title: `ALERT`, - message: `Lorem Ipsum is simply dummy text of the printing and typesetting industry.`, + message: `Lorem Ipsum is simply dummy text of the printing and typesetting industry.` // Optional properties (if not provided, default values are used from ENTRY_DIALOG_CONFIG): // confirmText: 'Ok', // buttonsAlignment: 'align-center', diff --git a/apps/demo-app/src/app/examples/dialog/confirm/confirm-example.component.ts b/apps/demo-app/src/app/examples/dialog/confirm/confirm-example.component.ts index 0a0276cd..092f2911 100644 --- a/apps/demo-app/src/app/examples/dialog/confirm/confirm-example.component.ts +++ b/apps/demo-app/src/app/examples/dialog/confirm/confirm-example.component.ts @@ -1,10 +1,10 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { IEntryConfirmDialogData, EntryDialogButtonsAlignment, EntryDialogService } from '@enigmatry/entry-components/dialog'; @Component({ - selector: 'app-confirm-example', - templateUrl: './confirm-example.component.html', - standalone: false + selector: 'app-confirm-example', + templateUrl: './confirm-example.component.html', + standalone: false }) export class ConfirmExampleComponent { confirmData: Partial = { @@ -19,8 +19,7 @@ export class ConfirmExampleComponent { }; confirmResponse: boolean | undefined; alignments: EntryDialogButtonsAlignment[] = ['start', 'center', 'end']; - - constructor(private _entryDialog: EntryDialogService) { } + private readonly _entryDialog: EntryDialogService = inject(EntryDialogService); openConfirm = () => this._entryDialog .openConfirm(this.confirmData) diff --git a/apps/demo-app/src/app/examples/dialog/custom/custom-dialog-example.component.ts b/apps/demo-app/src/app/examples/dialog/custom/custom-dialog-example.component.ts index 11f649a7..e824a76d 100644 --- a/apps/demo-app/src/app/examples/dialog/custom/custom-dialog-example.component.ts +++ b/apps/demo-app/src/app/examples/dialog/custom/custom-dialog-example.component.ts @@ -1,6 +1,6 @@ -import { Component } from '@angular/core'; -import { CustomDialogComponent, ICustomDialogResult } from './custom-dialog.component'; +import { Component, inject } from '@angular/core'; import { EntryDialogService } from '@enigmatry/entry-components/dialog'; +import { CustomDialogComponent, ICustomDialogResult } from './custom-dialog.component'; export interface ICustomDialogData { question: string; @@ -14,14 +14,12 @@ export interface ICustomDialogData { export class CustomDialogExampleComponent { question = 'Isn\'t this logo cute?'; result: ICustomDialogResult; - - constructor(private _entryDialog: EntryDialogService) { } + private readonly _entryDialog: EntryDialogService = inject(EntryDialogService); openCustom = () => this._entryDialog.open( CustomDialogComponent, { question: this.question } as ICustomDialogData, true - ) - .subscribe(result => this.result = result); + ).subscribe(result => this.result = result); } diff --git a/apps/demo-app/src/app/examples/dialog/custom/custom-dialog.component.ts b/apps/demo-app/src/app/examples/dialog/custom/custom-dialog.component.ts index e2a46062..b80bb85a 100644 --- a/apps/demo-app/src/app/examples/dialog/custom/custom-dialog.component.ts +++ b/apps/demo-app/src/app/examples/dialog/custom/custom-dialog.component.ts @@ -1,7 +1,7 @@ -import { Component, Inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { ICustomDialogData } from './custom-dialog-example.component'; import { EntryDialogComponent, ENTRY_DIALOG_CONFIG, EntryDialogConfig } from '@enigmatry/entry-components/dialog'; +import { ICustomDialogData } from './custom-dialog-example.component'; export interface ICustomDialogResult { response: string; @@ -16,13 +16,9 @@ export interface ICustomDialogResult { }) export class CustomDialogComponent extends EntryDialogComponent { comment: string; - - constructor( - protected mdDialogRef: MatDialogRef, - @Inject(ENTRY_DIALOG_CONFIG) protected config: EntryDialogConfig, - @Inject(MAT_DIALOG_DATA) public data: ICustomDialogData) { - super(mdDialogRef, config); - } + protected override readonly mdDialogRef: MatDialogRef = inject(MatDialogRef); + protected override readonly config: EntryDialogConfig = inject(ENTRY_DIALOG_CONFIG); + readonly data: ICustomDialogData = inject(MAT_DIALOG_DATA); onClick = (response: string) => this.close({ response, comment: this.comment } as ICustomDialogResult); diff --git a/apps/demo-app/src/app/examples/dialog/dialog-examples.module.ts b/apps/demo-app/src/app/examples/dialog/dialog-examples.module.ts index f0507d70..79a1c6a7 100644 --- a/apps/demo-app/src/app/examples/dialog/dialog-examples.module.ts +++ b/apps/demo-app/src/app/examples/dialog/dialog-examples.module.ts @@ -1,12 +1,12 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { EntryDialogModule } from '@enigmatry/entry-components/dialog'; import { SharedModule } from '../../shared/shared.module'; import { AlertExampleComponent } from './alert/alert-example.component'; import { ConfirmExampleComponent } from './confirm/confirm-example.component'; import { CustomDialogExampleComponent } from './custom/custom-dialog-example.component'; import { CustomDialogComponent } from './custom/custom-dialog.component'; -import { EntryDialogModule } from '@enigmatry/entry-components/dialog'; import { ErrorDialogExampleComponent } from './error/error-example.component'; @NgModule({ diff --git a/apps/demo-app/src/app/examples/dialog/error/error-example.component.ts b/apps/demo-app/src/app/examples/dialog/error/error-example.component.ts index 9a2de32f..5fca5ef6 100644 --- a/apps/demo-app/src/app/examples/dialog/error/error-example.component.ts +++ b/apps/demo-app/src/app/examples/dialog/error/error-example.component.ts @@ -1,24 +1,23 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { EntryDialogService } from '@enigmatry/entry-components/dialog'; import { ValidationService } from '../../validation/validation.service'; @Component({ - selector: 'app-error-dialog-example', - templateUrl: './error-example.component.html', - standalone: false + selector: 'app-error-dialog-example', + templateUrl: './error-example.component.html', + standalone: false }) export class ErrorDialogExampleComponent { - - constructor(private _entryDialog: EntryDialogService, private service: ValidationService) { } + private readonly _entryDialog: EntryDialogService = inject(EntryDialogService); + private readonly service: ValidationService = inject(ValidationService); openError() { this.service.submitWithValidationErrors() .subscribe({ - error: (err) => this._entryDialog.openError({ + error: err => this._entryDialog.openError({ title: `One or more validation errors occurred`, errors: err }) }); - } } diff --git a/apps/demo-app/src/app/examples/file-input/file-input-example.module.ts b/apps/demo-app/src/app/examples/file-input/file-input-example.module.ts index 6cf548d6..c9ab74c0 100644 --- a/apps/demo-app/src/app/examples/file-input/file-input-example.module.ts +++ b/apps/demo-app/src/app/examples/file-input/file-input-example.module.ts @@ -1,9 +1,9 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { SharedModule } from '../../shared/shared.module'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { EntryFileInputModule } from '@enigmatry/entry-components/file-input'; +import { SharedModule } from '../../shared/shared.module'; import { FileInputBasicExampleComponent } from './file-input-basic-example/file-input-basic-example.component'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FileInputFormExampleComponent } from './file-input-form-example/file-input-form-example.component'; import { FileInputValidationExampleComponent } from './file-input-validation-example/file-input-validation-example.component'; diff --git a/apps/demo-app/src/app/examples/file-input/file-input-validation-example/file-input-validation-example.component.ts b/apps/demo-app/src/app/examples/file-input/file-input-validation-example/file-input-validation-example.component.ts index 7f3132a0..d034f7e0 100644 --- a/apps/demo-app/src/app/examples/file-input/file-input-validation-example/file-input-validation-example.component.ts +++ b/apps/demo-app/src/app/examples/file-input/file-input-validation-example/file-input-validation-example.component.ts @@ -9,6 +9,6 @@ import { FormGroup, FormControl, Validators } from '@angular/forms'; }) export class FileInputValidationExampleComponent { form = new FormGroup({ - image: new FormControl(undefined, { validators: [Validators.required] }) + image: new FormControl(undefined, { validators: [Validators.required] }) }); } diff --git a/apps/demo-app/src/app/examples/form/checkbox/checkbox-example.component.ts b/apps/demo-app/src/app/examples/form/checkbox/checkbox-example.component.ts index a23b2e13..ec504a0c 100644 --- a/apps/demo-app/src/app/examples/form/checkbox/checkbox-example.component.ts +++ b/apps/demo-app/src/app/examples/form/checkbox/checkbox-example.component.ts @@ -18,7 +18,7 @@ export class CheckboxExampleComponent { placeholder: `Free shipping`, description: ``, typeFormatDef: { name: 'boolean' } - }, + } } ]; } diff --git a/apps/demo-app/src/app/examples/form/date-time-picker/date-time-picker-example.component.ts b/apps/demo-app/src/app/examples/form/date-time-picker/date-time-picker-example.component.ts index 54f11099..da588b77 100644 --- a/apps/demo-app/src/app/examples/form/date-time-picker/date-time-picker-example.component.ts +++ b/apps/demo-app/src/app/examples/form/date-time-picker/date-time-picker-example.component.ts @@ -18,7 +18,7 @@ export class DateTimePickerExampleComponent { description: ``, typeFormatDef: { name: 'date' } }, - modelOptions: { updateOn: 'blur' }, - }, + modelOptions: { updateOn: 'blur' } + } ]; } diff --git a/apps/demo-app/src/app/examples/form/form-example.module.ts b/apps/demo-app/src/app/examples/form/form-example.module.ts index 83d57586..d21cd80a 100644 --- a/apps/demo-app/src/app/examples/form/form-example.module.ts +++ b/apps/demo-app/src/app/examples/form/form-example.module.ts @@ -1,24 +1,25 @@ -import { NgModule } from '@angular/core'; +/* eslint-disable import/max-dependencies */ import { CommonModule } from '@angular/common'; -import { FormExampleComponent } from './form-example/form-example.component'; +import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { SharedModule } from '../../shared/shared.module'; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; +import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; +import { FormlyAutocompleteModule } from '@enigmatry/entry-form/autocomplete'; +import { ENTRY_CKEDITOR_OPTIONS, FormlyCkeditorModule } from '@enigmatry/entry-form/ckeditor'; +import { FormlyDateTimePickerModule } from '@enigmatry/entry-form/date-time-picker'; import { FormlyModule } from '@ngx-formly/core'; import { FormlyMaterialModule } from '@ngx-formly/material'; import { FormlyMatDatepickerModule } from '@ngx-formly/material/datepicker'; -import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; -import { FormlyAutocompleteModule } from '@enigmatry/entry-form/autocomplete'; -import { InputExampleComponent } from './input/input-example.component'; -import { SelectExampleComponent } from './select/select-example.component'; -import { TextareaExampleComponent } from './textarea/textarea-example.component'; +import { SharedModule } from '../../shared/shared.module'; import { CheckboxExampleComponent } from './checkbox/checkbox-example.component'; -import { RadioExampleComponent } from './radio/radio-example.component'; +import { DateTimePickerExampleComponent } from './date-time-picker/date-time-picker-example.component'; +import { FormExampleComponent } from './form-example/form-example.component'; import { ProductsGeneratedModule } from './form-example/generated/products-generated.module'; +import { InputExampleComponent } from './input/input-example.component'; +import { RadioExampleComponent } from './radio/radio-example.component'; import { RichTextExampleComponent } from './rich-text/rich-text-example.component'; -import { ENTRY_CKEDITOR_OPTIONS, FormlyCkeditorModule } from '@enigmatry/entry-form/ckeditor'; -import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; -import { FormlyDateTimePickerModule } from '@enigmatry/entry-form/date-time-picker'; -import { DateTimePickerExampleComponent } from './date-time-picker/date-time-picker-example.component'; +import { SelectExampleComponent } from './select/select-example.component'; +import { TextareaExampleComponent } from './textarea/textarea-example.component'; @NgModule({ declarations: [ diff --git a/apps/demo-app/src/app/examples/form/form-example/generated/product-edit/product-edit-generated.component.ts b/apps/demo-app/src/app/examples/form/form-example/generated/product-edit/product-edit-generated.component.ts index 0fb4f447..d462d140 100644 --- a/apps/demo-app/src/app/examples/form/form-example/generated/product-edit/product-edit-generated.component.ts +++ b/apps/demo-app/src/app/examples/form/form-example/generated/product-edit/product-edit-generated.component.ts @@ -16,10 +16,10 @@ import { map, throttleTime } from 'rxjs/operators'; type IGetProductDetailsResponse = {}; -@Component({ - selector: 'app-g-product-edit', - templateUrl: './product-edit-generated.component.html', - standalone: false +@Component({ + selector: 'app-g-product-edit', + templateUrl: './product-edit-generated.component.html', + standalone: false }) export class ProductEditGeneratedComponent implements OnInit, OnDestroy { @@ -87,12 +87,12 @@ export class ProductEditGeneratedComponent implements OnInit, OnDestroy { type: this.resolveFieldType('input', false), focus: false, className: `entry-name-field entry-input`, - hideExpression: this.fieldsHideExpressions?.name ?? false, + hideExpression: this.fieldsHideExpressions?.['name'] ?? false, expressionProperties: { - 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.name ? this.fieldsDisableExpressions.name(model) : false)), - 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.name ? this.fieldsRequiredExpressions.name(model) : false), - 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.name ? this.fieldsLabelExpressions.name(model) : `Name`), - 'model.name': (model) => (this.fieldsPropertyExpressions?.name ? this.fieldsPropertyExpressions.name(model) : model.name), + 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.['name'] ? this.fieldsDisableExpressions['name'](model) : false)), + 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.['name'] ? this.fieldsRequiredExpressions['name'](model) : false), + 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.['name'] ? this.fieldsLabelExpressions['name'](model) : `Name`), + 'model.name': (model) => (this.fieldsPropertyExpressions?.['name'] ? this.fieldsPropertyExpressions['name'](model) : model.name), }, templateOptions: { label: `Name`, @@ -108,12 +108,12 @@ className: `entry-name-field entry-input`, type: this.resolveFieldType('autocomplete', false), focus: false, className: `entry-type-field entry-autocomplete`, - hideExpression: this.fieldsHideExpressions?.type ?? false, + hideExpression: this.fieldsHideExpressions?.['type'] ?? false, expressionProperties: { - 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.type ? this.fieldsDisableExpressions.type(model) : false)), - 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.type ? this.fieldsRequiredExpressions.type(model) : false), - 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.type ? this.fieldsLabelExpressions.type(model) : `Type`), - 'model.type': (model) => (this.fieldsPropertyExpressions?.type ? this.fieldsPropertyExpressions.type(model) : model.type), + 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.['type'] ? this.fieldsDisableExpressions['type'](model) : false)), + 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.['type'] ? this.fieldsRequiredExpressions['type'](model) : false), + 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.['type'] ? this.fieldsLabelExpressions['type'](model) : `Type`), + 'model.type': (model) => (this.fieldsPropertyExpressions?.['type'] ? this.fieldsPropertyExpressions['type'](model) : model.type), }, templateOptions: { label: `Type`, @@ -132,12 +132,12 @@ className: `entry-type-field entry-autocomplete`, type: this.resolveFieldType('textarea', false), focus: false, className: `entry-description-field entry-textarea`, - hideExpression: this.fieldsHideExpressions?.description ?? false, + hideExpression: this.fieldsHideExpressions?.['description'] ?? false, expressionProperties: { - 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.description ? this.fieldsDisableExpressions.description(model) : false)), - 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.description ? this.fieldsRequiredExpressions.description(model) : false), - 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.description ? this.fieldsLabelExpressions.description(model) : `Description`), - 'model.description': (model) => (this.fieldsPropertyExpressions?.description ? this.fieldsPropertyExpressions.description(model) : model.description), + 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.['description'] ? this.fieldsDisableExpressions['description'](model) : false)), + 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.['description'] ? this.fieldsRequiredExpressions['description'](model) : false), + 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.['description'] ? this.fieldsLabelExpressions['description'](model) : `Description`), + 'model.description': (model) => (this.fieldsPropertyExpressions?.['description'] ? this.fieldsPropertyExpressions['description'](model) : model.description), }, templateOptions: { label: `Description`, @@ -158,12 +158,12 @@ className: `entry-description-field entry-textarea`, type: this.resolveFieldType('input', false), focus: false, className: `entry-amount-field entry-input`, - hideExpression: this.fieldsHideExpressions?.amount ?? false, + hideExpression: this.fieldsHideExpressions?.['amount'] ?? false, expressionProperties: { - 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.amount ? this.fieldsDisableExpressions.amount(model) : false)), - 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.amount ? this.fieldsRequiredExpressions.amount(model) : false), - 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.amount ? this.fieldsLabelExpressions.amount(model) : `Units`), - 'model.amount': (model) => (this.fieldsPropertyExpressions?.amount ? this.fieldsPropertyExpressions.amount(model) : model.amount), + 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.['amount'] ? this.fieldsDisableExpressions['amount'](model) : false)), + 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.['amount'] ? this.fieldsRequiredExpressions['amount'](model) : false), + 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.['amount'] ? this.fieldsLabelExpressions['amount'](model) : `Units`), + 'model.amount': (model) => (this.fieldsPropertyExpressions?.['amount'] ? this.fieldsPropertyExpressions['amount'](model) : model.amount), }, templateOptions: { label: `Units`, @@ -179,12 +179,12 @@ className: `entry-amount-field entry-input`, type: this.resolveFieldType('datepicker', false), focus: false, className: `entry-expires-on-field entry-datepicker`, - hideExpression: this.fieldsHideExpressions?.expiresOn ?? false, + hideExpression: this.fieldsHideExpressions?.['expiresOn'] ?? false, expressionProperties: { - 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.expiresOn ? this.fieldsDisableExpressions.expiresOn(model) : false)), - 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.expiresOn ? this.fieldsRequiredExpressions.expiresOn(model) : false), - 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.expiresOn ? this.fieldsLabelExpressions.expiresOn(model) : `Expires on`), - 'model.expiresOn': (model) => (this.fieldsPropertyExpressions?.expiresOn ? this.fieldsPropertyExpressions.expiresOn(model) : model.expiresOn), + 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.['expiresOn'] ? this.fieldsDisableExpressions['expiresOn'](model) : false)), + 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.['expiresOn'] ? this.fieldsRequiredExpressions['expiresOn'](model) : false), + 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.['expiresOn'] ? this.fieldsLabelExpressions['expiresOn'](model) : `Expires on`), + 'model.expiresOn': (model) => (this.fieldsPropertyExpressions?.['expiresOn'] ? this.fieldsPropertyExpressions['expiresOn'](model) : model.expiresOn), }, templateOptions: { label: `Expires on`, @@ -202,12 +202,12 @@ modelOptions: { updateOn: 'blur' }, focus: false, className: `entry-free-shipping-field entry-checkbox`, defaultValue: true, - hideExpression: this.fieldsHideExpressions?.freeShipping ?? false, + hideExpression: this.fieldsHideExpressions?.['freeShipping'] ?? false, expressionProperties: { - 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.freeShipping ? this.fieldsDisableExpressions.freeShipping(model) : false)), - 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.freeShipping ? this.fieldsRequiredExpressions.freeShipping(model) : false), - 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.freeShipping ? this.fieldsLabelExpressions.freeShipping(model) : `Free shipping`), - 'model.freeShipping': (model) => (this.fieldsPropertyExpressions?.freeShipping ? this.fieldsPropertyExpressions.freeShipping(model) : model.freeShipping), + 'templateOptions.disabled': (model) => (this.isReadonly || (this.fieldsDisableExpressions?.['freeShipping'] ? this.fieldsDisableExpressions['freeShipping'](model) : false)), + 'templateOptions.required': (model) => (this.fieldsRequiredExpressions?.['freeShipping'] ? this.fieldsRequiredExpressions['freeShipping'](model) : false), + 'templateOptions.label': (model) => (this.fieldsLabelExpressions?.['freeShipping'] ? this.fieldsLabelExpressions['freeShipping'](model) : `Free shipping`), + 'model.freeShipping': (model) => (this.fieldsPropertyExpressions?.['freeShipping'] ? this.fieldsPropertyExpressions['freeShipping'](model) : model.freeShipping), }, templateOptions: { label: `Free shipping`, @@ -219,7 +219,7 @@ defaultValue: true, }, }, { key: 'id' }, - + ]; } diff --git a/apps/demo-app/src/app/examples/form/radio/radio-example.component.ts b/apps/demo-app/src/app/examples/form/radio/radio-example.component.ts index 4a656443..2e5ae114 100644 --- a/apps/demo-app/src/app/examples/form/radio/radio-example.component.ts +++ b/apps/demo-app/src/app/examples/form/radio/radio-example.component.ts @@ -8,7 +8,6 @@ import { of } from 'rxjs'; standalone: false }) export class RadioExampleComponent { - @Input() typeOptions = [ { value: 0, displayName: `Food` }, { value: 1, displayName: `Drink` }, @@ -28,7 +27,7 @@ export class RadioExampleComponent { options: of(this.typeOptions), valueProp: 'value', labelProp: 'displayName' - }, - }, + } + } ]; } diff --git a/apps/demo-app/src/app/examples/form/select/select-example.component.ts b/apps/demo-app/src/app/examples/form/select/select-example.component.ts index e331885f..90901eaf 100644 --- a/apps/demo-app/src/app/examples/form/select/select-example.component.ts +++ b/apps/demo-app/src/app/examples/form/select/select-example.component.ts @@ -10,7 +10,6 @@ import { map } from 'rxjs/operators'; standalone: false }) export class SelectExampleComponent { - @Input() typeOptions = [ { value: 0, displayName: `Food` }, { value: 1, displayName: `Drink` }, @@ -31,7 +30,7 @@ export class SelectExampleComponent { valueProp: 'value', labelProp: 'displayName' } - }, + } ]; multi: FormlyFieldConfig[] = [ @@ -48,7 +47,7 @@ export class SelectExampleComponent { labelProp: 'displayName', multiple: true } - }, + } ]; autocomplete: FormlyFieldConfig[] = [ @@ -64,6 +63,6 @@ export class SelectExampleComponent { valueProp: 'value', labelProp: 'displayName' } - }, + } ]; } diff --git a/apps/demo-app/src/app/examples/permissions/permissions-example.module.ts b/apps/demo-app/src/app/examples/permissions/permissions-example.module.ts index 5337534e..7b1a7fa0 100644 --- a/apps/demo-app/src/app/examples/permissions/permissions-example.module.ts +++ b/apps/demo-app/src/app/examples/permissions/permissions-example.module.ts @@ -1,8 +1,8 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { PermissionsExampleComponent } from './permissions-example/permissions-example.component'; +import { NgModule } from '@angular/core'; import { EntryPermissionModule, EntryPermissionService } from '@enigmatry/entry-components/permissions'; import { PermissionExampleService } from './permissions-example/permission.service'; +import { PermissionsExampleComponent } from './permissions-example/permissions-example.component'; @NgModule({ declarations: [ diff --git a/apps/demo-app/src/app/examples/search-filter/search-filter-examples.module.ts b/apps/demo-app/src/app/examples/search-filter/search-filter-examples.module.ts index 7a738e20..52396f64 100644 --- a/apps/demo-app/src/app/examples/search-filter/search-filter-examples.module.ts +++ b/apps/demo-app/src/app/examples/search-filter/search-filter-examples.module.ts @@ -1,10 +1,10 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { SearchFilterExampleComponent } from './search-filter/search-filter-example.component'; -import { SharedModule } from '../../shared/shared.module'; -import { EntrySearchFilterModule, provideEntrySearchFilterConfig } from '@enigmatry/entry-components/search-filter'; +import { NgModule } from '@angular/core'; import { MatTableModule } from '@angular/material/table'; +import { EntrySearchFilterModule, provideEntrySearchFilterConfig } from '@enigmatry/entry-components/search-filter'; +import { SharedModule } from '../../shared/shared.module'; import { EnumToStringPipe } from './search-filter/enum-to-string.pipe'; +import { SearchFilterExampleComponent } from './search-filter/search-filter-example.component'; @NgModule({ declarations: [ diff --git a/apps/demo-app/src/app/examples/search-filter/search-filter/enum-to-string.pipe.ts b/apps/demo-app/src/app/examples/search-filter/search-filter/enum-to-string.pipe.ts index 6ef866aa..93a2a219 100644 --- a/apps/demo-app/src/app/examples/search-filter/search-filter/enum-to-string.pipe.ts +++ b/apps/demo-app/src/app/examples/search-filter/search-filter/enum-to-string.pipe.ts @@ -6,9 +6,8 @@ import { Occupation } from './users'; standalone: false }) export class EnumToStringPipe implements PipeTransform { - transform = (value: Occupation): string => value === Occupation.unknown ? '-' - : Occupation[value].replace(/^[a-z]/, x => x.toUpperCase()); + : Occupation[value].replace(/^[a-z]/u, x => x.toUpperCase()); } diff --git a/apps/demo-app/src/app/examples/search-filter/search-filter/users.service.ts b/apps/demo-app/src/app/examples/search-filter/search-filter/users.service.ts index 7d520380..3aa8d6a3 100644 --- a/apps/demo-app/src/app/examples/search-filter/search-filter/users.service.ts +++ b/apps/demo-app/src/app/examples/search-filter/search-filter/users.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; import { SearchFilterParams } from '@enigmatry/entry-components/search-filter'; +import { IValidationProblemDetails } from '@enigmatry/entry-components/validation'; import { Observable, of, throwError } from 'rxjs'; import { User, LIST_OF_USERS } from './users'; -import { IValidationProblemDetails } from '@enigmatry/entry-components/validation'; /** * A service that provides some example user data to help showcase the filtering. @@ -12,7 +12,6 @@ import { IValidationProblemDetails } from '@enigmatry/entry-components/validatio providedIn: 'root' }) export class UsersService { - private data: Array; constructor() { @@ -31,26 +30,26 @@ export class UsersService { let users = this.data; if (!this.noFilterParam(searchParams, 'name')) { - users = users?.filter(x => x.firstName?.toLowerCase().includes(searchParams.name.toLowerCase()) - || x.lastName?.toLowerCase().includes(searchParams.name.toLowerCase())); + users = users?.filter(x => x.firstName?.toLowerCase().includes(searchParams['name'].toLowerCase()) + || x.lastName?.toLowerCase().includes(searchParams['name'].toLowerCase())); } if (!this.noFilterParam(searchParams, 'occupation')) { - users = users?.filter(x => searchParams.occupation instanceof Array - ? searchParams.occupation.includes(x.occupation) - : searchParams.occupation === x.occupation); + users = users?.filter(x => searchParams['occupation'] instanceof Array + ? searchParams['occupation'].includes(x.occupation) + : searchParams['occupation'] === x.occupation); } if (!this.noFilterParam(searchParams, 'username')) { - users = users?.filter(x => searchParams.username instanceof Array - ? searchParams.username.includes(x.userName) - : searchParams.username === x.userName); + users = users?.filter(x => searchParams['username'] instanceof Array + ? searchParams['username'].includes(x.userName) + : searchParams['username'] === x.userName); } if (!this.noFilterParam(searchParams, 'country')) { - users = users.filter(x => x.country === searchParams.country.key); + users = users.filter(x => x.country === searchParams['country'].key); } if (!this.noFilterParam(searchParams, 'dateOfBirth')) { - users = users.filter(x => x.dateOfBirth >= searchParams.dateOfBirth); + users = users.filter(x => x.dateOfBirth >= searchParams['dateOfBirth']); } if (!this.noFilterParam(searchParams, 'score')) { @@ -60,23 +59,21 @@ export class UsersService { return of(users); } - private noFilterParam(searchParams: SearchFilterParams, paramName: string): boolean { - return searchParams[paramName] === undefined + noFilterParam = (searchParams: SearchFilterParams, paramName: string): boolean => searchParams[paramName] === undefined || searchParams[paramName] === null || searchParams[paramName]?.length === 0; - } - private validateSearchParams(searchParams: SearchFilterParams): Observable | null { - if (searchParams.dateOfBirth && new Date(searchParams.dateOfBirth) > new Date()) { + validateSearchParams = (searchParams: SearchFilterParams): Observable | null => { + if (searchParams['dateOfBirth'] && new Date(searchParams['dateOfBirth']) > new Date()) { const validationProblemDetails: IValidationProblemDetails = { - title: "Validation Error", + title: 'Validation Error', status: 400, errors: { - dateOfBirth: ["The date cannot be in the future."] + dateOfBirth: ['The date cannot be in the future.'] } }; return throwError(() => validationProblemDetails); } return null; - } + }; } \ No newline at end of file diff --git a/apps/demo-app/src/app/examples/spinner/spinner-example.module.ts b/apps/demo-app/src/app/examples/spinner/spinner-example.module.ts index 107a7f39..de29881d 100644 --- a/apps/demo-app/src/app/examples/spinner/spinner-example.module.ts +++ b/apps/demo-app/src/app/examples/spinner/spinner-example.module.ts @@ -1,8 +1,8 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { EntrySpinnerModule } from '@enigmatry/entry-components/spinner'; import { SharedModule } from '../../shared/shared.module'; import { SpinnerExampleComponent } from './spinner-example/spinner-example.component'; -import { EntrySpinnerModule } from '@enigmatry/entry-components/spinner'; @NgModule({ declarations: [ diff --git a/apps/demo-app/src/app/examples/spinner/spinner-example/spinner-example.component.ts b/apps/demo-app/src/app/examples/spinner/spinner-example/spinner-example.component.ts index 4c5e9d30..30ac10ca 100644 --- a/apps/demo-app/src/app/examples/spinner/spinner-example/spinner-example.component.ts +++ b/apps/demo-app/src/app/examples/spinner/spinner-example/spinner-example.component.ts @@ -15,6 +15,7 @@ export class SpinnerExampleComponent { showSpinner() { this.loading = true; + // eslint-disable-next-line @typescript-eslint/no-magic-numbers timer(3000).subscribe(_ => this.loading = false); } } diff --git a/apps/demo-app/src/app/examples/table/table-example.module.ts b/apps/demo-app/src/app/examples/table/table-example.module.ts index 59edd744..1e745ac0 100644 --- a/apps/demo-app/src/app/examples/table/table-example.module.ts +++ b/apps/demo-app/src/app/examples/table/table-example.module.ts @@ -1,7 +1,7 @@ -import { NgModule } from '@angular/core'; import { CommonModule, DATE_PIPE_DEFAULT_OPTIONS } from '@angular/common'; -import { TableExampleComponent } from './table-example/table-example.component'; +import { NgModule } from '@angular/core'; import { EntryTableModule } from '@enigmatry/entry-components/table'; +import { TableExampleComponent } from './table-example/table-example.component'; @NgModule({ declarations: [ @@ -15,7 +15,7 @@ import { EntryTableModule } from '@enigmatry/entry-components/table'; TableExampleComponent ], providers: [ - {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'dd-MM-yyyy HH:mm'}} + { provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: { dateFormat: 'dd-MM-yyyy HH:mm' } } ] }) export class TableExampleModule { } diff --git a/apps/demo-app/src/app/examples/table/table-example/table-example.component.ts b/apps/demo-app/src/app/examples/table/table-example/table-example.component.ts index fb6734a6..54d2ec1d 100644 --- a/apps/demo-app/src/app/examples/table/table-example/table-example.component.ts +++ b/apps/demo-app/src/app/examples/table/table-example/table-example.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, inject, OnInit } from '@angular/core'; import { ColumnDef, ContextMenuItem } from '@enigmatry/entry-components/table'; import { User } from '../../search-filter/search-filter/users'; import { UsersService } from '../../search-filter/search-filter/users.service'; @@ -14,13 +14,15 @@ export class TableExampleComponent implements OnInit { columns: ColumnDef[] = []; contextMenuItems: ContextMenuItem[] = []; + private readonly usersService: UsersService = inject(UsersService); - constructor(usersService: UsersService) { - usersService.getUsers({}).subscribe({ + constructor() { + this.usersService.getUsers({}).subscribe({ next: (users: User[]) => { this.users = users; }, - error: (err) => { + error: err => { + // eslint-disable-next-line no-console console.error('Failed to fetch users', err); } }); @@ -39,11 +41,11 @@ export class TableExampleComponent implements OnInit { this.contextMenuItems = [{ id: 'edit', - name: 'Edit', + name: 'Edit' }, { id: 'delete', - name: 'Delete', + name: 'Delete' }, { id: 'export', @@ -51,11 +53,11 @@ export class TableExampleComponent implements OnInit { items: [ { id: 'export_csv', - name: 'CSV', + name: 'CSV' }, { id: 'export_json', - name: 'JSON', + name: 'JSON' } ] }]; diff --git a/apps/demo-app/src/app/examples/validation/formly/complex-formly-form-validation/complex-formly-form-validation-example.component.ts b/apps/demo-app/src/app/examples/validation/formly/complex-formly-form-validation/complex-formly-form-validation-example.component.ts index 4dcdd3c2..cd091185 100644 --- a/apps/demo-app/src/app/examples/validation/formly/complex-formly-form-validation/complex-formly-form-validation-example.component.ts +++ b/apps/demo-app/src/app/examples/validation/formly/complex-formly-form-validation/complex-formly-form-validation-example.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { IValidationProblemDetails, setServerSideValidationErrors } from '@enigmatry/entry-components/validation'; import { FormlyFieldConfig } from '@ngx-formly/core'; @@ -21,7 +21,7 @@ export class ComplexFormlyFormValidationExampleComponent { firstName: 'Johanna', lastName: 'Doe' }, - children: [ 'Dragana', 'Jovana', 'Mila' ] + children: ['Dragana', 'Jovana', 'Mila'] }; fields: FormlyFieldConfig[] = [ { @@ -69,9 +69,8 @@ export class ComplexFormlyFormValidationExampleComponent { } } ]; - validationResult: IValidationProblemDetails; - - constructor(private _validationService: ValidationService) {} + validationResult: IValidationProblemDetails | undefined; + private readonly _validationService: ValidationService = inject(ValidationService); submitForm() { this._validationService.submitWithComplexValidationErrors() diff --git a/apps/demo-app/src/app/examples/validation/formly/formly-form-validation/formly-form-validation-example.component.ts b/apps/demo-app/src/app/examples/validation/formly/formly-form-validation/formly-form-validation-example.component.ts index 060eb088..7b2bb88e 100644 --- a/apps/demo-app/src/app/examples/validation/formly/formly-form-validation/formly-form-validation-example.component.ts +++ b/apps/demo-app/src/app/examples/validation/formly/formly-form-validation/formly-form-validation-example.component.ts @@ -1,8 +1,8 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { FormGroup } from '@angular/forms'; +import { IValidationProblemDetails, setServerSideValidationErrors } from '@enigmatry/entry-components/validation'; import { FormlyFieldConfig } from '@ngx-formly/core'; import { ValidationService } from '../../validation.service'; -import { IValidationProblemDetails, setServerSideValidationErrors } from '@enigmatry/entry-components/validation'; @Component({ selector: 'app-formly-form-validation-example', @@ -28,9 +28,8 @@ export class FormlyFormValidationExampleComponent { templateOptions: { label: 'Last name', required: true, minLength: 3 } } ]; - validationResult: IValidationProblemDetails; - - constructor(private _validationService: ValidationService) {} + validationResult: IValidationProblemDetails | undefined; + private readonly _validationService: ValidationService = inject(ValidationService); submitForm() { this._validationService.submitWithValidationErrors() diff --git a/apps/demo-app/src/app/examples/validation/formly/formly-validation-example.module.ts b/apps/demo-app/src/app/examples/validation/formly/formly-validation-example.module.ts index 16486043..33bc757b 100644 --- a/apps/demo-app/src/app/examples/validation/formly/formly-validation-example.module.ts +++ b/apps/demo-app/src/app/examples/validation/formly/formly-validation-example.module.ts @@ -1,15 +1,15 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { EntryValidationModule, FORM_FIELD_ERROR_KEY } from '@enigmatry/entry-components/validation'; +import { FormlyFieldConfig, FormlyModule } from '@ngx-formly/core'; +import { FormlyMaterialModule } from '@ngx-formly/material'; +import { SharedModule } from '../../../shared/shared.module'; import { ComplexFormlyFormValidationExampleComponent } from './complex-formly-form-validation/complex-formly-form-validation-example.component'; import { FieldSetComponent } from './complex-formly-form-validation/field-set.component'; import { RepeatNameComponent } from './complex-formly-form-validation/repeat-name.component'; import { FormlyFormValidationExampleComponent } from './formly-form-validation/formly-form-validation-example.component'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { EntryValidationModule, FORM_FIELD_ERROR_KEY } from '@enigmatry/entry-components/validation'; -import { FormlyMaterialModule } from '@ngx-formly/material'; -import { SharedModule } from '../../../shared/shared.module'; -import { FormlyFieldConfig, FormlyModule } from '@ngx-formly/core'; @NgModule({ declarations: [ @@ -27,16 +27,20 @@ import { FormlyFieldConfig, FormlyModule } from '@ngx-formly/core'; FormlyMaterialModule, FormlyModule.forChild({ validationMessages: [ - /** Map form fields server side validation errors to FORM_FIELD_ERROR_KEY key */ - { name: FORM_FIELD_ERROR_KEY, message: (error, _) => error }, - /** Map form fields client side validation errors */ - { name: 'required', message: 'Required field.' }, - { name: 'minlength', message: (_, config: FormlyFieldConfig) => - `Minimal length is ${config.formControl.errors.minlength.requiredLength}` } + /** Map form fields server side validation errors to FORM_FIELD_ERROR_KEY key */ + { name: FORM_FIELD_ERROR_KEY, message: (error, _) => error }, + /** Map form fields client side validation errors */ + { name: 'required', message: 'Required field.' }, + { + name: 'minlength', + message: (_, config: FormlyFieldConfig) => + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + `Minimal length is ${config.formControl!.errors!['minlength'].requiredLength}` + } ], types: [ { name: 'field-set', component: FieldSetComponent }, - { name: 'repeat-name', component: RepeatNameComponent }, + { name: 'repeat-name', component: RepeatNameComponent } ] }) ], diff --git a/apps/demo-app/src/app/examples/validation/reactive-form/reactive-form-validation-example.component.ts b/apps/demo-app/src/app/examples/validation/reactive-form/reactive-form-validation-example.component.ts index 11ad99c9..cd602bdb 100644 --- a/apps/demo-app/src/app/examples/validation/reactive-form/reactive-form-validation-example.component.ts +++ b/apps/demo-app/src/app/examples/validation/reactive-form/reactive-form-validation-example.component.ts @@ -1,30 +1,30 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, inject, OnInit } from '@angular/core'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { IValidationProblemDetails, setServerSideValidationErrors } from '@enigmatry/entry-components/validation'; import { ValidationService } from '../validation.service'; @Component({ - selector: 'app-reactive-form-validation-example', - templateUrl: './reactive-form-validation-example.component.html', - styleUrls: ['./reactive-form-validation-example.component.scss'], - standalone: false + selector: 'app-reactive-form-validation-example', + templateUrl: './reactive-form-validation-example.component.html', + styleUrls: ['./reactive-form-validation-example.component.scss'], + standalone: false }) export class ReactiveFormExampleComponent implements OnInit { form: FormGroup<{ - firstName: FormControl; - lastName: FormControl; - }> | undefined; + firstName: FormControl; + lastName: FormControl; + }>; - validationResult: IValidationProblemDetails; + validationResult: IValidationProblemDetails | undefined; + private readonly defaultLength = 3; - constructor( - private _formBuilder: FormBuilder, - private _validationService: ValidationService) { } + private readonly _formBuilder: FormBuilder = inject(FormBuilder); + private readonly _validationService: ValidationService = inject(ValidationService); ngOnInit(): void { this.form = this._formBuilder.group({ - firstName: new FormControl('John', [Validators.required, Validators.minLength(3)]), - lastName: new FormControl('Doe', [Validators.required, Validators.minLength(3)]) + firstName: new FormControl('John', [Validators.required, Validators.minLength(this.defaultLength)]), + lastName: new FormControl('Doe', [Validators.required, Validators.minLength(this.defaultLength)]) }); } diff --git a/apps/demo-app/src/app/examples/validation/reactive-form/reactive-form-validation-example.module.ts b/apps/demo-app/src/app/examples/validation/reactive-form/reactive-form-validation-example.module.ts index 90bc0952..5fe7b076 100644 --- a/apps/demo-app/src/app/examples/validation/reactive-form/reactive-form-validation-example.module.ts +++ b/apps/demo-app/src/app/examples/validation/reactive-form/reactive-form-validation-example.module.ts @@ -1,9 +1,9 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ReactiveFormExampleComponent } from './reactive-form-validation-example.component'; +import { NgModule } from '@angular/core'; import { AbstractControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { EntryValidationModule, provideEntryValidationConfig } from '@enigmatry/entry-components/validation'; import { SharedModule } from '../../../shared/shared.module'; +import { ReactiveFormExampleComponent } from './reactive-form-validation-example.component'; @NgModule({ declarations: [ @@ -14,7 +14,7 @@ import { SharedModule } from '../../../shared/shared.module'; SharedModule, FormsModule, ReactiveFormsModule, - EntryValidationModule, + EntryValidationModule ], exports: [ ReactiveFormExampleComponent @@ -23,7 +23,8 @@ import { SharedModule } from '../../../shared/shared.module'; provideEntryValidationConfig({ validationMessages: [ { name: 'required', message: 'This field is mandatory!' }, - { name: 'minlength', message: (control: AbstractControl) => `Minimal length is ${control.errors.minlength.requiredLength}!` } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + { name: 'minlength', message: (control: AbstractControl) => `Minimal length is ${control!.errors!['minlength'].requiredLength}!` } ] }) ] diff --git a/apps/demo-app/src/app/examples/validation/validation.service.ts b/apps/demo-app/src/app/examples/validation/validation.service.ts index 7d02c0d8..cbbd9f2d 100644 --- a/apps/demo-app/src/app/examples/validation/validation.service.ts +++ b/apps/demo-app/src/app/examples/validation/validation.service.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ + import { Injectable } from '@angular/core'; import { IValidationProblemDetails } from '@enigmatry/entry-components/validation'; import { Observable, throwError } from 'rxjs'; @@ -10,8 +10,7 @@ import { Observable, throwError } from 'rxjs'; providedIn: 'root' }) export class ValidationService { - submitWithValidationErrors(): Observable { - return throwError({ + submitWithValidationErrors = (): Observable => throwError(() => ({ errors: { '': [ 'The combination of First & Last name already exists.' @@ -21,11 +20,9 @@ export class ValidationService { ], lastName: ['Last name is also not cool.'] } - } as IValidationProblemDetails); - } + } as IValidationProblemDetails)); - submitWithComplexValidationErrors(): Observable { - return throwError({ + submitWithComplexValidationErrors = (): Observable => throwError(() => ({ errors: { '': ['Personal & Partner info do not match.'], 'personalInfo.firstName': ['This name is not cool enough.'], @@ -36,6 +33,5 @@ export class ValidationService { 'children.1': [`Middle child name is not 'Jovana'`], 'children.2': [`Last child name is not 'Mila'`] } - } as IValidationProblemDetails); - } + } as IValidationProblemDetails)); } diff --git a/apps/demo-app/src/app/features/button/button-documentation.component.ts b/apps/demo-app/src/app/features/button/button-documentation.component.ts index 9a99178e..2b5f178b 100644 --- a/apps/demo-app/src/app/features/button/button-documentation.component.ts +++ b/apps/demo-app/src/app/features/button/button-documentation.component.ts @@ -1,12 +1,12 @@ import { Component } from '@angular/core'; -import { SharedModule } from '../../shared/shared.module'; import { ButtonExampleModule } from '../../examples/button/button-example.module'; +import { SharedModule } from '../../shared/shared.module'; @Component({ templateUrl: './button-documentation.component.html', imports: [ SharedModule, - ButtonExampleModule, + ButtonExampleModule ] }) export class ButtonDocumentationComponent { } diff --git a/apps/demo-app/src/app/features/common/common-documentation.component.ts b/apps/demo-app/src/app/features/common/common-documentation.component.ts index bf8fc803..c7f9df57 100644 --- a/apps/demo-app/src/app/features/common/common-documentation.component.ts +++ b/apps/demo-app/src/app/features/common/common-documentation.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { SharedModule } from '../../shared/shared.module'; import { CommonExampleModule } from '../../examples/common/common-example.module'; +import { SharedModule } from '../../shared/shared.module'; @Component({ templateUrl: './common-documentation.component.html', diff --git a/apps/demo-app/src/app/features/component-definitions.ts b/apps/demo-app/src/app/features/component-definitions.ts index 7e9de357..d0a0feb8 100644 --- a/apps/demo-app/src/app/features/component-definitions.ts +++ b/apps/demo-app/src/app/features/component-definitions.ts @@ -90,6 +90,6 @@ const COMPONENT_DEFINITIONS: IComponentDefinition[] = [ ]; export { - IComponentDefinition, COMPONENT_DEFINITIONS }; +export type { IComponentDefinition }; diff --git a/apps/demo-app/src/app/features/date-time-picker/date-time-picker-documentation.component.ts b/apps/demo-app/src/app/features/date-time-picker/date-time-picker-documentation.component.ts index 290e115b..838aab32 100644 --- a/apps/demo-app/src/app/features/date-time-picker/date-time-picker-documentation.component.ts +++ b/apps/demo-app/src/app/features/date-time-picker/date-time-picker-documentation.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { SharedModule } from '../../shared/shared.module'; import { DateTimePickerExampleModule } from '../../examples/date-time-picker/date-time-picker-example.module'; +import { SharedModule } from '../../shared/shared.module'; @Component({ templateUrl: './date-time-picker-documentation.component.html', diff --git a/apps/demo-app/src/app/features/dialog/dialog-documentation.component.ts b/apps/demo-app/src/app/features/dialog/dialog-documentation.component.ts index 37fd7285..8adfbbb4 100644 --- a/apps/demo-app/src/app/features/dialog/dialog-documentation.component.ts +++ b/apps/demo-app/src/app/features/dialog/dialog-documentation.component.ts @@ -6,7 +6,7 @@ import { SharedModule } from '../../shared/shared.module'; templateUrl: './dialog-documentation.component.html', imports: [ SharedModule, - DialogExamplesModule, + DialogExamplesModule ] }) export class DialogDocumentationComponent { } diff --git a/apps/demo-app/src/app/features/file-input/file-input-documentation.component.ts b/apps/demo-app/src/app/features/file-input/file-input-documentation.component.ts index a0de03bd..8f3d8f3b 100644 --- a/apps/demo-app/src/app/features/file-input/file-input-documentation.component.ts +++ b/apps/demo-app/src/app/features/file-input/file-input-documentation.component.ts @@ -6,7 +6,7 @@ import { SharedModule } from '../../shared/shared.module'; templateUrl: './file-input-documentation.component.html', imports: [ SharedModule, - FileInputExampleModule, + FileInputExampleModule ] }) export class FileInputDocumentationComponent {} diff --git a/apps/demo-app/src/app/features/form/form-documentation.component.ts b/apps/demo-app/src/app/features/form/form-documentation.component.ts index c334b2d4..d044ca50 100644 --- a/apps/demo-app/src/app/features/form/form-documentation.component.ts +++ b/apps/demo-app/src/app/features/form/form-documentation.component.ts @@ -6,7 +6,7 @@ import { SharedModule } from '../../shared/shared.module'; templateUrl: './form-documentation.component.html', imports: [ SharedModule, - FormExampleModule, + FormExampleModule ] }) export class FormDocumentationComponent { } diff --git a/apps/demo-app/src/app/features/permissions/permissions-documentation.component.ts b/apps/demo-app/src/app/features/permissions/permissions-documentation.component.ts index 1c34433f..6286583e 100644 --- a/apps/demo-app/src/app/features/permissions/permissions-documentation.component.ts +++ b/apps/demo-app/src/app/features/permissions/permissions-documentation.component.ts @@ -6,7 +6,7 @@ import { SharedModule } from '../../shared/shared.module'; templateUrl: './permissions-documentation.component.html', imports: [ SharedModule, - PermissionsExampleModule, + PermissionsExampleModule ] }) export class PermissionsDocumentationComponent { } diff --git a/apps/demo-app/src/app/features/spinner/spinner-documentation.component.ts b/apps/demo-app/src/app/features/spinner/spinner-documentation.component.ts index 8f2f07c1..58af2fba 100644 --- a/apps/demo-app/src/app/features/spinner/spinner-documentation.component.ts +++ b/apps/demo-app/src/app/features/spinner/spinner-documentation.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { SharedModule } from '../../shared/shared.module'; import { SpinnerExampleModule } from '../../examples/spinner/spinner-example.module'; +import { SharedModule } from '../../shared/shared.module'; @Component({ templateUrl: './spinner-documentation.component.html', diff --git a/apps/demo-app/src/app/features/table/table-documentation.component.ts b/apps/demo-app/src/app/features/table/table-documentation.component.ts index a5a7f12a..447cae45 100644 --- a/apps/demo-app/src/app/features/table/table-documentation.component.ts +++ b/apps/demo-app/src/app/features/table/table-documentation.component.ts @@ -6,7 +6,7 @@ import { SharedModule } from '../../shared/shared.module'; templateUrl: './table-documentation.component.html', imports: [ SharedModule, - TableExampleModule, + TableExampleModule ] }) export class TableDocumentationComponent { } diff --git a/apps/demo-app/src/app/features/validation/validation-documentation.component.ts b/apps/demo-app/src/app/features/validation/validation-documentation.component.ts index 5b8e8bc2..91fbd4da 100644 --- a/apps/demo-app/src/app/features/validation/validation-documentation.component.ts +++ b/apps/demo-app/src/app/features/validation/validation-documentation.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; -import { SharedModule } from '../../shared/shared.module'; import { FormlyValidationExampleModule } from '../../examples/validation/formly/formly-validation-example.module'; import { ReactiveFormValidationExampleModule } from '../../examples/validation/reactive-form/reactive-form-validation-example.module'; +import { SharedModule } from '../../shared/shared.module'; @Component({ templateUrl: './validation-documentation.component.html', diff --git a/apps/demo-app/src/app/shared/documentation-content/documentation-content.component.html b/apps/demo-app/src/app/shared/documentation-content/documentation-content.component.html index 452cc118..7d352730 100644 --- a/apps/demo-app/src/app/shared/documentation-content/documentation-content.component.html +++ b/apps/demo-app/src/app/shared/documentation-content/documentation-content.component.html @@ -2,7 +2,7 @@
- +
@@ -10,9 +10,9 @@ - +
- +
diff --git a/apps/demo-app/src/app/shared/documentation-content/documentation-content.component.ts b/apps/demo-app/src/app/shared/documentation-content/documentation-content.component.ts index 0853541f..df5f15e0 100644 --- a/apps/demo-app/src/app/shared/documentation-content/documentation-content.component.ts +++ b/apps/demo-app/src/app/shared/documentation-content/documentation-content.component.ts @@ -1,18 +1,16 @@ -import { Component, OnInit } from '@angular/core'; -import { COMPONENT_DEFINITIONS, IComponentDefinition } from '../../features/component-definitions'; +import { Component, inject, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { COMPONENT_DEFINITIONS, IComponentDefinition } from '../../features/component-definitions'; @Component({ - selector: 'app-documentation-content', - templateUrl: './documentation-content.component.html', - styleUrls: ['./documentation-content.component.scss'], - standalone: false + selector: 'app-documentation-content', + templateUrl: './documentation-content.component.html', + styleUrls: ['./documentation-content.component.scss'], + standalone: false }) export class DocumentationContentComponent implements OnInit { - componentDefinition: IComponentDefinition; - - constructor(private activatedRoute: ActivatedRoute) { - } + componentDefinition: IComponentDefinition | undefined; + private readonly activatedRoute: ActivatedRoute = inject(ActivatedRoute); ngOnInit(): void { const componentRoute = this.activatedRoute.snapshot.url[0].path; diff --git a/apps/demo-app/src/app/shared/example-viewer/code-view/code-view.component.ts b/apps/demo-app/src/app/shared/example-viewer/code-view/code-view.component.ts index 2f06229b..b6360eab 100644 --- a/apps/demo-app/src/app/shared/example-viewer/code-view/code-view.component.ts +++ b/apps/demo-app/src/app/shared/example-viewer/code-view/code-view.component.ts @@ -1,27 +1,25 @@ -import { ChangeDetectionStrategy, Component, Input, OnInit, SecurityContext } from '@angular/core'; -import { MatSnackBar } from '@angular/material/snack-bar'; import { Clipboard } from '@angular/cdk/clipboard'; -import { FileExtension } from '../../models/file-extension.type'; +import { ChangeDetectionStrategy, Component, inject, Input, OnInit, SecurityContext } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import hljs from 'highlight.js'; +import { FileExtension } from '../../models/file-extension.type'; @Component({ - selector: 'app-code-view', - templateUrl: './code-view.component.html', - styleUrls: ['./code-view.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false + selector: 'app-code-view', + templateUrl: './code-view.component.html', + styleUrls: ['./code-view.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false }) export class CodeViewComponent implements OnInit { @Input() codeContent: string; @Input() codeType: FileExtension; highlightedCode: SafeHtml; - - constructor( - private _clipboard: Clipboard, - private _snackBar: MatSnackBar, - private _domSanitizer: DomSanitizer) { } + private readonly _clipboard: Clipboard = inject(Clipboard); + private readonly _snackBar: MatSnackBar = inject(MatSnackBar); + private readonly _domSanitizer: DomSanitizer = inject(DomSanitizer); ngOnInit(): void { this.highlightCode(); @@ -33,9 +31,9 @@ export class CodeViewComponent implements OnInit { }; private highlightCode() { - const highlightedCode = hljs.highlight(this.codeContent, { language: this.codeType, }); + const highlightedCode = hljs.highlight(this.codeContent, { language: this.codeType }); const sanitizedHtml = this._domSanitizer.sanitize(SecurityContext.HTML, highlightedCode.value); - this.highlightedCode = this._domSanitizer.bypassSecurityTrustHtml(sanitizedHtml); + this.highlightedCode = this._domSanitizer.bypassSecurityTrustHtml(sanitizedHtml || ''); } } diff --git a/apps/demo-app/src/app/shared/example-viewer/example-viewer.component.html b/apps/demo-app/src/app/shared/example-viewer/example-viewer.component.html index 053cf6ad..bf7456f1 100644 --- a/apps/demo-app/src/app/shared/example-viewer/example-viewer.component.html +++ b/apps/demo-app/src/app/shared/example-viewer/example-viewer.component.html @@ -7,16 +7,16 @@ - + - + - + - + diff --git a/apps/demo-app/src/app/shared/example-viewer/example-viewer.component.ts b/apps/demo-app/src/app/shared/example-viewer/example-viewer.component.ts index 744bafdc..f3776586 100644 --- a/apps/demo-app/src/app/shared/example-viewer/example-viewer.component.ts +++ b/apps/demo-app/src/app/shared/example-viewer/example-viewer.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnDestroy } from '@angular/core'; +import { Component, inject, Input, OnDestroy } from '@angular/core'; import { Observable, Subject, forkJoin, of } from 'rxjs'; import { catchError, map, takeUntil } from 'rxjs/operators'; import { FileExtension } from '../models/file-extension.type'; @@ -12,10 +12,10 @@ interface IExtraFile { } @Component({ - selector: 'app-example-viewer', - templateUrl: './example-viewer.component.html', - styleUrls: ['./example-viewer.component.scss'], - standalone: false + selector: 'app-example-viewer', + templateUrl: './example-viewer.component.html', + styleUrls: ['./example-viewer.component.scss'], + standalone: false }) export class ExampleViewerComponent implements OnDestroy { @Input() component: string; @@ -27,16 +27,14 @@ export class ExampleViewerComponent implements OnDestroy { @Input() extraFiles: string[] = []; viewCode = false; - typescriptFile: string; - htmlFile: string; - stylesFile: string; - docsFile: string; + typescriptFile: string | null; + htmlFile: string | null; + stylesFile: string | null; + docsFile: string | null; extraFilesToDisplay: IExtraFile[] = []; private _destroy$ = new Subject(); - - constructor( - private _fileLoad: FileLoadService) { } + private readonly _fileLoad: FileLoadService = inject(FileLoadService); ngOnDestroy(): void { this._destroy$.next(); @@ -78,7 +76,7 @@ export class ExampleViewerComponent implements OnDestroy { .map(path => { const pathWithoutExtension = path.substring(0, path.lastIndexOf('.')); const extension = path.substring(path.lastIndexOf('.') + 1) as FileExtension; - const name = path.split('/').pop(); + const name = path.split('/').pop() ?? ''; return this.loadFile(pathWithoutExtension, extension) .pipe(map(fileContent => ({ diff --git a/apps/demo-app/src/app/shared/landing/landing.component.ts b/apps/demo-app/src/app/shared/landing/landing.component.ts index bfc69138..6993ae6f 100644 --- a/apps/demo-app/src/app/shared/landing/landing.component.ts +++ b/apps/demo-app/src/app/shared/landing/landing.component.ts @@ -1,26 +1,25 @@ -import { Component } from '@angular/core'; -import { IComponentDefinition, COMPONENT_DEFINITIONS } from '../../features/component-definitions'; -import { ActivatedRoute, Router } from '@angular/router'; -import { MatSnackBar } from '@angular/material/snack-bar'; import { Clipboard } from '@angular/cdk/clipboard'; +import { Component, inject } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { ActivatedRoute, Router } from '@angular/router'; +import { IComponentDefinition, COMPONENT_DEFINITIONS } from '../../features/component-definitions'; @Component({ - selector: 'app-landing', - templateUrl: './landing.component.html', - styleUrls: ['./landing.component.scss'], - standalone: false + selector: 'app-landing', + templateUrl: './landing.component.html', + styleUrls: ['./landing.component.scss'], + standalone: false }) export class LandingComponent { menuItems = COMPONENT_DEFINITIONS; - constructor( - private _router: Router, - private _activatedRoute: ActivatedRoute, - private _clipboard: Clipboard, - private _snackBar: MatSnackBar) { } + private readonly _router: Router = inject(Router); + private _activatedRoute: ActivatedRoute = inject(ActivatedRoute); + private _clipboard: Clipboard = inject(Clipboard); + private _snackBar: MatSnackBar = inject(MatSnackBar); - redirect = (item: IComponentDefinition) => { - this._router.navigate([item.route], { relativeTo: this._activatedRoute }); + redirect = async(item: IComponentDefinition) => { + await this._router.navigate([item.route], { relativeTo: this._activatedRoute }); }; share = (item: IComponentDefinition) => { diff --git a/apps/demo-app/src/app/shared/markdown-viewer/markdown-viewer.component.ts b/apps/demo-app/src/app/shared/markdown-viewer/markdown-viewer.component.ts index fa6b1f6b..128a5634 100644 --- a/apps/demo-app/src/app/shared/markdown-viewer/markdown-viewer.component.ts +++ b/apps/demo-app/src/app/shared/markdown-viewer/markdown-viewer.component.ts @@ -1,15 +1,15 @@ -import { Component, ElementRef, Input, NgZone, OnInit, Renderer2, SecurityContext } from '@angular/core'; -import { FileLoadService } from '../services/file-load.service'; -import MarkdownIt from 'markdown-it'; -import { map } from 'rxjs/operators'; +import { Component, ElementRef, inject, Input, NgZone, OnInit, Renderer2, SecurityContext } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import hljs from 'highlight.js'; +import MarkdownIt from 'markdown-it'; +import { map } from 'rxjs/operators'; +import { FileLoadService } from '../services/file-load.service'; @Component({ - selector: 'app-markdown-viewer', - templateUrl: './markdown-viewer.component.html', - styleUrls: ['./markdown-viewer.component.scss'], - standalone: false + selector: 'app-markdown-viewer', + templateUrl: './markdown-viewer.component.html', + styleUrls: ['./markdown-viewer.component.scss'], + standalone: false }) export class MarkdownViewerComponent implements OnInit { @Input() fileUrl: string | undefined; @@ -17,12 +17,11 @@ export class MarkdownViewerComponent implements OnInit { markdownContentHtml: SafeHtml | undefined = ''; - constructor( - private _fileLoad: FileLoadService, - private _domSanitizer: DomSanitizer, - private _elementRef: ElementRef, - private _renderer: Renderer2, - private _ngZone: NgZone) { } + private readonly _fileLoad: FileLoadService = inject(FileLoadService); + private _domSanitizer: DomSanitizer = inject(DomSanitizer); + private _elementRef: ElementRef = inject(ElementRef); + private _renderer: Renderer2 = inject(Renderer2); + private _ngZone: NgZone = inject(NgZone); ngOnInit(): void { if (this.fileUrl) { @@ -36,10 +35,12 @@ export class MarkdownViewerComponent implements OnInit { private loadFileContent() { this._fileLoad - .loadDocumentationFile(this.fileUrl) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .loadDocumentationFile(this.fileUrl!) .pipe( map(response => this.convertMarkdownToHtml(response)) - ).subscribe({ + ) + .subscribe({ next: response => this.markdownContentHtml = response, error: _ => this.markdownContentHtml = `### No API documentation found :'(` }); @@ -63,7 +64,7 @@ export class MarkdownViewerComponent implements OnInit { private handleAnchorClicks() { this._ngZone.runOutsideAngular(() => { this._renderer.listen(this._elementRef.nativeElement, 'click', (event: MouseEvent) => { - const anchor: HTMLAnchorElement = (event.target as HTMLElement).closest('a[href]'); + const anchor: HTMLAnchorElement | null = (event.target as HTMLElement).closest('a[href]'); if (anchor && this.isHeadingLink(anchor)) { event.preventDefault(); @@ -89,22 +90,22 @@ export class MarkdownViewerComponent implements OnInit { return false; } - private isHeadingLink(anchor: HTMLAnchorElement): boolean { + isHeadingLink = (anchor: HTMLAnchorElement): boolean => { const href = anchor.getAttribute('href'); - return href && href.includes('#'); - } + return !!href && href.includes('#'); + }; - private getHeadingId(str: string): string { + getHeadingId = (str: string | null): string => { if (str) { return str - .replace(/(_|-|\s)+/g, '') - .replace(/[&+$,/:;=?@"#{}|^¨~[\]`\\*)(%.!'<>]/g, '') + .replace(/(_|-|\s)+/gu, '') + .replace(/[&+$,/:;=?@"#{}|^¨~[\]`\\*)(%.!'<>]/gu, '') .toLowerCase(); } return ''; - } + }; - private addIdsToHeadings(html: string): string { + private addIdsToHeadings(html: string | null): string { if (html) { const document = new DOMParser().parseFromString(html, 'text/html'); document @@ -113,15 +114,15 @@ export class MarkdownViewerComponent implements OnInit { const id = this.getHeadingId(heading.textContent); heading.setAttribute('id', id); }); - return document.querySelector('body').innerHTML; + return document.querySelector('body')?.innerHTML ?? ''; } - return html; + return html ?? ''; } - private highlightCode(str: string, lang: string) { + highlightCode = (str: string, lang: string) => { if (lang && hljs.getLanguage(lang)) { - return hljs.highlight(str, { language: lang, }).value; + return hljs.highlight(str, { language: lang }).value; } return str; - } + }; } diff --git a/apps/demo-app/src/app/shared/material/material.module.ts b/apps/demo-app/src/app/shared/material/material.module.ts index da8220fe..edb3193f 100644 --- a/apps/demo-app/src/app/shared/material/material.module.ts +++ b/apps/demo-app/src/app/shared/material/material.module.ts @@ -1,22 +1,22 @@ +import { ClipboardModule } from '@angular/cdk/clipboard'; import { NgModule } from '@angular/core'; -import { MatSidenavModule } from '@angular/material/sidenav'; -import { MatListModule } from '@angular/material/list'; -import { MatToolbarModule } from '@angular/material/toolbar'; -import { MatTabsModule } from '@angular/material/tabs'; -import { MatCardModule } from '@angular/material/card'; import { MatButtonModule } from '@angular/material/button'; -import { ClipboardModule } from '@angular/cdk/clipboard'; -import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from '@angular/material/snack-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatInputModule } from '@angular/material/input'; -import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatSelectModule } from '@angular/material/select'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatNativeDateModule } from '@angular/material/core'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatListModule } from '@angular/material/list'; +import { MatMenuModule } from '@angular/material/menu'; import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; @NgModule({ declarations: [], diff --git a/apps/demo-app/src/app/shared/pipes/sort.pipe.ts b/apps/demo-app/src/app/shared/pipes/sort.pipe.ts index 11ef25c3..371eb053 100644 --- a/apps/demo-app/src/app/shared/pipes/sort.pipe.ts +++ b/apps/demo-app/src/app/shared/pipes/sort.pipe.ts @@ -8,6 +8,8 @@ import { IComponentDefinition } from '../../features/component-definitions'; export class SortPipe implements PipeTransform { transform = (values: IComponentDefinition[], direction: 'asc' | 'desc' = 'asc'): IComponentDefinition[] => direction === 'asc' - ? values.sort((one, two) => (one.label < two.label ? -1 : 1)) - : values.sort((one, two) => (one.label > two.label ? -1 : 1)); + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + ? values.sort((one, two) => one.label < two.label ? -1 : 1) + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + : values.sort((one, two) => one.label > two.label ? -1 : 1); } diff --git a/apps/demo-app/src/app/shared/services/file-load.service.ts b/apps/demo-app/src/app/shared/services/file-load.service.ts index 7c522cea..b3e3eca3 100644 --- a/apps/demo-app/src/app/shared/services/file-load.service.ts +++ b/apps/demo-app/src/app/shared/services/file-load.service.ts @@ -1,14 +1,14 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; +import { environment } from 'apps/demo-app/src/environments/environment'; import { Observable } from 'rxjs'; import { FileExtension } from '../models/file-extension.type'; -import { environment } from 'apps/demo-app/src/environments/environment'; @Injectable({ providedIn: 'root' }) export class FileLoadService { - constructor(private _httpClient: HttpClient) { } + private readonly _httpClient: HttpClient = inject(HttpClient); loadDocumentationFile = (path: string): Observable => { const url = this.isAssetsUrl(path) ? path diff --git a/apps/demo-app/src/app/shared/shared.module.ts b/apps/demo-app/src/app/shared/shared.module.ts index 2c43d12b..7d879464 100644 --- a/apps/demo-app/src/app/shared/shared.module.ts +++ b/apps/demo-app/src/app/shared/shared.module.ts @@ -1,8 +1,8 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { DateFnsAdapter } from '@angular/material-date-fns-adapter'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; +import { DateFnsAdapter } from '@angular/material-date-fns-adapter'; import { EntryButtonModule, provideEntryButtonConfig } from '@enigmatry/entry-components/button'; import { EntryCommonModule, provideEntryNativeTimeAdapter } from '@enigmatry/entry-components/common'; import { getMatDateLocale } from '../../localization'; @@ -51,17 +51,17 @@ import { SortPipe } from './pipes/sort.pipe'; { provide: DateAdapter, useClass: DateFnsAdapter, - deps: [MAT_DATE_LOCALE], + deps: [MAT_DATE_LOCALE] }, provideEntryNativeTimeAdapter({ parse: { - dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy HH:mm:ss'], + dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy HH:mm:ss'] }, display: { dateInput: 'dd-MM-yyyy HH:mm:ss', monthYearLabel: 'LLL uuuu', dateA11yLabel: 'PP', - monthYearA11yLabel: 'LLLL uuuu', + monthYearA11yLabel: 'LLLL uuuu' } }) ] diff --git a/apps/demo-app/src/assets/api/@enigmatry/entry-components/README.md b/apps/demo-app/src/assets/api/@enigmatry/entry-components/README.md index 59e4fcfc..2af8a1b9 100644 --- a/apps/demo-app/src/assets/api/@enigmatry/entry-components/README.md +++ b/apps/demo-app/src/assets/api/@enigmatry/entry-components/README.md @@ -28,7 +28,15 @@ These guides provides detailed steps for setting up and configuring theming with |17.x| = 17 |18.x| = 18 |19.x| = 19 +|20.x| = 20 ## License Apache-2 © Enigmatry + +## Modules + +- [button/public-api](button/public-api.md) +- [dialog/public-api](dialog/public-api.md) +- [search-filter/public-api](search-filter/public-api.md) +- [validation/public-api](validation/public-api.md) diff --git a/apps/demo-app/src/assets/api/@enigmatry/entry-components/button/public-api.md b/apps/demo-app/src/assets/api/@enigmatry/entry-components/button/public-api.md index 15c3ec0f..7e2a1571 100644 --- a/apps/demo-app/src/assets/api/@enigmatry/entry-components/button/public-api.md +++ b/apps/demo-app/src/assets/api/@enigmatry/entry-components/button/public-api.md @@ -17,7 +17,7 @@ Used to provide button configuration on module or application level. ### ENTRY\_BUTTON\_CONFIG -> `const` **ENTRY\_BUTTON\_CONFIG**: `InjectionToken`\<[`EntryButtonConfig`](public-api.md#entrybuttonconfig)\> +> `const` **ENTRY\_BUTTON\_CONFIG**: `InjectionToken`\<[`EntryButtonConfig`](#entrybuttonconfig)\> Entry button config injection token. diff --git a/apps/demo-app/src/assets/api/@enigmatry/entry-components/dialog/public-api.md b/apps/demo-app/src/assets/api/@enigmatry/entry-components/dialog/public-api.md index c0adffa6..a7fe969e 100644 --- a/apps/demo-app/src/assets/api/@enigmatry/entry-components/dialog/public-api.md +++ b/apps/demo-app/src/assets/api/@enigmatry/entry-components/dialog/public-api.md @@ -100,7 +100,7 @@ Any result custom implementation provides ##### openAlert() -> **openAlert**(`data`): `Observable`\<`true`\> +> **openAlert**(`data`): `Observable`\<`undefined` \| `true`\> Opens alert dialog. @@ -114,13 +114,13 @@ Contains title, message and optional confirm button text ###### Returns -`Observable`\<`true`\> +`Observable`\<`undefined` \| `true`\> `true` if confirmed, `undefined` if closed by clicking on backdrop or pressing escape ##### openConfirm() -> **openConfirm**(`data`): `Observable`\<`boolean`\> +> **openConfirm**(`data`): `Observable`\<`undefined` \| `boolean`\> Opens confirm dialog. @@ -134,13 +134,13 @@ Contains title, message and optional confirm/cancel buttons text ###### Returns -`Observable`\<`boolean`\> +`Observable`\<`undefined` \| `boolean`\> `true` if confirmed, `false` if canceled or closed, `undefined` if closed by clicking on backdrop or pressing escape ##### openError() -> **openError**(`data`): `Observable`\<`true`\> +> **openError**(`data`): `Observable`\<`undefined` \| `true`\> Opens error dialog. @@ -154,7 +154,7 @@ Contains title, errors and optional confirm button text ###### Returns -`Observable`\<`true`\> +`Observable`\<`undefined` \| `true`\> `true` if confirmed, `undefined` if closed by clicking on backdrop or pressing escape @@ -166,8 +166,8 @@ Alert dialog data. #### Extended by -- [`IEntryConfirmDialogData`](public-api.md#ientryconfirmdialogdata) -- [`IEntryErrorDialogData`](public-api.md#ientryerrordialogdata) +- [`IEntryConfirmDialogData`](#ientryconfirmdialogdata) +- [`IEntryErrorDialogData`](#ientryerrordialogdata) #### Properties @@ -188,7 +188,7 @@ Confirm dialog data. Extends IEntryAlertDialogData. #### Extends -- [`IEntryAlertDialogData`](public-api.md#ientryalertdialogdata) +- [`IEntryAlertDialogData`](#ientryalertdialogdata) #### Properties @@ -210,7 +210,7 @@ Error dialog data. #### Extends -- [`IEntryAlertDialogData`](public-api.md#ientryalertdialogdata) +- [`IEntryAlertDialogData`](#ientryalertdialogdata) #### Properties @@ -228,7 +228,7 @@ Error dialog data. ### EntryDialogButtonsAlignment -> **EntryDialogButtonsAlignment**: `"start"` \| `"center"` \| `"end"` +> **EntryDialogButtonsAlignment** = `"start"` \| `"center"` \| `"end"` Defines horizontal alignment of dialog buttons. @@ -236,7 +236,7 @@ Defines horizontal alignment of dialog buttons. ### ENTRY\_DIALOG\_CONFIG -> `const` **ENTRY\_DIALOG\_CONFIG**: `InjectionToken`\<[`EntryDialogConfig`](public-api.md#entrydialogconfig)\> +> `const` **ENTRY\_DIALOG\_CONFIG**: `InjectionToken`\<[`EntryDialogConfig`](#entrydialogconfig)\> Entry dialog injection token of EntryDialogConfig type containing dialog default configurations. diff --git a/apps/demo-app/src/assets/api/@enigmatry/entry-components/modules.md b/apps/demo-app/src/assets/api/@enigmatry/entry-components/modules.md deleted file mode 100644 index 750d1305..00000000 --- a/apps/demo-app/src/assets/api/@enigmatry/entry-components/modules.md +++ /dev/null @@ -1,8 +0,0 @@ -# @enigmatry/entry-components - -## Modules - -- [button/public-api](button/public-api.md) -- [dialog/public-api](dialog/public-api.md) -- [search-filter/public-api](search-filter/public-api.md) -- [validation/public-api](validation/public-api.md) diff --git a/apps/demo-app/src/assets/api/@enigmatry/entry-components/search-filter/public-api.md b/apps/demo-app/src/assets/api/@enigmatry/entry-components/search-filter/public-api.md index 02b05563..eb7efc35 100644 --- a/apps/demo-app/src/assets/api/@enigmatry/entry-components/search-filter/public-api.md +++ b/apps/demo-app/src/assets/api/@enigmatry/entry-components/search-filter/public-api.md @@ -10,11 +10,13 @@ SelectOption #### Extends -- [`SearchFilterBase`](public-api.md#searchfilterbaset)\<[`SelectOption`](public-api.md#selectoptiont)\<`T`\>\> +- [`SearchFilterBase`](#searchfilterbase)\<[`SelectOption`](#selectoption)\<`T`\>\> #### Type Parameters -• **T** +##### T + +`T` #### Properties @@ -41,11 +43,13 @@ Search filter date input filed configuration. #### Extends -- [`SearchFilterBase`](public-api.md#searchfilterbaset)\<`D`\> +- [`SearchFilterBase`](#searchfilterbase)\<`D`\> #### Type Parameters -• **D** +##### D + +`D` #### Properties @@ -69,11 +73,13 @@ Search filter date time input filed configuration. #### Extends -- [`SearchFilterBase`](public-api.md#searchfilterbaset)\<`D`\> +- [`SearchFilterBase`](#searchfilterbase)\<`D`\> #### Type Parameters -• **D** +##### D + +`D` #### Properties @@ -163,15 +169,17 @@ Base Entry search filter input component. #### Extended by -- [`TextSearchFilter`](public-api.md#textsearchfilter) -- [`SelectSearchFilter`](public-api.md#selectsearchfiltert) -- [`AutocompleteSearchFilter`](public-api.md#autocompletesearchfiltert) -- [`DateTimeSearchFilter`](public-api.md#datetimesearchfilterd) -- [`DateSearchFilter`](public-api.md#datesearchfilterd) +- [`TextSearchFilter`](#textsearchfilter) +- [`SelectSearchFilter`](#selectsearchfilter) +- [`AutocompleteSearchFilter`](#autocompletesearchfilter) +- [`DateTimeSearchFilter`](#datetimesearchfilter) +- [`DateSearchFilter`](#datesearchfilter) #### Type Parameters -• **T** +##### T + +`T` #### Properties @@ -195,7 +203,9 @@ Model used to populate select or autocomplete options. #### Type Parameters -• **T** +##### T + +`T` #### Properties @@ -213,11 +223,13 @@ or observable (dynamic) list (`options$`). #### Extends -- [`SearchFilterBase`](public-api.md#searchfilterbaset)\<`T`\> +- [`SearchFilterBase`](#searchfilterbase)\<`T`\> #### Type Parameters -• **T** +##### T + +`T` #### Properties @@ -244,7 +256,7 @@ Search filter text input filed configuration. #### Extends -- [`SearchFilterBase`](public-api.md#searchfilterbaset)\<`string`\> +- [`SearchFilterBase`](#searchfilterbase)\<`string`\> #### Properties @@ -260,12 +272,10 @@ Search filter text input filed configuration. | `type` | `string` | `undefined` | Type of input control e.g. 'text' or 'email' | - | [`SearchFilterBase`](public-api.md#searchfilterbaset).[`type`](public-api.md#type-3) | | | `value` | `string` | `undefined` | Default value to be displayed/selected in the input control | - | [`SearchFilterBase`](public-api.md#searchfilterbaset).[`value`](public-api.md#value-3) | | -## Type Aliases +## Interfaces ### SearchFilterParams -> **SearchFilterParams**: `object` - SearchFilterParams are the same type as @angular/router type Params, containing a collection of query URL parameters for easy integration. diff --git a/apps/demo-app/src/assets/api/@enigmatry/entry-components/validation/public-api.md b/apps/demo-app/src/assets/api/@enigmatry/entry-components/validation/public-api.md index 51777468..aa0db7e2 100644 --- a/apps/demo-app/src/assets/api/@enigmatry/entry-components/validation/public-api.md +++ b/apps/demo-app/src/assets/api/@enigmatry/entry-components/validation/public-api.md @@ -115,7 +115,7 @@ Defines the api used as a container for server side validation errors. ### ENTRY\_VALIDATION\_CONFIG -> `const` **ENTRY\_VALIDATION\_CONFIG**: `InjectionToken`\<[`EntryValidationConfig`](public-api.md#entryvalidationconfig)\> +> `const` **ENTRY\_VALIDATION\_CONFIG**: `InjectionToken`\<[`EntryValidationConfig`](#entryvalidationconfig)\> Entry validation injection token of EntryValidationConfig type containing validation default configurations. Can be updated with custom configuration. diff --git a/apps/demo-app/src/assets/api/@enigmatry/entry-form/README.md b/apps/demo-app/src/assets/api/@enigmatry/entry-form.md similarity index 99% rename from apps/demo-app/src/assets/api/@enigmatry/entry-form/README.md rename to apps/demo-app/src/assets/api/@enigmatry/entry-form.md index 9bd10fc0..2407cb45 100644 --- a/apps/demo-app/src/assets/api/@enigmatry/entry-form/README.md +++ b/apps/demo-app/src/assets/api/@enigmatry/entry-form.md @@ -93,6 +93,7 @@ entry-codegen --source-assembly ../MyProject.CodeGeneration.Setup/bin/Debug/net7 |17.x| = 17 |18.x| = 18 |19.x| = 19 +|20.x| = 20 ## License diff --git a/apps/demo-app/src/assets/api/@enigmatry/entry-form/globals.md b/apps/demo-app/src/assets/api/@enigmatry/entry-form/globals.md deleted file mode 100644 index ca4f01de..00000000 --- a/apps/demo-app/src/assets/api/@enigmatry/entry-form/globals.md +++ /dev/null @@ -1 +0,0 @@ -# @enigmatry/entry-form diff --git a/apps/demo-app/src/assets/api/README.md b/apps/demo-app/src/assets/api/README.md index bcbc4141..62db6228 100644 --- a/apps/demo-app/src/assets/api/README.md +++ b/apps/demo-app/src/assets/api/README.md @@ -3,4 +3,4 @@ ## Packages - [@enigmatry/entry-components - v0.0.1](@enigmatry/entry-components/README.md) -- [@enigmatry/entry-form - v0.0.1](@enigmatry/entry-form/README.md) +- [@enigmatry/entry-form - v0.0.1](@enigmatry/entry-form.md) diff --git a/apps/demo-app/src/localization.ts b/apps/demo-app/src/localization.ts index 8569b072..7122a84c 100644 --- a/apps/demo-app/src/localization.ts +++ b/apps/demo-app/src/localization.ts @@ -1,5 +1,17 @@ -import { environment } from './environments/environment'; +// eslint-disable-next-line import/no-extraneous-dependencies import { de, enUS, fr, nl, Locale } from 'date-fns/locale'; +import { environment } from './environments/environment'; + +const getCulture = (): string => { + const culture = localStorage.getItem('language'); + if (!culture) { + // eslint-disable-next-line no-console + console.warn('Failed loading culture from local storage, using default ' + + `'${environment.defaultCulture}' culture.`); + return environment.defaultCulture; + } + return culture; +}; export const getMatDateLocale = (): Locale => { switch (getCulture()) { @@ -13,14 +25,3 @@ export const getMatDateLocale = (): Locale => { return enUS; } }; - -const getCulture = (): string => { - const culture = localStorage.getItem('language'); - if (!culture) { - // eslint-disable-next-line no-console - console.warn('Failed loading culture from local storage, using default ' + - `'${environment.defaultCulture}' culture.`); - return environment.defaultCulture; - } - return culture; -}; diff --git a/apps/demo-app/src/main.ts b/apps/demo-app/src/main.ts index c7b673cf..49b102e5 100644 --- a/apps/demo-app/src/main.ts +++ b/apps/demo-app/src/main.ts @@ -1,6 +1,5 @@ import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - +import { platformBrowser } from '@angular/platform-browser'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; @@ -8,5 +7,6 @@ if (environment.production) { enableProdMode(); } -platformBrowserDynamic().bootstrapModule(AppModule) +platformBrowser().bootstrapModule(AppModule) + // eslint-disable-next-line no-console .catch(err => console.error(err)); diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..08c9f363 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,14 @@ +import defaultEnigmatryConfiguration from './libs/eslint-config/index.js'; + +export default [ + ...defaultEnigmatryConfiguration, + { + ignores: ['libs/scss-foundation/coverage/**'] + }, + { + files: ['libs/**/*.ts'], + rules: { + '@angular-eslint/prefer-standalone': 'off' // TODO: Remove when we get rid of Formly + } + } +]; \ No newline at end of file diff --git a/libs/entry-components/.eslintrc.json b/libs/entry-components/.eslintrc.json deleted file mode 100644 index 494613ed..00000000 --- a/libs/entry-components/.eslintrc.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "extends": "../../.eslintrc.json", - "ignorePatterns": [ - "!**/*" - ], - "overrides": [ - { - "files": [ - "*.ts" - ], - "parserOptions": { - "project": [ - "libs/entry-components/tsconfig.lib.json", - "libs/entry-components/tsconfig.spec.json" - ], - "createDefaultProgram": true - }, - "rules": { - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": ["entry", ""], - "style": "camelCase" - } - ], - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": "entry", - "style": "kebab-case" - } - ], - "@typescript-eslint/no-explicit-any": "off", - "@angular-eslint/prefer-standalone": [ - "warn" - ] - } - }, - { - "files": [ - "*.html" - ], - "rules": {} - } - ] -} diff --git a/libs/entry-components/README.md b/libs/entry-components/README.md index b68e48b8..e31cd16b 100644 --- a/libs/entry-components/README.md +++ b/libs/entry-components/README.md @@ -29,6 +29,7 @@ These guides provides detailed steps for setting up and configuring theming with |17.x| = 17 |18.x| = 18 |19.x| = 19 +|20.x| = 20 ## License diff --git a/libs/entry-components/button/entry-button-config.ts b/libs/entry-components/button/entry-button-config.ts index 1c8b0eea..0b626b64 100644 --- a/libs/entry-components/button/entry-button-config.ts +++ b/libs/entry-components/button/entry-button-config.ts @@ -38,6 +38,5 @@ export const ENTRY_BUTTON_CONFIG = createInjectionToken(new EntryButtonConfig()) /** * Can be used to provide custom button configuration. */ -export function provideEntryButtonConfig(config: Partial): Provider { - return provideConfig(ENTRY_BUTTON_CONFIG, () => new EntryButtonConfig(config)); -} +export const provideEntryButtonConfig = (config: Partial): Provider => + provideConfig(ENTRY_BUTTON_CONFIG, () => new EntryButtonConfig(config)); diff --git a/libs/entry-components/button/entry-button.directive.ts b/libs/entry-components/button/entry-button.directive.ts index ea12ac7a..afca6ee6 100644 --- a/libs/entry-components/button/entry-button.directive.ts +++ b/libs/entry-components/button/entry-button.directive.ts @@ -1,15 +1,14 @@ -import { Directive, ElementRef, Inject, OnInit, Optional } from '@angular/core'; +import { Directive, ElementRef, inject, OnInit } from '@angular/core'; import { MatButton, MatAnchor } from '@angular/material/button'; import { ThemePalette } from '@angular/material/core'; import { ENTRY_BUTTON_CONFIG, EntryButtonConfig, MatButtonConfig } from './entry-button-config'; @Directive({ - // eslint-disable-next-line @angular-eslint/directive-selector - selector: `[mat-button][entry-submit-button],[mat-button][entry-cancel-button]`, - standalone: false + // eslint-disable-next-line @angular-eslint/directive-selector + selector: `[mat-button][entry-submit-button],[mat-button][entry-cancel-button]`, + standalone: false }) export class EntryButtonDirective implements OnInit { - matClasses: { [key: string]: string[] } = { basic: ['mdc-button', 'mat-mdc-button'], raised: ['mdc-button', 'mdc-button--raised', 'mat-mdc-raised-button'], @@ -17,15 +16,13 @@ export class EntryButtonDirective implements OnInit { flat: ['mdc-button', 'mdc-button--unelevated', 'mat-mdc-unelevated-button'] }; - constructor( - private _elementRef: ElementRef, - @Inject(ENTRY_BUTTON_CONFIG) private _config: EntryButtonConfig, - @Optional() private _matButton?: MatButton, - @Optional() private _matAnchor?: MatAnchor) { - } + private readonly _elementRef: ElementRef = inject(ElementRef); + private readonly _config: EntryButtonConfig = inject(ENTRY_BUTTON_CONFIG); + private readonly _matButton = inject(MatButton, { optional: true }); + private readonly _matAnchor = inject(MatAnchor, { optional: true }); ngOnInit(): void { - const entryButtonType = this.getEntryType(); + const entryButtonType: 'submit' | 'cancel' = this.getEntryType(); const buttonConfig: MatButtonConfig = this._config[entryButtonType]; const entryClasses: string[] = ['entry-button', `entry-${entryButtonType}-button`]; @@ -35,13 +32,16 @@ export class EntryButtonDirective implements OnInit { const color: ThemePalette = buttonConfig.color; if (color) { - if (this._matButton) { this._matButton.color = color; } - if (this._matAnchor) { this._matAnchor.color = color; } + if (this._matButton) { + this._matButton.color = color; + } + if (this._matAnchor) { + this._matAnchor.color = color; + } } } - private getEntryType(): string { - return this._elementRef.nativeElement.hasAttribute('entry-submit-button') - ? 'submit' : 'cancel'; - } + private readonly getEntryType = (): 'submit' | 'cancel' => { + return this._elementRef.nativeElement.hasAttribute('entry-submit-button') ? 'submit' : 'cancel'; + }; } diff --git a/libs/entry-components/button/entry-button.module.ts b/libs/entry-components/button/entry-button.module.ts index 7f09ccd9..e9606dd1 100644 --- a/libs/entry-components/button/entry-button.module.ts +++ b/libs/entry-components/button/entry-button.module.ts @@ -1,7 +1,7 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { EntryButtonDirective } from './entry-button.directive'; +import { NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; +import { EntryButtonDirective } from './entry-button.directive'; @NgModule({ declarations: [ diff --git a/libs/entry-components/common/common.module.ts b/libs/entry-components/common/common.module.ts index d63f32d3..993914ef 100644 --- a/libs/entry-components/common/common.module.ts +++ b/libs/entry-components/common/common.module.ts @@ -1,12 +1,18 @@ -import { ModuleWithProviders, NgModule, Provider } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { ModuleWithProviders, NgModule, Provider } from '@angular/core'; /** Directives */ +import { EVENT_MANAGER_PLUGINS } from '@angular/platform-browser'; import { AutoDisableButtonDirective } from './directives/auto-disable-button.directive'; -import { ScrollToInvalidControlDirective } from './directives/scroll-to-invalid-control.directive'; -import { NoopControlValueAccessorDirective } from './directives/noop-control-value-accessor'; import { NgControlAccessorDirective } from './directives/ng-control-accessor.directive'; +import { NoopControlValueAccessorDirective } from './directives/noop-control-value-accessor'; +import { ScrollToInvalidControlDirective } from './directives/scroll-to-invalid-control.directive'; + +/** Event plugins */ + +import { DebounceEventPlugin } from './event-plugins/debounce.plugin'; +import { ThrottleEventPlugin } from './event-plugins/throttle.plugin'; const DIRECTIVES = [ AutoDisableButtonDirective, @@ -15,12 +21,6 @@ const DIRECTIVES = [ NgControlAccessorDirective ]; -/** Event plugins */ - -import { DebounceEventPlugin } from './event-plugins/debounce.plugin'; -import { ThrottleEventPlugin } from './event-plugins/throttle.plugin'; -import { EVENT_MANAGER_PLUGINS } from '@angular/platform-browser'; - const EVENT_PLUGINS = [ DebounceEventPlugin, ThrottleEventPlugin @@ -45,6 +45,8 @@ export const NG_EVENT_PLUGINS: Provider[] = EVENT_PLUGINS.map(useClass => ({ ] }) export class EntryCommonModule { + // Has to be function since modules needs to work with statically defined providers + // eslint-disable-next-line prefer-arrow-functions/prefer-arrow-functions static forRoot(): ModuleWithProviders { return { ngModule: EntryCommonModule, diff --git a/libs/entry-components/common/date-time/entry-date-time-adapter.ts b/libs/entry-components/common/date-time/entry-date-time-adapter.ts index 2c83106c..4b05a21b 100644 --- a/libs/entry-components/common/date-time/entry-date-time-adapter.ts +++ b/libs/entry-components/common/date-time/entry-date-time-adapter.ts @@ -1,3 +1,4 @@ +/* eslint-disable @angular-eslint/prefer-inject */ import { Inject, Injectable, Optional, SkipSelf } from '@angular/core'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { EntryTimeAdapter } from './entry-time-adapter'; @@ -7,7 +8,6 @@ import { EntryTimeAdapter } from './entry-time-adapter'; */ @Injectable() export class EntryDateTimeAdapter extends DateAdapter implements EntryTimeAdapter { - constructor( @Optional() @Inject(MAT_DATE_LOCALE) matDateLocale: L, @SkipSelf() private readonly dateAdapter: DateAdapter, @@ -69,7 +69,7 @@ export class EntryDateTimeAdapter extends DateAdapter implements Ent } parse(value: any, parseFormat: any): D { - return this.dateAdapter.parse(value, parseFormat); + return this.dateAdapter.parse(value, parseFormat) as D; } format(date: D, displayFormat: any): string { @@ -104,19 +104,19 @@ export class EntryDateTimeAdapter extends DateAdapter implements Ent return this.dateAdapter.invalid(); } - getHours(date: D): number { + override getHours(date: D): number { return this.timeAdapter.getHours(date); } - getMinutes(date: D): number { + override getMinutes(date: D): number { return this.timeAdapter.getMinutes(date); } - getSeconds(date: D): number { + override getSeconds(date: D): number { return this.timeAdapter.getSeconds(date); } - setTime(date: D, hours: number, minutes: number, seconds: number): D { + override setTime(date: D, hours: number, minutes: number, seconds: number): D { return this.timeAdapter.setTime(date, hours, minutes, seconds); } @@ -133,5 +133,4 @@ export class EntryDateTimeAdapter extends DateAdapter implements Ent this.getMinutes(first) - this.getMinutes(second) || this.getSeconds(first) - this.getSeconds(second); } - } diff --git a/libs/entry-components/common/date-time/entry-date-time-formats.ts b/libs/entry-components/common/date-time/entry-date-time-formats.ts index a29a7c33..b174b41c 100644 --- a/libs/entry-components/common/date-time/entry-date-time-formats.ts +++ b/libs/entry-components/common/date-time/entry-date-time-formats.ts @@ -5,7 +5,7 @@ export type EntryDateTimeFormats = MatDateFormats; export const defaultDateTimeFormats = { parse: { - dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy HH:mm'], + dateInput: ['dd-MM-yyyy', 'dd-MM-yyyy HH', 'dd-MM-yyyy HH:mm'] }, display: { dateInput: 'dd-MM-yyyy HH:mm', diff --git a/libs/entry-components/common/date-time/entry-time-adapter.ts b/libs/entry-components/common/date-time/entry-time-adapter.ts index 275f7ea7..e15ca303 100644 --- a/libs/entry-components/common/date-time/entry-time-adapter.ts +++ b/libs/entry-components/common/date-time/entry-time-adapter.ts @@ -1,4 +1,4 @@ -import { Injectable } from "@angular/core"; +import { Injectable } from '@angular/core'; @Injectable() export abstract class EntryTimeAdapter { diff --git a/libs/entry-components/common/date-time/native-time-adapter.ts b/libs/entry-components/common/date-time/native-time-adapter.ts index 966cdc6d..1b77e2ad 100644 --- a/libs/entry-components/common/date-time/native-time-adapter.ts +++ b/libs/entry-components/common/date-time/native-time-adapter.ts @@ -1,31 +1,22 @@ -import { Injectable, Provider } from "@angular/core"; -import { EntryTimeAdapter } from "./entry-time-adapter"; -import { EntryDateTimeFormats, defaultDateTimeFormats, ENTRY_MAT_DATE_TIME_FORMATS } from "./entry-date-time-formats"; +import { Injectable, Provider } from '@angular/core'; +import { EntryDateTimeFormats, defaultDateTimeFormats, ENTRY_MAT_DATE_TIME_FORMATS } from './entry-date-time-formats'; +import { EntryTimeAdapter } from './entry-time-adapter'; @Injectable() export class EntryNativeTimeAdapter extends EntryTimeAdapter { + getHours = (date: Date): number => date?.getHours(); - getHours(date: Date): number { - return date?.getHours(); - } + getMinutes = (date: Date): number => date?.getMinutes(); - getMinutes(date: Date): number { - return date?.getMinutes(); - } + getSeconds = (date: Date): number => date?.getSeconds(); - getSeconds(date: Date): number { - return date?.getSeconds(); - } - - setTime(date: Date, hours: number, minutes: number, seconds: number): Date { + setTime = (date: Date, hours: number, minutes: number, seconds: number): Date => { date?.setHours(hours, minutes, seconds, 0); return date; - } + }; } -export function provideEntryNativeTimeAdapter(dateTimeFormats: EntryDateTimeFormats = defaultDateTimeFormats): Provider[] { - return [ +export const provideEntryNativeTimeAdapter = (dateTimeFormats: EntryDateTimeFormats = defaultDateTimeFormats): Provider[] => [ { provide: EntryTimeAdapter, useClass: EntryNativeTimeAdapter }, { provide: ENTRY_MAT_DATE_TIME_FORMATS, useValue: dateTimeFormats } - ] -} \ No newline at end of file + ]; \ No newline at end of file diff --git a/libs/entry-components/common/directives/auto-disable-button.directive.ts b/libs/entry-components/common/directives/auto-disable-button.directive.ts index 07df43c9..84b5c413 100644 --- a/libs/entry-components/common/directives/auto-disable-button.directive.ts +++ b/libs/entry-components/common/directives/auto-disable-button.directive.ts @@ -1,8 +1,8 @@ -import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { NumberInput, coerceNumberProperty } from '@angular/cdk/coercion'; +import { Directive, ElementRef, inject, Input, OnDestroy, OnInit } from '@angular/core'; import { Subject, fromEvent, timer } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { NG_VALID_CLASS } from '../constants'; -import { NumberInput, coerceNumberProperty } from '@angular/cdk/coercion'; /** * Auto disable button after click or submit with entry-auto-disable directive. @@ -20,24 +20,23 @@ import { NumberInput, coerceNumberProperty } from '@angular/cdk/coercion'; selector: 'button[entry-auto-disable]:not([disabled])' }) export class AutoDisableButtonDirective implements OnInit, OnDestroy { - private _destroy$ = new Subject(); private _disableIntervalInMs = 2000; - - constructor(private elementRef: ElementRef) { } + private readonly elementRef: ElementRef = inject(ElementRef); @Input('entry-auto-disable') get disableIntervalInMs() { return this._disableIntervalInMs; } set disableIntervalInMs(value: NumberInput) { + // eslint-disable-next-line @typescript-eslint/no-magic-numbers this._disableIntervalInMs = coerceNumberProperty(value, 2000); } ngOnInit(): void { const button = this.elementRef.nativeElement; const isTypeSubmit = button.getAttribute('type') === 'submit'; - const form: HTMLFormElement = button.closest('form'); + const form: HTMLFormElement | null = button.closest('form'); if (isTypeSubmit && form) { // listen to form submit event diff --git a/libs/entry-components/common/directives/ng-control-accessor.directive.ts b/libs/entry-components/common/directives/ng-control-accessor.directive.ts index e4f4230e..4c0f2568 100644 --- a/libs/entry-components/common/directives/ng-control-accessor.directive.ts +++ b/libs/entry-components/common/directives/ng-control-accessor.directive.ts @@ -1,6 +1,6 @@ -import { Directive, OnDestroy, OnInit, inject } from "@angular/core"; -import { FormControlDirective, FormControlName, NgControl, NgModel, UntypedFormControl } from "@angular/forms"; -import { Subject, takeUntil } from "rxjs"; +import { Directive, OnDestroy, OnInit, inject } from '@angular/core'; +import { FormControlDirective, FormControlName, NgControl, NgModel, UntypedFormControl } from '@angular/forms'; +import { Subject, takeUntil } from 'rxjs'; @Directive({ standalone: true @@ -32,7 +32,7 @@ export class NgControlAccessorDirective implements OnDestroy, OnInit { if (ngModel.model !== newValue || ngModel.viewModel !== newValue) { ngModel.viewToModelUpdate(newValue); } - }) + }); } } diff --git a/libs/entry-components/common/directives/noop-control-value-accessor.ts b/libs/entry-components/common/directives/noop-control-value-accessor.ts index 2205c3eb..ab21fae8 100644 --- a/libs/entry-components/common/directives/noop-control-value-accessor.ts +++ b/libs/entry-components/common/directives/noop-control-value-accessor.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ -import { Directive } from "@angular/core"; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; +import { Directive } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Directive({ standalone: true, @@ -13,8 +13,8 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; ] }) export class NoopControlValueAccessorDirective implements ControlValueAccessor { - writeValue(_obj: any): void { } - registerOnChange(_fn: any): void { } - registerOnTouched(_fn: any): void { } - setDisabledState?(_isDisabled: boolean): void { } + writeValue = (_obj: any): void => { }; + registerOnChange = (_fn: any): void => { }; + registerOnTouched = (_fn: any): void => { }; + setDisabledState = (_isDisabled: boolean): void => { }; } \ No newline at end of file diff --git a/libs/entry-components/common/directives/scroll-to-invalid-control.directive.ts b/libs/entry-components/common/directives/scroll-to-invalid-control.directive.ts index 3ecc1498..49b2b2ff 100644 --- a/libs/entry-components/common/directives/scroll-to-invalid-control.directive.ts +++ b/libs/entry-components/common/directives/scroll-to-invalid-control.directive.ts @@ -1,4 +1,4 @@ -import { Directive, ElementRef, OnDestroy, OnInit, Self } from '@angular/core'; +import { Directive, ElementRef, inject, OnDestroy, OnInit } from '@angular/core'; import { ControlContainer } from '@angular/forms'; import { Subject, fromEvent } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -10,15 +10,13 @@ import { NG_INVALID_CLASS } from '../constants'; */ @Directive({ standalone: true, + // eslint-disable-next-line @angular-eslint/directive-selector selector: 'form[formGroup],form[ngForm]' }) export class ScrollToInvalidControlDirective implements OnInit, OnDestroy { - private destroy$ = new Subject(); - - constructor( - @Self() private form: ControlContainer, - private elementRef: ElementRef) { } + private readonly form = inject(ControlContainer, { self: true }); + private readonly elementRef = inject(ElementRef); ngOnInit(): void { fromEvent(this.elementRef.nativeElement, 'submit') @@ -36,13 +34,13 @@ export class ScrollToInvalidControlDirective implements OnInit, OnDestroy { } private scrollToInvalidControl() { - const firstInvalidControl: HTMLElement = + const firstInvalidControl: HTMLElement | null = this.elementRef.nativeElement.querySelector(NG_INVALID_CLASS); if (firstInvalidControl) { firstInvalidControl.scrollIntoView({ behavior: 'smooth', - block: 'center' // vertical alignment + block: 'center' // vertical alignment }); } } diff --git a/libs/entry-components/common/event-plugins/debounce.plugin.ts b/libs/entry-components/common/event-plugins/debounce.plugin.ts index 85e1842f..92cfac1d 100644 --- a/libs/entry-components/common/event-plugins/debounce.plugin.ts +++ b/libs/entry-components/common/event-plugins/debounce.plugin.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -import { EntryEventManagerPlugin } from './abstract.plugin'; import { debounce } from 'lodash-es'; +import { EntryEventManagerPlugin } from './entry-event-manager.plugin'; /** * Provides event plugin for debouncing events. @@ -11,7 +11,6 @@ import { debounce } from 'lodash-es'; */ @Injectable() export class DebounceEventPlugin extends EntryEventManagerPlugin { - modifier = '.debounce'; // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type @@ -23,7 +22,7 @@ export class DebounceEventPlugin extends EntryEventManagerPlugin { const innerHandler = (event: any) => this.manager.getZone().runGuarded(() => originalHandler(event)); // create debounced handler - const debouncedHandler = debounce(innerHandler, milliseconds, + const debouncedHandler = debounce(innerHandler, milliseconds as number, { leading: option === 'leading', trailing: option === 'trailing' }); // register event with debounced handler diff --git a/libs/entry-components/common/event-plugins/entry-event-manager.plugin.ts b/libs/entry-components/common/event-plugins/entry-event-manager.plugin.ts new file mode 100644 index 00000000..0ec86505 --- /dev/null +++ b/libs/entry-components/common/event-plugins/entry-event-manager.plugin.ts @@ -0,0 +1,26 @@ +import { EventManagerPlugin } from './event-manager.plugin'; + +/** + * Entry event plugin base class + */ +export abstract class EntryEventManagerPlugin extends EventManagerPlugin { + abstract modifier: string; + + /** return `true` for every event name that has specified modifier */ + supports(eventName: string): boolean { + return eventName.includes(this.modifier); + } + + /** unwrap params e.g. (click.debounce.500) => ['debounce', 500] */ + unwrapParams(eventName: string): string[] { + return eventName + .substring(eventName.indexOf(this.modifier)) + .split('.') + .filter(x => !!x); + } + + /** get event name e.g. (click.debounce.500) => click */ + unwrapEventName(eventName: string): string { + return eventName.substring(0, eventName.indexOf(this.modifier)); + } +} diff --git a/libs/entry-components/common/event-plugins/abstract.plugin.ts b/libs/entry-components/common/event-plugins/event-manager.plugin.ts similarity index 65% rename from libs/entry-components/common/event-plugins/abstract.plugin.ts rename to libs/entry-components/common/event-plugins/event-manager.plugin.ts index 7f6ccccb..f0c22cfe 100644 --- a/libs/entry-components/common/event-plugins/abstract.plugin.ts +++ b/libs/entry-components/common/event-plugins/event-manager.plugin.ts @@ -1,59 +1,33 @@ -import { EventManager } from '@angular/platform-browser'; - -/** - * abstract class EventManagerPlugin will be exposed in the public api - * https://github.com/angular/angular/pull/49969 - * - * Until then creating it from reference - * https://github.com/angular/angular/blob/main/packages/platform-browser/src/dom/events/event_manager.ts#L93 - * - * How to create custom event modifiers - * https://github.com/Tinkoff/ng-event-plugins, - * https://github.com/angular/angular/blob/main/packages/platform-browser/src/dom/events/key_events.ts - * https://netbasal.com/lifting-the-veil-insights-into-angulars-eventmanagerplugin-ed9d14cbb31a - */ -export abstract class EventManagerPlugin { - - // Using non-null assertion because it's set by EventManager's constructor - manager!: EventManager; - - /** Should return `true` for every event name that should be supported by this plugin */ - abstract supports(eventName: string): boolean; - - /** - * Registers a handler for a specific element and event. - * - * @param element The HTML element to receive event notifications. - * @param eventName The name of the event to listen for. - * @param handler A function to call when the notification occurs. Receives the - * event object as an argument. - * @returns A callback function that can be used to remove the handler. - */ - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - abstract addEventListener(element: HTMLElement, eventName: string, handler: Function): Function; -} - -/** - * Entry event plugin base class - */ -export abstract class EntryEventManagerPlugin extends EventManagerPlugin { - abstract modifier: string; - - /** return `true` for every event name that has specified modifier */ - supports(eventName: string): boolean { - return eventName.includes(this.modifier); - } - - /** unwrap params e.g. (click.debounce.500) => ['debounce', 500] */ - unwrapParams(eventName: string): string[] { - return eventName - .substring(eventName.indexOf(this.modifier)) - .split('.') - .filter(x => !!x); - } - - /** get event name e.g. (click.debounce.500) => click */ - unwrapEventName(eventName: string): string { - return eventName.substring(0, eventName.indexOf(this.modifier)); - } -} +import { EventManager } from '@angular/platform-browser'; + +/** + * abstract class EventManagerPlugin will be exposed in the public api + * https://github.com/angular/angular/pull/49969 + * + * Until then creating it from reference + * https://github.com/angular/angular/blob/main/packages/platform-browser/src/dom/events/event_manager.ts#L93 + * + * How to create custom event modifiers + * https://github.com/Tinkoff/ng-event-plugins, + * https://github.com/angular/angular/blob/main/packages/platform-browser/src/dom/events/key_events.ts + * https://netbasal.com/lifting-the-veil-insights-into-angulars-eventmanagerplugin-ed9d14cbb31a + */ +export abstract class EventManagerPlugin { + // Using non-null assertion because it's set by EventManager's constructor + manager!: EventManager; + + /** Should return `true` for every event name that should be supported by this plugin */ + abstract supports(eventName: string): boolean; + + /** + * Registers a handler for a specific element and event. + * + * @param element The HTML element to receive event notifications. + * @param eventName The name of the event to listen for. + * @param handler A function to call when the notification occurs. Receives the + * event object as an argument. + * @returns A callback function that can be used to remove the handler. + */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + abstract addEventListener(element: HTMLElement, eventName: string, handler: Function): Function; +} \ No newline at end of file diff --git a/libs/entry-components/common/event-plugins/throttle.plugin.ts b/libs/entry-components/common/event-plugins/throttle.plugin.ts index 895fc07f..3c0cb139 100644 --- a/libs/entry-components/common/event-plugins/throttle.plugin.ts +++ b/libs/entry-components/common/event-plugins/throttle.plugin.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -import { EntryEventManagerPlugin } from './abstract.plugin'; import { throttle } from 'lodash-es'; +import { EntryEventManagerPlugin } from './entry-event-manager.plugin'; /** * Provides event plugin for throttling events. @@ -11,7 +11,6 @@ import { throttle } from 'lodash-es'; */ @Injectable() export class ThrottleEventPlugin extends EntryEventManagerPlugin { - modifier = '.throttle'; // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type @@ -23,7 +22,7 @@ export class ThrottleEventPlugin extends EntryEventManagerPlugin { const innerHandler = (event: any) => this.manager.getZone().runGuarded(() => originalHandler(event)); // create throttled handler - const throttledHandler = throttle(innerHandler, milliseconds); + const throttledHandler = throttle(innerHandler, milliseconds as number); // register event with throttled handler return this.manager.addEventListener(element, this.unwrapEventName(eventName), throttledHandler); diff --git a/libs/entry-components/common/interceptors/accept-language.interceptor.ts b/libs/entry-components/common/interceptors/accept-language.interceptor.ts index 829e6feb..e60f5316 100644 --- a/libs/entry-components/common/interceptors/accept-language.interceptor.ts +++ b/libs/entry-components/common/interceptors/accept-language.interceptor.ts @@ -1,5 +1,5 @@ -import { Injectable, LOCALE_ID, inject } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpInterceptorFn, HttpHandlerFn } from '@angular/common/http'; +import { Injectable, LOCALE_ID, inject } from '@angular/core'; import { Observable } from 'rxjs'; /** @@ -27,4 +27,4 @@ export const acceptLanguageInterceptor: HttpInterceptorFn = (request: HttpReques headers: request.headers.set('Accept-Language', localeId) }); return next(newRequest); -} +}; diff --git a/libs/entry-components/common/utils/provide-config.ts b/libs/entry-components/common/utils/provide-config.ts index 2e9f211f..60502787 100644 --- a/libs/entry-components/common/utils/provide-config.ts +++ b/libs/entry-components/common/utils/provide-config.ts @@ -1,17 +1,13 @@ import { InjectionToken, Provider } from '@angular/core'; -export function createInjectionToken(defaultValue: T): InjectionToken { - return new InjectionToken(defaultValue.constructor.name, +export const createInjectionToken = (defaultValue: T): InjectionToken => new InjectionToken(defaultValue?.constructor.name ?? 'DefaultToken', { providedIn: 'root', factory: () => defaultValue } ); -} -export function provideConfig(token: InjectionToken, factory: () => T): Provider { - return { +export const provideConfig = (token: InjectionToken, factory: () => T): Provider => ({ provide: token, useFactory: factory - }; -} + }); diff --git a/libs/entry-components/date-time-picker/date-time-picker-config.model.ts b/libs/entry-components/date-time-picker/date-time-picker-config.model.ts index 747fb9bb..43f39178 100644 --- a/libs/entry-components/date-time-picker/date-time-picker-config.model.ts +++ b/libs/entry-components/date-time-picker/date-time-picker-config.model.ts @@ -12,6 +12,7 @@ export class EntryDateTimePickerConfig { } } +// eslint-disable-next-line no-secrets/no-secrets /** * Entry date-time-picker injection token of EntryDateTimePickerConfig type containing dialog default configurations. * @@ -23,6 +24,5 @@ export const ENTRY_DATE_TIME_PICKER_CONFIG = createInjectionToken(new EntryDateT /** * Can be used to provide entry date-time-picker configuration. */ -export function provideEntryDateTimePickerConfig(config: Partial): Provider { - return provideConfig(ENTRY_DATE_TIME_PICKER_CONFIG, () => new EntryDateTimePickerConfig(config)); -} \ No newline at end of file +export const provideEntryDateTimePickerConfig = (config: Partial): Provider => + provideConfig(ENTRY_DATE_TIME_PICKER_CONFIG, () => new EntryDateTimePickerConfig(config)); \ No newline at end of file diff --git a/libs/entry-components/date-time-picker/date-time-picker.component.html b/libs/entry-components/date-time-picker/date-time-picker.component.html index 03c3f95f..19b1e19f 100644 --- a/libs/entry-components/date-time-picker/date-time-picker.component.html +++ b/libs/entry-components/date-time-picker/date-time-picker.component.html @@ -1,6 +1,6 @@ {{ label }} - + {{hint}} implements OnInit, OnDestroy { } // Control that is connected to calendar - calendarControl: FormControl = new FormControl(undefined); + calendarControl: FormControl = new FormControl(undefined); is12HourClock = this.dateTimeAdapter.is12HoursClock(this.format.display.dateInput); @@ -91,7 +93,7 @@ export class EntryDateTimePickerComponent implements OnInit, OnDestroy { this.calendarControl.enable({ emitEvent: false }); } this.changeDetectorRef.markForCheck(); - }) + }); this.formControl.valueChanges .pipe(takeUntil(this.$destroy)) @@ -105,8 +107,10 @@ export class EntryDateTimePickerComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.$destroy)) .subscribe(value => { this.timePicker.to24HourClock(); - this.dateTimeAdapter.setTime(value, this.timePicker.hours, this.timePicker.minutes, this.timePicker.seconds); - this.formControl.setValue(value); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.dateTimeAdapter.setTime(value!, this.timePicker.hours, this.timePicker.minutes, this.timePicker.seconds); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.formControl.setValue(value!); this.formControl.markAsDirty(); this.formControl.markAsTouched(); }); diff --git a/libs/entry-components/date-time-picker/date-time-picker.module.ts b/libs/entry-components/date-time-picker/date-time-picker.module.ts index 42f4318a..eb1607d5 100644 --- a/libs/entry-components/date-time-picker/date-time-picker.module.ts +++ b/libs/entry-components/date-time-picker/date-time-picker.module.ts @@ -1,14 +1,14 @@ -import { NgModule } from "@angular/core"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { MatDatepickerModule } from "@angular/material/datepicker"; -import { MatFormFieldModule } from "@angular/material/form-field"; -import { CommonModule } from "@angular/common"; -import { MatSelectModule } from "@angular/material/select"; -import { MatButtonModule } from "@angular/material/button"; -import { MatInputModule } from "@angular/material/input"; -import { MatIconModule } from "@angular/material/icon"; -import { EntryValidationModule } from "@enigmatry/entry-components/validation"; -import { EntryDateTimePickerComponent } from "./date-time-picker.component"; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { EntryValidationModule } from '@enigmatry/entry-components/validation'; +import { EntryDateTimePickerComponent } from './date-time-picker.component'; import { EntryTimePickerComponent } from './time-picker.component'; @NgModule({ diff --git a/libs/entry-components/date-time-picker/time-picker.component.ts b/libs/entry-components/date-time-picker/time-picker.component.ts index b63a7e8c..fae02819 100644 --- a/libs/entry-components/date-time-picker/time-picker.component.ts +++ b/libs/entry-components/date-time-picker/time-picker.component.ts @@ -5,14 +5,16 @@ import { EntryDateTimeAdapter } from '@enigmatry/entry-components/common'; export type meridiem = 'am' | 'pm'; @Component({ - selector: 'entry-time-picker', - templateUrl: './time-picker.component.html', - standalone: false + selector: 'entry-time-picker', + templateUrl: './time-picker.component.html', + standalone: false }) export class EntryTimePickerComponent implements OnChanges { @HostBinding('class') class = 'entry-time-picker'; - readonly timeAdapter = inject(DateAdapter) as EntryDateTimeAdapter; + private readonly hoursInDay = 24; + private readonly halfADay = 12; + private readonly minutesInHour = 60; @Input() date: D | undefined; @Input() showSeconds: boolean; @@ -24,9 +26,9 @@ export class EntryTimePickerComponent implements OnChanges { seconds = 0; meridiem: meridiem = 'am'; - readonly hours12 = Array.from(Array(12), (_, i) => i + 1); - readonly hours24 = Array.from(Array(24), (_, i) => i); - readonly sixty = Array.from(Array(60), (_, i) => i); + readonly hours12 = Array.from(Array(this.halfADay), (_, i) => i + 1); + readonly hours24 = Array.from(Array(this.hoursInDay), (_, i) => i); + readonly sixty = Array.from(Array(this.minutesInHour), (_, i) => i); get possibleHours() { return this.is12HourClock ? this.hours12 : this.hours24; @@ -47,11 +49,11 @@ export class EntryTimePickerComponent implements OnChanges { ? this.timeAdapter.getMinutes(this.date) : this.timeAdapter.getMinutes(this.defaultTime ?? now); - this.seconds = (this.showSeconds && this.date) + this.seconds = this.showSeconds && this.date ? this.timeAdapter.getSeconds(this.date) : this.timeAdapter.getSeconds(this.defaultTime ?? now); - this.meridiem = this.hours >= 12 ? 'pm' : 'am'; + this.meridiem = this.hours >= this.halfADay ? 'pm' : 'am'; if (this.is12HourClock) { this.to12HourClock(); @@ -59,11 +61,11 @@ export class EntryTimePickerComponent implements OnChanges { } to12HourClock() { - if (this.hours > 12) { - this.hours = this.hours - 12; + if (this.hours > this.halfADay) { + this.hours -= this.halfADay; } if (this.hours === 0) { - this.hours = 12; + this.hours = this.halfADay; } } @@ -71,11 +73,11 @@ export class EntryTimePickerComponent implements OnChanges { if (!this.is12HourClock) { return; } - if (this.meridiem === "am" && this.hours === 12) { + if (this.meridiem === 'am' && this.hours === this.halfADay) { this.hours = 0; } - if (this.meridiem == "pm" && this.hours != 12) { - this.hours = this.hours + 12; + if (this.meridiem === 'pm' && this.hours !== this.halfADay) { + this.hours += this.halfADay; } } } diff --git a/libs/entry-components/dialog/dialogs/alert/entry-alert-dialog.component.ts b/libs/entry-components/dialog/dialogs/alert/entry-alert-dialog.component.ts index 65153b54..360edb2e 100644 --- a/libs/entry-components/dialog/dialogs/alert/entry-alert-dialog.component.ts +++ b/libs/entry-components/dialog/dialogs/alert/entry-alert-dialog.component.ts @@ -1,20 +1,17 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { EntryDialogComponent } from '../entry-dialog.component'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { IEntryAlertDialogData } from './entry-alert-dialog-data.interface'; import { ENTRY_DIALOG_CONFIG, EntryDialogConfig } from '../../entry-dialog-config.model'; +import { EntryDialogComponent } from '../entry-dialog.component'; +import { IEntryAlertDialogData } from './entry-alert-dialog-data.interface'; @Component({ - selector: 'entry-alert-dialog', - templateUrl: './entry-alert-dialog.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false + selector: 'entry-alert-dialog', + templateUrl: './entry-alert-dialog.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false }) export class EntryAlertDialogComponent extends EntryDialogComponent { - constructor( - protected readonly mdDialogRef: MatDialogRef, - @Inject(ENTRY_DIALOG_CONFIG) public readonly config: EntryDialogConfig, - @Inject(MAT_DIALOG_DATA) public data: IEntryAlertDialogData) { - super(mdDialogRef, config); - } + protected override readonly mdDialogRef: MatDialogRef = inject(MatDialogRef); + override readonly config: EntryDialogConfig = inject(ENTRY_DIALOG_CONFIG); + readonly data: IEntryAlertDialogData = inject(MAT_DIALOG_DATA); } diff --git a/libs/entry-components/dialog/dialogs/confirm/entry-confirm-dialog.component.ts b/libs/entry-components/dialog/dialogs/confirm/entry-confirm-dialog.component.ts index b19758bb..065c7948 100644 --- a/libs/entry-components/dialog/dialogs/confirm/entry-confirm-dialog.component.ts +++ b/libs/entry-components/dialog/dialogs/confirm/entry-confirm-dialog.component.ts @@ -1,20 +1,17 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { ENTRY_DIALOG_CONFIG, EntryDialogConfig } from '../../entry-dialog-config.model'; import { EntryDialogComponent } from '../entry-dialog.component'; import { IEntryConfirmDialogData } from './entry-confirm-dialog-data.interface'; -import { ENTRY_DIALOG_CONFIG, EntryDialogConfig } from '../../entry-dialog-config.model'; @Component({ - selector: 'entry-confirm-dialog', - templateUrl: './entry-confirm-dialog.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false + selector: 'entry-confirm-dialog', + templateUrl: './entry-confirm-dialog.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false }) export class EntryConfirmDialogComponent extends EntryDialogComponent { - constructor( - protected readonly mdDialogRef: MatDialogRef, - @Inject(ENTRY_DIALOG_CONFIG) public readonly config: EntryDialogConfig, - @Inject(MAT_DIALOG_DATA) readonly data: IEntryConfirmDialogData) { - super(mdDialogRef, config); - } + protected override readonly mdDialogRef: MatDialogRef = inject(MatDialogRef); + override readonly config: EntryDialogConfig = inject(ENTRY_DIALOG_CONFIG); + readonly data: IEntryConfirmDialogData = inject(MAT_DIALOG_DATA); } diff --git a/libs/entry-components/dialog/dialogs/entry-dialog.component.html b/libs/entry-components/dialog/dialogs/entry-dialog.component.html index 6d22229b..ab67ba31 100644 --- a/libs/entry-components/dialog/dialogs/entry-dialog.component.html +++ b/libs/entry-components/dialog/dialogs/entry-dialog.component.html @@ -12,8 +12,13 @@

{{ tit - - + @if(buttonsTemplate) { + + } + + @else { + + } diff --git a/libs/entry-components/dialog/dialogs/entry-dialog.component.ts b/libs/entry-components/dialog/dialogs/entry-dialog.component.ts index 496865c4..b2d9ebf0 100644 --- a/libs/entry-components/dialog/dialogs/entry-dialog.component.ts +++ b/libs/entry-components/dialog/dialogs/entry-dialog.component.ts @@ -1,8 +1,9 @@ -import { Component, Inject, Input, TemplateRef } from '@angular/core'; +/* eslint-disable no-secrets/no-secrets */ +import { Component, inject, Input, TemplateRef } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; import { Observable, of } from 'rxjs'; -import { ENTRY_DIALOG_CONFIG, EntryDialogConfig } from '../entry-dialog-config.model'; import { EntryDialogButtonsAlignment } from '../entry-dialog-buttons-alignment.type'; +import { ENTRY_DIALOG_CONFIG, EntryDialogConfig } from '../entry-dialog-config.model'; /** * Base Entry dialog component. Must be extended when building custom dialogs. @@ -19,6 +20,9 @@ import { EntryDialogButtonsAlignment } from '../entry-dialog-buttons-alignment.t standalone: false }) export class EntryDialogComponent { + protected readonly mdDialogRef: MatDialogRef = inject(MatDialogRef); + protected readonly config: EntryDialogConfig = inject(ENTRY_DIALOG_CONFIG); + /** Dialog header title */ @Input() title: string; /** Dialog buttons horizontal alignment */ @@ -38,10 +42,6 @@ export class EntryDialogComponent { /** Provide custom buttons template */ @Input() buttonsTemplate: TemplateRef | null | undefined; - constructor( - protected readonly mdDialogRef: MatDialogRef, - @Inject(ENTRY_DIALOG_CONFIG) protected readonly config: EntryDialogConfig) { } - @Input() confirm: () => Observable = () => of(true); @Input() cancel = () => this.close(false); diff --git a/libs/entry-components/dialog/dialogs/error/entry-error-dialog.component.ts b/libs/entry-components/dialog/dialogs/error/entry-error-dialog.component.ts index d2eee695..e3667511 100644 --- a/libs/entry-components/dialog/dialogs/error/entry-error-dialog.component.ts +++ b/libs/entry-components/dialog/dialogs/error/entry-error-dialog.component.ts @@ -1,22 +1,23 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { EntryDialogComponent } from '../entry-dialog.component'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { IEntryErrorDialogData } from './entry-error-dialog-data.interface'; import { ENTRY_DIALOG_CONFIG, EntryDialogConfig } from '../../entry-dialog-config.model'; +import { EntryDialogComponent } from '../entry-dialog.component'; +import { IEntryErrorDialogData } from './entry-error-dialog-data.interface'; @Component({ - selector: 'entry-error-dialog', - templateUrl: './entry-error-dialog.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false + selector: 'entry-error-dialog', + templateUrl: './entry-error-dialog.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false }) export class EntryErrorDialogComponent extends EntryDialogComponent { errors: string[] = []; - constructor( - protected readonly mdDialogRef: MatDialogRef, - @Inject(ENTRY_DIALOG_CONFIG) public readonly config: EntryDialogConfig, - @Inject(MAT_DIALOG_DATA) public data: IEntryErrorDialogData) { - super(mdDialogRef, config); + protected override readonly mdDialogRef: MatDialogRef = inject(MatDialogRef); + override readonly config: EntryDialogConfig = inject(ENTRY_DIALOG_CONFIG); + readonly data: IEntryErrorDialogData = inject(MAT_DIALOG_DATA); + + constructor() { + super(); this.extractValidationErrors(); } diff --git a/libs/entry-components/dialog/entry-dialog-config.model.ts b/libs/entry-components/dialog/entry-dialog-config.model.ts index 4775af54..5773945b 100644 --- a/libs/entry-components/dialog/entry-dialog-config.model.ts +++ b/libs/entry-components/dialog/entry-dialog-config.model.ts @@ -1,6 +1,6 @@ +import { Provider } from '@angular/core'; import { createInjectionToken, provideConfig } from '@enigmatry/entry-components/common'; import { EntryDialogButtonsAlignment } from './entry-dialog-buttons-alignment.type'; -import { Provider } from '@angular/core'; /** * Used to provide default configurations on module level. @@ -41,6 +41,5 @@ export const ENTRY_DIALOG_CONFIG = createInjectionToken(new EntryDialogConfig()) /** * Can be used to provide entry dialog configuration. */ -export function provideEntryDialogConfig(config: Partial): Provider { - return provideConfig(ENTRY_DIALOG_CONFIG, () => new EntryDialogConfig(config)); -} +export const provideEntryDialogConfig = (config: Partial): Provider => + provideConfig(ENTRY_DIALOG_CONFIG, () => new EntryDialogConfig(config)); diff --git a/libs/entry-components/dialog/entry-dialog.module.ts b/libs/entry-components/dialog/entry-dialog.module.ts index c100b637..e70bd053 100644 --- a/libs/entry-components/dialog/entry-dialog.module.ts +++ b/libs/entry-components/dialog/entry-dialog.module.ts @@ -1,14 +1,14 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; -import { MatButtonModule } from '@angular/material/button'; +import { EntryButtonModule } from '@enigmatry/entry-components/button'; import { EntryAlertDialogComponent } from './dialogs/alert/entry-alert-dialog.component'; import { EntryConfirmDialogComponent } from './dialogs/confirm/entry-confirm-dialog.component'; import { EntryDialogComponent } from './dialogs/entry-dialog.component'; -import { EntryButtonModule } from '@enigmatry/entry-components/button'; -import { EntryDialogService } from './entry-dialog.service'; import { EntryErrorDialogComponent } from './dialogs/error/entry-error-dialog.component'; +import { EntryDialogService } from './entry-dialog.service'; @NgModule({ declarations: [ diff --git a/libs/entry-components/dialog/entry-dialog.service.ts b/libs/entry-components/dialog/entry-dialog.service.ts index 3a3a4165..6e37fd8a 100644 --- a/libs/entry-components/dialog/entry-dialog.service.ts +++ b/libs/entry-components/dialog/entry-dialog.service.ts @@ -1,24 +1,23 @@ -import { Inject, Injectable, Type } from '@angular/core'; +import { inject, Injectable, Type } from '@angular/core'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; -import { EntryDialogComponent } from './dialogs/entry-dialog.component'; +import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { IEntryAlertDialogData } from './dialogs/alert/entry-alert-dialog-data.interface'; import { EntryAlertDialogComponent } from './dialogs/alert/entry-alert-dialog.component'; -import { Observable } from 'rxjs'; -import { EntryConfirmDialogComponent } from './dialogs/confirm/entry-confirm-dialog.component'; import { IEntryConfirmDialogData } from './dialogs/confirm/entry-confirm-dialog-data.interface'; -import { ENTRY_DIALOG_CONFIG, EntryDialogConfig } from './entry-dialog-config.model'; +import { EntryConfirmDialogComponent } from './dialogs/confirm/entry-confirm-dialog.component'; +import { EntryDialogComponent } from './dialogs/entry-dialog.component'; import { IEntryErrorDialogData } from './dialogs/error/entry-error-dialog-data.interface'; import { EntryErrorDialogComponent } from './dialogs/error/entry-error-dialog.component'; +import { ENTRY_DIALOG_CONFIG, EntryDialogConfig } from './entry-dialog-config.model'; /** * Used to open built-in and custom entry dialogs. */ @Injectable() export class EntryDialogService { - constructor( - @Inject(ENTRY_DIALOG_CONFIG) protected readonly config: EntryDialogConfig, - private readonly matDialog: MatDialog) { } + protected readonly config: EntryDialogConfig = inject(ENTRY_DIALOG_CONFIG); + private readonly matDialog: MatDialog = inject(MatDialog); /** * Opens alert dialog. diff --git a/libs/entry-components/dialog/public-api.ts b/libs/entry-components/dialog/public-api.ts index 5f09bcb1..eef11d12 100644 --- a/libs/entry-components/dialog/public-api.ts +++ b/libs/entry-components/dialog/public-api.ts @@ -1,16 +1,16 @@ export { EntryAlertDialogComponent } from './dialogs/alert/entry-alert-dialog.component'; -export { IEntryAlertDialogData } from './dialogs/alert/entry-alert-dialog-data.interface'; +export type { IEntryAlertDialogData } from './dialogs/alert/entry-alert-dialog-data.interface'; export { EntryConfirmDialogComponent } from './dialogs/confirm/entry-confirm-dialog.component'; -export { IEntryConfirmDialogData } from './dialogs/confirm/entry-confirm-dialog-data.interface'; +export type { IEntryConfirmDialogData } from './dialogs/confirm/entry-confirm-dialog-data.interface'; export { EntryErrorDialogComponent } from './dialogs/error/entry-error-dialog.component'; -export { IEntryErrorDialogData } from './dialogs/error/entry-error-dialog-data.interface'; +export type { IEntryErrorDialogData } from './dialogs/error/entry-error-dialog-data.interface'; export { EntryDialogComponent } from './dialogs/entry-dialog.component'; -export { EntryDialogButtonsAlignment } from './entry-dialog-buttons-alignment.type'; +export type { EntryDialogButtonsAlignment } from './entry-dialog-buttons-alignment.type'; export { EntryDialogService } from './entry-dialog.service'; export { EntryDialogModule } from './entry-dialog.module'; diff --git a/libs/entry-components/file-input/entry-file-input.component.ts b/libs/entry-components/file-input/entry-file-input.component.ts index cd6d5bb9..c40e17f1 100644 --- a/libs/entry-components/file-input/entry-file-input.component.ts +++ b/libs/entry-components/file-input/entry-file-input.component.ts @@ -1,9 +1,11 @@ -/* eslint-disable @typescript-eslint/member-ordering */ +/* eslint-disable max-lines */ + import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, NgZone, - OnDestroy, OnInit, Output, Renderer2, ViewChild, forwardRef + OnDestroy, OnInit, Output, Renderer2, ViewChild, forwardRef, + inject } from '@angular/core'; import { AbstractControl, ControlValueAccessor, NG_VALIDATORS, @@ -15,11 +17,13 @@ import { takeUntil } from 'rxjs/operators'; const providers = [ { provide: NG_VALUE_ACCESSOR, + // eslint-disable-next-line @typescript-eslint/no-use-before-define useExisting: forwardRef(() => EntryFileInputComponent), multi: true }, { provide: NG_VALIDATORS, + // eslint-disable-next-line @typescript-eslint/no-use-before-define useExisting: forwardRef(() => EntryFileInputComponent), multi: true } @@ -34,6 +38,8 @@ const providers = [ providers }) export class EntryFileInputComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator { + private readonly _ngZone: NgZone = inject(NgZone); + private readonly _renderer: Renderer2 = inject(Renderer2); /** * Label for the select file button. Defaults to 'Choose file...' @@ -43,7 +49,7 @@ export class EntryFileInputComponent implements OnInit, OnDestroy, ControlValueA /** * MatIcon for the select file button. Defaults to 'insert_drive_file' (optional) */ - @Input() matIcon?= 'insert_drive_file'; + @Input() matIcon? = 'insert_drive_file'; /** * Same as 'accept' attribute in element. @@ -115,11 +121,6 @@ export class EntryFileInputComponent implements OnInit, OnDestroy, ControlValueA private _destroy$ = new Subject(); - constructor( - private readonly _ngZone: NgZone, - private readonly _renderer: Renderer2) { - } - get fileNames(): string { if (this.value instanceof File) { return this.value.name; @@ -147,10 +148,11 @@ export class EntryFileInputComponent implements OnInit, OnDestroy, ControlValueA onFileSelect(event: Event): void { const fileInputEl = event.target as HTMLInputElement; - const files: FileList = fileInputEl.files; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const files: FileList = fileInputEl.files!; const value = this._multiple - ? files?.length > 1 ? files : files[0] + ? files.length > 1 ? files : files[0] : files[0]; this.value = value; @@ -170,7 +172,7 @@ export class EntryFileInputComponent implements OnInit, OnDestroy, ControlValueA // implements ControlValueAccessor interface - onChange = (_: any) => { + onChange = (_: any) => { // set by registerOnChange }; @@ -196,7 +198,7 @@ export class EntryFileInputComponent implements OnInit, OnDestroy, ControlValueA // implements Validator interface - validate(control: AbstractControl): ValidationErrors { + validate(control: AbstractControl): ValidationErrors | null { const isSizeLimitExceeded = this.isFileSizeLimitExceeded(control.value); const isCountLimitExceeded = this.isFileCountLimitExceeded(control.value); @@ -204,24 +206,25 @@ export class EntryFileInputComponent implements OnInit, OnDestroy, ControlValueA return null; } return { - ...(isSizeLimitExceeded ? { maxFileSize: true } : {}), - ...(isCountLimitExceeded ? { maxFileCount: true } : {}) + ...isSizeLimitExceeded ? { maxFileSize: true } : {}, + ...isCountLimitExceeded ? { maxFileCount: true } : {} }; } - private isFileCountLimitExceeded(files: File | FileList): boolean { + private isFileCountLimitExceeded(files: File | FileList | undefined): boolean { const isMultiple = this.multiple && files instanceof FileList; const maxFileCount = this.maxFileCount; const actualFileCount = (files as FileList)?.length; - return isMultiple && maxFileCount && actualFileCount > maxFileCount; + return isMultiple && !!maxFileCount && actualFileCount > maxFileCount; } - private isFileSizeLimitExceeded(files: File | FileList): boolean { + private isFileSizeLimitExceeded(files: File | FileList | undefined): boolean { if (!this.maxFileSizeInKb) { return false; } - const maxFileSizeInBytes = this.maxFileSizeInKb * 1024; + const kilobyte = 1024; + const maxFileSizeInBytes = this.maxFileSizeInKb * kilobyte; if (files instanceof File) { return files.size > maxFileSizeInBytes; diff --git a/libs/entry-components/file-input/entry-file-input.module.ts b/libs/entry-components/file-input/entry-file-input.module.ts index 3085a358..de3c7f9b 100644 --- a/libs/entry-components/file-input/entry-file-input.module.ts +++ b/libs/entry-components/file-input/entry-file-input.module.ts @@ -1,10 +1,10 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { EntryFileInputComponent } from './entry-file-input.component'; -import { MatIconModule } from '@angular/material/icon'; -import { MatButtonModule } from '@angular/material/button'; +import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; import { EntryButtonModule } from '@enigmatry/entry-components/button'; +import { EntryFileInputComponent } from './entry-file-input.component'; @NgModule({ declarations: [ diff --git a/libs/entry-components/modules/entry-components.module.ts b/libs/entry-components/modules/entry-components.module.ts index 81700204..349acd9f 100644 --- a/libs/entry-components/modules/entry-components.module.ts +++ b/libs/entry-components/modules/entry-components.module.ts @@ -1,13 +1,13 @@ import { ModuleWithProviders, NgModule, Provider, Type } from '@angular/core'; import { EntryButtonModule } from '@enigmatry/entry-components/button'; +import { EntryCommonModule, NG_EVENT_PLUGINS } from '@enigmatry/entry-components/common'; import { EntryDialogModule } from '@enigmatry/entry-components/dialog'; +import { EntryFileInputModule } from '@enigmatry/entry-components/file-input'; import { EntryPermissionModule, EntryPermissionService } from '@enigmatry/entry-components/permissions'; import { EntrySearchFilterModule } from '@enigmatry/entry-components/search-filter'; -import { EntryValidationModule } from '@enigmatry/entry-components/validation'; -import { EntryFileInputModule } from '@enigmatry/entry-components/file-input'; -import { EntryTableModule } from '@enigmatry/entry-components/table'; -import { EntryCommonModule, NG_EVENT_PLUGINS } from '@enigmatry/entry-components/common'; import { EntrySpinnerModule } from '@enigmatry/entry-components/spinner'; +import { EntryTableModule } from '@enigmatry/entry-components/table'; +import { EntryValidationModule } from '@enigmatry/entry-components/validation'; interface EntryComponentsModuleOptions { permissionService?: Type; @@ -35,8 +35,7 @@ interface EntryComponentsModuleOptions { ] }) export class EntryComponentsModule { - static forRoot(options: EntryComponentsModuleOptions = {}): ModuleWithProviders { - + static forRoot = (options: EntryComponentsModuleOptions = {}): ModuleWithProviders => { const permissionServiceProvider: Provider[] = options.permissionService ? [{ provide: EntryPermissionService, useClass: options.permissionService }] : []; @@ -46,5 +45,5 @@ export class EntryComponentsModule { ngModule: EntryComponentsModule, providers }; - } + }; } diff --git a/libs/entry-components/package.json b/libs/entry-components/package.json index 1d0b459a..8745bd15 100644 --- a/libs/entry-components/package.json +++ b/libs/entry-components/package.json @@ -15,13 +15,15 @@ "lint": "stylelint --fix styles/**/*.scss --config ../../.stylelintrc.json" }, "peerDependencies": { - "@angular/animations": "^19.1.3", - "@angular/cdk": "^19.1.1", - "@angular/common": "^19.1.3", - "@angular/core": "^19.1.3", - "@angular/material": "^19.1.1", - "@angular/forms": "^19.1.3", - "@angular/platform-browser": "^19.1.3" + "@angular/animations": "^20.1.0", + "@angular/cdk": "^20.1.0", + "@angular/common": "^20.1.0", + "@angular/core": "^20.1.0", + "@angular/material": "^20.1.0", + "@angular/forms": "^20.1.0", + "@angular/platform-browser": "^20.1.0", + "@angular/router": "^20.1.0", + "rxjs": "7.8.1" }, "dependencies": { "lodash-es": "^4.17.21", diff --git a/libs/entry-components/permissions/permission.directive.ts b/libs/entry-components/permissions/permission.directive.ts index 3c497c29..1540393c 100644 --- a/libs/entry-components/permissions/permission.directive.ts +++ b/libs/entry-components/permissions/permission.directive.ts @@ -1,24 +1,29 @@ -import { NgIf } from '@angular/common'; -import { Directive, Input, inject } from '@angular/core'; +import { Directive, Input, inject, ElementRef, Renderer2 } from '@angular/core'; import { PermissionType } from './permission-type'; import { EntryPermissionService } from './permission.service'; @Directive({ selector: '[entryPermissionsOnly],[entryPermissionsExcept]', - hostDirectives: [{ - directive: NgIf - }], standalone: false }) export class EntryPermissionDirective { - private ngIfDirective = inject(NgIf); + private elementRef = inject(ElementRef); + private renderer = inject(Renderer2); private permissionService = inject(EntryPermissionService); @Input('entryPermissionsOnly') set only(permissions: T[]) { - this.ngIfDirective.ngIf = this.permissionService.hasPermissions(permissions); + this.toggleVisibility(this.permissionService.hasPermissions(permissions)); } @Input('entryPermissionsExcept') set except(permissions: T[]) { - this.ngIfDirective.ngIf = !this.permissionService.hasPermissions(permissions); + this.toggleVisibility(!this.permissionService.hasPermissions(permissions)); + } + + private toggleVisibility(show: boolean): void { + if (show) { + this.renderer.removeStyle(this.elementRef.nativeElement, 'display'); + } else { + this.renderer.setStyle(this.elementRef.nativeElement, 'display', 'none'); + } } } diff --git a/libs/entry-components/permissions/permission.guard.ts b/libs/entry-components/permissions/permission.guard.ts index cf1dcf26..1ecd5da8 100644 --- a/libs/entry-components/permissions/permission.guard.ts +++ b/libs/entry-components/permissions/permission.guard.ts @@ -11,7 +11,7 @@ interface RoutePermissionConfig { export const entryPermissionGuard: CanActivateFn = (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => { const permissionService = inject(EntryPermissionService); - const routePermissions = route.data.permissions as RoutePermissionConfig ?? {}; + const routePermissions = route.data['permissions'] as RoutePermissionConfig ?? {}; if (routePermissions.only) { return permissionService.hasPermissions(routePermissions.only); diff --git a/libs/entry-components/permissions/permission.module.ts b/libs/entry-components/permissions/permission.module.ts index 95c6b050..9abb86ad 100644 --- a/libs/entry-components/permissions/permission.module.ts +++ b/libs/entry-components/permissions/permission.module.ts @@ -1,5 +1,5 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { EntryPermissionDirective } from './permission.directive'; import { EntryPermissionPipe } from './permission.pipe'; diff --git a/libs/entry-components/permissions/permission.pipe.ts b/libs/entry-components/permissions/permission.pipe.ts index 23bd0cf9..0d5336c4 100644 --- a/libs/entry-components/permissions/permission.pipe.ts +++ b/libs/entry-components/permissions/permission.pipe.ts @@ -1,4 +1,4 @@ -import { Pipe, PipeTransform } from '@angular/core'; +import { inject, Pipe, PipeTransform } from '@angular/core'; import { PermissionType } from './permission-type'; import { EntryPermissionService } from './permission.service'; @@ -7,7 +7,7 @@ import { EntryPermissionService } from './permission.service'; standalone: false }) export class EntryPermissionPipe implements PipeTransform { - constructor(private permissionsService: EntryPermissionService) { } + private readonly permissionsService = inject(EntryPermissionService); transform(permissions: T[]): boolean { return this.permissionsService.hasPermissions(permissions); diff --git a/libs/entry-components/search-filter/autocomplete/autocomplete-search-filter.component.ts b/libs/entry-components/search-filter/autocomplete/autocomplete-search-filter.component.ts index 2c51f5a4..c8b3aaf8 100644 --- a/libs/entry-components/search-filter/autocomplete/autocomplete-search-filter.component.ts +++ b/libs/entry-components/search-filter/autocomplete/autocomplete-search-filter.component.ts @@ -1,16 +1,16 @@ -import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input, OnDestroy } from '@angular/core'; import { FormControl } from '@angular/forms'; +import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { Observable, Subject, of } from 'rxjs'; import { filter, tap, takeUntil, debounceTime } from 'rxjs/operators'; import { SelectOption } from '../select-option.model'; import { AutocompleteSearchFilter } from './autocomplete-search-filter.model'; -import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; @Component({ - selector: 'entry-autocomplete-search-filter', - templateUrl: './autocomplete-search-filter.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false + selector: 'entry-autocomplete-search-filter', + templateUrl: './autocomplete-search-filter.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false }) export class AutocompleteSearchFilterComponent implements AfterViewInit, OnDestroy { @Input() searchFilter: AutocompleteSearchFilter; @@ -20,8 +20,7 @@ export class AutocompleteSearchFilterComponent implements AfterViewInit, OnDe options$: Observable[]> = of([]); destroy$ = new Subject(); - - constructor(private cdr: ChangeDetectorRef) { } + private readonly cdr: ChangeDetectorRef = inject(ChangeDetectorRef); ngAfterViewInit(): void { this.searchField @@ -29,7 +28,7 @@ export class AutocompleteSearchFilterComponent implements AfterViewInit, OnDe .pipe( takeUntil(this.destroy$), tap(value => this.clearFilterIfLabelMismatch(value)), - filter(value => value?.length >= this.searchFilter.minimumCharacters), + filter(value => !!value && value.length >= this.searchFilter.minimumCharacters), debounceTime(this.searchFilter.debounceTime) ) .subscribe(searchValue => { @@ -45,14 +44,14 @@ export class AutocompleteSearchFilterComponent implements AfterViewInit, OnDe this.destroy$.complete(); } - displayFn = (_selectedValue: SelectOption): string => this.searchFilter.formControl.value?.label; + displayFn = (_selectedValue: SelectOption): string => this.searchFilter.formControl.value?.label ?? ''; onSelected = (event: MatAutocompleteSelectedEvent) => { this.searchFilter.formControl.patchValue(event.option.value); this.searchField.patchValue(event.option.value.label, { emitEvent: false }); }; - private clearFilterIfLabelMismatch(value: string) { + private clearFilterIfLabelMismatch(value: string | null) { const label = this.searchFilter.formControl.value?.label; if (label && label !== value) { this.searchFilter.formControl.patchValue(undefined); diff --git a/libs/entry-components/search-filter/autocomplete/autocomplete-search-filter.model.ts b/libs/entry-components/search-filter/autocomplete/autocomplete-search-filter.model.ts index 238da6c2..27ddf3be 100644 --- a/libs/entry-components/search-filter/autocomplete/autocomplete-search-filter.model.ts +++ b/libs/entry-components/search-filter/autocomplete/autocomplete-search-filter.model.ts @@ -1,28 +1,32 @@ +import { Observable } from 'rxjs'; import { ControlType } from '../control-type'; import { SearchFilterBase } from '../search-filter-base.model'; import { SelectOption } from '../select-option.model'; -import { Observable } from 'rxjs'; /** * Search filter autocomplete field configuration. Options for the autocomplete are provided * indirectly via the search function that takes a string and returns an observable array of * SelectOption */ -export class AutocompleteSearchFilter extends SearchFilterBase>{ - controlType = ControlType.autocomplete; +export class AutocompleteSearchFilter extends SearchFilterBase> { + override controlType = ControlType.autocomplete; /** Callback function for autocomplete options */ - search: (input: string) => Observable[]>; + search: (input: string | null) => Observable[]>; /** Minimum number of characters that must enter to trigger the search function(default is 3) */ minimumCharacters: number; /** Delay in typing before triggering the search function in milliseconds(default is 300) */ debounceTime: number; + private readonly defaultMinimumCharacters = 3; + private readonly defaultDebounceTime = 300; + constructor(options: Partial> = {}) { super(options); + if (!options.search) { + throw new Error('Search function must be provided for AutocompleteSearchFilter'); + } this.search = options.search; - this.placeholder = options.placeholder; - this.label = options.label; - this.debounceTime = options.debounceTime ?? 300; - this.minimumCharacters = options.minimumCharacters ?? 3; + this.debounceTime = options.debounceTime ?? this.defaultDebounceTime; + this.minimumCharacters = options.minimumCharacters ?? this.defaultMinimumCharacters; } } diff --git a/libs/entry-components/search-filter/date-time/date-time-search-filter.component.html b/libs/entry-components/search-filter/date-time/date-time-search-filter.component.html index b82b448b..a272e2ca 100644 --- a/libs/entry-components/search-filter/date-time/date-time-search-filter.component.html +++ b/libs/entry-components/search-filter/date-time/date-time-search-filter.component.html @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/libs/entry-components/search-filter/date-time/date-time-search-filter.component.ts b/libs/entry-components/search-filter/date-time/date-time-search-filter.component.ts index 5797c4eb..367d14f5 100644 --- a/libs/entry-components/search-filter/date-time/date-time-search-filter.component.ts +++ b/libs/entry-components/search-filter/date-time/date-time-search-filter.component.ts @@ -1,8 +1,8 @@ import { Component, Input, inject } from '@angular/core'; -import { DateTimeSearchFilter } from './date-time-search-filter.model'; import { UntypedFormGroup } from '@angular/forms'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; import { ENTRY_MAT_DATE_TIME_FORMATS, EntryDateTimeAdapter } from '@enigmatry/entry-components/common'; +import { DateTimeSearchFilter } from './date-time-search-filter.model'; @Component({ selector: 'entry-date-time-search-filter', diff --git a/libs/entry-components/search-filter/date-time/date-time-search-filter.model.ts b/libs/entry-components/search-filter/date-time/date-time-search-filter.model.ts index 1f37d50b..a69ff9b2 100644 --- a/libs/entry-components/search-filter/date-time/date-time-search-filter.model.ts +++ b/libs/entry-components/search-filter/date-time/date-time-search-filter.model.ts @@ -4,6 +4,6 @@ import { SearchFilterBase } from '../search-filter-base.model'; /** * Search filter date time input filed configuration. */ -export class DateTimeSearchFilter extends SearchFilterBase{ +export class DateTimeSearchFilter extends SearchFilterBase { override controlType = ControlType.dateTime; } diff --git a/libs/entry-components/search-filter/date/date-search-filter.component.html b/libs/entry-components/search-filter/date/date-search-filter.component.html index 19df5fe8..d894af1a 100644 --- a/libs/entry-components/search-filter/date/date-search-filter.component.html +++ b/libs/entry-components/search-filter/date/date-search-filter.component.html @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/libs/entry-components/search-filter/date/date-search-filter.model.ts b/libs/entry-components/search-filter/date/date-search-filter.model.ts index 50c012cb..bf4f7e2b 100644 --- a/libs/entry-components/search-filter/date/date-search-filter.model.ts +++ b/libs/entry-components/search-filter/date/date-search-filter.model.ts @@ -4,6 +4,6 @@ import { SearchFilterBase } from '../search-filter-base.model'; /** * Search filter date input filed configuration. */ -export class DateSearchFilter extends SearchFilterBase{ +export class DateSearchFilter extends SearchFilterBase { override controlType = ControlType.date; } diff --git a/libs/entry-components/search-filter/entry-search-filter.component.ts b/libs/entry-components/search-filter/entry-search-filter.component.ts index a067089f..1146d9b1 100644 --- a/libs/entry-components/search-filter/entry-search-filter.component.ts +++ b/libs/entry-components/search-filter/entry-search-filter.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectionStrategy, Component, DestroyRef, EventEmitter, inject, Input, OnInit, Output } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { UntypedFormGroup } from '@angular/forms'; import { AutocompleteSearchFilter } from './autocomplete/autocomplete-search-filter.model'; import { ControlType } from './control-type'; @@ -10,7 +11,6 @@ import { SearchFilterParams } from './search-filter-params.type'; import { SelectSearchFilter } from './select/select-search-filter.model'; import { SelectOption } from './select-option.model'; import { TextSearchFilter } from './text/text-search-filter.model'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; /** * Entry SearchFilter component. diff --git a/libs/entry-components/search-filter/entry-search-filter.module.ts b/libs/entry-components/search-filter/entry-search-filter.module.ts index 9fef5605..734e475d 100644 --- a/libs/entry-components/search-filter/entry-search-filter.module.ts +++ b/libs/entry-components/search-filter/entry-search-filter.module.ts @@ -1,20 +1,20 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatInputModule } from '@angular/material/input'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { EntryButtonModule } from '@enigmatry/entry-components/button'; -import { EntrySearchFilterComponent } from './entry-search-filter.component'; -import { TextSearchFilterComponent } from './text/text-search-filter.component'; -import { SelectSearchFilterComponent } from './select/select-search-filter.component'; +import { EntryValidationModule } from '@enigmatry/entry-components/validation'; import { AutocompleteSearchFilterComponent } from './autocomplete/autocomplete-search-filter.component'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import { DateTimeSearchFilterComponent } from './date-time/date-time-search-filter.component'; -import { MatDatepickerModule } from '@angular/material/datepicker'; -import { MatFormFieldModule } from '@angular/material/form-field'; import { DateSearchFilterComponent } from './date/date-search-filter.component'; -import { EntryValidationModule } from '@enigmatry/entry-components/validation'; +import { DateTimeSearchFilterComponent } from './date-time/date-time-search-filter.component'; +import { EntrySearchFilterComponent } from './entry-search-filter.component'; +import { SelectSearchFilterComponent } from './select/select-search-filter.component'; +import { TextSearchFilterComponent } from './text/text-search-filter.component'; @NgModule({ declarations: [ diff --git a/libs/entry-components/search-filter/public-api.ts b/libs/entry-components/search-filter/public-api.ts index c059690e..5fc15983 100644 --- a/libs/entry-components/search-filter/public-api.ts +++ b/libs/entry-components/search-filter/public-api.ts @@ -1,6 +1,6 @@ export { EntrySearchFilterComponent } from './entry-search-filter.component'; -export { SearchFilterParams } from './search-filter-params.type'; +export type { SearchFilterParams } from './search-filter-params.type'; export { SearchFilterBase } from './search-filter-base.model'; export { TextSearchFilter } from './text/text-search-filter.model'; export { SelectSearchFilter } from './select/select-search-filter.model'; diff --git a/libs/entry-components/search-filter/search-filter-base.model.ts b/libs/entry-components/search-filter/search-filter-base.model.ts index 2cc1f2c1..fa45c1a0 100644 --- a/libs/entry-components/search-filter/search-filter-base.model.ts +++ b/libs/entry-components/search-filter/search-filter-base.model.ts @@ -44,7 +44,7 @@ export class SearchFilterBase { } } - toFormControl(): FormControl { - return new FormControl(this.value); + toFormControl(): FormControl { + return new FormControl(this.value); } } diff --git a/libs/entry-components/search-filter/search-filter-config.model.ts b/libs/entry-components/search-filter/search-filter-config.model.ts index 4371bef3..48160d12 100644 --- a/libs/entry-components/search-filter/search-filter-config.model.ts +++ b/libs/entry-components/search-filter/search-filter-config.model.ts @@ -20,6 +20,5 @@ export const ENTRY_SEARCH_FILTER_CONFIG = createInjectionToken(new EntrySearchFi /** * Can be used to provide entry search filter configuration. */ -export function provideEntrySearchFilterConfig(config: Partial): Provider { - return provideConfig(ENTRY_SEARCH_FILTER_CONFIG, () => new EntrySearchFilterConfig(config)); -} +export const provideEntrySearchFilterConfig = (config: Partial): Provider => + provideConfig(ENTRY_SEARCH_FILTER_CONFIG, () => new EntrySearchFilterConfig(config)); diff --git a/libs/entry-components/search-filter/search-filter-params.type.ts b/libs/entry-components/search-filter/search-filter-params.type.ts index b8a48bb1..15a2cb25 100644 --- a/libs/entry-components/search-filter/search-filter-params.type.ts +++ b/libs/entry-components/search-filter/search-filter-params.type.ts @@ -2,6 +2,6 @@ * SearchFilterParams are the same type as @angular/router type Params, * containing a collection of query URL parameters for easy integration. */ -export type SearchFilterParams = { +export interface SearchFilterParams { [key: string]: any; -}; +} diff --git a/libs/entry-components/search-filter/select/select-search-filter.component.html b/libs/entry-components/search-filter/select/select-search-filter.component.html index 8dd125cc..bde6e614 100644 --- a/libs/entry-components/search-filter/select/select-search-filter.component.html +++ b/libs/entry-components/search-filter/select/select-search-filter.component.html @@ -12,5 +12,5 @@ {{option.label}} - + \ No newline at end of file diff --git a/libs/entry-components/search-filter/select/select-search-filter.component.ts b/libs/entry-components/search-filter/select/select-search-filter.component.ts index e461ff76..5f881b82 100644 --- a/libs/entry-components/search-filter/select/select-search-filter.component.ts +++ b/libs/entry-components/search-filter/select/select-search-filter.component.ts @@ -1,7 +1,7 @@ -import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core'; -import { SelectSearchFilter } from './select-search-filter.model'; +import { ChangeDetectionStrategy, Component, inject, Input } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; import { ENTRY_SEARCH_FILTER_CONFIG, EntrySearchFilterConfig } from '../search-filter-config.model'; +import { SelectSearchFilter } from './select-search-filter.model'; @Component({ selector: 'entry-select-search-filter', @@ -15,5 +15,5 @@ export class SelectSearchFilterComponent { /** Form group to which the search-filter input component will be added. */ @Input() form: UntypedFormGroup; - constructor(@Inject(ENTRY_SEARCH_FILTER_CONFIG) public config: EntrySearchFilterConfig) { } + public readonly config: EntrySearchFilterConfig = inject(ENTRY_SEARCH_FILTER_CONFIG); } diff --git a/libs/entry-components/search-filter/select/select-search-filter.model.ts b/libs/entry-components/search-filter/select/select-search-filter.model.ts index 20241a85..ee2cc4f3 100644 --- a/libs/entry-components/search-filter/select/select-search-filter.model.ts +++ b/libs/entry-components/search-filter/select/select-search-filter.model.ts @@ -1,6 +1,6 @@ import { Observable } from 'rxjs'; -import { SearchFilterBase } from '../search-filter-base.model'; import { ControlType } from '../control-type'; +import { SearchFilterBase } from '../search-filter-base.model'; import { SelectOption } from '../select-option.model'; /** @@ -22,9 +22,9 @@ export class SelectSearchFilter extends SearchFilterBase { constructor(options: Partial> = {}) { super(options); - this.options = options.options; + this.options = options.options ?? []; this.options$ = options.options$; - this.multiSelect = options.multiSelect; - this.showNoneOption = options.showNoneOption; + this.multiSelect = !!options.multiSelect; + this.showNoneOption = !!options.showNoneOption; } } diff --git a/libs/entry-components/search-filter/text/text-search-filter.component.html b/libs/entry-components/search-filter/text/text-search-filter.component.html index f0f22e90..9f56659d 100644 --- a/libs/entry-components/search-filter/text/text-search-filter.component.html +++ b/libs/entry-components/search-filter/text/text-search-filter.component.html @@ -2,5 +2,5 @@ {{searchFilter.label}} - + \ No newline at end of file diff --git a/libs/entry-components/search-filter/text/text-search-filter.model.ts b/libs/entry-components/search-filter/text/text-search-filter.model.ts index e76d04f0..34607c8c 100644 --- a/libs/entry-components/search-filter/text/text-search-filter.model.ts +++ b/libs/entry-components/search-filter/text/text-search-filter.model.ts @@ -1,5 +1,5 @@ -import { SearchFilterBase } from '../search-filter-base.model'; import { ControlType } from '../control-type'; +import { SearchFilterBase } from '../search-filter-base.model'; /** * Search filter text input filed configuration. diff --git a/libs/entry-components/spinner/entry-spinner/spinner.component.ts b/libs/entry-components/spinner/entry-spinner/spinner.component.ts index f43c1a52..10fcfe11 100644 --- a/libs/entry-components/spinner/entry-spinner/spinner.component.ts +++ b/libs/entry-components/spinner/entry-spinner/spinner.component.ts @@ -2,27 +2,26 @@ import { Overlay, OverlayConfig, OverlayContainer, OverlayRef } from '@angular/c import { TemplatePortal } from '@angular/cdk/portal'; import { ChangeDetectionStrategy, Component, - ElementRef, Input, OnDestroy, + ElementRef, inject, Input, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { ThemePalette } from '@angular/material/core'; import { SpinnerOverlayContainer } from '../spinner-overlay-container'; @Component({ - selector: 'entry-spinner', - templateUrl: './spinner.component.html', - providers: [ - Overlay, - { - provide: OverlayContainer, - useClass: SpinnerOverlayContainer - } - ], - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false + selector: 'entry-spinner', + templateUrl: './spinner.component.html', + providers: [ + Overlay, + { + provide: OverlayContainer, + useClass: SpinnerOverlayContainer + } + ], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false }) export class EntrySpinnerComponent implements OnInit, OnDestroy { - @Input() color: ThemePalette = 'primary'; @Input() diameter = 30; @Input() fullscreen = false; @@ -32,12 +31,10 @@ export class EntrySpinnerComponent implements OnInit, OnDestroy { private templateRef: TemplateRef; private overlayRef: OverlayRef; - constructor( - private overlay: Overlay, - private viewContainerRef: ViewContainerRef, - private overlayContainer: OverlayContainer, - private elementRef: ElementRef) { - } + private readonly overlay = inject(Overlay); + private readonly viewContainerRef = inject(ViewContainerRef); + private readonly overlayContainer = inject(OverlayContainer); + private readonly elementRef = inject(ElementRef); ngOnInit(): void { this.createOverlay(); diff --git a/libs/entry-components/spinner/spinner-overlay-container.ts b/libs/entry-components/spinner/spinner-overlay-container.ts index 7cef0926..70a452e7 100644 --- a/libs/entry-components/spinner/spinner-overlay-container.ts +++ b/libs/entry-components/spinner/spinner-overlay-container.ts @@ -5,10 +5,10 @@ import { Inject, Injectable, OnDestroy } from '@angular/core'; @Injectable() export class SpinnerOverlayContainer extends OverlayContainer implements OnDestroy { - private _appendTo: HTMLElement = this._document.body; private _options: { fullscreen: boolean }; + // eslint-disable-next-line @angular-eslint/prefer-inject constructor(@Inject(DOCUMENT) document: Document, platform: Platform) { super(document, platform); } @@ -18,14 +18,14 @@ export class SpinnerOverlayContainer extends OverlayContainer implements OnDestr this._options = options; } - getContainerElement(): HTMLElement { + override getContainerElement(): HTMLElement { if (!this._containerElement) { this.createContainer(); } return this._containerElement; } - ngOnDestroy() { + override ngOnDestroy() { this._containerElement?.remove(); } diff --git a/libs/entry-components/spinner/spinner.module.ts b/libs/entry-components/spinner/spinner.module.ts index 5bf184a4..0419980f 100644 --- a/libs/entry-components/spinner/spinner.module.ts +++ b/libs/entry-components/spinner/spinner.module.ts @@ -1,6 +1,6 @@ +import { OverlayModule } from '@angular/cdk/overlay'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { OverlayModule } from '@angular/cdk/overlay'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { EntrySpinnerComponent } from './entry-spinner/spinner.component'; diff --git a/libs/entry-components/styles/modules/_default-theme.scss b/libs/entry-components/styles/modules/_default-theme.scss index 7785312f..f6a12361 100644 --- a/libs/entry-components/styles/modules/_default-theme.scss +++ b/libs/entry-components/styles/modules/_default-theme.scss @@ -8,10 +8,6 @@ $theme: ( accent: #EA518D, warn: #BA1A1A, font: #323232, - disabled: ( - foreground: rgb(0 0 0 / .38), - background: rgb(0 0 0 / .12) - ) ), typography: null, fonts: ( diff --git a/libs/entry-components/styles/modules/vendors/angular-material/_generator.scss b/libs/entry-components/styles/modules/vendors/angular-material/_generator.scss index 875bc83c..2faaa634 100644 --- a/libs/entry-components/styles/modules/vendors/angular-material/_generator.scss +++ b/libs/entry-components/styles/modules/vendors/angular-material/_generator.scss @@ -44,4 +44,4 @@ $color: map.get($theme, 'general', 'colors', $key); @return palette-generator.generate-from($color); -} +} \ No newline at end of file diff --git a/libs/entry-components/styles/partials/generator-test.scss b/libs/entry-components/styles/partials/generator-test.scss index ab022b39..1440eacb 100644 --- a/libs/entry-components/styles/partials/generator-test.scss +++ b/libs/entry-components/styles/partials/generator-test.scss @@ -18,6 +18,16 @@ $custom-theme: ( background: yellow ) ), + buttons: ( + disabled: ( + foreground: cyan, + background: magenta + ), + checkboxes: ( + background: red, + border-hover-color: green + ) + ), buttons: ( disabled: ( foreground: cyan, diff --git a/libs/entry-components/table/README.md b/libs/entry-components/table/README.md index 694d60d5..9053e492 100644 --- a/libs/entry-components/table/README.md +++ b/libs/entry-components/table/README.md @@ -102,9 +102,12 @@ export class EntryComponentsModule { } | @enigmatry/entry-components | Angular version | | --------------------------- | --------------- | | 1.14.x | = 14 | -| 15.x | = 15 | -| 16.x | = 16 | -| 17.x | = 17 | +| 15.x | = 15 | +| 16.x | = 16 | +| 17.x | = 17 | +| 18.x | = 18 | +| 19.x | = 19 | +| 20.x | = 20 | ## License diff --git a/libs/entry-components/table/components/entry-cell-context-menu/entry-cell-context-menu.component.html b/libs/entry-components/table/components/entry-cell-context-menu/entry-cell-context-menu.component.html index 9cc3fafb..0aab4f56 100644 --- a/libs/entry-components/table/components/entry-cell-context-menu/entry-cell-context-menu.component.html +++ b/libs/entry-components/table/components/entry-cell-context-menu/entry-cell-context-menu.component.html @@ -3,7 +3,7 @@ - @@ -12,7 +12,7 @@ class="context-menu-item"> {{item.icon}} {{item.name}} - diff --git a/libs/entry-components/table/components/entry-cell-context-menu/entry-cell-context-menu.component.ts b/libs/entry-components/table/components/entry-cell-context-menu/entry-cell-context-menu.component.ts index caf5ceb7..a23f0945 100644 --- a/libs/entry-components/table/components/entry-cell-context-menu/entry-cell-context-menu.component.ts +++ b/libs/entry-components/table/components/entry-cell-context-menu/entry-cell-context-menu.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { MatMenuPanel } from '@angular/material/menu'; -import { RowContextMenuFormatter } from '../../interfaces/row-context-menu-formatter'; import { ContextMenuItem } from '../../interfaces/context-menu-item'; +import { RowContextMenuFormatter } from '../../interfaces/row-context-menu-formatter'; @Component({ selector: 'entry-cell-context-menu', @@ -10,7 +10,6 @@ import { ContextMenuItem } from '../../interfaces/context-menu-item'; standalone: false }) export class EntryCellContextMenuComponent implements OnInit { - @Input() items: ContextMenuItem[] = []; @Input() rowMenuFormatter: RowContextMenuFormatter; @Input() rowData: any; diff --git a/libs/entry-components/table/components/entry-cell-formatted-value/entry-cell-formatted-value.component.html b/libs/entry-components/table/components/entry-cell-formatted-value/entry-cell-formatted-value.component.html index 03c6257e..eb6baa33 100644 --- a/libs/entry-components/table/components/entry-cell-formatted-value/entry-cell-formatted-value.component.html +++ b/libs/entry-components/table/components/entry-cell-formatted-value/entry-cell-formatted-value.component.html @@ -13,7 +13,7 @@ - {{+value * (typeParameter?.multiplier ?? defaultPercentageMultiplier) | percent: typeParameter?.digitsInfo : typeParameter?.locale}} + {{+(value ?? 0) * (typeParameter?.multiplier ?? defaultPercentageMultiplier) | percent: typeParameter?.digitsInfo : typeParameter?.locale}} diff --git a/libs/entry-components/table/components/entry-cell-formatted-value/entry-cell-formatted-value.component.ts b/libs/entry-components/table/components/entry-cell-formatted-value/entry-cell-formatted-value.component.ts index 080b9197..4fe1dcd4 100644 --- a/libs/entry-components/table/components/entry-cell-formatted-value/entry-cell-formatted-value.component.ts +++ b/libs/entry-components/table/components/entry-cell-formatted-value/entry-cell-formatted-value.component.ts @@ -1,4 +1,5 @@ -import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, Input } from '@angular/core'; +import { ColumnTypeParameter } from '../../interfaces'; import { DEFAULT_PERCENTAGE_MULTIPLIER } from '../../interfaces/entry-table-config'; @Component({ @@ -8,12 +9,9 @@ import { DEFAULT_PERCENTAGE_MULTIPLIER } from '../../interfaces/entry-table-conf standalone: false }) export class EntryCellFormattedValueComponent { - @Input() value: string | undefined; @Input() type: string; - @Input() typeParameter: any | undefined; + @Input() typeParameter: ColumnTypeParameter & { multiplier?: number } | undefined; - constructor( - @Inject(DEFAULT_PERCENTAGE_MULTIPLIER) public defaultPercentageMultiplier: number) { - } + public readonly defaultPercentageMultiplier: number = inject(DEFAULT_PERCENTAGE_MULTIPLIER); } diff --git a/libs/entry-components/table/components/entry-cell/entry-cell.component.html b/libs/entry-components/table/components/entry-cell/entry-cell.component.html index 53464535..2716ad17 100644 --- a/libs/entry-components/table/components/entry-cell/entry-cell.component.html +++ b/libs/entry-components/table/components/entry-cell/entry-cell.component.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/libs/entry-components/table/components/entry-cell/entry-cell.component.ts b/libs/entry-components/table/components/entry-cell/entry-cell.component.ts index 73aab74e..9766864e 100644 --- a/libs/entry-components/table/components/entry-cell/entry-cell.component.ts +++ b/libs/entry-components/table/components/entry-cell/entry-cell.component.ts @@ -8,7 +8,6 @@ import { ColumnDef } from '../../interfaces'; standalone: false }) export class EntryCellComponent { - @Input() rowData: T; @Input() colDef: ColumnDef; @@ -16,9 +15,8 @@ export class EntryCellComponent { return this.getCellValue(this.rowData, this.colDef); } - private getCellValue(rowData: T, colDef: ColumnDef) { + getCellValue = (rowData: T, colDef: ColumnDef) => { const keys = colDef.field ? colDef.field.split('.') : []; return keys.reduce((data, key) => data && (data as any)[key], rowData); - } - + }; } diff --git a/libs/entry-components/table/components/entry-table/entry-table.component.html b/libs/entry-components/table/components/entry-table/entry-table.component.html index 4b1e92bd..99cd5627 100644 --- a/libs/entry-components/table/components/entry-table/entry-table.component.html +++ b/libs/entry-components/table/components/entry-table/entry-table.component.html @@ -72,9 +72,9 @@ - - @@ -98,9 +98,9 @@ - - diff --git a/libs/entry-components/table/components/entry-table/entry-table.component.ts b/libs/entry-components/table/components/entry-table/entry-table.component.ts index 9a702ced..5023f215 100644 --- a/libs/entry-components/table/components/entry-table/entry-table.component.ts +++ b/libs/entry-components/table/components/entry-table/entry-table.component.ts @@ -1,5 +1,5 @@ -/* eslint-disable @typescript-eslint/member-ordering */ - +/* eslint-disable max-lines */ +import { SelectionModel } from '@angular/cdk/collections'; import { Component, Input, @@ -12,12 +12,11 @@ import { ElementRef, SimpleChanges, HostBinding, - Inject, + inject } from '@angular/core'; -import { SelectionModel } from '@angular/cdk/collections'; -import { MatTableDataSource } from '@angular/material/table'; import { PageEvent } from '@angular/material/paginator'; import { Sort, SortDirection } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; import { ColumnDef, PagedData, RowSelectionFormatter, RowClassFormatter, @@ -25,21 +24,23 @@ import { } from '../../interfaces'; @Component({ - selector: 'entry-table', - templateUrl: './entry-table.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false + selector: 'entry-table', + templateUrl: './entry-table.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false }) export class EntryTableComponent implements OnChanges { @HostBinding('class') className = 'entry-table'; + private readonly _config: EntryTableConfig = inject(ENTRY_TABLE_CONFIG); + private readonly _elementRef = inject(ElementRef); + private readonly _changeDetectorRef = inject(ChangeDetectorRef); dataSource = new MatTableDataSource([]); @Input() displayedColumns: string[]; @Input() columns: ColumnDef[] = []; // Data - private _data: T[] = []; private _page: PagedData; @Input() data: T[] | PagedData | null | undefined = []; @@ -47,7 +48,6 @@ export class EntryTableComponent implements OnChanges { @Input() loading = false; // Pagination - @Input() showPaginator: boolean; @Input() pageDisabled = false; @Input() showFirstLastButtons: boolean; @@ -55,12 +55,10 @@ export class EntryTableComponent implements OnChanges { @Input() pageSize: number; @Input() pageSizeOptions: number[]; @Input() hidePageSize: boolean; - @Output() pageChange = new EventEmitter(); - @Input() paginationTemplate: TemplateRef; + @Output() pageChange = new EventEmitter(); // Sort - @Input() sortActive: string; @Input() sortDirection: SortDirection; @Input() sortDisableClear = false; @@ -69,14 +67,12 @@ export class EntryTableComponent implements OnChanges { @Output() sortChange = new EventEmitter(); // Row - @Input() rowHover = false; @Input() rowStriped = false; @Input() rowFocusVisible: boolean; @Output() rowClick = new EventEmitter(); // Row selection - @Input() multiSelectable = true; rowSelection: SelectionModel = new SelectionModel(true, []); @@ -88,7 +84,6 @@ export class EntryTableComponent implements OnChanges { @Output() rowSelectionChange = new EventEmitter(); // Context menu - @Input() showContextMenu = false; @Input() contextMenuItems: ContextMenuItem[] = []; @Input() contextMenuTemplate: TemplateRef | null; @@ -96,7 +91,6 @@ export class EntryTableComponent implements OnChanges { @Output() contextMenuItemSelected = new EventEmitter<{ itemId: string; rowData: T }>(); // No Result - @Input() noResultText: string; @Input() noResultTemplate: TemplateRef | null; @@ -107,29 +101,25 @@ export class EntryTableComponent implements OnChanges { return (!this.data || this._data.length === 0) && !this.loading; } - @Input() headerTemplate: TemplateRef | CellTemplate | any; - - @Input() cellTemplate: TemplateRef | CellTemplate | any; + @Input() headerTemplate: TemplateRef | CellTemplate | Record>; + @Input() cellTemplate: TemplateRef | CellTemplate | Record>; - constructor( - @Inject(ENTRY_TABLE_CONFIG) private _config: EntryTableConfig, - private _elementRef: ElementRef, - private _changeDetectorRef: ChangeDetectorRef) { } + readonly toTemplateIndex = (template: TemplateRef | CellTemplate | Record>, key: string) => + (template as unknown as Record>)[key]; detectChanges() { this._changeDetectorRef.detectChanges(); } - isTemplateRef(obj: any) { - return obj instanceof TemplateRef; - } + isTemplateRef = (obj: any) => obj instanceof TemplateRef; getRowClassList(rowData: T, index: number) { const classList = { selected: this.rowSelection.isSelected(rowData), - // eslint-disable-next-line @typescript-eslint/naming-convention - 'mat-row-odd': index % 2, - }; + + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + 'mat-row-odd': index % 2 + } as Record; if (this.rowClassFormatter) { for (const key of Object.keys(this.rowClassFormatter)) { classList[key] = this.rowClassFormatter[key](rowData); @@ -147,6 +137,32 @@ export class EntryTableComponent implements OnChanges { } ngOnChanges(changes: SimpleChanges) { + this.initializeVariables(); + + if (Array.isArray(this.data)) { + this._data = this.data as T[]; + } else { + this._page = this.data as PagedData; + this._data = this._page.items ?? []; + this.total = this._page.totalCount ?? 0; + this.pageSize = this._page.pageSize ?? this.pageSize ?? this._config.pageSize; + this.pageIndex = this._page.pageNumber ? this._page.pageNumber - 1 : this.pageIndex; + } + + if (this.dataSource) { + this.dataSource.disconnect(); + } + + this.dataSource = new MatTableDataSource(this._data); + + if (changes['data']) { + this.scrollToTop(); + } + } + + getIndex = (index: number, dataIndex: number) => typeof index === 'undefined' ? dataIndex : index; + + private readonly initializeVariables = () => { this.showPaginator = this.showPaginator ?? this._config.showPaginator; this.showFirstLastButtons = this.showFirstLastButtons ?? this._config.showFirstLastButtons; this.pageSizeOptions = this.pageSizeOptions ?? this._config.pageSizeOptions; @@ -171,31 +187,7 @@ export class EntryTableComponent implements OnChanges { if (!this.data) { this.data = []; } - - if (Array.isArray(this.data)) { - this._data = this.data as T[]; - } else { - this._page = this.data as PagedData; - this._data = this._page.items ?? []; - this.total = this._page.totalCount ?? 0; - this.pageSize = this._page.pageSize ?? this.pageSize ?? this._config.pageSize; - this.pageIndex = this._page.pageNumber ? this._page.pageNumber - 1 : this.pageIndex; - } - - if (this.dataSource) { - this.dataSource.disconnect(); - } - - this.dataSource = new MatTableDataSource(this._data); - - if (changes.data) { - this.scrollToTop(); - } - } - - getIndex(index: number, dataIndex: number) { - return typeof index === 'undefined' ? dataIndex : index; - } + }; isAllSelected() { const numSelected = this.rowSelection.selected.length; @@ -230,11 +222,9 @@ export class EntryTableComponent implements OnChanges { this._elementRef.nativeElement.scrollTop = 0; } - get shouldShowPaginator(){ + get shouldShowPaginator() { return this.showPaginator && this._data.length > 0; } - private convertToKebabCase(value: string): string { - return value?.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); - } + convertToKebabCase = (value: string): string => value?.replace(/([a-z0-9])([A-Z])/gu, '$1-$2').toLowerCase(); } diff --git a/libs/entry-components/table/entry-table.module.ts b/libs/entry-components/table/entry-table.module.ts index 2e0927ca..932975e5 100644 --- a/libs/entry-components/table/entry-table.module.ts +++ b/libs/entry-components/table/entry-table.module.ts @@ -1,19 +1,19 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { MatTableModule } from '@angular/material/table'; -import { MatSortModule } from '@angular/material/sort'; -import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginatorModule } from '@angular/material/paginator'; import { MatRadioModule } from '@angular/material/radio'; -import {MatButtonModule} from '@angular/material/button'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; -import { EntryTableComponent } from './components/entry-table/entry-table.component'; import { EntryCellComponent } from './components/entry-cell/entry-cell.component'; import { EntryCellContextMenuComponent } from './components/entry-cell-context-menu/entry-cell-context-menu.component'; import { EntryCellFormattedValueComponent } from './components/entry-cell-formatted-value/entry-cell-formatted-value.component'; +import { EntryTableComponent } from './components/entry-table/entry-table.component'; import { DEFAULT_PERCENTAGE_MULTIPLIER } from './interfaces'; @NgModule({ diff --git a/libs/entry-components/table/interfaces/column-def.ts b/libs/entry-components/table/interfaces/column-def.ts index e9909b41..d10df4d4 100644 --- a/libs/entry-components/table/interfaces/column-def.ts +++ b/libs/entry-components/table/interfaces/column-def.ts @@ -1,7 +1,7 @@ import { TemplateRef } from '@angular/core'; import { ColumnSortProp } from './column-sort-prop'; -import { ColumnTypeParameter } from './column-type-parameter'; import { ColumnType } from './column-type'; +import { ColumnTypeParameter } from './column-type-parameter'; export interface ColumnDef { diff --git a/libs/entry-components/table/interfaces/entry-table-config.ts b/libs/entry-components/table/interfaces/entry-table-config.ts index 3776eb7b..d674c8cb 100644 --- a/libs/entry-components/table/interfaces/entry-table-config.ts +++ b/libs/entry-components/table/interfaces/entry-table-config.ts @@ -9,6 +9,7 @@ export class EntryTableConfig { /** Page size, default 20 */ pageSize = 20; /** Page size options, default [20, 50, 100] */ + // eslint-disable-next-line @typescript-eslint/no-magic-numbers pageSizeOptions = [20, 50, 100]; /** Hide page size options, default is false */ hidePageSize = false; @@ -41,9 +42,8 @@ export class EntryTableConfig { export const ENTRY_TABLE_CONFIG = createInjectionToken(new EntryTableConfig()); /** Provide entry table config */ -export function provideEntryTableConfig(config: Partial): Provider { - return provideConfig(ENTRY_TABLE_CONFIG, () => new EntryTableConfig(config)); -} +export const provideEntryTableConfig = (config: Partial): Provider => + provideConfig(ENTRY_TABLE_CONFIG, () => new EntryTableConfig(config)); /** Default percentage multiplier injection token */ export const DEFAULT_PERCENTAGE_MULTIPLIER: InjectionToken = new InjectionToken(''); diff --git a/libs/entry-components/table/interfaces/index.ts b/libs/entry-components/table/interfaces/index.ts index 96bbafe5..8960abcb 100644 --- a/libs/entry-components/table/interfaces/index.ts +++ b/libs/entry-components/table/interfaces/index.ts @@ -3,12 +3,12 @@ export * from './pagination'; export * from './entry-table-config'; export * from './paged-query'; -export * from './cell-template'; -export * from './column-def'; -export * from './column-sort-prop'; -export * from './column-type-parameter'; -export * from './column-type'; -export * from './context-menu-item'; -export * from './row-class-formatter'; -export * from './row-context-menu-formatter'; -export * from './row-selection-formatter'; +export type * from './cell-template'; +export type * from './column-def'; +export type * from './column-sort-prop'; +export type * from './column-type-parameter'; +export type * from './column-type'; +export type * from './context-menu-item'; +export type * from './row-class-formatter'; +export type * from './row-context-menu-formatter'; +export type * from './row-selection-formatter'; diff --git a/libs/entry-components/table/interfaces/paged-query.ts b/libs/entry-components/table/interfaces/paged-query.ts index 7278919c..ef4a9b0c 100644 --- a/libs/entry-components/table/interfaces/paged-query.ts +++ b/libs/entry-components/table/interfaces/paged-query.ts @@ -24,10 +24,10 @@ export class PagedQuery implements OnPage, OnSort { } applyRouteChanges(queryParams: Params): void { - this.pageNumber = queryParams.pageNumber ? Number(queryParams.pageNumber) : defaultPageNumber; - this.pageSize = queryParams.pageSize ? Number(queryParams.pageSize) : this.pageSize; - this.sortBy = this.getValueIfNotEmpty(queryParams.sortBy ?? this.sortBy); - this.sortDirection = this.getValueIfNotEmpty(queryParams.sortDirection ?? this.sortDirection); + this.pageNumber = queryParams['pageNumber'] ? Number(queryParams['pageNumber']) : defaultPageNumber; + this.pageSize = queryParams['pageSize'] ? Number(queryParams['pageSize']) : this.pageSize; + this.sortBy = this.getValueIfNotEmpty(queryParams['sortBy'] ?? this.sortBy); + this.sortDirection = this.getValueIfNotEmpty(queryParams['sortDirection'] ?? this.sortDirection); } getRouteQueryParams(): Params { @@ -39,7 +39,5 @@ export class PagedQuery implements OnPage, OnSort { }; } - private getValueIfNotEmpty(value: T): T | undefined { - return value ? value : undefined; - } + getValueIfNotEmpty = (value: T): T | undefined => value ? value : undefined; } diff --git a/libs/entry-components/table/interfaces/pagination.ts b/libs/entry-components/table/interfaces/pagination.ts index cfa07b86..1079c8a2 100644 --- a/libs/entry-components/table/interfaces/pagination.ts +++ b/libs/entry-components/table/interfaces/pagination.ts @@ -1,7 +1,7 @@ -import { Sort as SortEvent } from '@angular/material/sort'; import { PageEvent } from '@angular/material/paginator'; +import { Sort as SortEvent } from '@angular/material/sort'; -export { Sort as SortEvent, SortDirection } from '@angular/material/sort'; +export type { Sort as SortEvent, SortDirection } from '@angular/material/sort'; export { PageEvent } from '@angular/material/paginator'; export interface PagedData { diff --git a/libs/entry-components/validation/entry-display-control-validation.directive.ts b/libs/entry-components/validation/entry-display-control-validation.directive.ts index d7e63698..1dc819ff 100644 --- a/libs/entry-components/validation/entry-display-control-validation.directive.ts +++ b/libs/entry-components/validation/entry-display-control-validation.directive.ts @@ -1,8 +1,8 @@ -import { Directive, ElementRef, Inject, Input, OnDestroy, OnInit } from '@angular/core'; -import { ENTRY_VALIDATION_CONFIG, EntryValidationConfig } from './entry-validation-config.model'; +import { Directive, ElementRef, inject, Input, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormControlStatus } from '@angular/forms'; import { Subscription } from 'rxjs'; import { FORM_FIELD_ERROR_KEY } from './entry-validation'; +import { ENTRY_VALIDATION_CONFIG } from './entry-validation-config.model'; /** * A directive that displays configured validation messages or server side validations for given form control. @@ -24,9 +24,8 @@ export class EntryDisplayControlValidationDirective implements OnInit, OnDestroy private _controlSubscription: Subscription | undefined; - constructor( - @Inject(ENTRY_VALIDATION_CONFIG) private readonly _config: EntryValidationConfig, - private readonly _element: ElementRef) {} + private readonly _config = inject(ENTRY_VALIDATION_CONFIG); + private readonly _element = inject(ElementRef); ngOnInit(): void { this._controlSubscription = this.control.statusChanges @@ -48,8 +47,9 @@ export class EntryDisplayControlValidationDirective implements OnInit, OnDestroy return ''; } const errorsString = this._config.validationMessages - .map(validationMessage => this.control.errors[validationMessage.name] - ? typeof(validationMessage.message) === 'string' + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .map(validationMessage => this.control.errors![validationMessage.name] + ? typeof validationMessage.message === 'string' ? validationMessage.message : validationMessage.message(this.control) : '' ) diff --git a/libs/entry-components/validation/entry-form-errors.component.ts b/libs/entry-components/validation/entry-form-errors.component.ts index ad14648a..9285b547 100644 --- a/libs/entry-components/validation/entry-form-errors.component.ts +++ b/libs/entry-components/validation/entry-form-errors.component.ts @@ -15,7 +15,7 @@ import { UntypedFormGroup } from '@angular/forms'; selector: 'entry-form-errors', template: `
- + {{error}}
diff --git a/libs/entry-components/validation/entry-validation-config.model.ts b/libs/entry-components/validation/entry-validation-config.model.ts index 15c54a16..ceff5307 100644 --- a/libs/entry-components/validation/entry-validation-config.model.ts +++ b/libs/entry-components/validation/entry-validation-config.model.ts @@ -53,6 +53,5 @@ export const ENTRY_VALIDATION_CONFIG = createInjectionToken(new EntryValidationC /** * Can be used to provide entry validation configuration. */ -export function provideEntryValidationConfig(config: Partial): Provider { - return provideConfig(ENTRY_VALIDATION_CONFIG, () => new EntryValidationConfig(config)); -} +export const provideEntryValidationConfig = (config: Partial): Provider => + provideConfig(ENTRY_VALIDATION_CONFIG, () => new EntryValidationConfig(config)); diff --git a/libs/entry-components/validation/entry-validation.module.ts b/libs/entry-components/validation/entry-validation.module.ts index 521623de..e3f82103 100644 --- a/libs/entry-components/validation/entry-validation.module.ts +++ b/libs/entry-components/validation/entry-validation.module.ts @@ -1,9 +1,9 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { EntryFormErrorsComponent } from './entry-form-errors.component'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { EntryDisplayControlValidationDirective } from './entry-display-control-validation.directive'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { EntryFormErrorsComponent } from './entry-form-errors.component'; @NgModule({ declarations: [ diff --git a/libs/entry-components/validation/public-api.ts b/libs/entry-components/validation/public-api.ts index 29b8547d..7a869150 100644 --- a/libs/entry-components/validation/public-api.ts +++ b/libs/entry-components/validation/public-api.ts @@ -3,7 +3,7 @@ export { EntryDisplayControlValidationDirective } from './entry-display-control- export { EntryValidationModule } from './entry-validation.module'; -export { IValidationProblemDetails } from './validation-problem-details.interface'; +export type { IValidationProblemDetails } from './validation-problem-details.interface'; export * from './entry-validation'; export * from './entry-validation-config.model'; diff --git a/libs/entry-form/.eslintrc.json b/libs/entry-form/.eslintrc.json deleted file mode 100644 index 7ed70de2..00000000 --- a/libs/entry-form/.eslintrc.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "extends": "../../.eslintrc.json", - "ignorePatterns": [ - "!**/*" - ], - "overrides": [ - { - "files": [ - "*.ts" - ], - "parserOptions": { - "project": [ - "libs/entry-form/tsconfig.lib.json", - "libs/entry-form/tsconfig.spec.json" - ], - "createDefaultProgram": true - }, - "rules": { - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": "entry", - "style": "camelCase" - } - ], - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": "entry", - "style": "kebab-case" - } - ], - "@typescript-eslint/no-explicit-any": "off" - } - }, - { - "files": [ - "*.html" - ], - "rules": {} - } - ] -} diff --git a/libs/entry-form/README.md b/libs/entry-form/README.md index 9bd10fc0..2407cb45 100644 --- a/libs/entry-form/README.md +++ b/libs/entry-form/README.md @@ -93,6 +93,7 @@ entry-codegen --source-assembly ../MyProject.CodeGeneration.Setup/bin/Debug/net7 |17.x| = 17 |18.x| = 18 |19.x| = 19 +|20.x| = 20 ## License diff --git a/libs/entry-form/autocomplete/check-autocomplete-input.directive.ts b/libs/entry-form/autocomplete/check-autocomplete-input.directive.ts index de03af19..06fa0d1c 100644 --- a/libs/entry-form/autocomplete/check-autocomplete-input.directive.ts +++ b/libs/entry-form/autocomplete/check-autocomplete-input.directive.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Directive, ElementRef, Host, Input, OnChanges, OnDestroy, Self, SimpleChanges } from '@angular/core'; +import { AfterViewInit, Directive, ElementRef, inject, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core'; import { AbstractControl, NgControl } from '@angular/forms'; import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; import { Subject } from 'rxjs'; @@ -10,15 +10,12 @@ import { SelectOption } from './select-configuration.interface'; standalone: false }) export class CheckAutocompleteInputDirective implements OnChanges, AfterViewInit, OnDestroy { - @Input() options: SelectOption[] = []; private destroy$ = new Subject(); - constructor( - @Host() @Self() private matAutocomplete: MatAutocompleteTrigger, - private ngControl: NgControl, - private elemRef: ElementRef) { - } + private readonly matAutocomplete = inject(MatAutocompleteTrigger, { host: true, self: true }); + private readonly ngControl = inject(NgControl); + private readonly elemRef = inject(ElementRef); get control(): AbstractControl | null { return this.ngControl.control; @@ -26,15 +23,15 @@ export class CheckAutocompleteInputDirective implements OnChanges, AfterViewInit ngOnChanges(_changes: SimpleChanges): void { if (this.options?.length) { - this.applySelectedValue(this.control.value); + this.applySelectedValue(this.control?.value); } } ngAfterViewInit(): void { this.matAutocomplete.panelClosingActions .pipe(takeUntil(this.destroy$)) - .subscribe((event) => { - if (!event || !event.source) { + .subscribe(event => { + if (!event?.source) { this.checkControlValue(); } }); diff --git a/libs/entry-form/autocomplete/display-with-autocomplete.pipe.ts b/libs/entry-form/autocomplete/display-with-autocomplete.pipe.ts index 71418c8c..113a74bf 100644 --- a/libs/entry-form/autocomplete/display-with-autocomplete.pipe.ts +++ b/libs/entry-form/autocomplete/display-with-autocomplete.pipe.ts @@ -6,8 +6,5 @@ import { SelectOption } from './select-configuration.interface'; standalone: false }) export class DisplayWithAutocompletePipe implements PipeTransform { - - transform(options: SelectOption[]): (value: any) => string { - return (value: any) => options.find(o => o.value === value)?.label ?? ''; - } + transform = (options: SelectOption[]): (value: any) => string => (value: any) => options.find(o => o.value === value)?.label ?? ''; } diff --git a/libs/entry-form/autocomplete/filter-with-autocomplete.pipe.ts b/libs/entry-form/autocomplete/filter-with-autocomplete.pipe.ts index a40b79c5..845b68cc 100644 --- a/libs/entry-form/autocomplete/filter-with-autocomplete.pipe.ts +++ b/libs/entry-form/autocomplete/filter-with-autocomplete.pipe.ts @@ -6,12 +6,11 @@ import { SelectOption } from './select-configuration.interface'; standalone: false }) export class FilterWithAutocompletePipe implements PipeTransform { - - transform(options: SelectOption[], filterWith: string | undefined): SelectOption[] { + transform = (options: SelectOption[], filterWith: string | undefined): SelectOption[] => { if (!filterWith) { return options; } const labelStartsWith = filterWith.toLowerCase(); return options.filter(option => option.label.toLowerCase().includes(labelStartsWith)); - } + }; } diff --git a/libs/entry-form/autocomplete/formly-autocomplete.module.ts b/libs/entry-form/autocomplete/formly-autocomplete.module.ts index aafcfa05..490d6dcd 100644 --- a/libs/entry-form/autocomplete/formly-autocomplete.module.ts +++ b/libs/entry-form/autocomplete/formly-autocomplete.module.ts @@ -1,9 +1,9 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { FormlyModule } from '@ngx-formly/core'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatInputModule } from '@angular/material/input'; +import { FormlyModule } from '@ngx-formly/core'; import { FormlySelectModule } from '@ngx-formly/core/select'; import { CheckAutocompleteInputDirective } from './check-autocomplete-input.directive'; import { DisplayWithAutocompletePipe } from './display-with-autocomplete.pipe'; diff --git a/libs/entry-form/autocomplete/select-configuration.interface.ts b/libs/entry-form/autocomplete/select-configuration.interface.ts index 320fa75c..df5ed052 100644 --- a/libs/entry-form/autocomplete/select-configuration.interface.ts +++ b/libs/entry-form/autocomplete/select-configuration.interface.ts @@ -12,17 +12,17 @@ export interface SelectConfiguration { disable?: (option: SelectOption) => boolean; } +const sortOptionsBy = (options: SelectOption[] | any[], sortProperty?: string, locale?: string): SelectOption[] | any[] => + sortProperty ? + options.sort((a, b) => + (a[sortProperty]?.toString() ?? '').localeCompare(b[sortProperty]?.toString() ?? '', locale || 'en-US', { sensitivity: 'base' })) : + options; + export const sortOptions = (options: SelectOption[] | any[], valueProperty?: string, sortProperty?: string, locale?: string): SelectOption[] | any[] => { const optionsCopy = [...options]; - return !valueProperty ? - sortOptionsBy(optionsCopy, sortProperty, locale) : + return valueProperty ? sortOptionsBy(optionsCopy.filter(opt => opt[valueProperty] === null), sortProperty, locale) - .concat(sortOptionsBy(optionsCopy.filter(opt => opt[valueProperty] !== null), sortProperty, locale)); + .concat(sortOptionsBy(optionsCopy.filter(opt => opt[valueProperty] !== null), sortProperty, locale)) : + sortOptionsBy(optionsCopy, sortProperty, locale); }; - -const sortOptionsBy = (options: SelectOption[] | any[], sortProperty?: string, locale?: string): SelectOption[] | any[] => - !sortProperty ? - options : - options.sort((a, b) => - (a[sortProperty]?.toString() ?? '').localeCompare(b[sortProperty]?.toString() ?? '', locale || 'en-US', { sensitivity: 'base' })); diff --git a/libs/entry-form/ckeditor/formly-ckeditor.component.ts b/libs/entry-form/ckeditor/formly-ckeditor.component.ts index de82d9f2..796da5fa 100644 --- a/libs/entry-form/ckeditor/formly-ckeditor.component.ts +++ b/libs/entry-form/ckeditor/formly-ckeditor.component.ts @@ -1,9 +1,9 @@ -import { Component, Inject } from '@angular/core'; -import { FormlyFieldConfig } from '@ngx-formly/core'; -import { CKEditor5 } from '@ckeditor/ckeditor5-angular'; -import { ENTRY_CKEDITOR_OPTIONS, EntryCkeditorOptions } from './ckeditor-options'; +import { Component, inject } from '@angular/core'; import { UntypedFormControl } from '@angular/forms'; +import { CKEditor5 } from '@ckeditor/ckeditor5-angular'; +import { FormlyFieldConfig } from '@ngx-formly/core'; import { FieldType } from '@ngx-formly/material'; +import { ENTRY_CKEDITOR_OPTIONS, EntryCkeditorOptions } from './ckeditor-options'; @Component({ selector: 'entry-formly-ckeditor', @@ -11,14 +11,15 @@ import { FieldType } from '@ngx-formly/material'; standalone: false }) export class FormlyCkeditorComponent extends FieldType { - editorBuild: CKEditor5.EditorConstructor; editorConfig: CKEditor5.Config = {}; + readonly ckeditorOptions: EntryCkeditorOptions; - constructor(@Inject(ENTRY_CKEDITOR_OPTIONS) options: EntryCkeditorOptions) { + constructor() { super(); - this.editorBuild = options.build; - this.editorConfig = {...this.editorConfig, ...options.config }; + this.ckeditorOptions = inject(ENTRY_CKEDITOR_OPTIONS); + this.editorBuild = this.ckeditorOptions.build; + this.editorConfig = { ...this.editorConfig, ...this.ckeditorOptions.config }; } get control(): UntypedFormControl { @@ -26,7 +27,6 @@ export class FormlyCkeditorComponent extends FieldType { } public onReady(editor: any) { - // https://ckeditor.com/docs/ckeditor5/latest/features/read-only.html#hiding-toolbar-in-read-only-mode const toolbarElement = editor.ui.view.toolbar.element; diff --git a/libs/entry-form/ckeditor/formly-ckeditor.module.ts b/libs/entry-form/ckeditor/formly-ckeditor.module.ts index a5451ad2..759c4cfc 100644 --- a/libs/entry-form/ckeditor/formly-ckeditor.module.ts +++ b/libs/entry-form/ckeditor/formly-ckeditor.module.ts @@ -1,9 +1,9 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { CKEditorModule } from '@ckeditor/ckeditor5-angular'; import { FormlyModule } from '@ngx-formly/core'; import { FormlyCkeditorComponent } from './formly-ckeditor.component'; -import { CKEditorModule } from '@ckeditor/ckeditor5-angular'; @NgModule({ declarations: [ diff --git a/libs/entry-form/date-time-picker/formly-date-time-picker.component.html b/libs/entry-form/date-time-picker/formly-date-time-picker.component.html index bb30d77e..badccef6 100644 --- a/libs/entry-form/date-time-picker/formly-date-time-picker.component.html +++ b/libs/entry-form/date-time-picker/formly-date-time-picker.component.html @@ -1,7 +1,7 @@