@ferhaps/easy-ui-lib 0.0.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +41 -22
  2. package/ng-package.json +7 -0
  3. package/package.json +26 -44
  4. package/src/lib/components/default-dialog/default-dialog.component.html +19 -0
  5. package/src/lib/components/default-dialog/default-dialog.component.scss +41 -0
  6. package/src/lib/components/default-dialog/default-dialog.component.ts +23 -0
  7. package/src/lib/components/error-dispaly.component.ts +48 -0
  8. package/src/lib/components/error-handler/error-handler.component.html +0 -0
  9. package/src/lib/components/error-handler/error-handler.component.scss +0 -0
  10. package/src/lib/components/error-handler/error-handler.component.ts +44 -0
  11. package/src/lib/components/error-handler/error-popup/error-popup.component.html +13 -0
  12. package/src/lib/components/error-handler/error-popup/error-popup.component.scss +19 -0
  13. package/src/lib/components/error-handler/error-popup/error-popup.component.ts +21 -0
  14. package/src/lib/components/global-loader/global-loader.component.html +5 -0
  15. package/src/lib/components/global-loader/global-loader.component.scss +12 -0
  16. package/src/lib/components/global-loader/global-loader.component.ts +17 -0
  17. package/src/lib/components/search-bar.component.ts +70 -0
  18. package/src/lib/components/table/table.component.html +121 -0
  19. package/src/lib/components/table/table.component.scss +116 -0
  20. package/src/lib/components/table/table.component.ts +105 -0
  21. package/src/lib/components/table-sort-header/table-sort-header.component.html +7 -0
  22. package/src/lib/components/table-sort-header/table-sort-header.component.scss +17 -0
  23. package/src/lib/components/table-sort-header/table-sort-header.component.ts +31 -0
  24. package/src/lib/directives/fields-match-validator.directive.ts +35 -0
  25. package/src/lib/directives/password-validator.directive.ts +26 -0
  26. package/src/lib/directives/phone-validation.directive.ts +24 -0
  27. package/src/lib/pipes/blank-filler.pipe.ts +13 -0
  28. package/src/lib/pipes/snake-case-parser.pipe.ts +17 -0
  29. package/src/lib/services/error.service.ts +15 -0
  30. package/src/lib/services/loader.service.ts +14 -0
  31. package/src/lib/utils/animations.ts +29 -0
  32. package/{lib/utils/types.d.ts → src/lib/utils/types.ts} +3 -2
  33. package/src/lib/utils/utils.ts +65 -0
  34. package/{public-api.d.ts → src/public-api.ts} +20 -17
  35. package/tsconfig.lib.json +14 -0
  36. package/tsconfig.lib.prod.json +10 -0
  37. package/tsconfig.spec.json +14 -0
  38. package/fesm2022/ferhaps-easy-ui-lib.mjs +0 -862
  39. package/fesm2022/ferhaps-easy-ui-lib.mjs.map +0 -1
  40. package/index.d.ts +0 -5
  41. package/lib/components/chip/chip.component.d.ts +0 -16
  42. package/lib/components/default-dialog/default-dialog.component.d.ts +0 -19
  43. package/lib/components/error-dispaly.component.d.ts +0 -17
  44. package/lib/components/error-handler/error-handler.component.d.ts +0 -18
  45. package/lib/components/error-handler/error-popup/error-popup.component.d.ts +0 -17
  46. package/lib/components/global-loader/global-loader.component.d.ts +0 -14
  47. package/lib/components/search-bar.component.d.ts +0 -18
  48. package/lib/components/table/table.component.d.ts +0 -58
  49. package/lib/components/table-sort-header/table-sort-header.component.d.ts +0 -17
  50. package/lib/directives/fields-match-validator.directive.d.ts +0 -16
  51. package/lib/directives/password-validator.directive.d.ts +0 -13
  52. package/lib/directives/phone-validation.directive.d.ts +0 -13
  53. package/lib/pipes/blank-filler.pipe.d.ts +0 -13
  54. package/lib/pipes/snake-case-parser.pipe.d.ts +0 -13
  55. package/lib/services/error.service.d.ts +0 -9
  56. package/lib/services/loader.service.d.ts +0 -8
  57. package/lib/utils/animations.d.ts +0 -3
  58. package/lib/utils/utils.d.ts +0 -1
