verben-ng-ui 0.0.1

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 (190) hide show
  1. package/README.md +24 -0
  2. package/ng-package.json +10 -0
  3. package/package.json +34 -0
  4. package/src/lib/Verbena-input/error.service.ts +23 -0
  5. package/src/lib/Verbena-input/verbena-input.component.css +59 -0
  6. package/src/lib/Verbena-input/verbena-input.component.html +64 -0
  7. package/src/lib/Verbena-input/verbena-input.component.spec.ts +25 -0
  8. package/src/lib/Verbena-input/verbena-input.component.ts +162 -0
  9. package/src/lib/Verbena-input/verbena-input.module.ts +12 -0
  10. package/src/lib/components/TemplateDirective.directive.ts +10 -0
  11. package/src/lib/components/card/card.component.css +20 -0
  12. package/src/lib/components/card/card.component.html +23 -0
  13. package/src/lib/components/card/card.component.spec.ts +23 -0
  14. package/src/lib/components/card/card.component.ts +33 -0
  15. package/src/lib/components/card/card.module.ts +10 -0
  16. package/src/lib/components/card-data-view/card-data-view-footer/card-data-view-footer.component.css +3 -0
  17. package/src/lib/components/card-data-view/card-data-view-footer/card-data-view-footer.component.html +1 -0
  18. package/src/lib/components/card-data-view/card-data-view-footer/card-data-view-footer.component.spec.ts +23 -0
  19. package/src/lib/components/card-data-view/card-data-view-footer/card-data-view-footer.component.ts +10 -0
  20. package/src/lib/components/card-data-view/card-data-view-header/card-data-view-header.component.css +7 -0
  21. package/src/lib/components/card-data-view/card-data-view-header/card-data-view-header.component.html +1 -0
  22. package/src/lib/components/card-data-view/card-data-view-header/card-data-view-header.component.spec.ts +23 -0
  23. package/src/lib/components/card-data-view/card-data-view-header/card-data-view-header.component.ts +10 -0
  24. package/src/lib/components/card-data-view/card-data-view.component.css +39 -0
  25. package/src/lib/components/card-data-view/card-data-view.component.html +29 -0
  26. package/src/lib/components/card-data-view/card-data-view.component.spec.ts +23 -0
  27. package/src/lib/components/card-data-view/card-data-view.component.ts +66 -0
  28. package/src/lib/components/card-data-view/card-data-view.module.ts +20 -0
  29. package/src/lib/components/card-data-view/card-data.ts +7 -0
  30. package/src/lib/components/card-data-view/left-card-data/left-card-data.component.css +15 -0
  31. package/src/lib/components/card-data-view/left-card-data/left-card-data.component.html +4 -0
  32. package/src/lib/components/card-data-view/left-card-data/left-card-data.component.spec.ts +23 -0
  33. package/src/lib/components/card-data-view/left-card-data/left-card-data.component.ts +19 -0
  34. package/src/lib/components/card-data-view/left-card-data-view/left-card-data-view.component.css +0 -0
  35. package/src/lib/components/card-data-view/left-card-data-view/left-card-data-view.component.html +4 -0
  36. package/src/lib/components/card-data-view/left-card-data-view/left-card-data-view.component.spec.ts +23 -0
  37. package/src/lib/components/card-data-view/left-card-data-view/left-card-data-view.component.ts +12 -0
  38. package/src/lib/components/card-data-view/right-card-data-view/right-card-data-view.component.css +3 -0
  39. package/src/lib/components/card-data-view/right-card-data-view/right-card-data-view.component.html +1 -0
  40. package/src/lib/components/card-data-view/right-card-data-view/right-card-data-view.component.spec.ts +23 -0
  41. package/src/lib/components/card-data-view/right-card-data-view/right-card-data-view.component.ts +10 -0
  42. package/src/lib/components/chip/ChipChangeEvent.ts +4 -0
  43. package/src/lib/components/chip/chip.component.css +94 -0
  44. package/src/lib/components/chip/chip.component.html +49 -0
  45. package/src/lib/components/chip/chip.component.spec.ts +23 -0
  46. package/src/lib/components/chip/chip.component.ts +209 -0
  47. package/src/lib/components/chip/chip.module.ts +24 -0
  48. package/src/lib/components/chip/documentation.md +26 -0
  49. package/src/lib/components/data-export/data-export.component.css +0 -0
  50. package/src/lib/components/data-export/data-export.component.html +90 -0
  51. package/src/lib/components/data-export/data-export.component.spec.ts +23 -0
  52. package/src/lib/components/data-export/data-export.component.ts +158 -0
  53. package/src/lib/components/data-export/data-export.module.ts +13 -0
  54. package/src/lib/components/data-export/data-export.service.spec.ts +16 -0
  55. package/src/lib/components/data-export/data-export.service.ts +152 -0
  56. package/src/lib/components/data-export/data-export.types.ts +21 -0
  57. package/src/lib/components/data-table/column.directive.spec.ts +8 -0
  58. package/src/lib/components/data-table/column.directive.ts +19 -0
  59. package/src/lib/components/data-table/data-table.component.css +0 -0
  60. package/src/lib/components/data-table/data-table.component.html +55 -0
  61. package/src/lib/components/data-table/data-table.component.spec.ts +21 -0
  62. package/src/lib/components/data-table/data-table.component.ts +336 -0
  63. package/src/lib/components/data-table/data-table.module.ts +11 -0
  64. package/src/lib/components/data-table/data-table.types.ts +13 -0
  65. package/src/lib/components/data-table/style.types.ts +55 -0
  66. package/src/lib/components/data-view/data-view-click-outside.directive.ts +44 -0
  67. package/src/lib/components/data-view/data-view.component.css +74 -0
  68. package/src/lib/components/data-view/data-view.component.html +161 -0
  69. package/src/lib/components/data-view/data-view.component.spec.ts +23 -0
  70. package/src/lib/components/data-view/data-view.component.ts +136 -0
  71. package/src/lib/components/data-view/data-view.module.ts +16 -0
  72. package/src/lib/components/date-picker/date-picker.component.css +65 -0
  73. package/src/lib/components/date-picker/date-picker.component.html +60 -0
  74. package/src/lib/components/date-picker/date-picker.component.specs.ts +23 -0
  75. package/src/lib/components/date-picker/date-picker.component.ts +143 -0
  76. package/src/lib/components/date-picker/date-picker.module.ts +12 -0
  77. package/src/lib/components/drop-down/DropdownChangeEvent.ts +4 -0
  78. package/src/lib/components/drop-down/DropdownLoadEvent.ts +19 -0
  79. package/src/lib/components/drop-down/DropdownMenuItem.ts +42 -0
  80. package/src/lib/components/drop-down/documentation.md +69 -0
  81. package/src/lib/components/drop-down/drop-down-item/drop-down-item.component.css +123 -0
  82. package/src/lib/components/drop-down/drop-down-item/drop-down-item.component.html +133 -0
  83. package/src/lib/components/drop-down/drop-down-item/drop-down-item.component.spec.ts +23 -0
  84. package/src/lib/components/drop-down/drop-down-item/drop-down-item.component.ts +88 -0
  85. package/src/lib/components/drop-down/drop-down.component.css +317 -0
  86. package/src/lib/components/drop-down/drop-down.component.html +177 -0
  87. package/src/lib/components/drop-down/drop-down.component.spec.ts +23 -0
  88. package/src/lib/components/drop-down/drop-down.component.ts +757 -0
  89. package/src/lib/components/drop-down/drop-down.module.ts +24 -0
  90. package/src/lib/components/image/image.component.css +12 -0
  91. package/src/lib/components/image/image.component.html +18 -0
  92. package/src/lib/components/image/image.component.spec.ts +23 -0
  93. package/src/lib/components/image/image.component.ts +45 -0
  94. package/src/lib/components/image/image.module.ts +11 -0
  95. package/src/lib/components/notification/notification.component.css +3 -0
  96. package/src/lib/components/notification/notification.component.html +19 -0
  97. package/src/lib/components/notification/notification.component.spec.ts +23 -0
  98. package/src/lib/components/notification/notification.component.ts +77 -0
  99. package/src/lib/components/notification/notification.module.ts +14 -0
  100. package/src/lib/components/shared.module.ts +9 -0
  101. package/src/lib/components/sort-table/sort-table.component.css +67 -0
  102. package/src/lib/components/sort-table/sort-table.component.html +164 -0
  103. package/src/lib/components/sort-table/sort-table.component.spec.ts +23 -0
  104. package/src/lib/components/sort-table/sort-table.component.ts +204 -0
  105. package/src/lib/components/sort-table/sort-table.module.ts +11 -0
  106. package/src/lib/components/svg/svg.component.css +0 -0
  107. package/src/lib/components/svg/svg.component.html +1 -0
  108. package/src/lib/components/svg/svg.component.spec.ts +23 -0
  109. package/src/lib/components/svg/svg.component.ts +64 -0
  110. package/src/lib/components/svg/svg.module.ts +10 -0
  111. package/src/lib/components/table-filter/table-filter.component.css +93 -0
  112. package/src/lib/components/table-filter/table-filter.component.html +293 -0
  113. package/src/lib/components/table-filter/table-filter.component.spec.ts +23 -0
  114. package/src/lib/components/table-filter/table-filter.component.ts +221 -0
  115. package/src/lib/components/table-filter/table-filter.module.ts +25 -0
  116. package/src/lib/components/tooltip/tooltip.component.css +8 -0
  117. package/src/lib/components/tooltip/tooltip.component.html +13 -0
  118. package/src/lib/components/tooltip/tooltip.component.spec.ts +23 -0
  119. package/src/lib/components/tooltip/tooltip.component.ts +84 -0
  120. package/src/lib/components/tooltip/tooltip.module.ts +11 -0
  121. package/src/lib/components/verben-mail/verben-mail.component.config.ts +11 -0
  122. package/src/lib/components/verben-mail/verben-mail.component.css +71 -0
  123. package/src/lib/components/verben-mail/verben-mail.component.html +139 -0
  124. package/src/lib/components/verben-mail/verben-mail.component.ts +151 -0
  125. package/src/lib/components/verben-mail/verben-mail.module.ts +18 -0
  126. package/src/lib/components/visible-column/visible-column.component.css +83 -0
  127. package/src/lib/components/visible-column/visible-column.component.html +77 -0
  128. package/src/lib/components/visible-column/visible-column.component.spec.ts +23 -0
  129. package/src/lib/components/visible-column/visible-column.component.ts +106 -0
  130. package/src/lib/components/visible-column/visible-column.module.ts +11 -0
  131. package/src/lib/config.ts +37 -0
  132. package/src/lib/control-options/control-options.directive.ts +129 -0
  133. package/src/lib/control-options/control-options.module.ts +10 -0
  134. package/src/lib/convert-to-integer/convert-to-integer.directive.ts +28 -0
  135. package/src/lib/convert-to-integer/convert-to-integer.module.ts +10 -0
  136. package/src/lib/convert-to-integer/public-api.ts +1 -0
  137. package/src/lib/convert-to-number/convert-to-number.directive.ts +22 -0
  138. package/src/lib/convert-to-number/convert-to-number.module.ts +8 -0
  139. package/src/lib/convert-to-number/public-api.ts +1 -0
  140. package/src/lib/email-validator/email-validator.directive.ts +24 -0
  141. package/src/lib/email-validator/email-validator.module.ts +14 -0
  142. package/src/lib/models/column-filter.ts +5 -0
  143. package/src/lib/models/data-filter.ts +8 -0
  144. package/src/lib/models/mail-model.ts +8 -0
  145. package/src/lib/models/sort-filter.ts +0 -0
  146. package/src/lib/models/table-filter.ts +34 -0
  147. package/src/lib/number-range/number-range.directive.ts +35 -0
  148. package/src/lib/number-range/number-range.module.ts +11 -0
  149. package/src/lib/phone-number/phone-number.directive.ts +22 -0
  150. package/src/lib/phone-number/phone-number.module.ts +10 -0
  151. package/src/lib/phone-number/public-api.ts +1 -0
  152. package/src/lib/required-input/required-input.directive.ts +18 -0
  153. package/src/lib/required-input/required-input.module.ts +8 -0
  154. package/src/lib/theme-switcher/theme-switcher.directive.spec.ts +8 -0
  155. package/src/lib/theme-switcher/theme-switcher.directive.ts +70 -0
  156. package/src/lib/theme-switcher/theme-switcher.module.ts +8 -0
  157. package/src/lib/validate/error-message.service.ts +104 -0
  158. package/src/lib/validate/validate.directive.ts +110 -0
  159. package/src/lib/validate/validate.module.ts +17 -0
  160. package/src/lib/validate-input/validate-input.directive.ts +8 -0
  161. package/src/lib/validate-input/validate-input.module.ts +8 -0
  162. package/src/lib/verben-ng-ui.component.spec.ts +23 -0
  163. package/src/lib/verben-ng-ui.component.ts +16 -0
  164. package/src/lib/verben-ng-ui.service.spec.ts +16 -0
  165. package/src/lib/verben-ng-ui.service.ts +9 -0
  166. package/src/lib/verbena-badge/verbena-badge.component.css +16 -0
  167. package/src/lib/verbena-badge/verbena-badge.component.html +13 -0
  168. package/src/lib/verbena-badge/verbena-badge.component.spec.ts +25 -0
  169. package/src/lib/verbena-badge/verbena-badge.component.ts +18 -0
  170. package/src/lib/verbena-badge/verbena-badge.module.ts +10 -0
  171. package/src/lib/verbena-button/verbena-button.component.css +20 -0
  172. package/src/lib/verbena-button/verbena-button.component.html +25 -0
  173. package/src/lib/verbena-button/verbena-button.component.spec.ts +25 -0
  174. package/src/lib/verbena-button/verbena-button.component.ts +92 -0
  175. package/src/lib/verbena-button/verbena-button.module.ts +11 -0
  176. package/src/lib/verbena-switch/verbena-switch.component.css +45 -0
  177. package/src/lib/verbena-switch/verbena-switch.component.html +8 -0
  178. package/src/lib/verbena-switch/verbena-switch.component.spec.ts +25 -0
  179. package/src/lib/verbena-switch/verbena-switch.component.ts +31 -0
  180. package/src/lib/verbena-switch/verbena-switch.module.ts +10 -0
  181. package/src/lib/verbena-textarea/verbena-textarea.component.css +0 -0
  182. package/src/lib/verbena-textarea/verbena-textarea.component.html +19 -0
  183. package/src/lib/verbena-textarea/verbena-textarea.component.spec.ts +33 -0
  184. package/src/lib/verbena-textarea/verbena-textarea.component.ts +44 -0
  185. package/src/lib/verbena-textarea/verbena-textarea.module.ts +11 -0
  186. package/src/public-api.ts +122 -0
  187. package/src/styles.css +103 -0
  188. package/tsconfig.lib.json +15 -0
  189. package/tsconfig.lib.prod.json +11 -0
  190. package/tsconfig.spec.json +15 -0
