@one-paragon/angular-utilities 2.8.3 → 2.8.4

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 (203) hide show
  1. package/karma.conf.js +43 -0
  2. package/ng-package.json +7 -0
  3. package/package.json +15 -27
  4. package/src/action-state/action-state-spinner/action-state-spinner.component.css +16 -0
  5. package/src/action-state/action-state-spinner/action-state-spinner.component.html +7 -0
  6. package/src/action-state/action-state-spinner/action-state-spinner.component.spec.ts +25 -0
  7. package/src/action-state/action-state-spinner/action-state-spinner.component.ts +26 -0
  8. package/src/action-state/action-state-ui/action-state-ui.module.ts +13 -0
  9. package/src/action-state/index.ts +8 -0
  10. package/src/action-state/ngrx-ext/ngrx-ext.module.ts +14 -0
  11. package/src/action-state/ngrx.ts +69 -0
  12. package/src/http-request-state/RequestStateFactory.ts +56 -0
  13. package/src/http-request-state/RequestStateStore.ts +360 -0
  14. package/src/http-request-state/deprecated.ts +20 -0
  15. package/src/http-request-state/directives/HttpStateDirectiveBase.ts +29 -0
  16. package/src/http-request-state/directives/http-error-state-directive.ts +21 -0
  17. package/src/http-request-state/directives/http-inProgress-state-directive.ts +19 -0
  18. package/src/http-request-state/directives/http-notStarted-state-directive.ts +19 -0
  19. package/src/http-request-state/directives/http-success-state-directive.ts +29 -0
  20. package/src/http-request-state/directives/index.ts +5 -0
  21. package/src/http-request-state/directives/request-state-directive.spec.ts +73 -0
  22. package/src/http-request-state/directives/request-state-directive.ts +78 -0
  23. package/src/http-request-state/documentation/CREATE-REQUESTOR.md +667 -0
  24. package/src/http-request-state/documentation/README.md +191 -0
  25. package/src/http-request-state/documentation/REQUEST-STATE-STORE-CONFIG.md +648 -0
  26. package/src/http-request-state/documentation/REQUESTOR.md +616 -0
  27. package/src/http-request-state/helpers.ts +30 -0
  28. package/src/http-request-state/http-state-module.ts +23 -0
  29. package/src/http-request-state/index.ts +7 -0
  30. package/src/http-request-state/models/view-context.ts +18 -0
  31. package/src/http-request-state/observable.spec.ts +43 -0
  32. package/src/http-request-state/request-state.ts +66 -0
  33. package/src/http-request-state/rxjs/getRequestorBody.ts +10 -0
  34. package/src/http-request-state/rxjs/getRequestorState.ts +8 -0
  35. package/src/http-request-state/rxjs/index.ts +4 -0
  36. package/src/http-request-state/rxjs/tapError.ts +16 -0
  37. package/src/http-request-state/rxjs/tapSuccess.ts +16 -0
  38. package/src/http-request-state/strategies.spec.ts +42 -0
  39. package/src/http-request-state/types.ts +54 -0
  40. package/src/ngrx/actionable-selector.ts +189 -0
  41. package/src/ngrx/index.ts +1 -0
  42. package/src/public-api.ts +40 -0
  43. package/src/rxjs/defaultShareReplay.ts +8 -0
  44. package/src/rxjs/index.ts +5 -0
  45. package/src/rxjs/mapError.ts +8 -0
  46. package/src/rxjs/rxjs-operators.ts +130 -0
  47. package/src/rxjs/subjectifier.ts +17 -0
  48. package/src/rxjs/subscriber.directive.ts +57 -0
  49. package/src/specs/clickSubject.spec.ts +99 -0
  50. package/src/specs/dialog.spec.ts +101 -0
  51. package/src/specs/toggleGroupDirective.spec.ts +229 -0
  52. package/src/table-builder/classes/DefaultSettings.ts +11 -0
  53. package/src/table-builder/classes/MatTableObservableDataSource.ts +23 -0
  54. package/src/table-builder/classes/TableBuilderConfig.ts +49 -0
  55. package/src/table-builder/classes/TableBuilderDataSource.ts +64 -0
  56. package/src/table-builder/classes/TableState.ts +96 -0
  57. package/src/table-builder/classes/data-store.ts +10 -0
  58. package/src/table-builder/classes/display-col.ts +5 -0
  59. package/src/table-builder/classes/filter-info.ts +129 -0
  60. package/src/table-builder/classes/table-builder-general-settings.ts +233 -0
  61. package/src/table-builder/classes/table-builder.ts +105 -0
  62. package/src/table-builder/classes/table-store.helpers.ts +109 -0
  63. package/src/table-builder/classes/table-store.ts +540 -0
  64. package/src/table-builder/components/array-column.component.ts +34 -0
  65. package/src/table-builder/components/column-builder/column-builder.component.html +109 -0
  66. package/src/table-builder/components/column-builder/column-builder.component.scss +43 -0
  67. package/src/table-builder/components/column-builder/column-builder.component.spec.ts +49 -0
  68. package/src/table-builder/components/column-builder/column-builder.component.ts +130 -0
  69. package/src/table-builder/components/column-builder/column-helpers.ts +54 -0
  70. package/src/table-builder/components/column-header-menu/column-header-menu.component.html +128 -0
  71. package/src/table-builder/components/column-header-menu/column-header-menu.component.scss +97 -0
  72. package/src/table-builder/components/column-header-menu/column-header-menu.component.ts +113 -0
  73. package/src/table-builder/components/date-filter/date-filter.component.html +39 -0
  74. package/src/table-builder/components/date-filter/date-filter.component.ts +33 -0
  75. package/src/table-builder/components/date-time-filter/date-time-filter.component.html +25 -0
  76. package/src/table-builder/components/date-time-filter/date-time-filter.component.ts +33 -0
  77. package/src/table-builder/components/filter/filter.component.html +120 -0
  78. package/src/table-builder/components/filter/filter.component.scss +60 -0
  79. package/src/table-builder/components/filter/filter.component.spec.ts +86 -0
  80. package/src/table-builder/components/filter/filter.component.ts +73 -0
  81. package/src/table-builder/components/filter/in-list/in-list-filter.component.ts +171 -0
  82. package/src/table-builder/components/gen-col-displayer/gen-col-displayer.component.html +60 -0
  83. package/src/table-builder/components/gen-col-displayer/gen-col-displayer.component.scss +57 -0
  84. package/src/table-builder/components/gen-col-displayer/gen-col-displayer.component.ts +44 -0
  85. package/src/table-builder/components/generic-table/generic-table.component.html +140 -0
  86. package/src/table-builder/components/generic-table/generic-table.component.scss +45 -0
  87. package/src/table-builder/components/generic-table/generic-table.component.ts +531 -0
  88. package/src/table-builder/components/generic-table/paginator.component.ts +125 -0
  89. package/src/table-builder/components/group-by-list/group-by-list.component.css +24 -0
  90. package/src/table-builder/components/group-by-list/group-by-list.component.html +21 -0
  91. package/src/table-builder/components/group-by-list/group-by-list.component.spec.ts +23 -0
  92. package/src/table-builder/components/group-by-list/group-by-list.component.ts +26 -0
  93. package/src/table-builder/components/in-filter/in-filter.component.css +22 -0
  94. package/src/table-builder/components/in-filter/in-filter.component.html +38 -0
  95. package/src/table-builder/components/in-filter/in-filter.component.ts +66 -0
  96. package/src/table-builder/components/index.ts +9 -0
  97. package/src/table-builder/components/initialization-component/initialization.component.html +78 -0
  98. package/src/table-builder/components/initialization-component/initialization.component.ts +28 -0
  99. package/src/table-builder/components/link-column.component.ts +42 -0
  100. package/src/table-builder/components/number-filter/number-filter.component.css +10 -0
  101. package/src/table-builder/components/number-filter/number-filter.component.html +32 -0
  102. package/src/table-builder/components/number-filter/number-filter.component.spec.ts +30 -0
  103. package/src/table-builder/components/number-filter/number-filter.component.ts +34 -0
  104. package/src/table-builder/components/profiles-menu/profiles-menu.component.html +77 -0
  105. package/src/table-builder/components/profiles-menu/profiles-menu.component.scss +126 -0
  106. package/src/table-builder/components/profiles-menu/profiles-menu.component.spec.ts +23 -0
  107. package/src/table-builder/components/profiles-menu/profiles-menu.component.ts +64 -0
  108. package/src/table-builder/components/reset-menu/reset-menu.component.css +3 -0
  109. package/src/table-builder/components/reset-menu/reset-menu.component.html +10 -0
  110. package/src/table-builder/components/reset-menu/reset-menu.component.ts +87 -0
  111. package/src/table-builder/components/scroll-strategy.ts +139 -0
  112. package/src/table-builder/components/sort-menu/sort-menu.component-store.ts +57 -0
  113. package/src/table-builder/components/sort-menu/sort-menu.component.html +115 -0
  114. package/src/table-builder/components/sort-menu/sort-menu.component.scss +119 -0
  115. package/src/table-builder/components/sort-menu/sort-menu.component.ts +88 -0
  116. package/src/table-builder/components/table-container/table-container.component.html +94 -0
  117. package/src/table-builder/components/table-container/table-container.component.scss +60 -0
  118. package/src/table-builder/components/table-container/table-container.component.ts +467 -0
  119. package/src/table-builder/components/table-container/table-container.helpers/data-state.helpers.ts +113 -0
  120. package/src/table-builder/components/table-container/table-container.helpers/filter-state.helpers.ts +125 -0
  121. package/src/table-builder/components/table-container/table-container.helpers/groupBy.helpers.ts +172 -0
  122. package/src/table-builder/components/table-container/table-container.helpers/meta-data.helpers.ts +19 -0
  123. package/src/table-builder/components/table-container/table-container.helpers/sort-state.helpers.ts +47 -0
  124. package/src/table-builder/components/table-container/tableProps.ts +21 -0
  125. package/src/table-builder/components/table-container/virtual-scroll-container.ts +216 -0
  126. package/src/table-builder/components/table-container-filter/filter-list/filter-list.component.html +42 -0
  127. package/src/table-builder/components/table-container-filter/filter-list/filter-list.component.ts +47 -0
  128. package/src/table-builder/components/table-container-filter/gen-filter-displayer/gen-filter-displayer.component.css +40 -0
  129. package/src/table-builder/components/table-container-filter/gen-filter-displayer/gen-filter-displayer.component.html +11 -0
  130. package/src/table-builder/components/table-container-filter/gen-filter-displayer/gen-filter-displayer.component.spec.ts +85 -0
  131. package/src/table-builder/components/table-container-filter/gen-filter-displayer/gen-filter-displayer.component.ts +35 -0
  132. package/src/table-builder/components/table-container-filter/table-wrapper-filter-store.ts +13 -0
  133. package/src/table-builder/components/table-header-menu/table-header-menu.component.css +21 -0
  134. package/src/table-builder/components/table-header-menu/table-header-menu.component.html +48 -0
  135. package/src/table-builder/components/table-header-menu/table-header-menu.component.ts +36 -0
  136. package/src/table-builder/directives/custom-cell-directive.ts +63 -0
  137. package/src/table-builder/directives/custom-header-directive.ts +16 -0
  138. package/src/table-builder/directives/group-row-directive.ts +91 -0
  139. package/src/table-builder/directives/index.ts +8 -0
  140. package/src/table-builder/directives/multi-sort.directive.spec.ts +124 -0
  141. package/src/table-builder/directives/multi-sort.directive.ts +58 -0
  142. package/src/table-builder/directives/resize-column.directive.ts +107 -0
  143. package/src/table-builder/directives/table-wrapper.directive.ts +13 -0
  144. package/src/table-builder/directives/tb-filter.directive.ts +376 -0
  145. package/src/table-builder/documentation/table-builder/CUSTOM-CELL.md +568 -0
  146. package/src/table-builder/documentation/table-builder/CUSTOM-GROUP-ROW.md +356 -0
  147. package/src/table-builder/documentation/table-builder/METADATA-DOCUMENTATION.md +517 -0
  148. package/src/table-builder/documentation/table-builder/STYLER-STYLE.md +228 -0
  149. package/src/table-builder/documentation/table-builder/TABLE-BUILDER-CONFIG.md +325 -0
  150. package/src/table-builder/documentation/table-builder/TABLE-BUILDER-SETTINGS.md +515 -0
  151. package/src/table-builder/documentation/table-builder/TABLE-BUILDER.md +430 -0
  152. package/src/table-builder/documentation/table-builder/TABLE-CONTAINER.md +628 -0
  153. package/src/table-builder/enums/filterTypes.ts +39 -0
  154. package/src/table-builder/functions/boolean-filter-function.ts +12 -0
  155. package/src/table-builder/functions/date-filter-function.ts +85 -0
  156. package/src/table-builder/functions/download-data.ts +11 -0
  157. package/src/table-builder/functions/null-filter-function.ts +9 -0
  158. package/src/table-builder/functions/number-filter-function.ts +47 -0
  159. package/src/table-builder/functions/sort-data-function.ts +80 -0
  160. package/src/table-builder/functions/string-filter-function.ts +59 -0
  161. package/src/table-builder/interfaces/ColumnInfo.ts +9 -0
  162. package/src/table-builder/interfaces/dictionary.ts +3 -0
  163. package/src/table-builder/interfaces/meta-data.ts +279 -0
  164. package/src/table-builder/ngrx/tableBuilderStateStore.ts +203 -0
  165. package/src/table-builder/pipes/column-total.pipe.ts +16 -0
  166. package/src/table-builder/pipes/format-filter-type.pipe.ts +12 -0
  167. package/src/table-builder/pipes/format-filter-value.pipe.ts +71 -0
  168. package/src/table-builder/pipes/key-display.ts +13 -0
  169. package/src/table-builder/services/all-values-filter-creator.service.ts +92 -0
  170. package/src/table-builder/services/export-to-csv.service.ts +117 -0
  171. package/src/table-builder/services/link-creator.service.ts +98 -0
  172. package/src/table-builder/services/table-template-service.ts +47 -0
  173. package/src/table-builder/services/transform-creator.ts +90 -0
  174. package/src/table-builder/specs/table-custom-filters.spec.ts +262 -0
  175. package/src/table-builder/styles/collapser.styles.scss +16 -0
  176. package/src/table-builder/table-builder.module.ts +42 -0
  177. package/src/table-builder/types/group-types.ts +42 -0
  178. package/src/table-builder/types/index.ts +1 -0
  179. package/src/test.ts +17 -0
  180. package/src/utilities/array-helpers.ts +13 -0
  181. package/src/utilities/directives/auto-focus.directive.ts +20 -0
  182. package/src/utilities/directives/clickEmitterDirective.ts +15 -0
  183. package/src/utilities/directives/clickSubject.ts +19 -0
  184. package/src/utilities/directives/conditional-classes.directive.ts +36 -0
  185. package/src/utilities/directives/dialog-service.ts +19 -0
  186. package/src/utilities/directives/dialog.ts +174 -0
  187. package/src/utilities/directives/mat-toggle-group-directive.ts +60 -0
  188. package/src/utilities/directives/prevent-enter.directive.ts +12 -0
  189. package/src/utilities/directives/stop-propagation.directive.ts +19 -0
  190. package/src/utilities/directives/styler.ts +45 -0
  191. package/src/utilities/directives/trim-whitespace.directive.ts +20 -0
  192. package/src/utilities/index.ts +22 -0
  193. package/src/utilities/module.ts +53 -0
  194. package/src/utilities/pipes/function.pipe.ts +21 -0
  195. package/src/utilities/pipes/phone.pipe.ts +20 -0
  196. package/src/utilities/pipes/space-case.pipes.spec.ts +47 -0
  197. package/src/utilities/pipes/space-case.pipes.ts +29 -0
  198. package/tsconfig.lib.json +20 -0
  199. package/tsconfig.lib.prod.json +10 -0
  200. package/tsconfig.spec.json +17 -0
  201. package/fesm2022/one-paragon-angular-utilities.mjs +0 -7328
  202. package/fesm2022/one-paragon-angular-utilities.mjs.map +0 -1
  203. package/types/one-paragon-angular-utilities.d.ts +0 -2197