package/README.md CHANGED
@@ -17,17 +17,6 @@ This library requires:
17
17
 
18
18
  ## Components
19
19
 
20
- ### ChipComponent
21
- A selectable chip component for displaying tags or filters.
22
- ```typescript
23
- <lib-chip
24
- [value]="value"
25
- [isSelected]="isSelected"
26
- [text]="displayText"
27
- (selected)="onChipSelected($event)">
28
- </lib-chip>
29
- ```
30
-
31
20
  ### TableComponent
32
21
  A feature-rich table component supporting:
33
22
  - Sorting
@@ -51,6 +40,7 @@ A feature-rich table component supporting:
51
40
  draggable: true
52
41
  }"
53
42
  (action)="handleTableAction($event)">
43
+ <div class="upper-part">Optional content to be displyed at the top of the table</div>
54
44
  </lib-table>
55
45
  ```
56
46
 
@@ -80,13 +70,26 @@ A customizable dialog component with optional back button.
80
70
  ### GlobalLoaderComponent
81
71
  A centered spinner overlay for loading states.
82
72
  ```typescript
83
- <lib-global-loader></lib-global-loader>
73
+ <lib-global-loader />
74
+
75
+ private loaderService = inject(LoaderService);
76
+ this.loaderService.setLoading(true);
77
+ // do stuff
78
+ this.loaderService.setLoading(false);
84
79
  ```
85
80
 
86
81
  ### ErrorHandlerComponent
87
82
  Displays error messages in a dialog format.
88
83
  ```typescript
89
- <lib-error-handler></lib-error-handler>
84
+ <lib-error-handler />
85
+
86
+ private errorService = inject(ErrorService);
87
+ try {
88
+ this.apiCall();
89
+ }
90
+ catch (e: HttpErrorResponse) {
91
+ this.errorService.sendError(e);
92
+ }
90
93
  ```
91
94
 
92
95
  ## Directives
@@ -99,13 +102,26 @@ Validates if two form fields match (useful for password confirmation).
99
102
  ```
100
103
 
101
104
  ### PasswordValidatorDirective
102
- Ensures password meets complexity requirements.
105
+ Ensures password meets the following requirments:
106
+ * At least one uppercase letter
107
+ * At least one lowercase letter
108
+ * At least one special character from the specified set
109
+ * At least one number
110
+ * Minimum length of 8 characters
111
+
103
112
  ```typescript
104
113
  <input type="password" libPasswordValidator />
105
114
  ```
106
115
 
107
116
  ### PhoneValidationDirective
108
- Formats and validates phone number input.
117
+ Formats and validates phone number input as follows:
118
+ * Ensures the input always starts with a '+' symbol
119
+ If missing, automatically prepends it to the value
120
+
121
+ * Allows only numbers and the plus sign
122
+
123
+ * Prevents removing the initial '+' symbol:
124
+
109
125
  ```typescript
110
126
  <input type="tel" libPhoneValidation />
111
127
  ```
@@ -129,18 +145,21 @@ Converts snake_case to Title Case text.
129
145
  ### LoaderService
130
146
  Manages global loading state.
131
147
  ```typescript
132
- constructor(private loaderService: LoaderService) {
133
- loaderService.setLoading(true);
134
- // ... async operation
135
- loaderService.setLoading(false);
136
- }
148
+ private loaderService = inject(LoaderService);
149
+ this.loaderService.setLoading(true);
150
+ // do stuff
151
+ this.loaderService.setLoading(false);
137
152
  ```
138
153
 
139
154
  ### ErrorService
140
155
  Handles global error display.