@@ -0,0 +1,152 @@
1
+ import { Injectable } from '@angular/core';
2
+ import {
3
+ ExportProfile,
4
+ Operation,
5
+ ExportItem,
6
+ ExportItemType,
7
+ } from './data-export.types';
8
+
9
+ @Injectable()
10
+ export class DataExportService {
11
+ private profiles: ExportProfile[] = [];
12
+ private operations: Operation[] = [];
13
+ private baseProperties: string[] = [];
14
+
15
+ constructor() {
16
+ this.initializeDefaultProfile();
17
+ }
18
+
19
+ private initializeDefaultProfile() {
20
+ this.profiles = [
21
+ {
22
+ id: 'default',
23
+ name: 'All',
24
+ items: [],
25
+ },
26
+ ];
27
+ }
28
+
29
+ addProfile(profile: ExportProfile): void {
30
+ this.profiles.push(profile);
31
+ }
32
+
33
+ getProfiles(): ExportProfile[] {
34
+ return this.profiles;
35
+ }
36
+
37
+ updateProfile(id: string, updatedProfile: ExportProfile): void {
38
+ const index = this.profiles.findIndex((p) => p.id === id);
39
+ if (index !== -1) {
40
+ this.profiles[index] = updatedProfile;
41
+ }
42
+ }
43
+
44
+ removeProfile(id: string): void {
45
+ this.profiles = this.profiles.filter((p) => p.id !== id);
46
+ }
47
+
48
+ addOperation(operation: Operation): void {
49
+ this.operations.push(operation);
50
+ this.updateDefaultProfile();
51
+ }
52
+
53
+ getOperations(): Operation[] {
54
+ return this.operations;
55
+ }
56
+
57
+ updateOperation(id: string, updatedOperation: Operation): void {
58
+ const index = this.operations.findIndex((o) => o.id === id);
59
+ if (index !== -1) {
60
+ this.operations[index] = updatedOperation;
61
+ this.updateDefaultProfile();
62
+ }
63
+ }
64
+
65
+ removeOperation(id: string): void {
66
+ this.operations = this.operations.filter((o) => o.id !== id);
67
+ this.updateDefaultProfile();
68
+ // Remove operation from all profiles
69
+ this.profiles.forEach((profile) => {
70
+ profile.items = profile.items.filter((item) => item.id !== id);
71
+ });
72
+ }
73
+
74
+ resetAll(): void {
75
+ this.operations = [];
76
+ this.baseProperties = [];
77
+ this.initializeDefaultProfile();
78
+ }
79
+
80
+ getAllItems(): ExportItem[] {
81
+ return [
82
+ ...this.baseProperties.map((prop) => ({
83
+ id: prop,
84
+ name: prop,
85
+ type: 'property' as ExportItemType,
86
+ })),
87
+ ...this.operations.map((op) => ({
88
+ id: op.id,
89
+ name: op.name,
90
+ type: 'operation' as ExportItemType,
91
+ })),
92
+ ];
93
+ }
94
+
95
+ getBaseProperties(): string[] {
96
+ return this.baseProperties;
97
+ }
98
+
99
+ setBaseProperties(properties: string[]): void {
100
+ this.baseProperties = properties;
101
+ this.updateDefaultProfile();
102
+ }
103
+
104
+ private updateDefaultProfile(): void {
105
+ this.profiles[0].items = this.getAllItems();
106
+ }
107
+
108
+ exportData<T>(
109
+ data: T[],
110
+ selectedProfiles: ExportProfile[]
111
+ ): Record<string, any>[] {
112
+ const uniqueItems = new Set<ExportItem>();
113
+ selectedProfiles.forEach((profile) => {
114
+ profile.items.forEach((item) => uniqueItems.add(item));
115
+ });
116
+
117
+ return data.map((item) => {
118
+ const exportedItem: Record<string, any> = {};
119
+ uniqueItems.forEach((exportItem) => {
120
+ if (exportItem.type === 'property') {
121
+ exportedItem[exportItem.id] = (item as any)[exportItem.id];
122
+ } else {
123
+ const operation = this.operations.find((o) => o.id === exportItem.id);
124
+ if (operation) {
125
+ exportedItem[exportItem.id] = this.calculateOperation(
126
+ item,
127
+ operation
128
+ );
129
+ }
130
+ }
131
+ });
132
+ return exportedItem;
133
+ });
134
+ }
135
+
136
+ private calculateOperation<T>(item: T, operation: Operation): number {
137
+ const value1 = Number((item as any)[operation.field1]);
138
+ const value2 = Number((item as any)[operation.field2]);
139
+ switch (operation.operator) {
140
+ case 'add':
141
+ return value1 + value2;
142
+ case 'subtract':
143
+ return value1 - value2;
144
+ case 'multiply':
145
+ return value1 * value2;
146
+ case 'divide':
147
+ return value2 !== 0 ? value1 / value2 : NaN;
148
+ default:
149
+ return NaN;
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,21 @@
1
+ export type ExportItemType = 'property' | 'operation';
2
+
3
+ export interface ExportItem {
4
+ id: string;
5
+ name: string;
6
+ type: ExportItemType;
7
+ }
8
+
9
+ export interface ExportProfile {
10
+ id: string;
11
+ name: string;
12
+ items: ExportItem[];
13
+ }
14
+
15
+ export interface Operation {
16
+ id: string;
17
+ name: string;
18
+ field1: string;
19
+ operator: 'add' | 'subtract' | 'multiply' | 'divide';
20
+ field2: string;
21
+ }
@@ -0,0 +1,8 @@
1
+ import { ColumnDirective } from './column.directive';
2
+
3
+ describe('ColumnDirective', () => {
4
+ it('should create an instance', () => {
5
+ const directive = new ColumnDirective();
6
+ expect(directive).toBeTruthy();
7
+ });
8
+ });
@@ -0,0 +1,19 @@
1
+ import {
2
+ ContentChild,
3
+ Directive,
4
+ Input,
5
+ TemplateRef,
6
+ ViewChild,
7
+ } from '@angular/core';
8
+
9
+ @Directive({
10
+ selector: '[libColumn]',
11
+ })
12
+ export class ColumnDirective {
13
+ @Input('libColumn') columnId!: string;
14
+
15
+ @ContentChild('cell', { static: true }) cellTemplate?: TemplateRef<any>;
16
+ @ContentChild('cellEdit', { static: true }) cellEditTemplate?: TemplateRef<any>;
17
+ @ContentChild('header', { static: true }) headerTemplate?: TemplateRef<any>;
18
+ @ContentChild('footer', { static: true }) footerTemplate?: TemplateRef<any>;
19
+ }
@@ -0,0 +1,55 @@
1
+ <!-- <table [ngStyle]="styleConfig">
2
+ <thead>
3
+ <tr [ngStyle]="styleConfig?.header">
4
+ @for (column of columnsSignal(); track column.id) {
5
+ <th [ngStyle]="styleConfig?.cells">
6
+ @if (column.headerTemplate) {
7
+ <ng-container
8
+ [ngTemplateOutlet]="column.headerTemplate"
9
+ [ngTemplateOutletContext]="getHeaderContext(column)"
10
+ ></ng-container>
11
+ } @else {
12
+ {{ column.header }}
13
+ }
14
+ </th>
15
+ }
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ @for (row of data; track row.id; let rowIndex = $index) {
20
+ <tr [ngStyle]="getRowStyle(rowIndex)">
21
+ @for (column of columnsSignal(); track column.id) {
22
+ <td [ngStyle]="styleConfig?.cells">
23
+ @if (isRowEditing(row.id) && column.cellEditTemplate) {
24
+ <ng-container
25
+ [ngTemplateOutlet]="column.cellEditTemplate"
26
+ [ngTemplateOutletContext]="getCellContext(row, column, rowIndex)"
27
+ ></ng-container>
28
+ } @else if (column.cellTemplate) {
29
+ <ng-container
30
+ [ngTemplateOutlet]="column.cellTemplate"
31
+ [ngTemplateOutletContext]="getCellContext(row, column, rowIndex)"
32
+ ></ng-container>
33
+ } @else {
34
+ {{ getCellValue(row, column) }}
35
+ }
36
+ </td>
37
+ }
38
+ </tr>
39
+ }
40
+ </tbody>
41
+ <tfoot>
42
+ <tr [ngStyle]="styleConfig?.footer">
43
+ @for (column of columnsSignal(); track column.id) {
44
+ <td [ngStyle]="styleConfig?.cells">
45
+ @if (column.footerTemplate) {
46
+ <ng-container
47
+ [ngTemplateOutlet]="column.footerTemplate"
48
+ [ngTemplateOutletContext]="getFooterContext(column)"
49
+ ></ng-container>
50
+ }
51
+ </td>
52
+ }
53
+ </tr>
54
+ </tfoot>
55
+ </table> -->
@@ -0,0 +1,21 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { DataTableComponent } from './data-table.component';
3
+
4
+ describe('DataTableComponent', () => {
5
+ let component: DataTableComponent;
6
+ let fixture: ComponentFixture<DataTableComponent>;
7
+
8
+ beforeEach(async () => {
9
+ await TestBed.configureTestingModule({
10
+ imports: [DataTableComponent],
11
+ }).compileComponents();
12
+
13
+ fixture = TestBed.createComponent(DataTableComponent);
14
+ component = fixture.componentInstance;
15
+ fixture.detectChanges();
16
+ });
17
+
18
+ it('should create', () => {
19
+ expect(component).toBeTruthy();
20
+ });
21
+ });
@@ -0,0 +1,336 @@
1
+ import {
2
+ AfterContentInit,
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ ContentChildren,
6
+ EventEmitter,
7
+ Input,
8
+ Output,
9
+ QueryList,
10
+ Signal,
11
+ signal,
12
+ WritableSignal,
13
+ } from '@angular/core';
14
+ import { ColumnDefinition } from './data-table.types';
15
+ import { ColumnDirective } from './column.directive';
16
+ import { BaseStyles, TableStyles } from './style.types';
17
+
18
+ @Component({
19
+ selector: 'lib-data-table',
20
+ templateUrl: './data-table.component.html',
21
+ styleUrl: './data-table.component.css',
22
+ changeDetection: ChangeDetectionStrategy.OnPush,
23
+ })
24
+ export class DataTableComponent<T extends { id: string | number }>
25
+ implements AfterContentInit
26
+ {
27
+ @Input({ required: true }) data!: T[];
28
+ @Input({ required: true }) columns!: ColumnDefinition<T>[];
29
+
30
+ @Input() styleConfig: TableStyles = defaultTableStyles;
31
+
32
+ @ContentChildren(ColumnDirective)
33
+ columnTemplates!: QueryList<ColumnDirective>;
34
+
35
+ @Output() rowEdit = new EventEmitter<T>();
36
+ @Output() rowSave = new EventEmitter<T>();
37
+ @Output() selectionChange = new EventEmitter<T[]>();
38
+
39
+ private editingRowsSignal = signal<Set<string | number>>(new Set());
40
+ private selectedRowsSignal = signal<Set<string | number>>(new Set());
41
+ private editedDataSignal = signal<Map<string | number, EditedData<T>>>(
42
+ new Map()
43
+ );
44
+
45
+ columnsSignal: WritableSignal<ColumnDefinition<T>[]> = signal([]);
46
+
47
+ ngAfterContentInit() {
48
+ this.columnTemplates.changes.subscribe(() => this.mergeColumnTemplates());
49
+ this.mergeColumnTemplates();
50
+ }
51
+
52
+ private mergeColumnTemplates() {
53
+ const updatedColumns = this.columns.map((column) => {
54
+ const matchingTemplate = this.columnTemplates.find(
55
+ (t) => t.columnId === column.id
56
+ );
57
+ if (matchingTemplate) {
58
+ return {
59
+ ...column,
60
+ cellTemplate: matchingTemplate.cellTemplate,
61
+ cellEditTemplate: matchingTemplate.cellEditTemplate,
62
+ headerTemplate: matchingTemplate.headerTemplate,
63
+ footerTemplate: matchingTemplate.footerTemplate,
64
+ };
65
+ }
66
+ return column;
67
+ });
68
+ this.columnsSignal.set(updatedColumns);
69
+ }
70
+
71
+ getCellValue = (row: T, column: ColumnDefinition<T>): any => {
72
+ if (column.accessorKey) {
73
+ return (row as any)[column.accessorKey];
74
+ }
75
+ return column.accessorFn ? column.accessorFn(row) : undefined;
76
+ };
77
+
78
+ isRowEditing = (rowId: string | number): boolean => {
79
+ return this.editingRowsSignal().has(rowId);
80
+ };
81
+
82
+ toggleRowEdit = (rowId: string | number) => {
83
+ this.editingRowsSignal.update((set) => {
84
+ const newSet = new Set(set);
85
+ if (newSet.has(rowId)) {
86
+ newSet.delete(rowId);
87
+ this.saveRow(rowId);
88
+ } else {
89
+ newSet.add(rowId);
90
+ this.initializeEditedData(rowId);
91
+ }
92
+ return newSet;
93
+ });
94
+ };
95
+
96
+ private initializeEditedData(rowId: string | number) {
97
+ const row = this.data.find((r) => r.id === rowId);
98
+ if (row) {
99
+ this.editedDataSignal.update((map) => {
100
+ const newMap = new Map(map);
101
+ newMap.set(rowId, { ...row });
102
+ return newMap;
103
+ });
104
+ }
105
+ }
106
+
107
+ private saveRow(rowId: string | number) {
108
+ const editedData = this.editedDataSignal().get(rowId);
109
+ if (editedData) {
110
+ const originalRowIndex = this.data.findIndex((r) => r.id === rowId);
111
+ if (originalRowIndex !== -1) {
112
+ const updatedRow = { ...this.data[originalRowIndex], ...editedData };
113
+ this.data[originalRowIndex] = updatedRow;
114
+ this.rowSave.emit(updatedRow);
115
+ this.editedDataSignal.update((map) => {
116
+ const newMap = new Map(map);
117
+ newMap.delete(rowId);
118
+ return newMap;
119
+ });
120
+ }
121
+ }
122
+ }
123
+
124
+ isRowSelected = (rowId: string | number): boolean => {
125
+ return this.selectedRowsSignal().has(rowId);
126
+ };
127
+
128
+ toggleRowSelection = (rowId: string | number) => {
129
+ this.selectedRowsSignal.update((set) => {
130
+ const newSet = new Set(set);
131
+ if (newSet.has(rowId)) {
132
+ newSet.delete(rowId);
133
+ } else {
134
+ newSet.add(rowId);
135
+ }
136
+ this.emitSelectionChange();
137
+ return newSet;
138
+ });
139
+ };
140
+
141
+ allRowsSelected = (): boolean => {
142
+ return this.selectedRowsSignal().size === this.data.length;
143
+ };
144
+
145
+ someRowsSelected = (): boolean => {
146
+ return (
147
+ this.selectedRowsSignal().size > 0 &&
148
+ this.selectedRowsSignal().size < this.data.length
149
+ );
150
+ };
151
+
152
+ toggleAllRows = () => {
153
+ if (this.allRowsSelected()) {
154
+ this.selectedRowsSignal.set(new Set());
155
+ } else {
156
+ this.selectedRowsSignal.set(new Set(this.data.map((row) => row.id)));
157
+ }
158
+ this.emitSelectionChange();
159
+ };
160
+
161
+ private emitSelectionChange() {
162
+ const selectedRows = this.data.filter((row) =>
163
+ this.selectedRowsSignal().has(row.id)
164
+ );
165
+ this.selectionChange.emit(selectedRows);
166
+ }
167
+
168
+ getHeaderContext(column: ColumnDefinition<T>) {
169
+ return {
170
+ $implicit: column,
171
+ column,
172
+ allRowsSelected: this.allRowsSelected,
173
+ someRowsSelected: this.someRowsSelected,
174
+ toggleAllRows: this.toggleAllRows,
175
+ };
176
+ }
177
+
178
+ // updateEditedField<K extends keyof T>(
179
+ // rowId: string | number,
180
+ // columnId: K,
181
+ // field: T[K] extends object ? keyof T[K] : never,
182
+ // value: any
183
+ // ) {
184
+ // this.editedDataSignal.update((map) => {
185
+ // const newMap = new Map(map);
186
+ // const rowData = newMap.get(rowId) || ({} as EditedData<T>);
187
+ // const columnData = (rowData[columnId] as any) || {};
188
+ // newMap.set(rowId, {
189
+ // ...rowData,
190
+ // [columnId]: {
191
+ // ...columnData,
192
+ // [field]: value,
193
+ // },
194
+ // });
195
+ // return newMap;
196
+ // });
197
+ // }
198
+
199
+ updateEditedValue(rowId: string | number, columnId: keyof T, value: any) {
200
+ this.editedDataSignal.update((map) => {
201
+ const newMap = new Map(map);
202
+ const rowData = newMap.get(rowId) || ({} as EditedData<T>);
203
+ newMap.set(rowId, { ...rowData, [columnId]: value });
204
+ return newMap;
205
+ });
206
+ }
207
+
208
+ updateNestedEditedValue(
209
+ rowId: string | number,
210
+ columnId: keyof T,
211
+ nestedField: string,
212
+ value: any
213
+ ) {
214
+ this.editedDataSignal.update((map) => {
215
+ const newMap = new Map(map);
216
+ const rowData = newMap.get(rowId) || ({} as EditedData<T>);
217
+ const columnData = (rowData[columnId] as any) || {};
218
+ newMap.set(rowId, {
219
+ ...rowData,
220
+ [columnId]: {
221
+ ...columnData,
222
+ [nestedField]: value,
223
+ },
224
+ });
225
+ return newMap;
226
+ });
227
+ }
228
+
229
+ getCellContext(row: T, column: ColumnDefinition<T>, rowIndex: number) {
230
+ const rowId = row.id;
231
+ const isEditing = this.isRowEditing(rowId);
232
+ const editedData = this.editedDataSignal().get(rowId);
233
+
234
+ let value: any;
235
+ if (isEditing && editedData && column.id in editedData) {
236
+ value = editedData[column.id as keyof T];
237
+ } else {
238
+ value = this.getCellValue(row, column);
239
+ }
240
+
241
+ return {
242
+ $implicit: value,
243
+ value,
244
+ row,
245
+ column,
246
+ rowIndex,
247
+ isEditing,
248
+ isSelected: this.isRowSelected(rowId),
249
+ toggleRowSelection: () => this.toggleRowSelection(rowId),
250
+ toggleRowEdit: () => this.toggleRowEdit(rowId),
251
+ updateValue: (newValue: any) =>
252
+ this.updateEditedValue(rowId, column.id as keyof T, newValue),
253
+ updateNestedValue: (nestedField: string, newValue: any) =>
254
+ this.updateNestedEditedValue(
255
+ rowId,
256
+ column.id as keyof T,
257
+ nestedField,
258
+ newValue
259
+ ),
260
+ };
261
+ }
262
+
263
+ getFooterContext(column: ColumnDefinition<T>) {
264
+ return {
265
+ $implicit: column,
266
+ column,
267
+ data: this.data,
268
+ };
269
+ }
270
+
271
+ getRowStyle(rowIndex: number): BaseStyles {
272
+ if (this.styleConfig?.rows) {
273
+ const rowStyles = this.styleConfig.rows;
274
+
275
+ if ('even' in rowStyles && 'odd' in rowStyles) {
276
+ // TableSectionStyles
277
+ return (rowIndex % 2 === 0 ? rowStyles.even : rowStyles.odd) || {};
278
+ } else if ('nth' in rowStyles && rowStyles.nth) {
279
+ // TableSectionStyles with nth
280
+ const { interval, style } = rowStyles.nth;
281
+ return ((rowIndex + 1) % (interval || 1) === 0 ? style : {}) || {};
282
+ } else {
283
+ // TableStyles
284
+ return rowStyles as BaseStyles;
285
+ }
286
+ }
287
+ return {};
288
+ }
289
+ }
290
+
291
+ type EditedData<T> = {
292
+ [K in keyof T]?: T[K] extends object ? Partial<T[K]> : T[K];
293
+ };
294
+
295
+ // Default styles
296
+ const defaultTableStyles: TableStyles = {
297
+ border: '1px solid #e0e0e0',
298
+ borderRadius: '4px',
299
+ overflow: 'hidden',
300
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
301
+ width: '100%',
302
+ header: {
303
+ backgroundColor: '#f5f5f5',
304
+ fontWeight: 'bold',
305
+ color: '#333',
306
+ textAlign: 'left',
307
+ padding: '12px 16px',
308
+ borderBottom: '2px solid #e0e0e0',
309
+ },
310
+ rows: {
311
+ even: {
312
+ backgroundColor: '#ffffff',
313
+ },
314
+ odd: {
315
+ backgroundColor: '#f9f9f9',
316
+ },
317
+ nth: {
318
+ interval: 5,
319
+ style: {
320
+ backgroundColor: '#f0f0f0',
321
+ },
322
+ },
323
+ },
324
+ cells: {
325
+ padding: '12px 16px',
326
+ borderBottom: '1px solid #e0e0e0',
327
+ },
328
+ footer: {
329
+ backgroundColor: '#f5f5f5',
330
+ fontWeight: 'bold',
331
+ color: '#333',
332
+ textAlign: 'left',
333
+ padding: '12px 16px',
334
+ borderTop: '2px solid #e0e0e0',
335
+ },
336
+ };
@@ -0,0 +1,11 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { ColumnDirective } from './column.directive';
4
+ import { DataTableComponent } from './data-table.component';
5
+
6
+ @NgModule({
7
+ declarations: [DataTableComponent, ColumnDirective],
8
+ imports: [CommonModule],
9
+ exports: [DataTableComponent, ColumnDirective],
10
+ })
11
+ export class DataTableModule {}
@@ -0,0 +1,13 @@
1
+ import { TemplateRef } from '@angular/core';
2
+
3
+ export interface ColumnDefinition<T> {
4
+ id: string;
5
+ header: string | ((context: any) => any);
6
+ accessorKey?: keyof T;
7
+ accessorFn?: (row: T) => any;
8
+ cellTemplate?: TemplateRef<any>;
9
+ cellEditTemplate?: TemplateRef<any>;
10
+ headerTemplate?: TemplateRef<any>;
11
+ footerTemplate?: TemplateRef<any>;
12
+ sortAction?: 'ASC' | 'DESC';
13
+ }
@@ -0,0 +1,55 @@
1
+ export interface BaseStyles {
2
+ border?: string;
3
+ borderTop?: string;
4
+ borderBottom?: string;
5
+ borderLeft?: string;
6
+ borderRight?: string;
7
+ borderColor?: string;
8
+ borderStyle?: string;
9
+ borderWidth?: string;
10
+ height?: string;
11
+ maxHeight?: string;
12
+ minHeight?: string;
13
+ width?: string;
14
+ maxWidth?: string;
15
+ minWidth?: string;
16
+ background?: string;
17
+ backgroundColor?: string;
18
+ color?: string;
19
+ shadow?: string;
20
+ boxShadow?: string;
21
+ position?: string;
22
+ top?: string;
23
+ left?: string;
24
+ right?: string;
25
+ bottom?: string;
26
+ borderRadius?: string;
27
+ padding?: string;
28
+ margin?: string;
29
+ fontSize?: string;
30
+ fontWeight?: string;
31
+ textAlign?: string;
32
+ verticalAlign?: string;
33
+ cursor?: string;
34
+ overflow?: string;
35
+ zIndex?: string;
36
+ opacity?: string;
37
+ }
38
+
39
+ interface NTHStyles {
40
+ interval?: number;
41
+ style?: BaseStyles;
42
+ }
43
+
44
+ export interface TableSectionStyles {
45
+ even?: BaseStyles;
46
+ odd?: BaseStyles;
47
+ nth?: NTHStyles;
48
+ }
49
+
50
+ export interface TableStyles extends BaseStyles {
51
+ rows?: TableSectionStyles | BaseStyles;
52
+ cells?: TableSectionStyles | BaseStyles;
53
+ footer?: BaseStyles;
54
+ header?: BaseStyles;
55
+ }