@@ -0,0 +1,531 @@
1
+ import { Component, ChangeDetectionStrategy, ViewContainerRef, Injector, computed, inject, input, viewChild, effect, untracked, signal, output, linkedSignal } from '@angular/core';
2
+ import { MatFooterRowDef, MatHeaderRowDef, MatRowDef, MatTable, MatTableDataSource, MatTableModule } from '@angular/material/table';
3
+ import { SelectionChange, SelectionModel } from '@angular/cdk/collections';
4
+ import { TableStore } from '../../classes/table-store';
5
+ import { switchMap } from 'rxjs/operators';
6
+ import { ColumnBuilderComponent } from '../column-builder/column-builder.component';
7
+ import { Dictionary } from '../../interfaces/dictionary';
8
+ import { ColumnInfo } from '../../interfaces/ColumnInfo';
9
+ import { CdkDragDrop, CdkDropList, DragDropModule } from '@angular/cdk/drag-drop';
10
+ import { ConditionalClassesDirective, FunctionPipe, StopPropagationDirective, StylerDirective } from '../../../utilities';
11
+ import { MatCheckboxModule } from '@angular/material/checkbox';
12
+ import { MatButtonModule } from '@angular/material/button';
13
+ import { MatIconModule } from '@angular/material/icon';
14
+ import { formatNumber, NgTemplateOutlet } from '@angular/common';
15
+ import { MatTooltipModule } from '@angular/material/tooltip';
16
+ import { createTransformer } from '../../services/transform-creator';
17
+ import { initIndexSymbol } from '../table-container/table-container.helpers/sort-state.helpers';
18
+ import { parseTbSizeToPixels } from '../../classes/table-builder-general-settings';
19
+ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
20
+ import { DataStore } from '../../classes/data-store';
21
+ import { TableBuilderConfigToken } from '../../classes/TableBuilderConfig';
22
+ import { getAllGroupHeaderNamesByKeys, mapGroupHeader } from '../table-container/table-container.helpers/groupBy.helpers';
23
+ import { MatMenuModule } from "@angular/material/menu";
24
+ import { MatSlideToggle } from "@angular/material/slide-toggle";
25
+ import { FieldType } from '../../interfaces/meta-data';
26
+ import { FilterType } from '../../enums/filterTypes';
27
+ import { TableContainerComponent } from '..';
28
+ import { Group, isGroupHeader } from '../../types/group-types';
29
+
30
+ @Component({
31
+ selector: 'tb-generic-table',
32
+ templateUrl: './generic-table.component.html',
33
+ styleUrl: './generic-table.component.scss',
34
+ changeDetection: ChangeDetectionStrategy.OnPush,
35
+ imports: [
36
+ MatTableModule, DragDropModule, MatCheckboxModule, MatButtonModule, MatIconModule, NgTemplateOutlet,
37
+ MatTooltipModule, FunctionPipe, StylerDirective, ConditionalClassesDirective, MatMenuModule, MatSlideToggle,
38
+ StopPropagationDirective
39
+ ],
40
+ })
41
+ export class GenericTableComponent {
42
+ protected state = inject(TableStore);
43
+ protected dataStore = inject(DataStore);
44
+ private viewContainer = inject(ViewContainerRef);
45
+ private config = inject(TableBuilderConfigToken);
46
+ private _injector = inject(Injector);
47
+ private tableContainer = inject(TableContainerComponent, { optional: true });
48
+
49
+ smallFooter = 10
50
+
51
+ $headerRow = viewChild(MatHeaderRowDef);
52
+ $footerRow = viewChild(MatFooterRowDef);
53
+ $table = viewChild(MatTable);
54
+ $dropList = viewChild(CdkDropList);
55
+
56
+ selection$ = output<SelectionChange<any>>({ alias: 'selection' });
57
+
58
+ $displayData = input.required<any[]>({ alias: 'displayData' });
59
+ $displayDataLength = computed(() => this.$displayData().length);
60
+ $data = input.required<any[]>({ alias: 'data' });
61
+ $rows = input<MatRowDef<any>[]>([], { alias: 'rows' });
62
+ $columnInfos = input.required<ColumnInfo[]>({ alias: 'columnInfos' });
63
+ $dataSource = input.required<MatTableDataSource<any>>({ alias: 'dataSource' });
64
+
65
+
66
+ $keys = computed(() => {
67
+ const displayed = this.state.$orderedVisibleColumns();
68
+ const built = this.$columns();
69
+ const keys = displayed.filter(d => !!built[d]);
70
+ if(this.$hasSelectColumn()){
71
+ keys.unshift('select');
72
+ }
73
+ if(this.$hasIndexColumn()) {
74
+ keys.unshift('index');
75
+ }
76
+ return keys;
77
+ });
78
+ keys$ = toObservable(this.$keys);
79
+
80
+ $trackBy = input<string | undefined |((item: any) => any)>(undefined, { alias: 'trackBy' });
81
+ $trackByFunction = computed(() => {
82
+ const trackBy = this.$trackBy();
83
+ if(!trackBy) return (index:number, item: any) => isGroupHeader(item) ? `${item.level}-${item[initIndexSymbol]}` : item[initIndexSymbol];
84
+ return ((index:number, item: any) => {
85
+ if(isGroupHeader(item)) return `${item.level}-${item[initIndexSymbol]}`;
86
+ return typeof trackBy === 'function' ? trackBy(item) : item[trackBy];
87
+ });
88
+ });
89
+
90
+ $hasFooterMeta = computed(() => this.state.$metaDataArray().some(md => !!md.additional?.footer))
91
+ $hasCustomFooter = computed(() => this.$columnInfos()?.some(ci => !!ci.customCell?.columnDef?.footerCell));
92
+ $footerRowStyle = computed(() => {
93
+ const hasData = !!this.$displayDataLength();
94
+ const metaFooter = this.$hasFooterMeta();
95
+ const customFooter = this.$hasCustomFooter();
96
+ const hasSelectionColumn = this.state.$props().selectionColumn;
97
+ return customFooter || hasSelectionColumn || (hasData && metaFooter) ? 'regular-footer' : hasData ? 'no-footer' : 'small-footer';
98
+ });
99
+ $showFooterRow = computed(() => this.$footerRowStyle() !== 'no-footer');
100
+
101
+ injector = Injector.create({
102
+ providers: [
103
+ { provide: MatTable, useFactory: () => this.$table() },
104
+ { provide: CdkDropList, useFactory: () => this.$dropList() },
105
+ ],
106
+ parent: this._injector
107
+ });
108
+
109
+ $hasIndexColumn = computed(() => this.state.selectSignal(state => state.props.indexColumn)());
110
+ $columns = signal<Dictionary<ColumnBuilderComponent>>({});
111
+ $showHeader = computed(() => !this.state.$tableSettings().hideColumnHeader);
112
+
113
+ $paginatorHeight = signal(0);
114
+ r = new ResizeObserver(entries => this.$paginatorHeight.set(entries[0]?.contentRect.height || 0));
115
+ #rr = effect((cleanup) => {
116
+ const paginatorWrapper = this.tableContainer?.$paginatorWrapper();
117
+ if(!paginatorWrapper) return;
118
+ this.r.observe(paginatorWrapper.nativeElement);
119
+ cleanup(() => { this.r.disconnect(); });
120
+ });
121
+ $offset = this.dataStore.selectSignal( s => s.virtualScrollOffset);
122
+ $footerOffset = computed(() => this.$useVirtualScroll() ? this.$offset() : this.$paginatorHeight());
123
+
124
+ drop(event: CdkDragDrop<string[]>) {
125
+ this.state.setUserDefinedOrder({ newOrder: event.currentIndex, oldOrder: event.previousIndex })
126
+ }
127
+
128
+ #buildColumnsEffect = effect(() => {
129
+ const columnInfos = this.$columnInfos() || [];
130
+ const table = this.$table();
131
+ if(!table) return;
132
+
133
+ untracked(() => {
134
+
135
+ Object.entries(this.$columns()).forEach(([key, value]) => {
136
+ const columnInfo = columnInfos.find(ci => ci.metaData.key === key);
137
+ if(!columnInfo || columnInfo.customCell !== value.$customCell()) {
138
+ delete this.$columns()[key];
139
+ table?.removeColumnDef(value.$columnDef()!);
140
+ }
141
+ });
142
+
143
+ columnInfos.forEach(ci => this.buildColumn(ci))
144
+ })
145
+ });
146
+
147
+ #buildRowsEffect = effect(() => {
148
+ const table = this.$table();
149
+ const rows = this.$rows();
150
+ const keys = this.$keys();
151
+ if(!table) return;
152
+
153
+ untracked(() => {
154
+ rows.forEach(row => {
155
+ table.removeRowDef(row);
156
+ row.columns = keys;
157
+ table.addRowDef(row);
158
+ });
159
+ })
160
+ });
161
+
162
+ #buildHeaderRowEffect = effect(() => {
163
+ const headerRow = this.$headerRow();
164
+ const showHeader = this.$showHeader();
165
+ const table = this.$table();
166
+ untracked(() => {
167
+ if(headerRow && showHeader && table) table.addHeaderRowDef(headerRow);
168
+ else if(headerRow && table) table.removeHeaderRowDef(headerRow);
169
+ })
170
+ });
171
+
172
+ #buildFooterEffect = effect(() => {
173
+ const footerRow = this.$footerRow();
174
+ const showFooter = this.$showFooterRow();
175
+ const table = this.$table();
176
+
177
+ untracked(() => {
178
+ if(footerRow && showFooter && table) table.addFooterRowDef(footerRow);
179
+ else if(footerRow && table) table.removeFooterRowDef(footerRow);
180
+ })
181
+ });
182
+
183
+ $usePaginator = computed(() => this.state.$tableSettings().usePaginator);
184
+ $useVirtualScroll = computed(() => this.state.$viewType().includes('virtual'));
185
+ $virtualStart = this.dataStore.selectSignal(d => d.virtualEnds.start);
186
+ $offsetIndex = computed(() => {
187
+ const virtualStart = this.$virtualStart();
188
+ const pageSize = this.state.$pageSize();
189
+ const currentPage = this.state.$currentPage();
190
+ if(this.$useVirtualScroll()){
191
+ return virtualStart;
192
+ }
193
+ if(this.$usePaginator()){
194
+ return pageSize * currentPage;
195
+ }
196
+ return 0;
197
+ });
198
+
199
+ rowClicked(t: any, event?: MouseEvent){
200
+ const tbs = this.state.$tableSettings();
201
+ if(tbs.rowClick) tbs.rowClick(t, event);
202
+ }
203
+
204
+ isGroupHeader(_: number, row: { isGroupHeader: boolean }) {
205
+ return row.isGroupHeader;
206
+ }
207
+
208
+ setExpanded(key: string, groupUniqueName: string, isExpanded: boolean): void {
209
+ if(!isExpanded && this.state.$expandedPropForKeyIsAll(key)()) {
210
+ const groupHeaders = getAllGroupHeaderNamesByKeys(this.$displayData(), [key]);
211
+ this.state.expandOfGroup(groupHeaders.map(g => ({ groupKey: g.groupKey, uniqueNameOfHeadersToExpand: g.headers })));
212
+ }
213
+ this.state.updateExpandedGroups({ key, isExpanded, groupUniqueName });
214
+ }
215
+
216
+ buildColumn(column: ColumnInfo) {
217
+ const alreadyBuiltColumn = this.$columns()[column.metaData.key];
218
+ if(alreadyBuiltColumn) {
219
+ alreadyBuiltColumn.setMetaData(column.metaData);
220
+ } else {
221
+ const component = this.viewContainer.createComponent(ColumnBuilderComponent, {
222
+ index: 0,
223
+ injector: this.injector
224
+ });
225
+ component.instance.$customCell.set(column.customCell);
226
+ component.instance.$customHeader.set(column.customHeader);
227
+ component.instance.setMetaData(column.metaData);
228
+ component.instance.$data = this.$data;
229
+ component.instance.whenViewInited(() => {
230
+ this.$columns.update(columnsDict => ({
231
+ ...columnsDict,
232
+ [column.metaData.key]: component.instance
233
+ }));
234
+ });
235
+ }
236
+ }
237
+
238
+ $hasSelectColumn = computed(() => this.state.selectSignal(state => state.props.selectionColumn)());
239
+ $selection = computed(() => {
240
+ const trackBy = this.$trackBy();
241
+ if(trackBy){
242
+ const trackByFn = typeof trackBy === 'function' ? trackBy : (item: any) => item[trackBy];
243
+ const model = new SelectionModel<any>(true, [], true, (a, b) => trackByFn(a) === trackByFn(b));
244
+ untracked(() => {
245
+ this.checkForPreviousSelection(model);
246
+ });
247
+ return model;
248
+ }
249
+ const model = new SelectionModel<any>(true, []);
250
+ untracked(() => {
251
+ this.checkForPreviousSelection(model);
252
+ });
253
+ return model;
254
+ });
255
+ checkForPreviousSelection(m: SelectionModel<any>){
256
+ const had = this.tableContainer?._selection$.getValue();
257
+ if(had && had.source.selected.length){
258
+ m.clear();
259
+ m.select(...had.source.selected);
260
+ return;
261
+ }
262
+ }
263
+ selectionChange$ = toObservable(this.$selection).pipe(switchMap(s => s.changed));
264
+ $selectionChange = toSignal(this.selectionChange$);
265
+ onSelectionChangeEffect = effect(() => {
266
+ const selectionChange = this.$selectionChange();
267
+ if(!selectionChange) return ;
268
+ untracked(() => this.selection$.emit(selectionChange))
269
+ });
270
+ $isAllSelected = computed(() => {
271
+ this.$selectionChange();
272
+ const selected = this.$selection()?.selected;
273
+ if(!selected?.length) return false;
274
+ return this.$selectableData()?.length === selected.length;
275
+ });
276
+ $masterToggleChecked = this.$isAllSelected;
277
+ $masterToggleIndeterminate = computed(() => {
278
+ this.$selectionChange();
279
+ return !!this.$selection()?.selected.length && !this.$masterToggleChecked()});
280
+
281
+ $paginated = computed(() => this.state.$viewType() === 'virtual paginator' || this.state.$viewType() === 'paginator');
282
+ $selectableData = computed(() => {
283
+ const isGrouped = !!this.state.$groupByData().length;
284
+ this.state.$expandGroups();
285
+ if(this.$paginated()){
286
+ const previousPageRecords = this.state.$currentPage() * this.state.$pageSize();
287
+ if(isGrouped){
288
+ const onScreen = this.$dataSource().data.slice(previousPageRecords, previousPageRecords + this.state.$pageSize());
289
+ const nested = onScreen.flatMap(
290
+ (group: Group) => (group.isGroupHeader && !this.state.$getIsExpanded(group.key, group.uniqueName)()) ? mapGroupHeader(group, true) : []
291
+ );
292
+ return onScreen.concat(nested).filter(row => !row.isGroupHeader);
293
+ }
294
+ return this.$data().slice(previousPageRecords, previousPageRecords + this.state.$pageSize());
295
+ }
296
+ else if(this.state.$viewType() === 'all' || this.state.$viewType() === 'virtual all'){
297
+ return this.$data();
298
+ }
299
+ return [];
300
+ });
301
+
302
+ $selectAllMessage = computed(() => {
303
+ if(this.$isAllSelected()) return `Deselect all ${this.$selection().selected.length} selected`;
304
+ const selectable = formatNumber(this.$selectableData().length, 'en-US');
305
+ let message = `Select all ${selectable}`;
306
+ if(this.$paginated()){
307
+ message += ' on this page';
308
+ }
309
+ return message;
310
+ })
311
+
312
+ #onSelectableDataChangeEffect = effect(() => {
313
+ const selectableData = this.$selectableData();
314
+ untracked(() => {
315
+ const selected = this.$selection()?.selected;
316
+ if(!selected.length) return;
317
+ const trackBy = this.$trackBy();
318
+ const trackByFunc = trackBy ?
319
+ (s) => {
320
+ const trackByFn = typeof trackBy === 'function' ? trackBy : (item: any) => item[trackBy];
321
+ return selectableData.every(d => trackByFn(d) !== trackByFn(s))}
322
+ : s => !selectableData.includes(s);
323
+ const removed = selected.filter(trackByFunc);
324
+ this.$selection().deselect(...removed)
325
+ })
326
+ });
327
+
328
+ /** Selects all rows if they are not all selected; otherwise clear selection. */
329
+ masterToggle() {
330
+ if(this.$isAllSelected()){
331
+ this.$selection().clear()
332
+ } else {
333
+ this.$selection().select(...this.$selectableData());
334
+ }
335
+ }
336
+
337
+ selectTop(size: number){
338
+ const pageSize = this.state.$pageSize();
339
+ const all = this.state.$showAll();
340
+
341
+ if(size > pageSize && !all){
342
+ setTimeout(() => {
343
+ this.state.setUserDefinedShowAll(true);
344
+ }, 2);
345
+ }
346
+ this.$selection().clear();
347
+ this.$selection().select(...this.$data().slice(0, size));
348
+ }
349
+
350
+ $tableWidth = linkedSignal<number | undefined, { width: string }>({
351
+ source: this.state.$getUserDefinedTableWidth,
352
+ computation: (currentUserDefinedWidth, { source: previousUserDefinedWidth } = { value: null as any, source: 0 as any }) => {
353
+ if( currentUserDefinedWidth){
354
+ return ({ width: `${currentUserDefinedWidth}px`, minWidth: 'initial' });
355
+ } if( wasReset() ){
356
+ return ({ width: 'initial' });
357
+ }
358
+ return ({} as { width: string });
359
+
360
+ function wasReset(){
361
+ return (previousUserDefinedWidth ?? 0) >= 0 && currentUserDefinedWidth == null;
362
+ }
363
+ }
364
+ });
365
+
366
+ getGroupHeaderTransform = (key: string, val: string) => computed(() => {
367
+ if(val == undefined || val === 'null') return 'BLANK';
368
+ try {
369
+ const meta = this.state.$getMetaData(key)();
370
+ if(meta.groupByLogic?.groupByHeader){
371
+ return meta.groupByLogic.groupByHeader(val);
372
+ }
373
+ return createTransformer(this.state.$getMetaData(key)(), this.config, true, true)({ [`${key}`]: val }) || val;
374
+ } catch (error) {
375
+ return val;
376
+ }
377
+ })
378
+
379
+ $rowHeight = computed(() => {
380
+ if(this.state.$userDefinedRowHeight()){
381
+ return this.state.$userDefinedRowHeight() + 'px';
382
+ }
383
+ const notPersistedTableSettings = this.state.$notPersistedTableSettings();
384
+ if(this.state.$isVirtual() && notPersistedTableSettings.virtualSettings?.enforceRowHeight){
385
+ const height = notPersistedTableSettings.virtualSettings!.rowHeight;
386
+ return parseTbSizeToPixels(height) + 'px';
387
+ }
388
+ if(notPersistedTableSettings.rowHeight){
389
+ return parseTbSizeToPixels(notPersistedTableSettings.rowHeight) + 'px';
390
+ }
391
+ return undefined;
392
+ });
393
+
394
+ $headerHeight = computed(() => {
395
+ if(this.state.$userDefinedHeaderHeight()){
396
+ return this.state.$userDefinedHeaderHeight() + 'px';
397
+ }
398
+ const notPersistedTableSettings = this.state.$notPersistedTableSettings();
399
+ if(this.state.$isVirtual() && notPersistedTableSettings.virtualSettings?.enforceHeaderHeight){
400
+ const height = notPersistedTableSettings.virtualSettings!.headerHeight;
401
+ return parseTbSizeToPixels(height) + 'px';
402
+ }
403
+ if(notPersistedTableSettings.headerHeight){
404
+ return parseTbSizeToPixels(notPersistedTableSettings.headerHeight) + 'px';
405
+ }
406
+ return undefined;
407
+ });
408
+
409
+ $groupHeaderHeight = computed(() => {
410
+ const groupHeaderHeight = this.state.$notPersistedTableSettings().groupHeaderHeight;
411
+ if(groupHeaderHeight){
412
+ return parseTbSizeToPixels(groupHeaderHeight) + 'px';
413
+ }
414
+ return this.$rowHeight();
415
+ });
416
+
417
+ $footerHeight = computed(() => {
418
+ const footerStyle = this.$footerRowStyle();
419
+ switch (footerStyle) {
420
+ case 'regular-footer':
421
+ return this.$rowHeight();
422
+ case 'small-footer':
423
+ return `${this.smallFooter}px`;
424
+ default:
425
+ return '0px';
426
+ }
427
+ });
428
+
429
+ $stickyFooter = computed(() => this.state.$props().stickyFooter || this.state.$isVirtual());
430
+
431
+ $rowStyles = computed(() => this.state.$tableSettings().rowStyles || {});
432
+ $rowClasses = computed(() => this.state.$tableSettings().rowClasses || {});
433
+
434
+ allOfGroupSelected = (uniqueName: string) => computed(() => {
435
+ //make sure signal is marked dirty when selection or over all data changes
436
+ this.$selectionChange();
437
+ this.$data();
438
+ const header = this.$dataSource().data.find(d => isGroupHeader(d) && d.uniqueName === uniqueName);
439
+ const children = mapGroupHeader(header, true).filter(i => !isGroupHeader(i));
440
+ if(!children.length) return { containsAll: false, containsSome: false, length: 0 };
441
+ let containsAll = true;
442
+ let containsSome = false;
443
+ for(var a of children){
444
+ const contains = this.$selection().isSelected(a);
445
+ if(contains) containsSome = true;
446
+ else containsAll = false;
447
+ if(!containsAll && containsSome) break;
448
+ }
449
+ return { containsAll, containsSome, length: children.length };
450
+ });
451
+
452
+ toggleGroup = (uniqueName: string, allSelected: boolean) => {
453
+ const header = this.$dataSource().data.find(d => isGroupHeader(d) && d.uniqueName === uniqueName);
454
+ const children = mapGroupHeader(header, true).filter(i => !isGroupHeader(i));
455
+ if(allSelected){
456
+ this.$selection().deselect(...children)
457
+ } else {
458
+ this.$selection().select(...children);
459
+ }
460
+ }
461
+
462
+ toggleGroupMessage = (amountOfItems: number, allSelected: boolean) => {
463
+ if(allSelected) return `Deselect all in this group`;
464
+ const selectable = formatNumber(amountOfItems, 'en-US');
465
+ return `Select all ${selectable} in this group`;
466
+ }
467
+
468
+ getCustomGroupRowTemplate = (groupingKey: string) => computed(() => {
469
+ const customGroupRows = this.state.$props().customGroupRows || [];
470
+
471
+ // Find the best matching custom group row template
472
+ // First, look for exact key matches, then for global templates (null key)
473
+ const exactMatches = customGroupRows.filter(cgr => {
474
+ if(!cgr.appliesTo(groupingKey)) return false;
475
+ const key = cgr.$groupingKey();
476
+ return key === groupingKey || (Array.isArray(key) && key.includes(groupingKey));
477
+ });
478
+ const globalMatches = customGroupRows.filter(cgr => cgr.appliesTo(groupingKey) && cgr.$groupingKey() === null);
479
+
480
+ // Sort by priority (higher priority first)
481
+ const allMatches = [...exactMatches, ...globalMatches].sort((a, b) => b.$priority() - a.$priority());
482
+
483
+ return allMatches[0]?.template();
484
+ });
485
+
486
+ addSelectFilter(e: boolean){
487
+ if(!e){
488
+ this.state.removeFilter(this.$selectFilterKey()!);
489
+ return;
490
+ }
491
+ this.state.addFilter({
492
+ fieldType: FieldType.Boolean,
493
+ filterId: `header-column-select-${crypto.randomUUID()}`,
494
+ filterValue: true,
495
+ filterType: FilterType.Custom,
496
+ key: 'tb_select',
497
+ predicate: (item: any) => this.$selection().isSelected(item),
498
+ active: e,
499
+ chipLabel: 'Show Only Selected',
500
+ notSavable: true,
501
+ });
502
+ }
503
+ $selectFilterKey = computed(() => {
504
+ const selectFilter = Object.keys(this.state.$filters()).find(key => key.startsWith('header-column-select'));
505
+ return selectFilter;
506
+ });
507
+
508
+ $hasSelectFilter = computed(() => !!this.$selectFilterKey());
509
+
510
+ _onSelect = effect(() => {
511
+ const selectionChange = this.$selectionChange();
512
+ untracked(() => {
513
+ const selectFilterKey = this.$selectFilterKey();
514
+ if(!selectionChange || !selectFilterKey || this.$isAllSelected()) return;
515
+ this.state.removeFilter(selectFilterKey);
516
+ this.addSelectFilter(true);
517
+ });
518
+ });
519
+
520
+ $indexColumnWidth = computed(() => {
521
+ const indexColumnWidth = this.state.$notPersistedTableSettings()?.indexColumnWidth;
522
+ if (indexColumnWidth) return { flex: `0 0 ${parseTbSizeToPixels(indexColumnWidth) + 'px'}`, maxWidth: 'none' } ;
523
+ return undefined;
524
+ });
525
+
526
+ $selectionColumnWidth = computed(() => {
527
+ const selectionColumnWidth = this.state.$notPersistedTableSettings()?.selectionColumnWidth;
528
+ if (selectionColumnWidth) return { flex: `0 0 ${parseTbSizeToPixels(selectionColumnWidth) + 'px'}`, maxWidth: 'none' } ;
529
+ return undefined;
530
+ });
531
+ }
@@ -0,0 +1,125 @@
1
+ import { Component, ChangeDetectionStrategy, computed, inject, viewChild, effect, untracked } from '@angular/core';
2
+ import { MatPaginator, MatPaginatorModule, PageEvent } from '@angular/material/paginator';
3
+ import { switchMap } from 'rxjs/operators';
4
+ import { TableStore } from '../../classes/table-store';
5
+ import { MatButtonModule } from '@angular/material/button';
6
+ import { DataStore } from '../../classes/data-store';
7
+ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
8
+ import { notNull } from '../../../rxjs';
9
+ import { InitializationState } from '../../classes/TableState';
10
+
11
+ @Component({
12
+ selector: 'tb-paginator',
13
+ imports: [MatPaginatorModule, MatButtonModule],
14
+ template: `
15
+ <div class="paginator-row">
16
+ @if($currentPageData(); as pageData)
17
+ {
18
+ <div [class.page-amounts]="true">
19
+ @if(!$showAll() && $collapseFooter())
20
+ {
21
+ <span>
22
+ {{pageData.currentStart}} - {{pageData.currentEnd}} of
23
+ </span>
24
+ }
25
+ @if($showAll() || $collapseFooter())
26
+ {
27
+ {{pageData.total}}
28
+ }
29
+ </div>
30
+ }
31
+ <mat-paginator [pageSizeOptions]="[5, 10, 20, 50, 100, 500]" showFirstLastButtons
32
+ [class]="{ 'hide' : $collapseFooter() || $showAll() }">
33
+ </mat-paginator>
34
+ @if ($showAllOption() && !$collapseFooter())
35
+ {
36
+ <button mat-button (click)="showAll()">
37
+ <span [style.text-decoration]="$showAll() ? 'line-through' : ''" >All</span>
38
+ </button>
39
+ }
40
+ </div>
41
+ `,
42
+ styleUrls: ['./generic-table.component.scss', '../../styles/collapser.styles.scss'],
43
+ changeDetection: ChangeDetectionStrategy.OnPush,
44
+ })
45
+ export class PaginatorComponent {
46
+ private state = inject(TableStore);
47
+ private data = inject(DataStore);
48
+ $paginator = viewChild(MatPaginator);
49
+
50
+ $dataLength = this.data.selectSignal(d => d.sortedFilteredDataLength);
51
+ $viewableDataLength = this.data.selectSignal(d => d.sortedFilteredGroupedDataLength);
52
+ pageEvent$ = toObservable(this.$paginator).pipe(notNull(), switchMap(p => p.page));
53
+ $pageEvent = toSignal(this.pageEvent$);
54
+ $pageIndexChangeEvent = computed(() => this.$pageEvent()?.pageIndex);
55
+ $pageSizeChangeEvent = computed(() => this.$pageEvent()?.pageSize);
56
+ $currentPageData = computed(() => {
57
+ const pageEvent = this.$pageEvent();
58
+ if(!pageEvent) return;
59
+ const paginator = this.$paginator()!;
60
+ // Reset because initial page event does not have page length
61
+ pageEvent.pageIndex = paginator.pageIndex;
62
+ pageEvent.pageSize = paginator.pageSize;
63
+ pageEvent.length = paginator.length;
64
+ const pageDetails = mapPaginationEventToCurrentPageDetails(pageEvent);
65
+ const dataLength = this.$dataLength();
66
+ return ({ ...pageDetails, total: dataLength })
67
+ })
68
+
69
+ #onPageIndexEffect = effect(() => {
70
+ const index = this.$pageIndexChangeEvent();
71
+ if(index === undefined) return;
72
+ untracked(() => this.state.setCurrentPage(index))
73
+ });
74
+ #onPageSizeEffect = effect(() => {
75
+ const size = this.$pageSizeChangeEvent();
76
+ const initialized = this.state.$initializationState() >= InitializationState.Ready;
77
+ if(!size || !initialized) return;
78
+ untracked(() => this.state.setUserDefinedPageSize(size));
79
+ });
80
+ #onMetaPageSizeEffect = effect(() => {
81
+ const paginator = this.$paginator();
82
+ if(!paginator) return;
83
+ const metaPageSize = this.state.$pageSize();
84
+ const initialized = this.state.$initializationState() >= InitializationState.Ready;
85
+ untracked(() => initialized && paginator._changePageSize(metaPageSize));
86
+ });
87
+ onDataLengthEffect = effect(() => {
88
+ const paginator = this.$paginator();
89
+ const dataLength = this.$viewableDataLength();
90
+ untracked(() => {
91
+ if(paginator){
92
+ paginator.length = dataLength;
93
+ const maxIndex = Math.max(0, Math.ceil(dataLength / paginator.pageSize) - 1);
94
+ if(paginator.pageIndex > maxIndex){
95
+ paginator.pageIndex = maxIndex;
96
+ this.state.setCurrentPage(maxIndex);
97
+ }
98
+ }
99
+ })
100
+ });
101
+ $collapseFooter = computed(() => this.state.selectSignal(state => state.persistedTableSettings.collapseFooter)());
102
+ $showAllOption = this.state.selectSignal(s => s.notPersistedTableSettings?.paginatorSettings?.includeAllInOptions);
103
+ $showAll = this.state.$showAll;
104
+ showAll(){
105
+ const showAll = !this.$showAll();
106
+ this.state.setUserDefinedShowAll(showAll);
107
+ if(showAll){
108
+ this.state.toggleCollapseFooter({ collapseFooter: true });
109
+ }
110
+ }
111
+ }
112
+
113
+ const mapPaginationEventToCurrentPageDetails = (pageData: PageEvent):CurrentPageDetails => {
114
+ return ({
115
+ currentStart: (pageData.pageIndex * pageData.pageSize) + 1,
116
+ currentEnd: Math.min(pageData.length, ((pageData.pageIndex + 1) * pageData.pageSize)),
117
+ total: pageData.length
118
+ })};
119
+
120
+
121
+ interface CurrentPageDetails {
122
+ currentStart:number,
123
+ currentEnd:number,
124
+ total:number
125
+ }
@@ -0,0 +1,24 @@
1
+ .tb-group-label {
2
+ font-weight: bolder;
3
+ padding-right: 15px;
4
+ }
5
+ :host{
6
+ display: flex;
7
+ padding-left: 8px;
8
+ align-items: center;
9
+ }
10
+ :host mat-chip-set mat-chip{
11
+ margin: 0;
12
+ }
13
+ :host mat-chip-set .nested-arrow{
14
+ display: flex;
15
+ width: 15px;
16
+ justify-content: center;
17
+ }
18
+ .chip-c{
19
+ display: flex;
20
+ align-items: center;
21
+ }
22
+ .sort-icon{
23
+ cursor: pointer;
24
+ }