141
156
  ```typescript
142
- constructor(private errorService: ErrorService) {
143
- errorService.sendError(error);
157
+ private errorService = inject(ErrorService);
158
+ try {
159
+ this.apiCall();
160
+ }
161
+ catch (e: HttpErrorResponse) {
162
+ this.errorService.sendError(e);
144
163
  }
145
164
  ```
146
165
 
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/ui-lib",
4
+ "lib": {
5
+ "entryFile": "src/public-api.ts"
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,44 +1,26 @@
1
- {
2
- "name": "@ferhaps/easy-ui-lib",
3
- "version": "0.0.7",
4
- "description": "Angular UI components, directives and pipes library with Angular Material",
5
- "keywords": [
6
- "angular",
7
- "ui",
8
- "components",
9
- "pipes",
10
- "directives",
11
- "library"
12
- ],
13
- "author": {
14
- "name": "Ferhan Cherkez",
15
- "email": "fericherkez14052002@gmail.com",
16
- "url": "https://www.linkedin.com/in/ferhan-cherkez-6931a1190/"
17
- },
18
- "license": "MIT",
19
- "repository": {
20
- "type": "git",
21
- "url": "https://github.com/Ferhaps/angular-ui-library/tree/modules"
22
- },
23
- "peerDependencies": {
24
- "@angular/common": "^19.0.6",
25
- "@angular/core": "^19.0.6",
26
- "@angular/material": "^19.0.5",
27
- "@angular/cdk": "^19.0.5"
28
- },
29
- "dependencies": {
30
- "tslib": "^2.3.0"
31
- },
32
- "sideEffects": false,
33
- "module": "fesm2022/ferhaps-easy-ui-lib.mjs",
34
- "typings": "index.d.ts",
35
- "exports": {
36
- "./package.json": {
37
- "default": "./package.json"
38
- },
39
- ".": {
40
- "types": "./index.d.ts",
41
- "default": "./fesm2022/ferhaps-easy-ui-lib.mjs"
42
- }
43
- }
44
- }
1
+ {
2
+ "name": "@ferhaps/easy-ui-lib",
3
+ "version": "1.0.0",
4
+ "description": "Angular UI components, directives and pipes library with Angular Material",
5
+ "keywords": ["angular", "ui", "components", "pipes", "directives", "library"],
6
+ "author": {
7
+ "name": "Ferhan Cherkez",
8
+ "email": "fericherkez14052002@gmail.com",
9
+ "url": "https://www.linkedin.com/in/ferhan-cherkez-6931a1190/"
10
+ },
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/Ferhaps/angular-ui-library"
15
+ },
16
+ "peerDependencies": {
17
+ "@angular/common": "^19.0.6",
18
+ "@angular/core": "^19.0.6",
19
+ "@angular/material": "^19.0.5",
20
+ "@angular/cdk": "^19.0.5"
21
+ },
22
+ "dependencies": {
23
+ "tslib": "^2.3.0"
24
+ },
25
+ "sideEffects": false
26
+ }
@@ -0,0 +1,19 @@
1
+ <div class="modal" [style]="{'height': height()}">
2
+ <div class="dialog-title">
3
+ @if (withBack()) {
4
+ <div class="back-arrow" (click)="back.emit()">
5
+ <mat-icon>keyboard_arrow_left</mat-icon>
6
+ </div>
7
+ }
8
+ <h4 class="title">{{ dialogTitle() }}</h4>
9
+ <div class="closer" mat-dialog-close>
10
+ <mat-icon>close</mat-icon>
11
+ </div>
12
+ </div>
13
+
14
+ <div class="dialog-content">
15
+ <ng-content select=".dialog-content"></ng-content>
16
+ <ng-template #tempBody></ng-template>
17
+ <ng-container *ngTemplateOutlet="temRef ? temRef: tempBody" />
18
+ </div>
19
+ </div>
@@ -0,0 +1,41 @@
1
+ .modal {
2
+ display: flex;
3
+ flex-direction: column;
4
+ }
5
+
6
+ .dialog-title {
7
+ padding-inline: 2rem;
8
+ padding-top: 1rem;
9
+ margin-bottom: 1rem;
10
+ position: relative;
11
+ display: flex;
12
+ font-size: 20px;
13
+ justify-content: center;
14
+
15
+ .back-arrow {
16
+ cursor: pointer;
17
+ margin-right: 0.5rem;
18
+ }
19
+
20
+ .title {
21
+ font-weight: 700;
22
+ font-size: 24px;
23
+ margin: 0 !important;
24
+ }
25
+
26
+ .closer {
27
+ right: 3%;
28
+ position: absolute;
29
+ cursor: pointer;
30
+ }
31
+ }
32
+
33
+ .dialog-content {
34
+ flex: 1;
35
+ min-height: 0;
36
+ overflow: hidden;
37
+ padding-bottom: 1rem;
38
+ display: flex;
39
+ flex-direction: column;
40
+ align-items: center;
41
+ }
@@ -0,0 +1,23 @@
1
+ import { Component, Input, input, output, TemplateRef } from '@angular/core';
2
+ import { MatIconModule } from '@angular/material/icon';
3
+ import { MatDialogModule } from '@angular/material/dialog';
4
+ import { CommonModule } from '@angular/common';
5
+
6
+ @Component({
7
+ selector: 'lib-default-dialog',
8
+ imports: [
9
+ CommonModule,
10
+ MatIconModule,
11
+ MatDialogModule
12
+ ],
13
+ templateUrl: './default-dialog.component.html',
14
+ styleUrls: ['./default-dialog.component.scss']
15
+ })
16
+ export class DefaultDialogComponent {
17
+ @Input() temRef!: TemplateRef<any>;
18
+ public height = input<string>();
19
+ public dialogTitle = input<string>();
20
+ public withBack = input<boolean>();
21
+
22
+ protected back = output<void>();
23
+ }
@@ -0,0 +1,48 @@
1
+ import { Component, input, OnInit } from '@angular/core';
2
+
3
+ import { HttpErrorResponse } from '@angular/common/http';
4
+ import { SnakeCaseParserPipe } from '../pipes/snake-case-parser.pipe';
5
+ import { SystemError } from '../utils/types';
6
+
7
+ @Component({
8
+ selector: 'lib-error-dispaly',
9
+ imports: [
10
+ SnakeCaseParserPipe
11
+ ],
12
+ template: `<strong class="err-container">{{ displayError | snakeCaseParser }}</strong>`,
13
+ styles: [`
14
+ .err-container {
15
+ display: block;
16
+ max-width: 300px;
17
+ font-size: 20px;
18
+ text-align: center;
19
+ border: 1px solid red;
20
+ border-radius: 5px;
21
+ padding: 0.5rem 1.5rem;
22
+ background-color: #ffe6e6;
23
+ color: #ff0000;
24
+ overflow-wrap: break-word;
25
+ }
26
+ `]
27
+ })
28
+ export class ErrorDispalyComponent implements OnInit {
29
+ public error = input.required<SystemError>();
30
+
31
+ protected displayError: string = '';
32
+
33
+ public ngOnInit(): void {
34
+ if (this.error() instanceof HttpErrorResponse) {
35
+ if (typeof (this.error() as HttpErrorResponse).error === 'string') {
36
+ this.displayError = (this.error() as HttpErrorResponse).error;
37
+ } else if (this.error && (this.error() as HttpErrorResponse)?.error?.message) {
38
+ this.displayError = (this.error() as HttpErrorResponse).error.message;
39
+ } else {
40
+ this.displayError = 'Unknown error';
41
+ }
42
+ } else if (typeof this.error() === 'string') {
43
+ this.displayError = (this.error() as string);
44
+ } else {
45
+ this.displayError = 'Unknown error';
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,44 @@
1
+ import { OnDestroy, inject } from '@angular/core';
2
+ import { Subscription } from 'rxjs';
3
+ import { Component } from '@angular/core';
4
+ import { HttpErrorResponse } from '@angular/common/http';
5
+ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
6
+ import { ErrorPopupComponent } from './error-popup/error-popup.component';
7
+ import { NoopScrollStrategy } from '@angular/cdk/overlay';
8
+ import { ErrorService } from '../../services/error.service';
9
+
10
+ @Component({
11
+ selector: 'lib-error-handler',
12
+ templateUrl: 'error-handler.component.html',
13
+ styleUrls: ['error-handler.component.scss'],
14
+ imports: [
15
+ MatDialogModule
16
+ ]
17
+ })
18
+ export class ErrorHandlerComponent implements OnDestroy {
19
+ private errSubscriptions = new Subscription();
20
+ private errorService = inject(ErrorService);
21
+ private dialog = inject(MatDialog);
22
+
23
+ constructor() {
24
+ this.errSubscriptions.add(
25
+ this.errorService.error$.subscribe((err: HttpErrorResponse) => {
26
+ console.log(err);
27
+ this.showPopup(err);
28
+ }),
29
+ );
30
+ }
31
+
32
+ private showPopup(error: HttpErrorResponse): void {
33
+ this.dialog.closeAll();
34
+ this.dialog.open(ErrorPopupComponent, {
35
+ data: error,
36
+ width: '400px',
37
+ scrollStrategy: new NoopScrollStrategy(),
38
+ });
39
+ }
40
+
41
+ public ngOnDestroy(): void {
42
+ this.errSubscriptions.unsubscribe();
43
+ }
44
+ }
@@ -0,0 +1,13 @@
1
+ <lib-default-dialog [dialogTitle]="'Аn error has occurred'">
2
+ <div class="dialog-content">
3
+ <div class="status-info">
4
+ <div>{{ error.status }}</div>
5
+ @if (error.status) {
6
+ <div>{{ httpStatusCodes[error.status] }}</div>
7
+ }
8
+ </div>
9
+ <div class="error-info">
10
+ <lib-error-dispaly [error]="error" />
11
+ </div>
12
+ </div>
13
+ </lib-default-dialog>
@@ -0,0 +1,19 @@
1
+ .dialog-content {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ padding-inline: 1rem;
6
+
7
+ .status-info {
8
+ display: flex;
9
+ margin-bottom: 0.5rem;
10
+ font-weight: 600;
11
+ font-size: 18px;
12
+ gap: 0.5rem;
13
+ }
14
+
15
+ .error-info {
16
+ display: grid;
17
+ place-items: center;
18
+ }
19
+ }
@@ -0,0 +1,21 @@
1
+ import { Component, Inject } from '@angular/core';
2
+ import { MAT_DIALOG_DATA } from '@angular/material/dialog';
3
+ import { HttpErrorResponse } from '@angular/common/http';
4
+ import { DefaultDialogComponent } from '../../default-dialog/default-dialog.component';
5
+ import { ErrorDispalyComponent } from '../../error-dispaly.component';
6
+ import { HTTP_STATUS_CODES } from '../../../utils/utils';
7
+
8
+ @Component({
9
+ selector: 'lib-error-popup',
10
+ imports: [
11
+ DefaultDialogComponent,
12
+ ErrorDispalyComponent
13
+ ],
14
+ templateUrl: './error-popup.component.html',
15
+ styleUrls: ['./error-popup.component.scss']
16
+ })
17
+ export class ErrorPopupComponent {
18
+ protected httpStatusCodes = HTTP_STATUS_CODES;
19
+
20
+ constructor(@Inject(MAT_DIALOG_DATA) public error: HttpErrorResponse) { }
21
+ }
@@ -0,0 +1,5 @@
1
+ @if (loaderService.loading$ | async) {
2
+ <div class="loader">
3
+ <mat-spinner />
4
+ </div>
5
+ }
@@ -0,0 +1,12 @@
1
+ .loader {
2
+ display: flex;
3
+ justify-content: center;
4
+ align-items: center;
5
+ height: 100%;
6
+ width: 100%;
7
+ position: fixed;
8
+ top: 0;
9
+ left: 0;
10
+ background-color: rgba(0, 0, 0, 0.2);
11
+ z-index: 9999;
12
+ }
@@ -0,0 +1,17 @@
1
+ import { Component, inject } from '@angular/core';
2
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
3
+ import { LoaderService } from '../../services/loader.service';
4
+ import { AsyncPipe } from '@angular/common';
5
+
6
+ @Component({
7
+ selector: 'lib-global-loader',
8
+ imports: [
9
+ AsyncPipe,
10
+ MatProgressSpinnerModule
11
+ ],
12
+ templateUrl: './global-loader.component.html',
13
+ styleUrl: './global-loader.component.scss'
14
+ })
15
+ export class GlobalLoaderComponent {
16
+ protected loaderService = inject(LoaderService);
17
+ }
@@ -0,0 +1,70 @@
1
+ import { Component, output, input } from '@angular/core';
2
+ import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
3
+ import { MatIconModule } from '@angular/material/icon';
4
+ import { debounceTime, distinctUntilChanged } from 'rxjs';
5
+
6
+ @Component({
7
+ selector: 'lib-search-bar',
8
+ template: `
9
+ <form class="search-bar" [formGroup]="searchForm">
10
+ <mat-icon>search</mat-icon>
11
+ <input class="search-input" type="search" name="field"
12
+ [placeholder]="'Search ' + for()" autocomplete="off" formControlName="search" />
13
+ </form>
14
+ `,
15
+ styles: [`
16
+ .search-bar {
17
+ width: 270px;
18
+ border: 1px solid #A4A4A4;
19
+ display: flex;
20
+ align-items: center;
21
+ }
22
+
23
+ .search-input {
24
+ border: none;
25
+ padding: 7px 11px;
26
+ height: 100%;
27
+ width: 100%;
28
+ background-color: transparent;
29
+ }
30
+
31
+ mat-icon {
32
+ margin-inline: 8px;
33
+ }
34
+
35
+ .search-input:focus {
36
+ border: none;
37
+ outline: none;
38
+ }
39
+
40
+ @media (max-width: 1086px) {
41
+ .search-bar {
42
+ width: 170px;
43
+ }
44
+ }
45
+ `],
46
+ imports: [
47
+ MatIconModule,
48
+ ReactiveFormsModule
49
+ ]
50
+ })
51
+ export class SearchBarComponent {
52
+ public for = input.required<string>();
53
+ protected search = output<string>();
54
+
55
+ protected searchForm: FormGroup = new FormGroup({
56
+ search: new FormControl('')
57
+ });
58
+
59
+ constructor() {
60
+ this.searchForm.get('search')?.valueChanges.
61
+ pipe(
62
+ debounceTime(1000),
63
+ distinctUntilChanged(),
64
+ ).subscribe((searchTerm: string) => {
65
+ if (searchTerm.trim() !== '') {
66
+ this.search.emit(searchTerm);
67
+ }
68
+ });
69
+ }
70
+ }
@@ -0,0 +1,121 @@
1
+ <div class="flexer">
2
+ @if (config().title) {
3
+ <div class="row-heading-labels mb05">{{config().title}}</div>
4
+ }
5
+ <ng-content select=".upper-part"></ng-content>
6
+ @if (config().withAdd) {
7
+ <div class="flexer action pointer gap05 mb05" (click)="action.emit({action: 'add'})">
8
+ <mat-icon>add_circle_outline</mat-icon>
9
+ <div>add</div>
10
+ </div>
11
+ }
12
+ </div>
13
+
14
+ <div class="scroll" #scrollContainer (scroll)="onScroll()">
15
+ <table [class.with-options]="config().options?.length">
16
+ <thead>
17
+ <tr>
18
+ @for (heading of config().tableHeadings; track heading; let i = $index) {
19
+ <th>
20
+ <div class="flexer gap05">
21
+ {{heading}}
22
+ @if (config().sortable && !config().draggable) {
23
+ <lib-table-sort-header
24
+ [selected]="currentSortColumn === i"
25
+ (click)="currentSortColumn = i"
26
+ (sort)="sortByProp(config().dataProps[i], $event)"
27
+ />
28
+ }
29
+ </div>
30
+ </th>
31
+ }
32
+ @if (config().options?.length) {
33
+ <th></th>
34
+ }
35
+ </tr>
36
+ </thead>
37
+ @if (config().draggable) {
38
+ <tbody cdkDropList (cdkDropListDropped)="drop($event)">
39
+ @for (obj of config().data; track trackById(i, obj); let i = $index) {
40
+ <tr
41
+ (mouseover)="hoverRowIndex = i"
42
+ (mouseleave)="hoverRowIndex = -1"
43
+ cdkDrag cdkDragLockAxis="y"
44
+ [class.pointer]="config().selectableRows"
45
+ [class.hover-row]="hoverRowIndex == i"
46
+ [class.selected-row]="config().selectableRows && selectedRowIndex == i"
47
+ (click)="onRowClick($event, obj, i)"
48
+ @fadeInOut>
49
+ @for (prop of config().dataProps; track prop; let cellIndex = $index) {
50
+ <td
51
+ [class.dragCol]="cellIndex === 0">
52
+ <div class="data" [classList]="getClass(obj, prop)"
53
+ [class.flexer]="cellIndex === 0">
54
+ @if (cellIndex === 0) {
55
+ <mat-icon class="draggable" cdkDragHandle>
56
+ drag_indicator
57
+ </mat-icon>
58
+ }
59
+ {{obj[prop] | blankFiller}}
60
+ </div>
61
+ </td>
62
+ }
63
+ @if (config().options?.length) {
64
+ <td class="right-align" (click)="$event.stopPropagation()">
65
+ <button mat-icon-button class="pointer dots right" [matMenuTriggerFor]="optionsMenu">
66
+ <mat-icon>more_vert</mat-icon>
67
+ </button>
68
+ <mat-menu #optionsMenu="matMenu">
69
+ @for (option of config().options; track option) {
70
+ <div
71
+ mat-menu-item (click)="selectOption(option, obj, i)"
72
+ [class.red]="option == 'Remove' || option == 'Delete'">
73
+ {{option}}
74
+ </div>
75
+ }
76
+ </mat-menu>
77
+ </td>
78
+ }
79
+ </tr>
80
+ }
81
+ </tbody>
82
+ } @else {
83
+ <tbody>
84
+ @for (obj of config().data; track trackById(i, obj); let i = $index) {
85
+ <tr
86
+ (mouseover)="hoverRowIndex = i"
87
+ (mouseleave)="hoverRowIndex = -1"
88
+ [class.pointer]="config().selectableRows"
89
+ [class.hover-row]="hoverRowIndex == i"
90
+ [class.selected-row]="config().selectableRows && selectedRowIndex == i"
91
+ (click)="onRowClick($event, obj, i)"
92
+ @fadeInOut>
93
+ @for (prop of config().dataProps; track prop; let cellIndex = $index) {
94
+ <td>
95
+ <div class="data" [classList]="getClass(obj, prop)">
96
+ {{obj[prop] | blankFiller}}
97
+ </div>
98
+ </td>
99
+ }
100
+ @if (config().options?.length) {
101
+ <td class="right-align" (click)="$event.stopPropagation()">
102
+ <button mat-icon-button class="pointer dots right" [matMenuTriggerFor]="optionsMenu">
103
+ <mat-icon>more_vert</mat-icon>
104
+ </button>
105
+ <mat-menu #optionsMenu="matMenu">
106
+ @for (option of config().options; track option) {
107
+ <div
108
+ mat-menu-item (click)="selectOption(option, obj, i)"
109
+ [class.red]="option == 'Remove' || option == 'Delete'">
110
+ {{option}}
111
+ </div>
112
+ }
113
+ </mat-menu>
114
+ </td>
115
+ }
116
+ </tr>
117
+ }
118
+ </tbody>
119
+ }
120
+ </table>
121
+ </div>