@memberjunction/ng-entity-viewer 2.132.0 → 3.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 (42) hide show
  1. package/README.md +317 -124
  2. package/dist/lib/entity-cards/entity-cards.component.js +2 -2
  3. package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
  4. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +793 -0
  5. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -0
  6. package/dist/lib/entity-data-grid/entity-data-grid.component.js +3781 -0
  7. package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -0
  8. package/dist/lib/entity-data-grid/events/grid-events.d.ts +398 -0
  9. package/dist/lib/entity-data-grid/events/grid-events.d.ts.map +1 -0
  10. package/dist/lib/entity-data-grid/events/grid-events.js +556 -0
  11. package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -0
  12. package/dist/lib/entity-data-grid/models/grid-types.d.ts +437 -0
  13. package/dist/lib/entity-data-grid/models/grid-types.d.ts.map +1 -0
  14. package/dist/lib/entity-data-grid/models/grid-types.js +37 -0
  15. package/dist/lib/entity-data-grid/models/grid-types.js.map +1 -0
  16. package/dist/lib/entity-grid/entity-grid.component.js +1 -1
  17. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +2 -2
  18. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
  19. package/dist/lib/entity-viewer/entity-viewer.component.d.ts +136 -2
  20. package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
  21. package/dist/lib/entity-viewer/entity-viewer.component.js +321 -94
  22. package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
  23. package/dist/lib/pagination/pagination.component.js +2 -2
  24. package/dist/lib/pagination/pagination.component.js.map +1 -1
  25. package/dist/lib/pill/pill.component.js +2 -2
  26. package/dist/lib/pill/pill.component.js.map +1 -1
  27. package/dist/lib/types.d.ts +14 -31
  28. package/dist/lib/types.d.ts.map +1 -1
  29. package/dist/lib/types.js.map +1 -1
  30. package/dist/lib/view-config-panel/view-config-panel.component.d.ts +363 -0
  31. package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -0
  32. package/dist/lib/view-config-panel/view-config-panel.component.js +2006 -0
  33. package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -0
  34. package/dist/module.d.ts +16 -13
  35. package/dist/module.d.ts.map +1 -1
  36. package/dist/module.js +25 -15
  37. package/dist/module.js.map +1 -1
  38. package/dist/public-api.d.ts +4 -1
  39. package/dist/public-api.d.ts.map +1 -1
  40. package/dist/public-api.js +6 -1
  41. package/dist/public-api.js.map +1 -1
  42. package/package.json +15 -11
@@ -0,0 +1,3781 @@
1
+ import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
2
+ import { trigger, transition, style, animate } from '@angular/animations';
3
+ import { Subject } from 'rxjs';
4
+ import { debounceTime, takeUntil } from 'rxjs/operators';
5
+ import { BaseEntity, RunView, RunViewParams, Metadata } from '@memberjunction/core';
6
+ import { ViewInfo, UserViewEngine, UserInfoEngine } from '@memberjunction/core-entities';
7
+ import { ModuleRegistry, AllCommunityModule, themeAlpine } from 'ag-grid-community';
8
+ // Use the existing HighlightUtil from entity-viewer package
9
+ import { HighlightUtil } from '../utils/highlight.util';
10
+ import { DEFAULT_VISUAL_CONFIG } from './models/grid-types';
11
+ import { BeforeRowSelectEventArgs, AfterRowSelectEventArgs, BeforeRowDeselectEventArgs, AfterRowDeselectEventArgs, BeforeRowClickEventArgs, AfterRowClickEventArgs, BeforeRowDoubleClickEventArgs, AfterRowDoubleClickEventArgs, BeforeDataLoadEventArgs, AfterDataLoadEventArgs, BeforeDataRefreshEventArgs, AfterDataRefreshEventArgs, BeforeSortEventArgs, AfterSortEventArgs } from './events/grid-events';
12
+ import * as i0 from "@angular/core";
13
+ import * as i1 from "@memberjunction/ng-export-service";
14
+ import * as i2 from "@angular/common";
15
+ import * as i3 from "ag-grid-angular";
16
+ import * as i4 from "@memberjunction/ng-shared-generic";
17
+ const _c0 = ["gridContainer"];
18
+ function EntityDataGridComponent_div_2_div_2_button_3_Template(rf, ctx) { if (rf & 1) {
19
+ const _r4 = i0.ɵɵgetCurrentView();
20
+ i0.ɵɵelementStart(0, "button", 36);
21
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_2_button_3_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.FilterText = ""); });
22
+ i0.ɵɵelement(1, "i", 37);
23
+ i0.ɵɵelementEnd();
24
+ } }
25
+ function EntityDataGridComponent_div_2_div_2_Template(rf, ctx) { if (rf & 1) {
26
+ const _r2 = i0.ɵɵgetCurrentView();
27
+ i0.ɵɵelementStart(0, "div", 32);
28
+ i0.ɵɵelement(1, "i", 33);
29
+ i0.ɵɵelementStart(2, "input", 34);
30
+ i0.ɵɵlistener("input", function EntityDataGridComponent_div_2_div_2_Template_input_input_2_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.FilterText = $event.target.value); });
31
+ i0.ɵɵelementEnd();
32
+ i0.ɵɵtemplate(3, EntityDataGridComponent_div_2_div_2_button_3_Template, 2, 0, "button", 35);
33
+ i0.ɵɵelementEnd();
34
+ } if (rf & 2) {
35
+ const ctx_r2 = i0.ɵɵnextContext(2);
36
+ i0.ɵɵadvance(2);
37
+ i0.ɵɵproperty("placeholder", ctx_r2.ToolbarConfig.searchPlaceholder || "Search...")("value", ctx_r2.FilterText);
38
+ i0.ɵɵadvance();
39
+ i0.ɵɵproperty("ngIf", ctx_r2.FilterText);
40
+ } }
41
+ function EntityDataGridComponent_div_2_ng_container_3_button_1_i_1_Template(rf, ctx) { if (rf & 1) {
42
+ i0.ɵɵelement(0, "i");
43
+ } if (rf & 2) {
44
+ const button_r6 = i0.ɵɵnextContext(2).$implicit;
45
+ i0.ɵɵclassMap(button_r6.icon);
46
+ } }
47
+ function EntityDataGridComponent_div_2_ng_container_3_button_1_span_2_Template(rf, ctx) { if (rf & 1) {
48
+ i0.ɵɵelementStart(0, "span");
49
+ i0.ɵɵtext(1);
50
+ i0.ɵɵelementEnd();
51
+ } if (rf & 2) {
52
+ const button_r6 = i0.ɵɵnextContext(2).$implicit;
53
+ i0.ɵɵadvance();
54
+ i0.ɵɵtextInterpolate(button_r6.text);
55
+ } }
56
+ function EntityDataGridComponent_div_2_ng_container_3_button_1_Template(rf, ctx) { if (rf & 1) {
57
+ const _r5 = i0.ɵɵgetCurrentView();
58
+ i0.ɵɵelementStart(0, "button", 39);
59
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_ng_container_3_button_1_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r5); const button_r6 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onToolbarButtonClick(button_r6)); });
60
+ i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_ng_container_3_button_1_i_1_Template, 1, 2, "i", 40)(2, EntityDataGridComponent_div_2_ng_container_3_button_1_span_2_Template, 2, 1, "span", 41);
61
+ i0.ɵɵelementEnd();
62
+ } if (rf & 2) {
63
+ const button_r6 = i0.ɵɵnextContext().$implicit;
64
+ const ctx_r2 = i0.ɵɵnextContext(2);
65
+ i0.ɵɵclassMap(button_r6.cssClass);
66
+ i0.ɵɵproperty("disabled", ctx_r2.isButtonDisabled(button_r6))("title", button_r6.tooltip || "");
67
+ i0.ɵɵadvance();
68
+ i0.ɵɵproperty("ngIf", button_r6.icon);
69
+ i0.ɵɵadvance();
70
+ i0.ɵɵproperty("ngIf", button_r6.text);
71
+ } }
72
+ function EntityDataGridComponent_div_2_ng_container_3_Template(rf, ctx) { if (rf & 1) {
73
+ i0.ɵɵelementContainerStart(0);
74
+ i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_ng_container_3_button_1_Template, 3, 6, "button", 38);
75
+ i0.ɵɵelementContainerEnd();
76
+ } if (rf & 2) {
77
+ const button_r6 = ctx.$implicit;
78
+ const ctx_r2 = i0.ɵɵnextContext(2);
79
+ i0.ɵɵadvance();
80
+ i0.ɵɵproperty("ngIf", button_r6.position !== "right" && ctx_r2.isButtonVisible(button_r6));
81
+ } }
82
+ function EntityDataGridComponent_div_2_span_5_Template(rf, ctx) { if (rf & 1) {
83
+ i0.ɵɵelementStart(0, "span", 42);
84
+ i0.ɵɵtext(1);
85
+ i0.ɵɵelementEnd();
86
+ } if (rf & 2) {
87
+ const ctx_r2 = i0.ɵɵnextContext(2);
88
+ i0.ɵɵadvance();
89
+ i0.ɵɵtextInterpolate2(" ", ctx_r2.totalRowCount, " ", ctx_r2.totalRowCount === 1 ? "row" : "rows", " ");
90
+ } }
91
+ function EntityDataGridComponent_div_2_span_6_Template(rf, ctx) { if (rf & 1) {
92
+ i0.ɵɵelementStart(0, "span", 43);
93
+ i0.ɵɵtext(1);
94
+ i0.ɵɵelementEnd();
95
+ } if (rf & 2) {
96
+ const ctx_r2 = i0.ɵɵnextContext(2);
97
+ i0.ɵɵadvance();
98
+ i0.ɵɵtextInterpolate1(" (", ctx_r2.SelectedKeys.length, " selected) ");
99
+ } }
100
+ function EntityDataGridComponent_div_2_button_8_Template(rf, ctx) { if (rf & 1) {
101
+ const _r7 = i0.ɵɵgetCurrentView();
102
+ i0.ɵɵelementStart(0, "button", 44);
103
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onAddClick()); });
104
+ i0.ɵɵelement(1, "i", 45);
105
+ i0.ɵɵelementStart(2, "span", 46);
106
+ i0.ɵɵtext(3, "New");
107
+ i0.ɵɵelementEnd()();
108
+ } }
109
+ function EntityDataGridComponent_div_2_button_9_Template(rf, ctx) { if (rf & 1) {
110
+ const _r8 = i0.ɵɵgetCurrentView();
111
+ i0.ɵɵelementStart(0, "button", 47);
112
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_9_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onRefreshClick()); });
113
+ i0.ɵɵelement(1, "i", 48);
114
+ i0.ɵɵelementStart(2, "span", 46);
115
+ i0.ɵɵtext(3, "Refresh");
116
+ i0.ɵɵelementEnd()();
117
+ } if (rf & 2) {
118
+ const ctx_r2 = i0.ɵɵnextContext(2);
119
+ i0.ɵɵproperty("disabled", ctx_r2.loading);
120
+ i0.ɵɵadvance();
121
+ i0.ɵɵclassProp("fa-spin", ctx_r2.loading);
122
+ } }
123
+ function EntityDataGridComponent_div_2_button_10_Template(rf, ctx) { if (rf & 1) {
124
+ const _r9 = i0.ɵɵgetCurrentView();
125
+ i0.ɵɵelementStart(0, "button", 49);
126
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_10_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r9); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onExportClick()); });
127
+ i0.ɵɵelement(1, "i", 50);
128
+ i0.ɵɵelementStart(2, "span", 46);
129
+ i0.ɵɵtext(3, "Export");
130
+ i0.ɵɵelementEnd()();
131
+ } }
132
+ function EntityDataGridComponent_div_2_button_11_Template(rf, ctx) { if (rf & 1) {
133
+ const _r10 = i0.ɵɵgetCurrentView();
134
+ i0.ɵɵelementStart(0, "button", 51);
135
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_11_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r10); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onDeleteClick()); });
136
+ i0.ɵɵelement(1, "i", 52);
137
+ i0.ɵɵelementStart(2, "span", 46);
138
+ i0.ɵɵtext(3, "Delete");
139
+ i0.ɵɵelementEnd()();
140
+ } }
141
+ function EntityDataGridComponent_div_2_button_12_Template(rf, ctx) { if (rf & 1) {
142
+ const _r11 = i0.ɵɵgetCurrentView();
143
+ i0.ɵɵelementStart(0, "button", 53);
144
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_12_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r11); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onCompareClick()); });
145
+ i0.ɵɵelement(1, "i", 54);
146
+ i0.ɵɵelementStart(2, "span", 46);
147
+ i0.ɵɵtext(3, "Compare");
148
+ i0.ɵɵelementEnd()();
149
+ } if (rf & 2) {
150
+ const ctx_r2 = i0.ɵɵnextContext(2);
151
+ i0.ɵɵproperty("disabled", !ctx_r2.HasMultipleSelection);
152
+ } }
153
+ function EntityDataGridComponent_div_2_button_13_Template(rf, ctx) { if (rf & 1) {
154
+ const _r12 = i0.ɵɵgetCurrentView();
155
+ i0.ɵɵelementStart(0, "button", 55);
156
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_13_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r12); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onMergeClick()); });
157
+ i0.ɵɵelement(1, "i", 56);
158
+ i0.ɵɵelementStart(2, "span", 46);
159
+ i0.ɵɵtext(3, "Merge");
160
+ i0.ɵɵelementEnd()();
161
+ } if (rf & 2) {
162
+ const ctx_r2 = i0.ɵɵnextContext(2);
163
+ i0.ɵɵproperty("disabled", !ctx_r2.HasMultipleSelection);
164
+ } }
165
+ function EntityDataGridComponent_div_2_button_14_Template(rf, ctx) { if (rf & 1) {
166
+ const _r13 = i0.ɵɵgetCurrentView();
167
+ i0.ɵɵelementStart(0, "button", 57);
168
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_14_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r13); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onAddToListClick()); });
169
+ i0.ɵɵelement(1, "i", 58);
170
+ i0.ɵɵelementStart(2, "span", 46);
171
+ i0.ɵɵtext(3, "Add to List");
172
+ i0.ɵɵelementEnd()();
173
+ } if (rf & 2) {
174
+ const ctx_r2 = i0.ɵɵnextContext(2);
175
+ i0.ɵɵproperty("disabled", !ctx_r2.HasSelection);
176
+ } }
177
+ function EntityDataGridComponent_div_2_button_15_Template(rf, ctx) { if (rf & 1) {
178
+ const _r14 = i0.ɵɵgetCurrentView();
179
+ i0.ɵɵelementStart(0, "button", 59);
180
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_15_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r14); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onDuplicateSearchClick()); });
181
+ i0.ɵɵelement(1, "i", 60);
182
+ i0.ɵɵelementStart(2, "span", 46);
183
+ i0.ɵɵtext(3, "Find Duplicates");
184
+ i0.ɵɵelementEnd()();
185
+ } if (rf & 2) {
186
+ const ctx_r2 = i0.ɵɵnextContext(2);
187
+ i0.ɵɵproperty("disabled", !ctx_r2.HasMultipleSelection);
188
+ } }
189
+ function EntityDataGridComponent_div_2_button_16_Template(rf, ctx) { if (rf & 1) {
190
+ const _r15 = i0.ɵɵgetCurrentView();
191
+ i0.ɵɵelementStart(0, "button", 61);
192
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_16_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r15); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onCommunicationClick()); });
193
+ i0.ɵɵelement(1, "i", 62);
194
+ i0.ɵɵelementStart(2, "span", 46);
195
+ i0.ɵɵtext(3, "Send Message");
196
+ i0.ɵɵelementEnd()();
197
+ } if (rf & 2) {
198
+ const ctx_r2 = i0.ɵɵnextContext(2);
199
+ i0.ɵɵproperty("disabled", !ctx_r2.HasSelection);
200
+ } }
201
+ function EntityDataGridComponent_div_2_button_17_Template(rf, ctx) { if (rf & 1) {
202
+ const _r16 = i0.ɵɵgetCurrentView();
203
+ i0.ɵɵelementStart(0, "button", 63);
204
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_17_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r16); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onAddClick()); });
205
+ i0.ɵɵelement(1, "i", 45);
206
+ i0.ɵɵelementEnd();
207
+ } }
208
+ function EntityDataGridComponent_div_2_button_18_Template(rf, ctx) { if (rf & 1) {
209
+ const _r17 = i0.ɵɵgetCurrentView();
210
+ i0.ɵɵelementStart(0, "button", 64);
211
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_18_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r17); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onRefreshClick()); });
212
+ i0.ɵɵelement(1, "i", 65);
213
+ i0.ɵɵelementEnd();
214
+ } if (rf & 2) {
215
+ const ctx_r2 = i0.ɵɵnextContext(2);
216
+ i0.ɵɵproperty("disabled", ctx_r2.loading);
217
+ i0.ɵɵadvance();
218
+ i0.ɵɵclassProp("fa-spin", ctx_r2.loading);
219
+ } }
220
+ function EntityDataGridComponent_div_2_button_19_Template(rf, ctx) { if (rf & 1) {
221
+ const _r18 = i0.ɵɵgetCurrentView();
222
+ i0.ɵɵelementStart(0, "button", 66);
223
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_19_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r18); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onDeleteClick()); });
224
+ i0.ɵɵelement(1, "i", 52);
225
+ i0.ɵɵelementEnd();
226
+ } }
227
+ function EntityDataGridComponent_div_2_button_20_Template(rf, ctx) { if (rf & 1) {
228
+ const _r19 = i0.ɵɵgetCurrentView();
229
+ i0.ɵɵelementStart(0, "button", 67);
230
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_20_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r19); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onExportClick()); });
231
+ i0.ɵɵelement(1, "i", 68);
232
+ i0.ɵɵelementEnd();
233
+ } }
234
+ function EntityDataGridComponent_div_2_button_21_Template(rf, ctx) { if (rf & 1) {
235
+ const _r20 = i0.ɵɵgetCurrentView();
236
+ i0.ɵɵelementStart(0, "button", 69);
237
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_21_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r20); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onColumnChooserClick()); });
238
+ i0.ɵɵelement(1, "i", 70);
239
+ i0.ɵɵelementEnd();
240
+ } }
241
+ function EntityDataGridComponent_div_2_ng_container_22_button_1_i_1_Template(rf, ctx) { if (rf & 1) {
242
+ i0.ɵɵelement(0, "i");
243
+ } if (rf & 2) {
244
+ const button_r22 = i0.ɵɵnextContext(2).$implicit;
245
+ i0.ɵɵclassMap(button_r22.icon);
246
+ } }
247
+ function EntityDataGridComponent_div_2_ng_container_22_button_1_span_2_Template(rf, ctx) { if (rf & 1) {
248
+ i0.ɵɵelementStart(0, "span");
249
+ i0.ɵɵtext(1);
250
+ i0.ɵɵelementEnd();
251
+ } if (rf & 2) {
252
+ const button_r22 = i0.ɵɵnextContext(2).$implicit;
253
+ i0.ɵɵadvance();
254
+ i0.ɵɵtextInterpolate(button_r22.text);
255
+ } }
256
+ function EntityDataGridComponent_div_2_ng_container_22_button_1_Template(rf, ctx) { if (rf & 1) {
257
+ const _r21 = i0.ɵɵgetCurrentView();
258
+ i0.ɵɵelementStart(0, "button", 39);
259
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_ng_container_22_button_1_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r21); const button_r22 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onToolbarButtonClick(button_r22)); });
260
+ i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_ng_container_22_button_1_i_1_Template, 1, 2, "i", 40)(2, EntityDataGridComponent_div_2_ng_container_22_button_1_span_2_Template, 2, 1, "span", 41);
261
+ i0.ɵɵelementEnd();
262
+ } if (rf & 2) {
263
+ const button_r22 = i0.ɵɵnextContext().$implicit;
264
+ const ctx_r2 = i0.ɵɵnextContext(2);
265
+ i0.ɵɵclassMap(button_r22.cssClass);
266
+ i0.ɵɵproperty("disabled", ctx_r2.isButtonDisabled(button_r22))("title", button_r22.tooltip || "");
267
+ i0.ɵɵadvance();
268
+ i0.ɵɵproperty("ngIf", button_r22.icon);
269
+ i0.ɵɵadvance();
270
+ i0.ɵɵproperty("ngIf", button_r22.text);
271
+ } }
272
+ function EntityDataGridComponent_div_2_ng_container_22_Template(rf, ctx) { if (rf & 1) {
273
+ i0.ɵɵelementContainerStart(0);
274
+ i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_ng_container_22_button_1_Template, 3, 6, "button", 38);
275
+ i0.ɵɵelementContainerEnd();
276
+ } if (rf & 2) {
277
+ const button_r22 = ctx.$implicit;
278
+ const ctx_r2 = i0.ɵɵnextContext(2);
279
+ i0.ɵɵadvance();
280
+ i0.ɵɵproperty("ngIf", button_r22.position === "right" && ctx_r2.isButtonVisible(button_r22));
281
+ } }
282
+ function EntityDataGridComponent_div_2_div_23_div_3_button_1_Template(rf, ctx) { if (rf & 1) {
283
+ const _r24 = i0.ɵɵgetCurrentView();
284
+ i0.ɵɵelementStart(0, "button", 78);
285
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_div_3_button_1_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r24); const ctx_r2 = i0.ɵɵnextContext(4); ctx_r2.onExportClick(); return i0.ɵɵresetView(ctx_r2.closeOverflowMenu()); });
286
+ i0.ɵɵelement(1, "i", 50);
287
+ i0.ɵɵelementStart(2, "span");
288
+ i0.ɵɵtext(3, "Export to Excel");
289
+ i0.ɵɵelementEnd()();
290
+ } }
291
+ function EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_button_4_Template(rf, ctx) { if (rf & 1) {
292
+ const _r25 = i0.ɵɵgetCurrentView();
293
+ i0.ɵɵelementStart(0, "button", 82);
294
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_button_4_Template_button_click_0_listener() { const action_r26 = i0.ɵɵrestoreView(_r25).$implicit; const ctx_r2 = i0.ɵɵnextContext(5); ctx_r2.onEntityActionClick(action_r26); return i0.ɵɵresetView(ctx_r2.closeOverflowMenu()); });
295
+ i0.ɵɵelement(1, "i");
296
+ i0.ɵɵelementStart(2, "span");
297
+ i0.ɵɵtext(3);
298
+ i0.ɵɵelementEnd()();
299
+ } if (rf & 2) {
300
+ const action_r26 = ctx.$implicit;
301
+ const ctx_r2 = i0.ɵɵnextContext(5);
302
+ i0.ɵɵproperty("disabled", !ctx_r2.isEntityActionEnabled(action_r26));
303
+ i0.ɵɵadvance();
304
+ i0.ɵɵclassMap(action_r26.icon || "fa-solid fa-bolt");
305
+ i0.ɵɵadvance(2);
306
+ i0.ɵɵtextInterpolate(action_r26.name);
307
+ } }
308
+ function EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_Template(rf, ctx) { if (rf & 1) {
309
+ i0.ɵɵelementContainerStart(0);
310
+ i0.ɵɵelement(1, "div", 79);
311
+ i0.ɵɵelementStart(2, "div", 80);
312
+ i0.ɵɵtext(3, "Actions");
313
+ i0.ɵɵelementEnd();
314
+ i0.ɵɵtemplate(4, EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_button_4_Template, 4, 4, "button", 81);
315
+ i0.ɵɵelementContainerEnd();
316
+ } if (rf & 2) {
317
+ const ctx_r2 = i0.ɵɵnextContext(4);
318
+ i0.ɵɵadvance(4);
319
+ i0.ɵɵproperty("ngForOf", ctx_r2.EntityActions);
320
+ } }
321
+ function EntityDataGridComponent_div_2_div_23_div_3_div_3_Template(rf, ctx) { if (rf & 1) {
322
+ i0.ɵɵelement(0, "div", 79);
323
+ } }
324
+ function EntityDataGridComponent_div_2_div_23_div_3_button_4_Template(rf, ctx) { if (rf & 1) {
325
+ const _r27 = i0.ɵɵgetCurrentView();
326
+ i0.ɵɵelementStart(0, "button", 78);
327
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_div_3_button_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); ctx_r2.onColumnChooserClick(); return i0.ɵɵresetView(ctx_r2.closeOverflowMenu()); });
328
+ i0.ɵɵelement(1, "i", 70);
329
+ i0.ɵɵelementStart(2, "span");
330
+ i0.ɵɵtext(3, "Manage Columns");
331
+ i0.ɵɵelementEnd()();
332
+ } }
333
+ function EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_button_2_Template(rf, ctx) { if (rf & 1) {
334
+ const _r28 = i0.ɵɵgetCurrentView();
335
+ i0.ɵɵelementStart(0, "button", 78);
336
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_button_2_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r28); const ctx_r2 = i0.ɵɵnextContext(5); ctx_r2.onCommunicationClick(); return i0.ɵɵresetView(ctx_r2.closeOverflowMenu()); });
337
+ i0.ɵɵelement(1, "i", 62);
338
+ i0.ɵɵelementStart(2, "span");
339
+ i0.ɵɵtext(3, "Send Message");
340
+ i0.ɵɵelementEnd()();
341
+ } }
342
+ function EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_Template(rf, ctx) { if (rf & 1) {
343
+ i0.ɵɵelementContainerStart(0);
344
+ i0.ɵɵelement(1, "div", 79);
345
+ i0.ɵɵtemplate(2, EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_button_2_Template, 4, 0, "button", 76);
346
+ i0.ɵɵelementContainerEnd();
347
+ } if (rf & 2) {
348
+ const ctx_r2 = i0.ɵɵnextContext(4);
349
+ i0.ɵɵadvance(2);
350
+ i0.ɵɵproperty("ngIf", ctx_r2.showCommunicationInOverflow);
351
+ } }
352
+ function EntityDataGridComponent_div_2_div_23_div_3_Template(rf, ctx) { if (rf & 1) {
353
+ i0.ɵɵelementStart(0, "div", 75);
354
+ i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_div_23_div_3_button_1_Template, 4, 0, "button", 76)(2, EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_Template, 5, 1, "ng-container", 41)(3, EntityDataGridComponent_div_2_div_23_div_3_div_3_Template, 1, 0, "div", 77)(4, EntityDataGridComponent_div_2_div_23_div_3_button_4_Template, 4, 0, "button", 76)(5, EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_Template, 3, 1, "ng-container", 41);
355
+ i0.ɵɵelementEnd();
356
+ } if (rf & 2) {
357
+ const ctx_r2 = i0.ɵɵnextContext(3);
358
+ i0.ɵɵproperty("@fadeIn", undefined);
359
+ i0.ɵɵadvance();
360
+ i0.ɵɵproperty("ngIf", ctx_r2.showExportInOverflow);
361
+ i0.ɵɵadvance();
362
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowEntityActionButtons && ctx_r2.EntityActions.length > 0);
363
+ i0.ɵɵadvance();
364
+ i0.ɵɵproperty("ngIf", ctx_r2.showColumnChooserInOverflow);
365
+ i0.ɵɵadvance();
366
+ i0.ɵɵproperty("ngIf", ctx_r2.showColumnChooserInOverflow);
367
+ i0.ɵɵadvance();
368
+ i0.ɵɵproperty("ngIf", ctx_r2.HasSelection && ctx_r2.hasSelectionDependentOverflowActions);
369
+ } }
370
+ function EntityDataGridComponent_div_2_div_23_Template(rf, ctx) { if (rf & 1) {
371
+ const _r23 = i0.ɵɵgetCurrentView();
372
+ i0.ɵɵelementStart(0, "div", 71);
373
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r23); return i0.ɵɵresetView($event.stopPropagation()); });
374
+ i0.ɵɵelementStart(1, "button", 72);
375
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r23); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.toggleOverflowMenu()); });
376
+ i0.ɵɵelement(2, "i", 73);
377
+ i0.ɵɵelementEnd();
378
+ i0.ɵɵtemplate(3, EntityDataGridComponent_div_2_div_23_div_3_Template, 6, 6, "div", 74);
379
+ i0.ɵɵelementEnd();
380
+ } if (rf & 2) {
381
+ const ctx_r2 = i0.ɵɵnextContext(2);
382
+ i0.ɵɵadvance(3);
383
+ i0.ɵɵproperty("ngIf", ctx_r2.showOverflowMenu);
384
+ } }
385
+ function EntityDataGridComponent_div_2_Template(rf, ctx) { if (rf & 1) {
386
+ i0.ɵɵelementStart(0, "div", 9)(1, "div", 10);
387
+ i0.ɵɵtemplate(2, EntityDataGridComponent_div_2_div_2_Template, 4, 3, "div", 11)(3, EntityDataGridComponent_div_2_ng_container_3_Template, 2, 1, "ng-container", 12);
388
+ i0.ɵɵelementEnd();
389
+ i0.ɵɵelementStart(4, "div", 13);
390
+ i0.ɵɵtemplate(5, EntityDataGridComponent_div_2_span_5_Template, 2, 2, "span", 14)(6, EntityDataGridComponent_div_2_span_6_Template, 2, 1, "span", 15);
391
+ i0.ɵɵelementEnd();
392
+ i0.ɵɵelementStart(7, "div", 16);
393
+ i0.ɵɵtemplate(8, EntityDataGridComponent_div_2_button_8_Template, 4, 0, "button", 17)(9, EntityDataGridComponent_div_2_button_9_Template, 4, 3, "button", 18)(10, EntityDataGridComponent_div_2_button_10_Template, 4, 0, "button", 19)(11, EntityDataGridComponent_div_2_button_11_Template, 4, 0, "button", 20)(12, EntityDataGridComponent_div_2_button_12_Template, 4, 1, "button", 21)(13, EntityDataGridComponent_div_2_button_13_Template, 4, 1, "button", 22)(14, EntityDataGridComponent_div_2_button_14_Template, 4, 1, "button", 23)(15, EntityDataGridComponent_div_2_button_15_Template, 4, 1, "button", 24)(16, EntityDataGridComponent_div_2_button_16_Template, 4, 1, "button", 25)(17, EntityDataGridComponent_div_2_button_17_Template, 2, 0, "button", 26)(18, EntityDataGridComponent_div_2_button_18_Template, 2, 3, "button", 27)(19, EntityDataGridComponent_div_2_button_19_Template, 2, 0, "button", 28)(20, EntityDataGridComponent_div_2_button_20_Template, 2, 0, "button", 29)(21, EntityDataGridComponent_div_2_button_21_Template, 2, 0, "button", 30)(22, EntityDataGridComponent_div_2_ng_container_22_Template, 2, 1, "ng-container", 12)(23, EntityDataGridComponent_div_2_div_23_Template, 4, 1, "div", 31);
394
+ i0.ɵɵelementEnd()();
395
+ } if (rf & 2) {
396
+ const ctx_r2 = i0.ɵɵnextContext();
397
+ i0.ɵɵadvance(2);
398
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowSearch);
399
+ i0.ɵɵadvance();
400
+ i0.ɵɵproperty("ngForOf", ctx_r2.ToolbarConfig.customButtons);
401
+ i0.ɵɵadvance(2);
402
+ i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showRowCount !== false);
403
+ i0.ɵɵadvance();
404
+ i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showSelectionCount && ctx_r2.SelectedKeys.length > 0);
405
+ i0.ɵɵadvance(2);
406
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowNewButton);
407
+ i0.ɵɵadvance();
408
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowRefreshButton);
409
+ i0.ɵɵadvance();
410
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowExportButton);
411
+ i0.ɵɵadvance();
412
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowDeleteButton && ctx_r2.HasSelection);
413
+ i0.ɵɵadvance();
414
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowCompareButton);
415
+ i0.ɵɵadvance();
416
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowMergeButton);
417
+ i0.ɵɵadvance();
418
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowAddToListButton);
419
+ i0.ɵɵadvance();
420
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowDuplicateSearchButton);
421
+ i0.ɵɵadvance();
422
+ i0.ɵɵproperty("ngIf", ctx_r2.ShowCommunicationButton);
423
+ i0.ɵɵadvance();
424
+ i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showAdd && ctx_r2.AllowAdd && !ctx_r2.ShowNewButton);
425
+ i0.ɵɵadvance();
426
+ i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showRefresh !== false && !ctx_r2.ShowRefreshButton);
427
+ i0.ɵɵadvance();
428
+ i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showDelete && ctx_r2.AllowDelete && ctx_r2.HasSelection && !ctx_r2.ShowDeleteButton);
429
+ i0.ɵɵadvance();
430
+ i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showExport && !ctx_r2.ShowExportButton);
431
+ i0.ɵɵadvance();
432
+ i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showColumnChooser && ctx_r2.AllowColumnToggle);
433
+ i0.ɵɵadvance();
434
+ i0.ɵɵproperty("ngForOf", ctx_r2.ToolbarConfig.customButtons);
435
+ i0.ɵɵadvance();
436
+ i0.ɵɵproperty("ngIf", ctx_r2.hasOverflowMenuItems);
437
+ } }
438
+ function EntityDataGridComponent_div_4_Template(rf, ctx) { if (rf & 1) {
439
+ i0.ɵɵelementStart(0, "div", 83);
440
+ i0.ɵɵelement(1, "mj-loading", 84);
441
+ i0.ɵɵelementEnd();
442
+ } }
443
+ function EntityDataGridComponent_div_5_Template(rf, ctx) { if (rf & 1) {
444
+ const _r29 = i0.ɵɵgetCurrentView();
445
+ i0.ɵɵelementStart(0, "div", 85);
446
+ i0.ɵɵelement(1, "i", 86);
447
+ i0.ɵɵelementStart(2, "span");
448
+ i0.ɵɵtext(3);
449
+ i0.ɵɵelementEnd();
450
+ i0.ɵɵelementStart(4, "button", 87);
451
+ i0.ɵɵlistener("click", function EntityDataGridComponent_div_5_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r29); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.Refresh()); });
452
+ i0.ɵɵtext(5, "Retry");
453
+ i0.ɵɵelementEnd()();
454
+ } if (rf & 2) {
455
+ const ctx_r2 = i0.ɵɵnextContext();
456
+ i0.ɵɵadvance(3);
457
+ i0.ɵɵtextInterpolate(ctx_r2.errorMessage);
458
+ } }
459
+ function EntityDataGridComponent_div_6_Template(rf, ctx) { if (rf & 1) {
460
+ i0.ɵɵelementStart(0, "div", 88);
461
+ i0.ɵɵelement(1, "i", 89);
462
+ i0.ɵɵelementStart(2, "span");
463
+ i0.ɵɵtext(3, "No data to display");
464
+ i0.ɵɵelementEnd()();
465
+ } }
466
+ function EntityDataGridComponent_ag_grid_angular_7_Template(rf, ctx) { if (rf & 1) {
467
+ const _r30 = i0.ɵɵgetCurrentView();
468
+ i0.ɵɵelementStart(0, "ag-grid-angular", 90);
469
+ i0.ɵɵlistener("gridReady", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_gridReady_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onGridReady($event)); })("rowClicked", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_rowClicked_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgRowClicked($event)); })("rowDoubleClicked", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_rowDoubleClicked_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgRowDoubleClicked($event)); })("sortChanged", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_sortChanged_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgSortChanged($event)); })("selectionChanged", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_selectionChanged_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgSelectionChanged($event)); })("columnResized", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_columnResized_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgColumnResized($event)); })("columnMoved", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_columnMoved_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgColumnMoved($event)); });
470
+ i0.ɵɵelementEnd();
471
+ } if (rf & 2) {
472
+ const ctx_r2 = i0.ɵɵnextContext();
473
+ i0.ɵɵproperty("theme", ctx_r2.agGridTheme)("columnDefs", ctx_r2.agColumnDefs)("rowData", ctx_r2.rowData)("defaultColDef", ctx_r2.defaultColDef)("rowSelection", ctx_r2.agRowSelection)("getRowId", ctx_r2.getRowId)("suppressCellFocus", true)("rowHeight", ctx_r2.RowHeight)("headerHeight", ctx_r2.ShowHeader ? undefined : 0);
474
+ } }
475
+ function EntityDataGridComponent_ag_grid_angular_8_Template(rf, ctx) { if (rf & 1) {
476
+ const _r31 = i0.ɵɵgetCurrentView();
477
+ i0.ɵɵelementStart(0, "ag-grid-angular", 91);
478
+ i0.ɵɵlistener("gridReady", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_gridReady_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onGridReady($event)); })("rowClicked", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_rowClicked_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgRowClicked($event)); })("rowDoubleClicked", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_rowDoubleClicked_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgRowDoubleClicked($event)); })("sortChanged", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_sortChanged_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgSortChanged($event)); })("selectionChanged", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_selectionChanged_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgSelectionChanged($event)); })("columnResized", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_columnResized_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgColumnResized($event)); })("columnMoved", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_columnMoved_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgColumnMoved($event)); });
479
+ i0.ɵɵelementEnd();
480
+ } if (rf & 2) {
481
+ const ctx_r2 = i0.ɵɵnextContext();
482
+ i0.ɵɵproperty("theme", ctx_r2.agGridTheme)("columnDefs", ctx_r2.agColumnDefs)("defaultColDef", ctx_r2.defaultColDef)("rowSelection", ctx_r2.agRowSelection)("getRowId", ctx_r2.getRowId)("suppressCellFocus", true)("rowHeight", ctx_r2.RowHeight)("headerHeight", ctx_r2.ShowHeader ? undefined : 0)("rowModelType", "infinite")("cacheBlockSize", ctx_r2.CacheBlockSize)("maxBlocksInCache", ctx_r2.MaxBlocksInCache)("infiniteInitialRowCount", 1)("cacheOverflowSize", 2);
483
+ } }
484
+ // Register AG Grid modules (required for v34+)
485
+ ModuleRegistry.registerModules([AllCommunityModule]);
486
+ /**
487
+ * A modern, flexible data grid component for displaying and managing entity data.
488
+ * Built on AG Grid Community edition with a rich Before/After cancelable event system.
489
+ *
490
+ * Features:
491
+ * - RunView-based data loading with automatic refresh
492
+ * - AG Grid for high-performance rendering
493
+ * - Rich event system with Before/After cancelable patterns
494
+ * - Column configuration with type-aware formatters
495
+ * - Row selection (single, multiple, checkbox modes)
496
+ * - Inline cell and row editing
497
+ * - Column reordering, resizing, and visibility toggle
498
+ * - State persistence to User Settings
499
+ * - Compatible with ViewGridStateConfig from User Views
500
+ *
501
+ * @example
502
+ * ```html
503
+ * <mj-entity-data-grid
504
+ * [entityName]="'Contacts'"
505
+ * [showToolbar]="true"
506
+ * [allowSorting]="true"
507
+ * [selectionMode]="'multiple'"
508
+ * (afterRowDoubleClick)="onRowDoubleClick($event)">
509
+ * </mj-entity-data-grid>
510
+ * ```
511
+ */
512
+ export class EntityDataGridComponent {
513
+ cdr;
514
+ elementRef;
515
+ exportService;
516
+ // ========================================
517
+ // Data Source Inputs (RunViewParams-based)
518
+ // ========================================
519
+ _params = null;
520
+ /**
521
+ * RunViewParams for data loading - the primary way to specify data source.
522
+ * Supports stored views (ViewID, ViewName, ViewEntity) and dynamic views (EntityName + filters).
523
+ * Mutually exclusive with the legacy entityName/extraFilter inputs.
524
+ * When Params is provided, it takes precedence over legacy inputs.
525
+ */
526
+ set Params(value) {
527
+ const previousValue = this._params;
528
+ this._params = value;
529
+ // Use deep comparison to avoid triggering changes when the same params are passed
530
+ // as a new object reference (common with template bindings like BuildRelationshipViewParamsByEntityName)
531
+ if (!RunViewParams.Equals(value, previousValue)) {
532
+ this.onParamsChanged();
533
+ }
534
+ }
535
+ get Params() {
536
+ return this._params;
537
+ }
538
+ _allowLoad = true;
539
+ /**
540
+ * Controls whether data loading is allowed.
541
+ * Set to false to defer loading until ready (useful for complex forms where params may change multiple times).
542
+ * When set to true and Params is provided, triggers a data load.
543
+ */
544
+ set AllowLoad(value) {
545
+ const previousValue = this._allowLoad;
546
+ this._allowLoad = value;
547
+ if (value && !previousValue && this._params) {
548
+ // AllowLoad was just enabled and we have params - load now
549
+ this.loadData(false);
550
+ }
551
+ }
552
+ get AllowLoad() {
553
+ return this._allowLoad;
554
+ }
555
+ _autoRefreshOnParamsChange = true;
556
+ /**
557
+ * When true, automatically refreshes data when Params changes.
558
+ * When false, you must call Refresh() manually after changing Params.
559
+ */
560
+ set AutoRefreshOnParamsChange(value) {
561
+ this._autoRefreshOnParamsChange = value;
562
+ }
563
+ get AutoRefreshOnParamsChange() {
564
+ return this._autoRefreshOnParamsChange;
565
+ }
566
+ // ========================================
567
+ // Pagination Inputs
568
+ // ========================================
569
+ _paginationMode = 'client';
570
+ /**
571
+ * Pagination mode for the grid:
572
+ * - 'client': All data loaded upfront (default, legacy behavior)
573
+ * - 'infinite': Server-side pagination with infinite scroll (recommended for large datasets)
574
+ */
575
+ set PaginationMode(value) {
576
+ this._paginationMode = value;
577
+ }
578
+ get PaginationMode() {
579
+ return this._paginationMode;
580
+ }
581
+ _pageSize = 100;
582
+ /**
583
+ * Number of rows to fetch per page when using infinite pagination mode.
584
+ * Default is 100 rows per page.
585
+ */
586
+ set PageSize(value) {
587
+ this._pageSize = value;
588
+ }
589
+ get PageSize() {
590
+ return this._pageSize;
591
+ }
592
+ _cacheBlockSize = 100;
593
+ /**
594
+ * Size of each cache block in infinite scroll mode.
595
+ * Should match or be a multiple of PageSize for optimal performance.
596
+ */
597
+ set CacheBlockSize(value) {
598
+ this._cacheBlockSize = value;
599
+ }
600
+ get CacheBlockSize() {
601
+ return this._cacheBlockSize;
602
+ }
603
+ _maxBlocksInCache = 10;
604
+ /**
605
+ * Maximum number of blocks to keep in cache.
606
+ * When exceeded, oldest blocks are purged.
607
+ */
608
+ set MaxBlocksInCache(value) {
609
+ this._maxBlocksInCache = value;
610
+ }
611
+ get MaxBlocksInCache() {
612
+ return this._maxBlocksInCache;
613
+ }
614
+ // ========================================
615
+ // External Data Input
616
+ // ========================================
617
+ _data = [];
618
+ /**
619
+ * Pre-loaded data (bypass RunView, use provided data).
620
+ * When provided, the grid displays this data instead of loading via RunView.
621
+ * Parent component is responsible for data loading and passing results here.
622
+ */
623
+ set Data(value) {
624
+ const hadData = this._data.length > 0;
625
+ this._data = value || [];
626
+ this._useExternalData = this._data.length > 0;
627
+ if (this._useExternalData || hadData) {
628
+ this.processData();
629
+ // Reapply sort state to grid after data changes to maintain visual indicators
630
+ // Use microtask to ensure AG Grid has processed the new row data first
631
+ if (this.gridApi && this._sortState.length > 0) {
632
+ Promise.resolve().then(() => {
633
+ this.applySortStateToGrid();
634
+ });
635
+ }
636
+ }
637
+ }
638
+ get Data() {
639
+ return this._data;
640
+ }
641
+ // ========================================
642
+ // Column Configuration Inputs
643
+ // ========================================
644
+ _columns = [];
645
+ /**
646
+ * Column definitions - if not provided, auto-generates from entity metadata
647
+ */
648
+ set Columns(value) {
649
+ this._columns = value || [];
650
+ if (this._columns.length > 0) {
651
+ this.initializeColumnStates();
652
+ this.buildAgColumnDefs();
653
+ }
654
+ }
655
+ get Columns() {
656
+ return this._columns;
657
+ }
658
+ _gridState = null;
659
+ /**
660
+ * Grid state from a User View - controls columns, widths, order, sort
661
+ * When provided, this takes precedence over auto-generated columns
662
+ */
663
+ set GridState(value) {
664
+ if (!!value) {
665
+ const previousValue = this._gridState;
666
+ this._gridState = value;
667
+ if (value !== previousValue) {
668
+ this.onGridStateChanged();
669
+ }
670
+ }
671
+ }
672
+ get GridState() {
673
+ return this._gridState;
674
+ }
675
+ _allowColumnReorder = true;
676
+ set AllowColumnReorder(value) {
677
+ this._allowColumnReorder = value;
678
+ }
679
+ get AllowColumnReorder() {
680
+ return this._allowColumnReorder;
681
+ }
682
+ _allowColumnResize = true;
683
+ set AllowColumnResize(value) {
684
+ this._allowColumnResize = value;
685
+ }
686
+ get AllowColumnResize() {
687
+ return this._allowColumnResize;
688
+ }
689
+ _allowColumnToggle = true;
690
+ set AllowColumnToggle(value) {
691
+ this._allowColumnToggle = value;
692
+ }
693
+ get AllowColumnToggle() {
694
+ return this._allowColumnToggle;
695
+ }
696
+ _showHeader = true;
697
+ set ShowHeader(value) {
698
+ this._showHeader = value;
699
+ }
700
+ get ShowHeader() {
701
+ return this._showHeader;
702
+ }
703
+ // ========================================
704
+ // Sorting & Filtering Inputs
705
+ // ========================================
706
+ _allowSorting = true;
707
+ set AllowSorting(value) {
708
+ this._allowSorting = value;
709
+ }
710
+ get AllowSorting() {
711
+ return this._allowSorting;
712
+ }
713
+ _allowMultiSort = true;
714
+ set AllowMultiSort(value) {
715
+ this._allowMultiSort = value;
716
+ }
717
+ get AllowMultiSort() {
718
+ return this._allowMultiSort;
719
+ }
720
+ _serverSideSorting = true;
721
+ /**
722
+ * Whether sorting is handled server-side
723
+ * When true, sort changes trigger a new data load
724
+ */
725
+ set ServerSideSorting(value) {
726
+ this._serverSideSorting = value;
727
+ }
728
+ get ServerSideSorting() {
729
+ return this._serverSideSorting;
730
+ }
731
+ _allowColumnFilters = false;
732
+ set AllowColumnFilters(value) {
733
+ this._allowColumnFilters = value;
734
+ }
735
+ get AllowColumnFilters() {
736
+ return this._allowColumnFilters;
737
+ }
738
+ _showSearch = true;
739
+ set ShowSearch(value) {
740
+ this._showSearch = value;
741
+ }
742
+ get ShowSearch() {
743
+ return this._showSearch;
744
+ }
745
+ // ========================================
746
+ // Selection Inputs
747
+ // ========================================
748
+ _selectionMode = 'single';
749
+ set SelectionMode(value) {
750
+ this._selectionMode = value;
751
+ this.updateAgRowSelection();
752
+ if (value === 'none') {
753
+ this.ClearSelection();
754
+ }
755
+ }
756
+ get SelectionMode() {
757
+ return this._selectionMode;
758
+ }
759
+ _selectedKeys = [];
760
+ set SelectedKeys(value) {
761
+ this._selectedKeys = value || [];
762
+ this.updateRowSelectionState();
763
+ }
764
+ get SelectedKeys() {
765
+ return this._selectedKeys;
766
+ }
767
+ _keyField = 'ID';
768
+ set KeyField(value) {
769
+ this._keyField = value || 'ID';
770
+ }
771
+ get KeyField() {
772
+ return this._keyField;
773
+ }
774
+ // ========================================
775
+ // Editing Inputs
776
+ // ========================================
777
+ _editMode = 'none';
778
+ set EditMode(value) {
779
+ this._editMode = value;
780
+ }
781
+ get EditMode() {
782
+ return this._editMode;
783
+ }
784
+ _allowAdd = false;
785
+ set AllowAdd(value) {
786
+ this._allowAdd = value;
787
+ }
788
+ get AllowAdd() {
789
+ return this._allowAdd;
790
+ }
791
+ _allowDelete = false;
792
+ set AllowDelete(value) {
793
+ this._allowDelete = value;
794
+ }
795
+ get AllowDelete() {
796
+ return this._allowDelete;
797
+ }
798
+ // ========================================
799
+ // Display Inputs
800
+ // ========================================
801
+ _height = 'auto';
802
+ set Height(value) {
803
+ this._height = value;
804
+ }
805
+ get Height() {
806
+ return this._height;
807
+ }
808
+ _rowHeight = 40;
809
+ set RowHeight(value) {
810
+ this._rowHeight = value;
811
+ }
812
+ get RowHeight() {
813
+ return this._rowHeight;
814
+ }
815
+ _virtualScroll = true;
816
+ set VirtualScroll(value) {
817
+ this._virtualScroll = value;
818
+ }
819
+ get VirtualScroll() {
820
+ return this._virtualScroll;
821
+ }
822
+ _showRowNumbers = false;
823
+ set ShowRowNumbers(value) {
824
+ this._showRowNumbers = value;
825
+ this.buildAgColumnDefs();
826
+ }
827
+ get ShowRowNumbers() {
828
+ return this._showRowNumbers;
829
+ }
830
+ _striped = true;
831
+ set Striped(value) {
832
+ this._striped = value;
833
+ }
834
+ get Striped() {
835
+ return this._striped;
836
+ }
837
+ _gridLines = 'horizontal';
838
+ set GridLines(value) {
839
+ this._gridLines = value;
840
+ }
841
+ get GridLines() {
842
+ return this._gridLines;
843
+ }
844
+ // ========================================
845
+ // Visual Customization Inputs
846
+ // ========================================
847
+ _visualConfig = {};
848
+ /**
849
+ * Visual configuration for the grid appearance.
850
+ * Allows customization of header styles, row colors, cell formatting, and more.
851
+ * All properties are optional - unset properties use attractive defaults.
852
+ */
853
+ set VisualConfig(value) {
854
+ this._visualConfig = value || {};
855
+ this.applyVisualConfig();
856
+ }
857
+ get VisualConfig() {
858
+ return this._visualConfig;
859
+ }
860
+ /**
861
+ * Get the effective visual config (user config merged with defaults)
862
+ */
863
+ get effectiveVisualConfig() {
864
+ return { ...DEFAULT_VISUAL_CONFIG, ...this._visualConfig };
865
+ }
866
+ // ========================================
867
+ // Toolbar Inputs
868
+ // ========================================
869
+ _showToolbar = true;
870
+ set ShowToolbar(value) {
871
+ this._showToolbar = value;
872
+ }
873
+ get ShowToolbar() {
874
+ return this._showToolbar;
875
+ }
876
+ _toolbarConfig = {};
877
+ set ToolbarConfig(value) {
878
+ this._toolbarConfig = value || {};
879
+ }
880
+ get ToolbarConfig() {
881
+ return this._toolbarConfig;
882
+ }
883
+ // ========================================
884
+ // State Inputs
885
+ // ========================================
886
+ _stateKey = '';
887
+ set StateKey(value) {
888
+ this._stateKey = value || '';
889
+ if (this._stateKey) {
890
+ this.loadPersistedState();
891
+ }
892
+ }
893
+ get StateKey() {
894
+ return this._stateKey;
895
+ }
896
+ _autoPersistState = true;
897
+ /**
898
+ * When true and using a stored view (ViewID/ViewName/ViewEntity in Params),
899
+ * automatically persists grid state changes (column widths, order, sort) to the UserView entity.
900
+ * Only saves if the user has edit permission on the view.
901
+ * For dynamic views, only emits the gridStateChanged event.
902
+ */
903
+ set AutoPersistState(value) {
904
+ this._autoPersistState = value;
905
+ }
906
+ get AutoPersistState() {
907
+ return this._autoPersistState;
908
+ }
909
+ _statePersistDebounce = 5000;
910
+ /**
911
+ * Debounce time in milliseconds before persisting state changes to the server.
912
+ * Default is 5000ms (5 seconds) to avoid excessive server calls during rapid column adjustments.
913
+ */
914
+ set StatePersistDebounce(value) {
915
+ this._statePersistDebounce = value;
916
+ // Re-setup the debounce subscription with new timing
917
+ this.setupStatePersistDebounce();
918
+ }
919
+ get StatePersistDebounce() {
920
+ return this._statePersistDebounce;
921
+ }
922
+ _refreshDebounce = 300;
923
+ set RefreshDebounce(value) {
924
+ this._refreshDebounce = value;
925
+ }
926
+ get RefreshDebounce() {
927
+ return this._refreshDebounce;
928
+ }
929
+ // ========================================
930
+ // Highlighting Input
931
+ // ========================================
932
+ _filterText = '';
933
+ /**
934
+ * Text to highlight in grid cells.
935
+ * Supports SQL-style % wildcards for pattern matching.
936
+ */
937
+ set FilterText(value) {
938
+ const previousValue = this._filterText;
939
+ this._filterText = value || '';
940
+ if (value !== previousValue) {
941
+ this.onFilterTextChanged();
942
+ }
943
+ }
944
+ get FilterText() {
945
+ return this._filterText;
946
+ }
947
+ // ========================================
948
+ // Predefined Toolbar Button Inputs
949
+ // ========================================
950
+ _showNewButton = true;
951
+ /**
952
+ * Show the "New" button in toolbar (creates new record)
953
+ */
954
+ set ShowNewButton(value) {
955
+ this._showNewButton = value;
956
+ }
957
+ get ShowNewButton() {
958
+ return this._showNewButton;
959
+ }
960
+ _showRefreshButton = true;
961
+ /**
962
+ * Show the "Refresh" button in toolbar
963
+ */
964
+ set ShowRefreshButton(value) {
965
+ this._showRefreshButton = value;
966
+ }
967
+ get ShowRefreshButton() {
968
+ return this._showRefreshButton;
969
+ }
970
+ _showExportButton = true;
971
+ /**
972
+ * Show the "Export to Excel" button in toolbar
973
+ */
974
+ set ShowExportButton(value) {
975
+ this._showExportButton = value;
976
+ }
977
+ get ShowExportButton() {
978
+ return this._showExportButton;
979
+ }
980
+ _showDeleteButton = false;
981
+ /**
982
+ * Show the "Delete" button in toolbar (deletes selected rows)
983
+ */
984
+ set ShowDeleteButton(value) {
985
+ this._showDeleteButton = value;
986
+ }
987
+ get ShowDeleteButton() {
988
+ return this._showDeleteButton;
989
+ }
990
+ _showCompareButton = false;
991
+ /**
992
+ * Show the "Compare" button in toolbar (compare selected records)
993
+ */
994
+ set ShowCompareButton(value) {
995
+ this._showCompareButton = value;
996
+ }
997
+ get ShowCompareButton() {
998
+ return this._showCompareButton;
999
+ }
1000
+ _showMergeButton = false;
1001
+ /**
1002
+ * Show the "Merge" button in toolbar (merge selected records)
1003
+ */
1004
+ set ShowMergeButton(value) {
1005
+ this._showMergeButton = value;
1006
+ }
1007
+ get ShowMergeButton() {
1008
+ return this._showMergeButton;
1009
+ }
1010
+ _showAddToListButton = false;
1011
+ /**
1012
+ * Show the "Add to List" button in toolbar
1013
+ */
1014
+ set ShowAddToListButton(value) {
1015
+ this._showAddToListButton = value;
1016
+ }
1017
+ get ShowAddToListButton() {
1018
+ return this._showAddToListButton;
1019
+ }
1020
+ _showDuplicateSearchButton = false;
1021
+ /**
1022
+ * Show the "Search for Duplicates" button in toolbar
1023
+ */
1024
+ set ShowDuplicateSearchButton(value) {
1025
+ this._showDuplicateSearchButton = value;
1026
+ }
1027
+ get ShowDuplicateSearchButton() {
1028
+ return this._showDuplicateSearchButton;
1029
+ }
1030
+ _showCommunicationButton = false;
1031
+ /**
1032
+ * Show the "Send Message" button in toolbar (if entity supports communication)
1033
+ */
1034
+ set ShowCommunicationButton(value) {
1035
+ this._showCommunicationButton = value;
1036
+ }
1037
+ get ShowCommunicationButton() {
1038
+ return this._showCommunicationButton;
1039
+ }
1040
+ // ========================================
1041
+ // Navigation Inputs
1042
+ // ========================================
1043
+ _autoNavigate = true;
1044
+ /**
1045
+ * When true, clicking or double-clicking a row will emit a navigation request.
1046
+ * The parent component can handle the navigationRequested event to open the record.
1047
+ */
1048
+ set AutoNavigate(value) {
1049
+ this._autoNavigate = value;
1050
+ }
1051
+ get AutoNavigate() {
1052
+ return this._autoNavigate;
1053
+ }
1054
+ _navigateOnDoubleClick = true;
1055
+ /**
1056
+ * When true, navigation is triggered on double-click only.
1057
+ * When false, navigation is triggered on single click.
1058
+ * Only applies when AutoNavigate is true.
1059
+ */
1060
+ set NavigateOnDoubleClick(value) {
1061
+ this._navigateOnDoubleClick = value;
1062
+ }
1063
+ get NavigateOnDoubleClick() {
1064
+ return this._navigateOnDoubleClick;
1065
+ }
1066
+ _createRecordMode = 'Tab';
1067
+ /**
1068
+ * How to open new records when the "New" button is clicked:
1069
+ * - 'Tab': Emit event to open in a new tab (default)
1070
+ * - 'Dialog': Emit event to open in a dialog
1071
+ */
1072
+ set CreateRecordMode(value) {
1073
+ this._createRecordMode = value;
1074
+ }
1075
+ get CreateRecordMode() {
1076
+ return this._createRecordMode;
1077
+ }
1078
+ _newRecordValues = {};
1079
+ /**
1080
+ * Default values to set on new records.
1081
+ * These values are passed with the newRecordDialogRequested event.
1082
+ */
1083
+ set NewRecordValues(value) {
1084
+ this._newRecordValues = value || {};
1085
+ }
1086
+ get NewRecordValues() {
1087
+ return this._newRecordValues;
1088
+ }
1089
+ // ========================================
1090
+ // Entity Actions Inputs
1091
+ // ========================================
1092
+ _showEntityActionButtons = false;
1093
+ /**
1094
+ * When true, displays entity action buttons in the toolbar overflow menu.
1095
+ * Entity actions are loaded dynamically based on the current entity.
1096
+ * The entityActionsLoaded event is emitted when actions are ready.
1097
+ */
1098
+ set ShowEntityActionButtons(value) {
1099
+ this._showEntityActionButtons = value;
1100
+ if (value && this._entityInfo) {
1101
+ this.LoadEntityActionsRequested.emit({ entityInfo: this._entityInfo });
1102
+ }
1103
+ }
1104
+ get ShowEntityActionButtons() {
1105
+ return this._showEntityActionButtons;
1106
+ }
1107
+ _entityActions = [];
1108
+ /**
1109
+ * Array of entity actions to display in the toolbar.
1110
+ * Set this after receiving the entityActionsLoaded event.
1111
+ */
1112
+ set EntityActions(value) {
1113
+ this._entityActions = value || [];
1114
+ }
1115
+ get EntityActions() {
1116
+ return this._entityActions;
1117
+ }
1118
+ // ========================================
1119
+ // Event Outputs
1120
+ // ========================================
1121
+ // Row Selection
1122
+ BeforeRowSelect = new EventEmitter();
1123
+ AfterRowSelect = new EventEmitter();
1124
+ BeforeRowDeselect = new EventEmitter();
1125
+ AfterRowDeselect = new EventEmitter();
1126
+ SelectionChange = new EventEmitter();
1127
+ // Row Click
1128
+ BeforeRowClick = new EventEmitter();
1129
+ AfterRowClick = new EventEmitter();
1130
+ BeforeRowDoubleClick = new EventEmitter();
1131
+ AfterRowDoubleClick = new EventEmitter();
1132
+ // Editing
1133
+ BeforeCellEdit = new EventEmitter();
1134
+ AfterCellEditBegin = new EventEmitter();
1135
+ BeforeCellEditCommit = new EventEmitter();
1136
+ AfterCellEditCommit = new EventEmitter();
1137
+ BeforeCellEditCancel = new EventEmitter();
1138
+ AfterCellEditCancel = new EventEmitter();
1139
+ BeforeRowSave = new EventEmitter();
1140
+ AfterRowSave = new EventEmitter();
1141
+ BeforeRowDelete = new EventEmitter();
1142
+ AfterRowDelete = new EventEmitter();
1143
+ // Data Loading
1144
+ BeforeDataLoad = new EventEmitter();
1145
+ AfterDataLoad = new EventEmitter();
1146
+ BeforeDataRefresh = new EventEmitter();
1147
+ AfterDataRefresh = new EventEmitter();
1148
+ // Sorting
1149
+ BeforeSort = new EventEmitter();
1150
+ AfterSort = new EventEmitter();
1151
+ // Column Management
1152
+ BeforeColumnReorder = new EventEmitter();
1153
+ AfterColumnReorder = new EventEmitter();
1154
+ BeforeColumnResize = new EventEmitter();
1155
+ AfterColumnResize = new EventEmitter();
1156
+ BeforeColumnVisibilityChange = new EventEmitter();
1157
+ AfterColumnVisibilityChange = new EventEmitter();
1158
+ // Grid State
1159
+ GridStateChanged = new EventEmitter();
1160
+ // Toolbar Actions (legacy names)
1161
+ AddRequested = new EventEmitter();
1162
+ DeleteRequested = new EventEmitter();
1163
+ ExportRequested = new EventEmitter();
1164
+ // Predefined Toolbar Button Events
1165
+ NewButtonClick = new EventEmitter();
1166
+ RefreshButtonClick = new EventEmitter();
1167
+ ExportButtonClick = new EventEmitter();
1168
+ DeleteButtonClick = new EventEmitter();
1169
+ CompareButtonClick = new EventEmitter();
1170
+ MergeButtonClick = new EventEmitter();
1171
+ AddToListButtonClick = new EventEmitter();
1172
+ DuplicateSearchButtonClick = new EventEmitter();
1173
+ CommunicationButtonClick = new EventEmitter();
1174
+ // Navigation Events
1175
+ /**
1176
+ * Emitted when a row is clicked/double-clicked and AutoNavigate is enabled.
1177
+ * Parent components should handle this to open the entity record.
1178
+ */
1179
+ NavigationRequested = new EventEmitter();
1180
+ /**
1181
+ * Emitted when the "New" button is clicked and CreateRecordMode is 'Dialog'.
1182
+ * Parent components should handle this to show a new record dialog.
1183
+ */
1184
+ NewRecordDialogRequested = new EventEmitter();
1185
+ /**
1186
+ * Emitted when the "New" button is clicked and CreateRecordMode is 'Tab'.
1187
+ * Parent components should handle this to open a new record in a tab.
1188
+ */
1189
+ NewRecordTabRequested = new EventEmitter();
1190
+ // Dialog Request Events (for Explorer-specific dialogs)
1191
+ /**
1192
+ * Emitted when Compare Records functionality is requested.
1193
+ * Parent components should handle this to show the compare dialog.
1194
+ */
1195
+ CompareRecordsRequested = new EventEmitter();
1196
+ /**
1197
+ * Emitted when Merge Records functionality is requested.
1198
+ * Parent components should handle this to show the merge dialog.
1199
+ */
1200
+ MergeRecordsRequested = new EventEmitter();
1201
+ /**
1202
+ * Emitted when Communication/Send Message functionality is requested.
1203
+ * Parent components should handle this to show the communication dialog.
1204
+ */
1205
+ CommunicationRequested = new EventEmitter();
1206
+ /**
1207
+ * Emitted when duplicate search functionality is requested.
1208
+ * Parent components should handle this to show the duplicate search results.
1209
+ */
1210
+ DuplicateSearchRequested = new EventEmitter();
1211
+ /**
1212
+ * Emitted when the Add to List button is clicked.
1213
+ * Parent components should handle this to show the list management dialog.
1214
+ */
1215
+ AddToListRequested = new EventEmitter();
1216
+ // Entity Action Events
1217
+ /**
1218
+ * Emitted when entity actions need to be loaded.
1219
+ * Parent components should load actions and set the EntityActions input.
1220
+ */
1221
+ LoadEntityActionsRequested = new EventEmitter();
1222
+ /**
1223
+ * Emitted when an entity action is selected for execution.
1224
+ * Parent components should handle the action execution.
1225
+ */
1226
+ EntityActionRequested = new EventEmitter();
1227
+ // ========================================
1228
+ // View Children
1229
+ // ========================================
1230
+ gridContainer;
1231
+ // ========================================
1232
+ // AG Grid Properties
1233
+ // ========================================
1234
+ /** AG Grid column definitions */
1235
+ agColumnDefs = [];
1236
+ /** AG Grid row data */
1237
+ rowData = [];
1238
+ /** AG Grid API reference */
1239
+ gridApi = null;
1240
+ /** AG Grid theme (v34+) with custom selection colors */
1241
+ agGridTheme = themeAlpine.withParams({
1242
+ selectedRowBackgroundColor: '#fff3cd', // More visible mellow yellow selection
1243
+ rowHoverColor: '#f5f5f5'
1244
+ });
1245
+ /** AG Grid row selection configuration */
1246
+ agRowSelection = { mode: 'singleRow' };
1247
+ /** Default column settings */
1248
+ defaultColDef = {
1249
+ sortable: true,
1250
+ filter: false,
1251
+ resizable: true,
1252
+ minWidth: 80
1253
+ };
1254
+ /** Get row ID function for AG Grid */
1255
+ getRowId = (params) => params.data['__pk'];
1256
+ /** Suppress sort changed events during programmatic updates */
1257
+ suppressSortEvents = false;
1258
+ /** AG Grid options for infinite scroll mode */
1259
+ gridOptions = {};
1260
+ /** Datasource for infinite scroll mode */
1261
+ infiniteDatasource = null;
1262
+ // ========================================
1263
+ // Internal State
1264
+ // ========================================
1265
+ _useExternalData = false;
1266
+ _allData = [];
1267
+ _rowDataMap = new Map();
1268
+ _entityInfo = null;
1269
+ _viewEntity = null;
1270
+ _columnStates = [];
1271
+ _sortState = [];
1272
+ _filterState = [];
1273
+ _editingRowKey = null;
1274
+ _editingField = null;
1275
+ _pendingChanges = [];
1276
+ // ========================================
1277
+ // Public Read-Only Properties
1278
+ // ========================================
1279
+ /**
1280
+ * The loaded view entity if using a stored view (ViewID, ViewName, or ViewEntity in Params).
1281
+ * Null for dynamic views or when using legacy entityName input.
1282
+ */
1283
+ get ViewEntity() {
1284
+ return this._viewEntity;
1285
+ }
1286
+ /**
1287
+ * The entity metadata for the current data source.
1288
+ * Available after Params is set and data is loaded.
1289
+ */
1290
+ get EntityInfo() {
1291
+ return this._entityInfo;
1292
+ }
1293
+ /**
1294
+ * True if using a dynamic view (EntityName only) rather than a stored view.
1295
+ * False if using ViewID, ViewName, ViewEntity in Params, or viewEntity input.
1296
+ */
1297
+ get IsDynamicView() {
1298
+ // If we have a viewEntity (from either Params or input), it's a stored view
1299
+ if (this._viewEntity)
1300
+ return false;
1301
+ // Check Params for stored view indicators
1302
+ if (this._params) {
1303
+ return !this._params.ViewID && !this._params.ViewName && !this._params.ViewEntity;
1304
+ }
1305
+ // Legacy mode without viewEntity is considered dynamic
1306
+ return true;
1307
+ }
1308
+ // Loading state
1309
+ loading = false;
1310
+ errorMessage = '';
1311
+ totalRowCount = 0;
1312
+ _loadDataPromise = null;
1313
+ // Cleanup
1314
+ destroy$ = new Subject();
1315
+ refreshSubject = new Subject();
1316
+ statesSaveSubject = new Subject();
1317
+ statePersistSubject = new Subject();
1318
+ userDefaultsPersistSubject = new Subject();
1319
+ // Persist state tracking
1320
+ pendingStateToSave = null;
1321
+ isSavingState = false;
1322
+ /**
1323
+ * Pending state waiting to be persisted (for flushing on destroy).
1324
+ * Tracks state for both saved views and user defaults separately.
1325
+ */
1326
+ _pendingViewStateToPersist = null;
1327
+ _pendingUserDefaultsToPersist = null;
1328
+ /**
1329
+ * Flag to suppress state persistence during view transitions.
1330
+ * When true, emitGridStateChanged will not trigger persistence.
1331
+ * This prevents the old view's column state from being saved to the new view.
1332
+ */
1333
+ _suppressPersist = false;
1334
+ /**
1335
+ * Dirty flag to track if grid state has actually been modified by user actions.
1336
+ * Only set to true when: column resize, column move, or sort change.
1337
+ * Reset to false: when view loads, after successful persistence.
1338
+ * This prevents emitting gridStateChanged events when no real change occurred.
1339
+ */
1340
+ _isGridStateDirty = false;
1341
+ /**
1342
+ * Whether the current user can edit the view.
1343
+ * For dynamic views, always true (persists to user settings).
1344
+ * For saved views, depends on view ownership and permissions.
1345
+ */
1346
+ get canEditCurrentView() {
1347
+ if (this.IsDynamicView) {
1348
+ return true; // Dynamic views persist to user settings
1349
+ }
1350
+ return this._viewEntity?.UserCanEdit ?? false;
1351
+ }
1352
+ // Overflow menu state
1353
+ showOverflowMenu = false;
1354
+ // Export dialog state
1355
+ showExportDialog = false;
1356
+ exportDialogConfig = null;
1357
+ constructor(cdr, elementRef, exportService) {
1358
+ this.cdr = cdr;
1359
+ this.elementRef = elementRef;
1360
+ this.exportService = exportService;
1361
+ }
1362
+ // ========================================
1363
+ // Lifecycle Hooks
1364
+ // ========================================
1365
+ async ngOnInit() {
1366
+ this.setupRefreshDebounce();
1367
+ this.setupStatePersistDebounce();
1368
+ this.setupUserDefaultsPersistDebounce();
1369
+ this.updateAgRowSelection();
1370
+ }
1371
+ ngOnDestroy() {
1372
+ // Flush any pending state persistence immediately before destroying
1373
+ this.EnsurePendingChangesSaved();
1374
+ this.destroy$.next();
1375
+ this.destroy$.complete();
1376
+ }
1377
+ /**
1378
+ * Immediately persists any pending state changes without waiting for debounce.
1379
+ * Called on component destroy to ensure changes aren't lost.
1380
+ * Also exposed publicly so parent components can flush before view/entity switches.
1381
+ */
1382
+ EnsurePendingChangesSaved() {
1383
+ // Flush pending view state
1384
+ if (this._pendingViewStateToPersist && this._viewEntity) {
1385
+ this.persistGridStateToView(this._pendingViewStateToPersist);
1386
+ this._pendingViewStateToPersist = null;
1387
+ }
1388
+ // Flush pending user defaults
1389
+ if (this._pendingUserDefaultsToPersist && this._entityInfo) {
1390
+ this.persistUserDefaultGridState(this._pendingUserDefaultsToPersist);
1391
+ this._pendingUserDefaultsToPersist = null;
1392
+ }
1393
+ }
1394
+ // ========================================
1395
+ // Setup Methods
1396
+ // ========================================
1397
+ setupRefreshDebounce() {
1398
+ this.refreshSubject.pipe(debounceTime(this._refreshDebounce), takeUntil(this.destroy$)).subscribe(() => {
1399
+ this.loadData(true);
1400
+ });
1401
+ }
1402
+ setupStatePersistDebounce() {
1403
+ this.statePersistSubject.pipe(debounceTime(this._statePersistDebounce), takeUntil(this.destroy$)).subscribe((state) => {
1404
+ this.persistGridStateToView(state);
1405
+ });
1406
+ }
1407
+ setupUserDefaultsPersistDebounce() {
1408
+ this.userDefaultsPersistSubject.pipe(debounceTime(this._statePersistDebounce), takeUntil(this.destroy$)).subscribe((state) => {
1409
+ this.persistUserDefaultGridState(state);
1410
+ });
1411
+ }
1412
+ /**
1413
+ * Called when Params input changes. Handles loading view entity metadata
1414
+ * and triggering data load if allowed.
1415
+ */
1416
+ async onParamsChanged() {
1417
+ if (!this._params) {
1418
+ // Params cleared - reset view entity
1419
+ this._viewEntity = null;
1420
+ return;
1421
+ }
1422
+ // Suppress persistence during view transition to prevent old view's state
1423
+ // from being saved to the new view's storage
1424
+ this._suppressPersist = true;
1425
+ // Reset dirty flag - no changes have been made to the new view yet
1426
+ this._isGridStateDirty = false;
1427
+ // Reset internal grid state when params change - this ensures we don't
1428
+ // carry over column/sort settings from a previous view when switching views
1429
+ this._gridState = null;
1430
+ this._sortState = [];
1431
+ try {
1432
+ // If using a stored view, load the view entity first
1433
+ if (this._params.ViewEntity) {
1434
+ // ViewEntity was provided directly
1435
+ this._viewEntity = this._params.ViewEntity;
1436
+ this._entityInfo = this._viewEntity.ViewEntityInfo;
1437
+ this.applyViewEntitySettings();
1438
+ }
1439
+ else if (this._params.ViewID) {
1440
+ // Load view entity by ID from engine
1441
+ const cachedView = this.getViewFromEngine(this._params.ViewID);
1442
+ if (cachedView) {
1443
+ this._viewEntity = cachedView;
1444
+ }
1445
+ else {
1446
+ // View not in cache - use ViewInfo (which also uses engine)
1447
+ this._viewEntity = await ViewInfo.GetViewEntity(this._params.ViewID);
1448
+ }
1449
+ this._entityInfo = this._viewEntity.ViewEntityInfo;
1450
+ this.applyViewEntitySettings();
1451
+ }
1452
+ else if (this._params.ViewName) {
1453
+ // Load view entity by name from engine
1454
+ const cachedView = this.getViewFromEngineByName(this._params.ViewName);
1455
+ if (cachedView) {
1456
+ this._viewEntity = cachedView;
1457
+ }
1458
+ else {
1459
+ // View not in cache - use ViewInfo (which also uses engine)
1460
+ this._viewEntity = await ViewInfo.GetViewEntityByName(this._params.ViewName);
1461
+ }
1462
+ this._entityInfo = this._viewEntity.ViewEntityInfo;
1463
+ this.applyViewEntitySettings();
1464
+ }
1465
+ else if (this._params.EntityName) {
1466
+ // Dynamic view - just get entity metadata
1467
+ this._viewEntity = null;
1468
+ const md = new Metadata();
1469
+ this._entityInfo = md.Entities.find(e => e.Name === this._params.EntityName) || null;
1470
+ // Reset columns to force regeneration from metadata when switching to dynamic view
1471
+ // This ensures we don't carry over column config from a previous saved view
1472
+ this._columns = [];
1473
+ // For dynamic views, try to load user's saved defaults
1474
+ if (this._entityInfo && this._autoPersistState) {
1475
+ this.loadUserDefaultGridState(this._entityInfo.Name);
1476
+ }
1477
+ }
1478
+ // Generate columns if not already set
1479
+ if (this._columns.length === 0 && this._entityInfo) {
1480
+ this.generateColumnsFromMetadata();
1481
+ }
1482
+ // Rebuild AG Grid column definitions to reflect the new view's settings
1483
+ this.buildAgColumnDefs();
1484
+ // Load data if auto-refresh is enabled
1485
+ if (this._autoRefreshOnParamsChange) {
1486
+ await this.loadData(false);
1487
+ }
1488
+ }
1489
+ catch (error) {
1490
+ this.errorMessage = error instanceof Error ? error.message : 'Failed to load view';
1491
+ this.cdr.detectChanges();
1492
+ }
1493
+ finally {
1494
+ // Re-enable persistence now that the new view is fully loaded
1495
+ this._suppressPersist = false;
1496
+ }
1497
+ }
1498
+ /**
1499
+ * Gets a view from the UserViewEngine cache by ID.
1500
+ * Returns undefined if not found or engine not initialized.
1501
+ */
1502
+ getViewFromEngine(viewId) {
1503
+ try {
1504
+ return UserViewEngine.Instance.GetViewById(viewId);
1505
+ }
1506
+ catch {
1507
+ // Engine may not be initialized yet
1508
+ return undefined;
1509
+ }
1510
+ }
1511
+ /**
1512
+ * Gets a view from the UserViewEngine cache by name.
1513
+ * Returns undefined if not found or engine not initialized.
1514
+ */
1515
+ getViewFromEngineByName(viewName) {
1516
+ try {
1517
+ return UserViewEngine.Instance.GetViewByName(viewName);
1518
+ }
1519
+ catch {
1520
+ // Engine may not be initialized yet
1521
+ return undefined;
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Loads user's saved grid state defaults for a dynamic view.
1526
+ * Uses UserInfoEngine to retrieve settings stored with key format: "default-view-setting/{entityName}"
1527
+ */
1528
+ loadUserDefaultGridState(entityName) {
1529
+ try {
1530
+ const settingKey = `default-view-setting/${entityName}`;
1531
+ const savedState = UserInfoEngine.Instance.GetSetting(settingKey);
1532
+ if (savedState) {
1533
+ const gridState = JSON.parse(savedState);
1534
+ // Only apply if not already set via props (props take precedence)
1535
+ if (!this._gridState && gridState.columnSettings?.length) {
1536
+ this._gridState = {
1537
+ columnSettings: gridState.columnSettings,
1538
+ sortSettings: gridState.sortSettings || []
1539
+ };
1540
+ }
1541
+ // Apply sort state if not already set
1542
+ if (this._sortState.length === 0 && gridState.sortSettings?.length) {
1543
+ this._sortState = gridState.sortSettings.map((s, index) => ({
1544
+ field: s.field,
1545
+ direction: s.dir,
1546
+ index
1547
+ }));
1548
+ }
1549
+ }
1550
+ }
1551
+ catch (error) {
1552
+ console.error('[entity-data-grid] Failed to load user default grid state:', error);
1553
+ }
1554
+ }
1555
+ /**
1556
+ * Persists the current grid state as user defaults for a dynamic view.
1557
+ * Uses UserInfoEngine to store settings with key format: "default-view-setting/{entityName}"
1558
+ */
1559
+ async persistUserDefaultGridState(state) {
1560
+ if (!this._entityInfo) {
1561
+ this._pendingUserDefaultsToPersist = null;
1562
+ return;
1563
+ }
1564
+ try {
1565
+ const settingKey = `default-view-setting/${this._entityInfo.Name}`;
1566
+ const gridStateJson = {
1567
+ columnSettings: state.columnSettings,
1568
+ sortSettings: state.sortSettings
1569
+ };
1570
+ await UserInfoEngine.Instance.SetSetting(settingKey, JSON.stringify(gridStateJson));
1571
+ // Clear pending state and reset dirty flag after successful save
1572
+ this._pendingUserDefaultsToPersist = null;
1573
+ this._isGridStateDirty = false;
1574
+ }
1575
+ catch (error) {
1576
+ console.error('[entity-data-grid] Failed to persist user default grid state:', error);
1577
+ }
1578
+ }
1579
+ /**
1580
+ * Applies settings from a loaded ViewEntity (column configuration, sort state, etc.)
1581
+ *
1582
+ * Precedence rules (highest to lowest):
1583
+ * 1. Props passed directly (gridState, orderBy, etc.) - always take precedence
1584
+ * 2. Settings from ViewEntity.GridState (persisted column/sort configuration)
1585
+ * 3. Settings from ViewEntity.SortInfo (legacy sort configuration)
1586
+ * 4. Auto-generated defaults from entity metadata
1587
+ */
1588
+ applyViewEntitySettings() {
1589
+ if (!this._viewEntity)
1590
+ return;
1591
+ // Only apply grid state from view entity if not already set via props
1592
+ // (gridState input takes precedence)
1593
+ if (!this._gridState && this._viewEntity.GridState) {
1594
+ try {
1595
+ const gridState = JSON.parse(this._viewEntity.GridState);
1596
+ if (gridState.columnSettings?.length) {
1597
+ this._gridState = {
1598
+ columnSettings: gridState.columnSettings,
1599
+ sortSettings: gridState.sortSettings || []
1600
+ };
1601
+ }
1602
+ }
1603
+ catch (e) {
1604
+ console.warn('Failed to parse view GridState:', e);
1605
+ }
1606
+ }
1607
+ // Only apply sort state if:
1608
+ // 1. No explicit OrderBy in Params
1609
+ // 2. No sort already set via gridState (either prop or from view)
1610
+ const hasExplicitOrderBy = this._params?.OrderBy && this._params.OrderBy.trim().length > 0;
1611
+ const hasSortFromGridState = this._gridState?.sortSettings && this._gridState.sortSettings.length > 0;
1612
+ if (!hasExplicitOrderBy && !hasSortFromGridState && this._sortState.length === 0) {
1613
+ const sortInfo = this._viewEntity.ViewSortInfo;
1614
+ if (sortInfo?.length) {
1615
+ this._sortState = sortInfo.map((s, index) => ({
1616
+ field: s.field,
1617
+ direction: s.direction?.toLowerCase() === 'desc' ? 'desc' : 'asc',
1618
+ index
1619
+ }));
1620
+ }
1621
+ }
1622
+ }
1623
+ onGridStateChanged() {
1624
+ if (this._gridState && this._entityInfo) {
1625
+ this.buildAgColumnDefs();
1626
+ // Update AG Grid with new column definitions to apply header styles
1627
+ if (this.gridApi) {
1628
+ this.gridApi.setGridOption('columnDefs', this.agColumnDefs);
1629
+ // Refresh header to apply new header styles
1630
+ this.gridApi.refreshHeader();
1631
+ }
1632
+ // Apply sort if present
1633
+ if (this._gridState.sortSettings?.length && this.gridApi) {
1634
+ const sortSetting = this._gridState.sortSettings[0];
1635
+ this._sortState = [{
1636
+ field: sortSetting.field,
1637
+ direction: sortSetting.dir,
1638
+ index: 0
1639
+ }];
1640
+ this.applySortStateToGrid();
1641
+ }
1642
+ }
1643
+ }
1644
+ onFilterTextChanged() {
1645
+ // Rebuild column defs to update cell renderers with new filter text
1646
+ this.buildAgColumnDefs();
1647
+ // Update AG Grid with new column definitions and refresh cells
1648
+ if (this.gridApi) {
1649
+ // Update the column definitions in AG Grid
1650
+ this.gridApi.setGridOption('columnDefs', this.agColumnDefs);
1651
+ // Force refresh all cells to apply new highlighting
1652
+ this.gridApi.refreshCells({ force: true });
1653
+ }
1654
+ }
1655
+ generateColumnsFromMetadata() {
1656
+ if (!this._entityInfo)
1657
+ return;
1658
+ this._columns = this._entityInfo.Fields
1659
+ .filter(f => this.shouldShowField(f))
1660
+ .map(field => ({
1661
+ field: field.Name,
1662
+ title: field.DisplayName || field.Name,
1663
+ type: this.mapFieldTypeToGridType(field.Type),
1664
+ sortable: true,
1665
+ filterable: true,
1666
+ visible: true,
1667
+ width: this.estimateColumnWidth(field)
1668
+ }));
1669
+ this.initializeColumnStates();
1670
+ this.buildAgColumnDefs();
1671
+ }
1672
+ shouldShowField(field) {
1673
+ if (field.Name.startsWith('__mj_'))
1674
+ return false;
1675
+ if (field.IsPrimaryKey && field.SQLFullType?.toLowerCase() === 'uniqueidentifier') {
1676
+ return false;
1677
+ }
1678
+ if (field.DefaultInView === true)
1679
+ return true;
1680
+ if (field.Length > 500)
1681
+ return false;
1682
+ return true;
1683
+ }
1684
+ estimateColumnWidth(field) {
1685
+ const fieldNameLower = field.Name.toLowerCase();
1686
+ const displayNameLower = (field.DisplayName || field.Name).toLowerCase();
1687
+ // Fixed-width types
1688
+ if (field.TSType === 'boolean')
1689
+ return 80;
1690
+ if (field.TSType === 'Date')
1691
+ return 120;
1692
+ // Numeric fields - compact
1693
+ if (field.TSType === 'number') {
1694
+ if (fieldNameLower.includes('year') || fieldNameLower.includes('age'))
1695
+ return 80;
1696
+ if (fieldNameLower.includes('amount') || fieldNameLower.includes('price') || fieldNameLower.includes('total'))
1697
+ return 120;
1698
+ return 100;
1699
+ }
1700
+ // ID fields - compact
1701
+ if (fieldNameLower.endsWith('id') && field.Length <= 50)
1702
+ return 80;
1703
+ // Email - needs more space
1704
+ if (fieldNameLower.includes('email'))
1705
+ return 220;
1706
+ // Phone numbers
1707
+ if (fieldNameLower.includes('phone') || fieldNameLower.includes('mobile') || fieldNameLower.includes('fax'))
1708
+ return 130;
1709
+ // Name fields - medium width
1710
+ if (fieldNameLower.includes('name') || fieldNameLower.includes('title')) {
1711
+ if (fieldNameLower === 'firstname' || fieldNameLower === 'lastname' || fieldNameLower === 'first name' || fieldNameLower === 'last name')
1712
+ return 120;
1713
+ return 160;
1714
+ }
1715
+ // Location fields
1716
+ if (fieldNameLower.includes('city'))
1717
+ return 120;
1718
+ if (fieldNameLower.includes('state') || fieldNameLower.includes('country'))
1719
+ return 100;
1720
+ if (fieldNameLower.includes('zip') || fieldNameLower.includes('postal'))
1721
+ return 90;
1722
+ if (fieldNameLower.includes('address'))
1723
+ return 200;
1724
+ // Date-like strings
1725
+ if (fieldNameLower.includes('date') || fieldNameLower.includes('time'))
1726
+ return 120;
1727
+ // Status/Type fields - usually short values
1728
+ if (fieldNameLower.includes('status') || fieldNameLower.includes('type') || fieldNameLower.includes('category'))
1729
+ return 110;
1730
+ // Code/abbreviation fields
1731
+ if (fieldNameLower.includes('code') || fieldNameLower.includes('abbr'))
1732
+ return 100;
1733
+ // Long text fields - limit width, they'll truncate
1734
+ if (field.Length > 500)
1735
+ return 150;
1736
+ if (field.Length > 200)
1737
+ return 180;
1738
+ // Default: estimate based on field length but with tighter bounds
1739
+ const estimatedChars = Math.min(field.Length, 50);
1740
+ const charWidth = 7;
1741
+ const padding = 24;
1742
+ return Math.min(Math.max(estimatedChars * charWidth / 2 + padding, 80), 200);
1743
+ }
1744
+ mapFieldTypeToGridType(fieldType) {
1745
+ switch (fieldType.toLowerCase()) {
1746
+ case 'int':
1747
+ case 'bigint':
1748
+ case 'smallint':
1749
+ case 'tinyint':
1750
+ case 'decimal':
1751
+ case 'numeric':
1752
+ case 'float':
1753
+ case 'real':
1754
+ case 'money':
1755
+ case 'smallmoney':
1756
+ return 'number';
1757
+ case 'bit':
1758
+ return 'boolean';
1759
+ case 'date':
1760
+ return 'date';
1761
+ case 'datetime':
1762
+ case 'datetime2':
1763
+ case 'smalldatetime':
1764
+ case 'datetimeoffset':
1765
+ return 'datetime';
1766
+ default:
1767
+ return 'string';
1768
+ }
1769
+ }
1770
+ initializeColumnStates() {
1771
+ this._columnStates = this._columns.map((config, index) => ({
1772
+ config,
1773
+ computedWidth: typeof config.width === 'number' ? config.width : 150,
1774
+ sortDirection: config.sortDirection || 'none',
1775
+ sortIndex: config.sortIndex || 0,
1776
+ filterValue: undefined,
1777
+ visible: config.visible !== false,
1778
+ order: index
1779
+ }));
1780
+ }
1781
+ // ========================================
1782
+ // AG Grid Column Building
1783
+ // ========================================
1784
+ buildAgColumnDefs() {
1785
+ if (this._gridState?.columnSettings?.length && this._entityInfo) {
1786
+ this.agColumnDefs = this.buildAgColumnDefsFromGridState(this._gridState.columnSettings);
1787
+ }
1788
+ else if (this._columns.length > 0) {
1789
+ this.agColumnDefs = this._columns.map(col => this.mapColumnConfigToColDef(col));
1790
+ }
1791
+ else if (this._entityInfo) {
1792
+ this.agColumnDefs = this.generateAgColumnDefs(this._entityInfo);
1793
+ }
1794
+ else {
1795
+ this.agColumnDefs = [];
1796
+ }
1797
+ // Add row number column if enabled
1798
+ if (this._showRowNumbers && this.agColumnDefs.length > 0) {
1799
+ this.agColumnDefs.unshift({
1800
+ headerName: '#',
1801
+ field: '__rowNumber',
1802
+ width: 60,
1803
+ minWidth: 50,
1804
+ maxWidth: 80,
1805
+ sortable: false,
1806
+ resizable: false,
1807
+ valueGetter: (params) => params.node ? params.node.rowIndex + 1 : ''
1808
+ });
1809
+ }
1810
+ }
1811
+ buildAgColumnDefsFromGridState(columnSettings) {
1812
+ if (!this._entityInfo)
1813
+ return [];
1814
+ const sortedColumns = [...columnSettings].sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0));
1815
+ const cols = [];
1816
+ for (const colConfig of sortedColumns) {
1817
+ if (colConfig.hidden)
1818
+ continue;
1819
+ const field = this._entityInfo.Fields.find(f => f.Name.toLowerCase() === colConfig.Name.toLowerCase());
1820
+ if (!field)
1821
+ continue;
1822
+ const colDef = {
1823
+ field: field.Name,
1824
+ // Use userDisplayName if set, otherwise fall back to DisplayName or field name
1825
+ headerName: colConfig.userDisplayName || colConfig.DisplayName || field.DisplayNameOrName,
1826
+ width: colConfig.width || this.estimateColumnWidth(field),
1827
+ sortable: this._allowSorting,
1828
+ resizable: this._allowColumnResize
1829
+ };
1830
+ // Add type-specific formatters with optional custom format
1831
+ this.applyFieldFormatter(colDef, field, colConfig.format);
1832
+ cols.push(colDef);
1833
+ }
1834
+ return cols;
1835
+ }
1836
+ mapColumnConfigToColDef(col) {
1837
+ const colDef = {
1838
+ field: col.field,
1839
+ headerName: col.title || col.field,
1840
+ width: typeof col.width === 'number' ? col.width : undefined,
1841
+ minWidth: col.minWidth,
1842
+ maxWidth: col.maxWidth,
1843
+ sortable: col.sortable !== false && this._allowSorting,
1844
+ filter: false,
1845
+ resizable: col.resizable !== false && this._allowColumnResize,
1846
+ hide: col.visible === false
1847
+ };
1848
+ // Apply field formatter for highlighting and type-specific formatting
1849
+ // if we have entity metadata for this field
1850
+ if (this._entityInfo) {
1851
+ const field = this._entityInfo.Fields.find(f => f.Name.toLowerCase() === col.field.toLowerCase());
1852
+ if (field) {
1853
+ this.applyFieldFormatter(colDef, field);
1854
+ }
1855
+ }
1856
+ return colDef;
1857
+ }
1858
+ generateAgColumnDefs(entity) {
1859
+ const cols = [];
1860
+ const visibleFields = entity.Fields.filter(f => this.shouldShowField(f));
1861
+ for (const field of visibleFields) {
1862
+ const colDef = {
1863
+ field: field.Name,
1864
+ headerName: field.DisplayNameOrName,
1865
+ width: this.estimateColumnWidth(field),
1866
+ sortable: this._allowSorting,
1867
+ resizable: this._allowColumnResize
1868
+ };
1869
+ this.applyFieldFormatter(colDef, field);
1870
+ cols.push(colDef);
1871
+ }
1872
+ return cols;
1873
+ }
1874
+ applyFieldFormatter(colDef, field, customFormat) {
1875
+ // Store type info for use in cell renderer
1876
+ const fieldType = field.TSType;
1877
+ const fieldNameLower = field.Name.toLowerCase();
1878
+ const extendedType = field.ExtendedType?.toLowerCase() || '';
1879
+ const vc = this.effectiveVisualConfig;
1880
+ // Determine special field types using ExtendedType metadata first, then field name patterns as fallback
1881
+ const isCurrency = customFormat?.type === 'currency' ||
1882
+ fieldNameLower.includes('amount') ||
1883
+ fieldNameLower.includes('price') ||
1884
+ fieldNameLower.includes('cost') ||
1885
+ fieldNameLower.includes('total');
1886
+ // Use ExtendedType='Email' from metadata, fallback to field name pattern
1887
+ const isEmail = extendedType === 'email' ||
1888
+ (!extendedType && fieldNameLower.includes('email'));
1889
+ // Use ExtendedType='URL' from metadata, fallback to field name pattern
1890
+ const isUrl = extendedType === 'url' ||
1891
+ (!extendedType && (fieldNameLower.includes('url') ||
1892
+ fieldNameLower.includes('website') ||
1893
+ fieldNameLower.includes('link')));
1894
+ // Use ExtendedType='Tel' from metadata, fallback to field name pattern
1895
+ const isPhone = extendedType === 'tel' ||
1896
+ (!extendedType && (fieldNameLower.includes('phone') ||
1897
+ fieldNameLower.includes('mobile') ||
1898
+ fieldNameLower.includes('fax')));
1899
+ // Apply alignment - use custom format alignment if provided, otherwise default to right for numbers
1900
+ const customAlign = customFormat?.align;
1901
+ if (customAlign) {
1902
+ colDef.cellClass = `cell-align-${customAlign}`;
1903
+ colDef.headerClass = `header-align-${customAlign}`;
1904
+ }
1905
+ else if (vc.rightAlignNumbers && (fieldType === 'number' || isCurrency)) {
1906
+ colDef.cellClass = 'cell-align-right';
1907
+ colDef.headerClass = 'header-align-right';
1908
+ }
1909
+ // Apply custom header style if provided
1910
+ if (customFormat?.headerStyle) {
1911
+ const headerStyle = this.buildCssStyle(customFormat.headerStyle);
1912
+ if (headerStyle) {
1913
+ colDef.headerClass = (colDef.headerClass || '') + ' custom-header-style';
1914
+ // AG Grid uses headerStyle for inline styles
1915
+ colDef.headerStyle = this.buildStyleObject(customFormat.headerStyle);
1916
+ }
1917
+ }
1918
+ // Use cellRenderer for highlighting support and enhanced formatting
1919
+ colDef.cellRenderer = (params) => {
1920
+ if (params.value === null || params.value === undefined) {
1921
+ return '<span class="cell-empty">—</span>';
1922
+ }
1923
+ let displayValue = '';
1924
+ let extraClass = '';
1925
+ let inlineStyle = '';
1926
+ let isHtmlContent = false; // Track if displayValue contains HTML (icons, links)
1927
+ // Build inline style from custom cell style
1928
+ if (customFormat?.cellStyle) {
1929
+ inlineStyle = this.buildCssStyle(customFormat.cellStyle);
1930
+ }
1931
+ // Check if custom format is provided and has a non-auto type
1932
+ const useCustomFormat = customFormat && customFormat.type && customFormat.type !== 'auto';
1933
+ if (useCustomFormat) {
1934
+ // Use custom formatting
1935
+ displayValue = this.formatValueWithCustomFormat(params.value, customFormat);
1936
+ // Check if formatCustomBoolean returned HTML (icon or checkbox)
1937
+ if (customFormat.type === 'boolean' &&
1938
+ (customFormat.booleanDisplay === 'icon' || customFormat.booleanDisplay === 'checkbox')) {
1939
+ isHtmlContent = true;
1940
+ }
1941
+ }
1942
+ else {
1943
+ // Use default formatting based on field type
1944
+ // Boolean formatting
1945
+ if (fieldType === 'boolean') {
1946
+ if (vc.booleanIcons) {
1947
+ const iconClass = params.value
1948
+ ? 'fa-solid fa-check cell-boolean-true'
1949
+ : 'fa-solid fa-xmark cell-boolean-false';
1950
+ displayValue = `<i class="${iconClass}"></i>`;
1951
+ isHtmlContent = true;
1952
+ }
1953
+ else {
1954
+ displayValue = params.value ? 'Yes' : 'No';
1955
+ }
1956
+ }
1957
+ // Date formatting
1958
+ else if (fieldType === 'Date') {
1959
+ const date = params.value instanceof Date ? params.value : new Date(params.value);
1960
+ if (isNaN(date.getTime())) {
1961
+ displayValue = String(params.value);
1962
+ }
1963
+ else if (vc.friendlyDates) {
1964
+ displayValue = date.toLocaleDateString(undefined, {
1965
+ month: 'short',
1966
+ day: 'numeric',
1967
+ year: 'numeric'
1968
+ });
1969
+ }
1970
+ else {
1971
+ displayValue = date.toISOString().split('T')[0];
1972
+ }
1973
+ }
1974
+ // Currency formatting
1975
+ else if (fieldType === 'number' && isCurrency) {
1976
+ const num = Number(params.value);
1977
+ displayValue = isNaN(num) ? String(params.value) : `$${num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
1978
+ }
1979
+ // Regular number formatting
1980
+ else if (fieldType === 'number') {
1981
+ const num = Number(params.value);
1982
+ displayValue = isNaN(num) ? String(params.value) : num.toLocaleString();
1983
+ }
1984
+ // Email formatting
1985
+ else if (isEmail && vc.clickableEmails) {
1986
+ const email = String(params.value);
1987
+ const escapedEmail = HighlightUtil.escapeHtml(email);
1988
+ if (this._filterText) {
1989
+ const highlighted = HighlightUtil.highlight(email, this._filterText, true);
1990
+ return this.wrapWithStyle(`<a href="mailto:${escapedEmail}" class="cell-link cell-email" onclick="event.stopPropagation()">${highlighted}</a>`, inlineStyle);
1991
+ }
1992
+ return this.wrapWithStyle(`<a href="mailto:${escapedEmail}" class="cell-link cell-email" onclick="event.stopPropagation()">${escapedEmail}</a>`, inlineStyle);
1993
+ }
1994
+ // URL formatting
1995
+ else if (isUrl && vc.clickableUrls) {
1996
+ let url = String(params.value);
1997
+ if (url && !url.startsWith('http')) {
1998
+ url = 'https://' + url;
1999
+ }
2000
+ const displayUrl = url.replace(/^https?:\/\//, '').replace(/\/$/, '');
2001
+ const escapedDisplay = HighlightUtil.escapeHtml(displayUrl);
2002
+ if (this._filterText) {
2003
+ const highlighted = HighlightUtil.highlight(displayUrl, this._filterText, true);
2004
+ return this.wrapWithStyle(`<a href="${HighlightUtil.escapeHtml(url)}" target="_blank" class="cell-link cell-url" onclick="event.stopPropagation()">${highlighted}</a>`, inlineStyle);
2005
+ }
2006
+ return this.wrapWithStyle(`<a href="${HighlightUtil.escapeHtml(url)}" target="_blank" class="cell-link cell-url" onclick="event.stopPropagation()">${escapedDisplay}</a>`, inlineStyle);
2007
+ }
2008
+ // Phone formatting
2009
+ else if (isPhone) {
2010
+ displayValue = String(params.value);
2011
+ extraClass = 'cell-phone';
2012
+ }
2013
+ // Default string formatting
2014
+ else {
2015
+ displayValue = String(params.value);
2016
+ }
2017
+ }
2018
+ // Build the span with optional style and class
2019
+ const styleAttr = inlineStyle ? ` style="${inlineStyle}"` : '';
2020
+ const classAttr = extraClass ? ` class="${extraClass}"` : '';
2021
+ // Handle content that's already HTML (icons, etc.)
2022
+ if (isHtmlContent) {
2023
+ if (inlineStyle) {
2024
+ return `<span${styleAttr}>${displayValue}</span>`;
2025
+ }
2026
+ return displayValue;
2027
+ }
2028
+ // Apply highlighting if filterText is set
2029
+ if (this._filterText && displayValue) {
2030
+ return `<span${classAttr}${styleAttr}>${HighlightUtil.highlight(displayValue, this._filterText, true)}</span>`;
2031
+ }
2032
+ return `<span${classAttr}${styleAttr}>${HighlightUtil.escapeHtml(displayValue)}</span>`;
2033
+ };
2034
+ }
2035
+ /**
2036
+ * Helper to wrap content with an inline style span
2037
+ */
2038
+ wrapWithStyle(content, style) {
2039
+ if (!style)
2040
+ return content;
2041
+ return `<span style="${style}">${content}</span>`;
2042
+ }
2043
+ /**
2044
+ * Build a CSS style string from a ColumnTextStyle object
2045
+ */
2046
+ buildCssStyle(style) {
2047
+ const parts = [];
2048
+ if (style.bold)
2049
+ parts.push('font-weight: bold');
2050
+ if (style.italic)
2051
+ parts.push('font-style: italic');
2052
+ if (style.underline)
2053
+ parts.push('text-decoration: underline');
2054
+ if (style.color)
2055
+ parts.push(`color: ${style.color}`);
2056
+ if (style.backgroundColor)
2057
+ parts.push(`background-color: ${style.backgroundColor}`);
2058
+ return parts.join('; ');
2059
+ }
2060
+ /**
2061
+ * Build a style object for AG Grid from a ColumnTextStyle object
2062
+ */
2063
+ buildStyleObject(style) {
2064
+ const obj = {};
2065
+ if (style.bold)
2066
+ obj['fontWeight'] = 'bold';
2067
+ if (style.italic)
2068
+ obj['fontStyle'] = 'italic';
2069
+ if (style.underline)
2070
+ obj['textDecoration'] = 'underline';
2071
+ if (style.color)
2072
+ obj['color'] = style.color;
2073
+ if (style.backgroundColor)
2074
+ obj['backgroundColor'] = style.backgroundColor;
2075
+ return obj;
2076
+ }
2077
+ /**
2078
+ * Format a value using custom ColumnFormat settings
2079
+ */
2080
+ formatValueWithCustomFormat(value, format) {
2081
+ if (value == null)
2082
+ return '—';
2083
+ switch (format.type) {
2084
+ case 'number':
2085
+ return this.formatCustomNumber(value, format);
2086
+ case 'currency':
2087
+ return this.formatCustomCurrency(value, format);
2088
+ case 'percent':
2089
+ return this.formatCustomPercent(value, format);
2090
+ case 'date':
2091
+ case 'datetime':
2092
+ return this.formatCustomDate(value, format);
2093
+ case 'boolean':
2094
+ return this.formatCustomBoolean(value, format);
2095
+ default:
2096
+ return String(value);
2097
+ }
2098
+ }
2099
+ formatCustomNumber(value, format) {
2100
+ const num = Number(value);
2101
+ if (isNaN(num))
2102
+ return String(value);
2103
+ const options = {
2104
+ minimumFractionDigits: format.decimals ?? 0,
2105
+ maximumFractionDigits: format.decimals ?? 0,
2106
+ useGrouping: format.thousandsSeparator ?? true
2107
+ };
2108
+ return new Intl.NumberFormat('en-US', options).format(num);
2109
+ }
2110
+ formatCustomCurrency(value, format) {
2111
+ const num = Number(value);
2112
+ if (isNaN(num))
2113
+ return String(value);
2114
+ const options = {
2115
+ style: 'currency',
2116
+ currency: format.currencyCode || 'USD',
2117
+ minimumFractionDigits: format.decimals ?? 2,
2118
+ maximumFractionDigits: format.decimals ?? 2
2119
+ };
2120
+ return new Intl.NumberFormat('en-US', options).format(num);
2121
+ }
2122
+ formatCustomPercent(value, format) {
2123
+ const num = Number(value);
2124
+ if (isNaN(num))
2125
+ return String(value);
2126
+ const options = {
2127
+ style: 'percent',
2128
+ minimumFractionDigits: format.decimals ?? 0,
2129
+ maximumFractionDigits: format.decimals ?? 0
2130
+ };
2131
+ // Assume value is already a percentage (e.g., 50 = 50%), divide by 100
2132
+ return new Intl.NumberFormat('en-US', options).format(num / 100);
2133
+ }
2134
+ formatCustomDate(value, format) {
2135
+ const date = value instanceof Date ? value : new Date(value);
2136
+ if (isNaN(date.getTime()))
2137
+ return String(value);
2138
+ // Parse format string - check for weekday variants
2139
+ const formatStr = format.dateFormat || 'medium';
2140
+ const includeWeekday = formatStr.includes('-weekday');
2141
+ const baseFormat = formatStr.replace('-weekday', '');
2142
+ let options;
2143
+ // Intl.DateTimeFormat doesn't allow combining dateStyle with weekday
2144
+ // So we must use individual components when weekday is requested
2145
+ if (includeWeekday) {
2146
+ if (baseFormat === 'short') {
2147
+ options = { weekday: 'short', month: 'numeric', day: 'numeric', year: '2-digit' };
2148
+ }
2149
+ else if (baseFormat === 'long') {
2150
+ options = { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' };
2151
+ }
2152
+ else {
2153
+ // medium
2154
+ options = { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' };
2155
+ }
2156
+ if (format.type === 'datetime') {
2157
+ options.hour = 'numeric';
2158
+ options.minute = '2-digit';
2159
+ }
2160
+ }
2161
+ else {
2162
+ // No weekday - can use dateStyle shorthand
2163
+ options = {
2164
+ dateStyle: baseFormat === 'short' ? 'short' : baseFormat === 'long' ? 'long' : 'medium'
2165
+ };
2166
+ if (format.type === 'datetime') {
2167
+ options.timeStyle = 'short';
2168
+ }
2169
+ }
2170
+ return new Intl.DateTimeFormat('en-US', options).format(date);
2171
+ }
2172
+ formatCustomBoolean(value, format) {
2173
+ if (format.booleanDisplay === 'icon') {
2174
+ const iconClass = value
2175
+ ? 'fa-solid fa-check cell-boolean-true'
2176
+ : 'fa-solid fa-xmark cell-boolean-false';
2177
+ return `<i class="${iconClass}"></i>`;
2178
+ }
2179
+ if (format.booleanDisplay === 'checkbox') {
2180
+ return value
2181
+ ? '<i class="fa-regular fa-square-check cell-boolean-true"></i>'
2182
+ : '<i class="fa-regular fa-square cell-boolean-false"></i>';
2183
+ }
2184
+ return value ? (format.trueLabel || 'Yes') : (format.falseLabel || 'No');
2185
+ }
2186
+ // ========================================
2187
+ // AG Grid Row Selection Configuration
2188
+ // ========================================
2189
+ updateAgRowSelection() {
2190
+ switch (this._selectionMode) {
2191
+ case 'none':
2192
+ this.agRowSelection = { mode: 'singleRow', enableClickSelection: false, checkboxes: false };
2193
+ break;
2194
+ case 'single':
2195
+ this.agRowSelection = { mode: 'singleRow', enableClickSelection: true, checkboxes: false };
2196
+ break;
2197
+ case 'multiple':
2198
+ // enableSelectionWithoutKeys replaces deprecated rowMultiSelectWithClick (as of AG Grid v32.2)
2199
+ this.agRowSelection = { mode: 'multiRow', enableClickSelection: true, checkboxes: false, enableSelectionWithoutKeys: true };
2200
+ break;
2201
+ case 'checkbox':
2202
+ this.agRowSelection = {
2203
+ mode: 'multiRow',
2204
+ enableClickSelection: true,
2205
+ checkboxes: true,
2206
+ headerCheckbox: true,
2207
+ enableSelectionWithoutKeys: true
2208
+ };
2209
+ break;
2210
+ }
2211
+ // Update the grid if it's already initialized
2212
+ if (this.gridApi) {
2213
+ this.gridApi.setGridOption('rowSelection', this.agRowSelection);
2214
+ }
2215
+ }
2216
+ // ========================================
2217
+ // Data Loading
2218
+ // ========================================
2219
+ async loadData(isAutoRefresh = false) {
2220
+ if (this._useExternalData) {
2221
+ this.processData();
2222
+ return;
2223
+ }
2224
+ // Check if we have a valid data source via Params
2225
+ const hasDataSource = this._params && (this._params.ViewID ||
2226
+ this._params.ViewName ||
2227
+ this._params.ViewEntity ||
2228
+ this._params.EntityName);
2229
+ if (!hasDataSource) {
2230
+ return;
2231
+ }
2232
+ // Check AllowLoad for deferred loading
2233
+ if (!this._allowLoad) {
2234
+ return;
2235
+ }
2236
+ // For infinite scroll mode, setup or refresh the datasource
2237
+ if (this._paginationMode === 'infinite') {
2238
+ if (this.gridApi) {
2239
+ if (!this.infiniteDatasource) {
2240
+ this.setupInfiniteScroll();
2241
+ }
2242
+ else {
2243
+ this.refreshInfiniteCache();
2244
+ }
2245
+ }
2246
+ // In infinite mode, the datasource handles loading, so we're done
2247
+ return;
2248
+ }
2249
+ // If a load is already in progress, return the existing promise
2250
+ // This prevents redundant API calls when loadData is called multiple times
2251
+ if (this._loadDataPromise) {
2252
+ return this._loadDataPromise;
2253
+ }
2254
+ // Wrap the actual loading logic in a promise we can track
2255
+ this._loadDataPromise = this.executeLoadData(isAutoRefresh);
2256
+ try {
2257
+ await this._loadDataPromise;
2258
+ }
2259
+ finally {
2260
+ this._loadDataPromise = null;
2261
+ }
2262
+ }
2263
+ /**
2264
+ * Internal method that performs the actual data loading.
2265
+ * Called by loadData() which handles promise deduplication.
2266
+ */
2267
+ async executeLoadData(isAutoRefresh) {
2268
+ // Client-side mode - load all data upfront
2269
+ const beforeRefreshEvent = new BeforeDataRefreshEventArgs(this, isAutoRefresh);
2270
+ this.BeforeDataRefresh.emit(beforeRefreshEvent);
2271
+ if (beforeRefreshEvent.cancel) {
2272
+ return;
2273
+ }
2274
+ // Build the RunViewParams - prefer new Params input over legacy inputs
2275
+ const runViewParams = this.buildRunViewParams();
2276
+ // Create GridRunViewParams for events (backward compatibility)
2277
+ const gridParams = {
2278
+ entityName: runViewParams.EntityName || this._entityInfo?.Name || '',
2279
+ extraFilter: runViewParams.ExtraFilter || '',
2280
+ orderBy: runViewParams.OrderBy || '',
2281
+ maxRows: runViewParams.MaxRows || 0,
2282
+ fields: runViewParams.Fields,
2283
+ searchString: runViewParams.UserSearchString || ''
2284
+ };
2285
+ const beforeLoadEvent = new BeforeDataLoadEventArgs(this, gridParams);
2286
+ this.BeforeDataLoad.emit(beforeLoadEvent);
2287
+ if (beforeLoadEvent.cancel) {
2288
+ return;
2289
+ }
2290
+ // Apply any modifications from beforeLoadEvent
2291
+ if (beforeLoadEvent.modifiedParams) {
2292
+ if (beforeLoadEvent.modifiedParams.extraFilter) {
2293
+ runViewParams.ExtraFilter = beforeLoadEvent.modifiedParams.extraFilter;
2294
+ }
2295
+ if (beforeLoadEvent.modifiedParams.orderBy) {
2296
+ runViewParams.OrderBy = beforeLoadEvent.modifiedParams.orderBy;
2297
+ }
2298
+ if (beforeLoadEvent.modifiedParams.maxRows) {
2299
+ runViewParams.MaxRows = beforeLoadEvent.modifiedParams.maxRows;
2300
+ }
2301
+ if (beforeLoadEvent.modifiedParams.fields) {
2302
+ runViewParams.Fields = beforeLoadEvent.modifiedParams.fields;
2303
+ }
2304
+ if (beforeLoadEvent.modifiedParams.searchString) {
2305
+ runViewParams.UserSearchString = beforeLoadEvent.modifiedParams.searchString;
2306
+ }
2307
+ }
2308
+ this.loading = true;
2309
+ this.errorMessage = '';
2310
+ this.cdr.detectChanges();
2311
+ const startTime = performance.now();
2312
+ try {
2313
+ const rv = new RunView();
2314
+ const result = await rv.RunView({
2315
+ ...runViewParams,
2316
+ ResultType: 'entity_object'
2317
+ });
2318
+ const loadTimeMs = performance.now() - startTime;
2319
+ if (result.Success) {
2320
+ this._allData = result.Results || [];
2321
+ this.totalRowCount = result.TotalRowCount || this._allData.length;
2322
+ this.processData();
2323
+ // Reapply sort state to grid after data load to maintain visual indicators
2324
+ // Use Promise.resolve() to defer until after Angular's change detection cycle
2325
+ // has completed and AG Grid has processed the new row data
2326
+ if (this._sortState.length > 0) {
2327
+ Promise.resolve().then(() => {
2328
+ this.applySortStateToGrid();
2329
+ });
2330
+ }
2331
+ const afterLoadEvent = new AfterDataLoadEventArgs(this, gridParams, true, this.totalRowCount, this._allData.length, loadTimeMs);
2332
+ this.AfterDataLoad.emit(afterLoadEvent);
2333
+ const afterRefreshEvent = new AfterDataRefreshEventArgs(this, true, this.totalRowCount, loadTimeMs);
2334
+ this.AfterDataRefresh.emit(afterRefreshEvent);
2335
+ }
2336
+ else {
2337
+ this.errorMessage = result.ErrorMessage || 'Failed to load data';
2338
+ const afterLoadEvent = new AfterDataLoadEventArgs(this, gridParams, false, 0, 0, loadTimeMs, this.errorMessage);
2339
+ this.AfterDataLoad.emit(afterLoadEvent);
2340
+ }
2341
+ }
2342
+ catch (error) {
2343
+ const loadTimeMs = performance.now() - startTime;
2344
+ this.errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
2345
+ const afterLoadEvent = new AfterDataLoadEventArgs(this, gridParams, false, 0, 0, loadTimeMs, this.errorMessage);
2346
+ this.AfterDataLoad.emit(afterLoadEvent);
2347
+ }
2348
+ finally {
2349
+ this.loading = false;
2350
+ this.cdr.detectChanges();
2351
+ }
2352
+ }
2353
+ /**
2354
+ * Builds RunViewParams from the Params input.
2355
+ * Requires Params to be set - the grid needs a data source configured.
2356
+ */
2357
+ buildRunViewParams() {
2358
+ if (!this._params) {
2359
+ throw new Error('Params must be set before loading data');
2360
+ }
2361
+ const params = { ...this._params };
2362
+ // Apply sort state (may be modified by user interactions)
2363
+ const orderBy = this.buildOrderByClause();
2364
+ if (orderBy) {
2365
+ params.OrderBy = orderBy;
2366
+ }
2367
+ return params;
2368
+ }
2369
+ buildOrderByClause() {
2370
+ if (this._sortState.length > 0) {
2371
+ return this._sortState
2372
+ .sort((a, b) => a.index - b.index)
2373
+ .map(s => `${s.field} ${s.direction.toUpperCase()}`)
2374
+ .join(', ');
2375
+ }
2376
+ // Fall back to OrderBy from Params if set
2377
+ return this._params?.OrderBy || '';
2378
+ }
2379
+ // ========================================
2380
+ // Infinite Scroll Data Source
2381
+ // ========================================
2382
+ /**
2383
+ * Creates and returns an AG Grid IDatasource for infinite scroll mode.
2384
+ * This datasource fetches data in pages from the server as the user scrolls.
2385
+ */
2386
+ createInfiniteDatasource() {
2387
+ return {
2388
+ getRows: async (params) => {
2389
+ const startRow = params.startRow;
2390
+ const endRow = params.endRow;
2391
+ const blockSize = endRow - startRow;
2392
+ try {
2393
+ // Build params with pagination
2394
+ const runViewParams = this.buildRunViewParams();
2395
+ runViewParams.StartRow = startRow;
2396
+ runViewParams.MaxRows = blockSize;
2397
+ const rv = new RunView();
2398
+ const result = await rv.RunView({
2399
+ ...runViewParams,
2400
+ ResultType: 'entity_object'
2401
+ });
2402
+ if (result.Success) {
2403
+ const entities = result.Results || [];
2404
+ // Process entities into row data
2405
+ const rowData = entities.map((entity, index) => {
2406
+ return this.entityToRowData(entity, startRow + index);
2407
+ });
2408
+ // Update total row count
2409
+ this.totalRowCount = result.TotalRowCount || 0;
2410
+ this.cdr.detectChanges();
2411
+ // Determine if we've reached the last row
2412
+ const lastRow = result.TotalRowCount != null && startRow + entities.length >= result.TotalRowCount
2413
+ ? result.TotalRowCount
2414
+ : undefined;
2415
+ params.successCallback(rowData, lastRow);
2416
+ }
2417
+ else {
2418
+ this.errorMessage = result.ErrorMessage || 'Failed to load data';
2419
+ this.cdr.detectChanges();
2420
+ params.failCallback();
2421
+ }
2422
+ }
2423
+ catch (error) {
2424
+ this.errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
2425
+ this.cdr.detectChanges();
2426
+ params.failCallback();
2427
+ }
2428
+ }
2429
+ };
2430
+ }
2431
+ /**
2432
+ * Converts a BaseEntity to AG Grid row data format.
2433
+ * This is used by both client and infinite scroll modes for consistent data formatting.
2434
+ */
2435
+ entityToRowData(entity, index) {
2436
+ const key = this.getRowKey(entity);
2437
+ // Store in row data map for later retrieval
2438
+ const rowData = {
2439
+ key,
2440
+ index,
2441
+ entity,
2442
+ selected: this._selectedKeys.includes(key),
2443
+ editing: this._editingRowKey === key,
2444
+ dirty: this._pendingChanges.some(c => c.rowKey === key),
2445
+ cssClasses: this.computeRowClasses(index, entity)
2446
+ };
2447
+ this._rowDataMap.set(key, rowData);
2448
+ // Build AG Grid row data
2449
+ const row = {
2450
+ __pk: key
2451
+ };
2452
+ if (this._entityInfo) {
2453
+ for (const field of this._entityInfo.Fields) {
2454
+ row[field.Name] = entity.Get(field.Name);
2455
+ }
2456
+ }
2457
+ return row;
2458
+ }
2459
+ /**
2460
+ * Sets up the grid for infinite scroll mode.
2461
+ * Called when PaginationMode is 'infinite'.
2462
+ */
2463
+ setupInfiniteScroll() {
2464
+ if (!this.gridApi)
2465
+ return;
2466
+ // Create datasource
2467
+ this.infiniteDatasource = this.createInfiniteDatasource();
2468
+ // Set datasource on grid
2469
+ this.gridApi.setGridOption('datasource', this.infiniteDatasource);
2470
+ }
2471
+ /**
2472
+ * Refreshes the infinite scroll cache.
2473
+ * Call this when data source parameters change.
2474
+ */
2475
+ refreshInfiniteCache() {
2476
+ if (!this.gridApi || this._paginationMode !== 'infinite')
2477
+ return;
2478
+ // Clear the row data map since we're refreshing
2479
+ this._rowDataMap.clear();
2480
+ // Purge cache and refresh
2481
+ this.gridApi.refreshInfiniteCache();
2482
+ // Reapply sort state to grid after refresh to maintain visual indicators
2483
+ // Use Promise.resolve() to defer until after AG Grid has processed the cache refresh
2484
+ if (this._sortState.length > 0) {
2485
+ Promise.resolve().then(() => {
2486
+ this.applySortStateToGrid();
2487
+ });
2488
+ }
2489
+ }
2490
+ processData() {
2491
+ this._rowDataMap.clear();
2492
+ const dataSource = this._useExternalData ? this._data : this._allData;
2493
+ this.rowData = dataSource.map((entity, index) => {
2494
+ const key = this.getRowKey(entity);
2495
+ const rowData = {
2496
+ key,
2497
+ index,
2498
+ entity,
2499
+ selected: this._selectedKeys.includes(key),
2500
+ editing: this._editingRowKey === key,
2501
+ dirty: this._pendingChanges.some(c => c.rowKey === key),
2502
+ cssClasses: this.computeRowClasses(index, entity)
2503
+ };
2504
+ this._rowDataMap.set(key, rowData);
2505
+ // Build AG Grid row data
2506
+ const row = {
2507
+ __pk: key
2508
+ };
2509
+ if (this._entityInfo) {
2510
+ for (const field of this._entityInfo.Fields) {
2511
+ row[field.Name] = entity.Get(field.Name);
2512
+ }
2513
+ }
2514
+ return row;
2515
+ });
2516
+ this.cdr.detectChanges();
2517
+ }
2518
+ getRowKey(entity) {
2519
+ // Use composite key if available
2520
+ if (entity.PrimaryKey) {
2521
+ return entity.PrimaryKey.ToConcatenatedString();
2522
+ }
2523
+ const keyValue = entity.Get(this._keyField);
2524
+ return keyValue != null ? String(keyValue) : '';
2525
+ }
2526
+ computeRowClasses(index, _entity) {
2527
+ const classes = [];
2528
+ if (this._striped && index % 2 === 1) {
2529
+ classes.push('grid-row-alt');
2530
+ }
2531
+ return classes;
2532
+ }
2533
+ // ========================================
2534
+ // AG Grid Event Handlers
2535
+ // ========================================
2536
+ onGridReady(event) {
2537
+ this.gridApi = event.api;
2538
+ this.updateSelection();
2539
+ if (this._sortState.length > 0) {
2540
+ this.applySortStateToGrid();
2541
+ }
2542
+ // Smart column sizing: use our estimated widths but ensure grid fills available space
2543
+ // without making columns excessively wide
2544
+ this.autoSizeColumnsSmartly(event.api);
2545
+ // Setup infinite scroll if in that mode and we have params
2546
+ if (this._paginationMode === 'infinite' && this._allowLoad && this._params) {
2547
+ this.setupInfiniteScroll();
2548
+ }
2549
+ }
2550
+ /**
2551
+ * Smart column auto-sizing that respects our width estimates
2552
+ * while ensuring the grid fills available space appropriately
2553
+ */
2554
+ autoSizeColumnsSmartly(api) {
2555
+ // Get total estimated width from our column definitions
2556
+ const totalEstimatedWidth = this.agColumnDefs.reduce((sum, col) => sum + (col.width || 150), 0);
2557
+ // Get available width from the grid container
2558
+ const gridElement = this.elementRef.nativeElement.querySelector('.mj-ag-grid');
2559
+ const availableWidth = gridElement?.clientWidth || 0;
2560
+ if (availableWidth > 0 && totalEstimatedWidth < availableWidth) {
2561
+ // If our columns are narrower than available space, stretch proportionally
2562
+ // but cap individual column growth to prevent excessive widths
2563
+ const ratio = availableWidth / totalEstimatedWidth;
2564
+ const maxRatio = 1.5; // Don't let columns grow more than 50%
2565
+ if (ratio <= maxRatio) {
2566
+ // Moderate stretch - use sizeColumnsToFit
2567
+ api.sizeColumnsToFit();
2568
+ }
2569
+ else {
2570
+ // Too much stretch - just use our estimated widths
2571
+ // The grid will have some empty space on the right, which is fine
2572
+ }
2573
+ }
2574
+ // If our columns are wider than available space, let the user scroll horizontally
2575
+ // (AG Grid handles this automatically)
2576
+ }
2577
+ onAgRowClicked(event) {
2578
+ if (!this._entityInfo || !event.data)
2579
+ return;
2580
+ const pkString = event.data['__pk'];
2581
+ const rowData = this._rowDataMap.get(pkString);
2582
+ if (!rowData)
2583
+ return;
2584
+ // Fire before click event
2585
+ const mouseEvent = event.event;
2586
+ const beforeEvent = new BeforeRowClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
2587
+ this.BeforeRowClick.emit(beforeEvent);
2588
+ if (beforeEvent.cancel)
2589
+ return;
2590
+ // Fire after click event
2591
+ const afterEvent = new AfterRowClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
2592
+ this.AfterRowClick.emit(afterEvent);
2593
+ // Auto-navigate on single click (if enabled and not navigating on double-click only)
2594
+ if (this._autoNavigate && !this._navigateOnDoubleClick && this._editMode === 'none') {
2595
+ this.emitNavigationRequest(rowData.entity, pkString);
2596
+ }
2597
+ }
2598
+ onAgRowDoubleClicked(event) {
2599
+ if (!this._entityInfo || !event.data)
2600
+ return;
2601
+ const pkString = event.data['__pk'];
2602
+ const rowData = this._rowDataMap.get(pkString);
2603
+ if (!rowData)
2604
+ return;
2605
+ // Fire before double-click event
2606
+ const mouseEvent = event.event;
2607
+ const beforeEvent = new BeforeRowDoubleClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
2608
+ this.BeforeRowDoubleClick.emit(beforeEvent);
2609
+ if (beforeEvent.cancel)
2610
+ return;
2611
+ // Fire after double-click event
2612
+ const afterEvent = new AfterRowDoubleClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
2613
+ this.AfterRowDoubleClick.emit(afterEvent);
2614
+ // Auto-navigate on double-click (if enabled)
2615
+ if (this._autoNavigate && this._navigateOnDoubleClick && this._editMode === 'none') {
2616
+ this.emitNavigationRequest(rowData.entity, pkString);
2617
+ }
2618
+ }
2619
+ /**
2620
+ * Emits a navigation request for the given entity record.
2621
+ */
2622
+ emitNavigationRequest(entity, compositeKey) {
2623
+ if (!this._entityInfo)
2624
+ return;
2625
+ this.NavigationRequested.emit({
2626
+ entityInfo: this._entityInfo,
2627
+ record: entity,
2628
+ compositeKey
2629
+ });
2630
+ }
2631
+ onAgSortChanged(event) {
2632
+ if (this.suppressSortEvents)
2633
+ return;
2634
+ const sortModel = event.api.getColumnState()
2635
+ .filter(col => col.sort)
2636
+ .map(col => ({
2637
+ field: col.colId,
2638
+ direction: col.sort,
2639
+ index: col.sortIndex ?? 0
2640
+ }));
2641
+ if (sortModel.length > 0) {
2642
+ // Find the column config
2643
+ const column = this._columnStates.find(c => c.config.field === sortModel[0].field);
2644
+ if (column) {
2645
+ // Fire before sort event
2646
+ const beforeEvent = new BeforeSortEventArgs(this, column.config, sortModel[0].direction, sortModel.length > 1, this._sortState);
2647
+ this.BeforeSort.emit(beforeEvent);
2648
+ if (beforeEvent.cancel) {
2649
+ // Revert to previous sort state
2650
+ this.applySortStateToGrid();
2651
+ return;
2652
+ }
2653
+ }
2654
+ this._sortState = sortModel;
2655
+ if (column) {
2656
+ // Fire after sort event
2657
+ const afterEvent = new AfterSortEventArgs(this, column.config, sortModel[0].direction, this._sortState);
2658
+ this.AfterSort.emit(afterEvent);
2659
+ }
2660
+ // User changed sort - mark as dirty and emit grid state changed
2661
+ this._isGridStateDirty = true;
2662
+ this.emitGridStateChanged('sort');
2663
+ // Determine if we need to reload data from server or can sort client-side
2664
+ // Client-side sorting is only possible if we have ALL the data loaded
2665
+ if (this._serverSideSorting && !this._useExternalData) {
2666
+ this.loadData(true);
2667
+ }
2668
+ else if (this._useExternalData) {
2669
+ // Using external data - check if we have all data or just a page
2670
+ // If totalRowCount > current data length, we only have partial data and parent must handle sorting
2671
+ // Parent receives afterSort event and can reload with new sort order
2672
+ // If we have all data, AG Grid handles client-side sorting automatically
2673
+ }
2674
+ }
2675
+ else {
2676
+ this._sortState = [];
2677
+ // User cleared sort - mark as dirty and emit grid state changed
2678
+ this._isGridStateDirty = true;
2679
+ this.emitGridStateChanged('sort');
2680
+ }
2681
+ }
2682
+ onAgSelectionChanged(event) {
2683
+ const selectedNodes = event.api.getSelectedNodes();
2684
+ const previousSelection = [...this._selectedKeys];
2685
+ const newSelection = selectedNodes.map(node => node.data['__pk']);
2686
+ // Find newly selected rows
2687
+ const addedKeys = newSelection.filter(k => !previousSelection.includes(k));
2688
+ const removedKeys = previousSelection.filter(k => !newSelection.includes(k));
2689
+ // Handle deselections
2690
+ for (const key of removedKeys) {
2691
+ const rowData = this._rowDataMap.get(key);
2692
+ if (rowData) {
2693
+ const beforeEvent = new BeforeRowDeselectEventArgs(this, rowData.entity, rowData.index, key, this._selectedKeys);
2694
+ this.BeforeRowDeselect.emit(beforeEvent);
2695
+ // Note: Can't cancel AG Grid's selection, but we emit the event
2696
+ }
2697
+ }
2698
+ // Handle selections
2699
+ for (const key of addedKeys) {
2700
+ const rowData = this._rowDataMap.get(key);
2701
+ if (rowData) {
2702
+ const beforeEvent = new BeforeRowSelectEventArgs(this, rowData.entity, rowData.index, key, addedKeys.length > 1, this._selectedKeys);
2703
+ this.BeforeRowSelect.emit(beforeEvent);
2704
+ // Note: Can't cancel AG Grid's selection, but we emit the event
2705
+ }
2706
+ }
2707
+ this._selectedKeys = newSelection;
2708
+ this.updateRowSelectionState();
2709
+ // Fire after events
2710
+ for (const key of removedKeys) {
2711
+ const rowData = this._rowDataMap.get(key);
2712
+ if (rowData) {
2713
+ const afterEvent = new AfterRowDeselectEventArgs(this, rowData.entity, rowData.index, key, this._selectedKeys, previousSelection);
2714
+ this.AfterRowDeselect.emit(afterEvent);
2715
+ }
2716
+ }
2717
+ for (const key of addedKeys) {
2718
+ const rowData = this._rowDataMap.get(key);
2719
+ if (rowData) {
2720
+ const afterEvent = new AfterRowSelectEventArgs(this, rowData.entity, rowData.index, key, addedKeys.length > 1, this._selectedKeys, previousSelection);
2721
+ this.AfterRowSelect.emit(afterEvent);
2722
+ }
2723
+ }
2724
+ this.SelectionChange.emit(this._selectedKeys);
2725
+ }
2726
+ onAgColumnResized(event) {
2727
+ if (event.finished && event.source !== 'api') {
2728
+ // User manually resized a column - mark as dirty
2729
+ this._isGridStateDirty = true;
2730
+ this.emitGridStateChanged('columns');
2731
+ }
2732
+ }
2733
+ onAgColumnMoved(event) {
2734
+ if (event.finished && event.source !== 'api') {
2735
+ // User manually moved a column - mark as dirty
2736
+ this._isGridStateDirty = true;
2737
+ this.emitGridStateChanged('columns');
2738
+ }
2739
+ }
2740
+ // ========================================
2741
+ // Grid State Management
2742
+ // ========================================
2743
+ emitGridStateChanged(changeType) {
2744
+ if (!this.gridApi || !this._entityInfo)
2745
+ return;
2746
+ // Only emit and persist if we're dirty (user made actual changes)
2747
+ // This prevents emitting stale state during view transitions
2748
+ if (!this._isGridStateDirty) {
2749
+ return;
2750
+ }
2751
+ const currentState = this.buildCurrentGridState();
2752
+ // Emit the event for external consumers
2753
+ this.GridStateChanged.emit({
2754
+ gridState: currentState,
2755
+ changeType
2756
+ });
2757
+ // Schedule auto-persist if enabled, but NOT during view transitions
2758
+ // (suppressPersist prevents old view's state from being saved to new view)
2759
+ if (this._autoPersistState && !this._suppressPersist && this.canEditCurrentView) {
2760
+ if (!this.IsDynamicView && this._viewEntity) {
2761
+ // Stored view - persist to UserView.GridState (debounced)
2762
+ // Track pending state for flush on destroy
2763
+ this._pendingViewStateToPersist = currentState;
2764
+ this.statePersistSubject.next(currentState);
2765
+ }
2766
+ else if (this.IsDynamicView) {
2767
+ // Dynamic view - persist to User Settings as defaults (debounced via same subject)
2768
+ // Track pending state for flush on destroy
2769
+ this._pendingUserDefaultsToPersist = currentState;
2770
+ this.userDefaultsPersistSubject.next(currentState);
2771
+ }
2772
+ }
2773
+ }
2774
+ /**
2775
+ * Persists the grid state to the UserView entity.
2776
+ * Only saves if the user has edit permission on the view.
2777
+ */
2778
+ async persistGridStateToView(state) {
2779
+ if (!this._viewEntity || this.isSavingState) {
2780
+ return;
2781
+ }
2782
+ // Check permission before saving
2783
+ if (!this._viewEntity.UserCanEdit) {
2784
+ this._pendingViewStateToPersist = null; // Clear pending since we can't save anyway
2785
+ return;
2786
+ }
2787
+ try {
2788
+ this.isSavingState = true;
2789
+ this.pendingStateToSave = state;
2790
+ // Build the grid state JSON matching ViewGridState format
2791
+ const gridStateJson = {
2792
+ columnSettings: state.columnSettings,
2793
+ sortSettings: state.sortSettings
2794
+ };
2795
+ // Update the view entity's GridState
2796
+ this._viewEntity.GridState = JSON.stringify(gridStateJson);
2797
+ // Update SortState separately if changed
2798
+ if (state.sortSettings?.length) {
2799
+ this._viewEntity.SortState = JSON.stringify(state.sortSettings.map(s => ({
2800
+ field: s.field,
2801
+ dir: s.dir
2802
+ })));
2803
+ }
2804
+ // Save the view entity
2805
+ const success = await this._viewEntity.Save();
2806
+ if (!success) {
2807
+ console.warn('[entity-data-grid] Failed to save view state:', this._viewEntity.LatestResult?.Message);
2808
+ }
2809
+ else {
2810
+ // Clear pending state and reset dirty flag after successful save
2811
+ this._pendingViewStateToPersist = null;
2812
+ this._isGridStateDirty = false;
2813
+ }
2814
+ }
2815
+ catch (error) {
2816
+ console.error('[entity-data-grid] Error persisting grid state:', error);
2817
+ }
2818
+ finally {
2819
+ this.isSavingState = false;
2820
+ this.pendingStateToSave = null;
2821
+ }
2822
+ }
2823
+ buildCurrentGridState() {
2824
+ if (!this.gridApi || !this._entityInfo || !this._entityInfo.Fields) {
2825
+ return { columnSettings: [], sortSettings: [] };
2826
+ }
2827
+ const columnState = this.gridApi.getColumnState();
2828
+ if (!columnState) {
2829
+ return { columnSettings: [], sortSettings: [] };
2830
+ }
2831
+ // Build lookup maps for existing custom properties to preserve them
2832
+ // AG Grid's column state doesn't track our custom format/userDisplayName properties,
2833
+ // so we need to carry them over from the current gridState
2834
+ const existingFormatsByName = new Map();
2835
+ const existingUserDisplayNames = new Map();
2836
+ if (this._gridState?.columnSettings) {
2837
+ for (const col of this._gridState.columnSettings) {
2838
+ const keyLower = col.Name.toLowerCase();
2839
+ if (col.format) {
2840
+ existingFormatsByName.set(keyLower, col.format);
2841
+ }
2842
+ if (col.userDisplayName) {
2843
+ existingUserDisplayNames.set(keyLower, col.userDisplayName);
2844
+ }
2845
+ }
2846
+ }
2847
+ const columnSettings = [];
2848
+ const sortSettings = [];
2849
+ for (let i = 0; i < columnState.length; i++) {
2850
+ const col = columnState[i];
2851
+ if (col.colId === '__rowNumber')
2852
+ continue; // Skip row number column
2853
+ const field = this._entityInfo.Fields.find(f => f.Name === col.colId);
2854
+ if (field) {
2855
+ const keyLower = field.Name.toLowerCase();
2856
+ const colConfig = {
2857
+ ID: field.ID,
2858
+ Name: field.Name,
2859
+ DisplayName: field.DisplayNameOrName,
2860
+ hidden: col.hide ?? false,
2861
+ width: col.width ?? undefined,
2862
+ orderIndex: i
2863
+ };
2864
+ // Preserve format from existing gridState if present
2865
+ const existingFormat = existingFormatsByName.get(keyLower);
2866
+ if (existingFormat) {
2867
+ colConfig.format = existingFormat;
2868
+ }
2869
+ // Preserve userDisplayName from existing gridState if present
2870
+ const existingUserDisplayName = existingUserDisplayNames.get(keyLower);
2871
+ if (existingUserDisplayName) {
2872
+ colConfig.userDisplayName = existingUserDisplayName;
2873
+ }
2874
+ columnSettings.push(colConfig);
2875
+ }
2876
+ if (col.sort) {
2877
+ sortSettings.push({
2878
+ field: col.colId,
2879
+ dir: col.sort
2880
+ });
2881
+ }
2882
+ }
2883
+ return { columnSettings, sortSettings };
2884
+ }
2885
+ applySortStateToGrid() {
2886
+ if (!this.gridApi || this._sortState.length === 0)
2887
+ return;
2888
+ const currentColumnState = this.gridApi.getColumnState();
2889
+ if (!currentColumnState)
2890
+ return;
2891
+ this.suppressSortEvents = true;
2892
+ try {
2893
+ const columnState = currentColumnState.map(col => {
2894
+ const sort = this._sortState.find(s => s.field === col.colId);
2895
+ return {
2896
+ ...col,
2897
+ sort: sort ? sort.direction : null,
2898
+ sortIndex: sort ? sort.index : null
2899
+ };
2900
+ });
2901
+ this.gridApi.applyColumnState({ state: columnState });
2902
+ }
2903
+ finally {
2904
+ this.suppressSortEvents = false;
2905
+ }
2906
+ }
2907
+ updateSelection() {
2908
+ if (!this.gridApi || this._selectedKeys.length === 0) {
2909
+ this.gridApi?.deselectAll();
2910
+ return;
2911
+ }
2912
+ for (const key of this._selectedKeys) {
2913
+ const node = this.gridApi.getRowNode(key);
2914
+ if (node) {
2915
+ node.setSelected(true);
2916
+ }
2917
+ }
2918
+ }
2919
+ updateRowSelectionState() {
2920
+ for (const rowData of this._rowDataMap.values()) {
2921
+ rowData.selected = this._selectedKeys.includes(rowData.key);
2922
+ }
2923
+ this.cdr.detectChanges();
2924
+ }
2925
+ // ========================================
2926
+ // Selection Public Methods
2927
+ // ========================================
2928
+ SelectRows(keys, additive = false) {
2929
+ if (!this.gridApi)
2930
+ return;
2931
+ if (!additive) {
2932
+ this.gridApi.deselectAll();
2933
+ }
2934
+ for (const key of keys) {
2935
+ const node = this.gridApi.getRowNode(key);
2936
+ if (node) {
2937
+ node.setSelected(true, !additive);
2938
+ }
2939
+ }
2940
+ }
2941
+ DeselectRows(keys) {
2942
+ if (!this.gridApi)
2943
+ return;
2944
+ for (const key of keys) {
2945
+ const node = this.gridApi.getRowNode(key);
2946
+ if (node) {
2947
+ node.setSelected(false);
2948
+ }
2949
+ }
2950
+ }
2951
+ SelectAll() {
2952
+ this.gridApi?.selectAll();
2953
+ }
2954
+ ClearSelection() {
2955
+ this.gridApi?.deselectAll();
2956
+ }
2957
+ GetSelectedRows() {
2958
+ return this._selectedKeys
2959
+ .map(key => this._rowDataMap.get(key)?.entity)
2960
+ .filter((e) => e !== undefined);
2961
+ }
2962
+ IsRowSelected(key) {
2963
+ return this._selectedKeys.includes(key);
2964
+ }
2965
+ /**
2966
+ * Scrolls the grid to make the specified row visible.
2967
+ * @param key The primary key of the row to scroll to
2968
+ * @param position Where to position the row: 'top', 'middle', or 'bottom'
2969
+ */
2970
+ EnsureRowVisible(key, position = 'middle') {
2971
+ if (!this.gridApi)
2972
+ return;
2973
+ const node = this.gridApi.getRowNode(key);
2974
+ if (node) {
2975
+ this.gridApi.ensureNodeVisible(node, position);
2976
+ }
2977
+ }
2978
+ // ========================================
2979
+ // Public Methods
2980
+ // ========================================
2981
+ async Refresh() {
2982
+ await this.loadData(false);
2983
+ }
2984
+ Clear() {
2985
+ this._allData = [];
2986
+ this._data = [];
2987
+ this._rowDataMap.clear();
2988
+ this._selectedKeys = [];
2989
+ this.rowData = [];
2990
+ this.totalRowCount = 0;
2991
+ this.cdr.detectChanges();
2992
+ }
2993
+ GetData() {
2994
+ return this._useExternalData ? [...this._data] : [...this._allData];
2995
+ }
2996
+ GetRowByKey(key) {
2997
+ return this._rowDataMap.get(key)?.entity;
2998
+ }
2999
+ GetRowByIndex(index) {
3000
+ const dataSource = this._useExternalData ? this._data : this._allData;
3001
+ return dataSource[index];
3002
+ }
3003
+ GetState() {
3004
+ return {
3005
+ columns: this._columnStates.map(c => ({
3006
+ field: c.config.field,
3007
+ width: c.computedWidth,
3008
+ visible: c.visible,
3009
+ order: c.order
3010
+ })),
3011
+ sort: [...this._sortState],
3012
+ filters: [...this._filterState],
3013
+ selection: [...this._selectedKeys]
3014
+ };
3015
+ }
3016
+ SetState(state) {
3017
+ for (const colState of state.columns) {
3018
+ const column = this._columnStates.find(c => c.config.field === colState.field);
3019
+ if (column) {
3020
+ column.computedWidth = colState.width;
3021
+ column.visible = colState.visible;
3022
+ column.order = colState.order;
3023
+ }
3024
+ }
3025
+ this._sortState = [...state.sort];
3026
+ for (const sort of this._sortState) {
3027
+ const column = this._columnStates.find(c => c.config.field === sort.field);
3028
+ if (column) {
3029
+ column.sortDirection = sort.direction;
3030
+ column.sortIndex = sort.index;
3031
+ }
3032
+ }
3033
+ this._filterState = [...state.filters];
3034
+ this._selectedKeys = [...state.selection];
3035
+ this.updateRowSelectionState();
3036
+ this.buildAgColumnDefs();
3037
+ this.cdr.detectChanges();
3038
+ }
3039
+ ResetState() {
3040
+ this.initializeColumnStates();
3041
+ this._sortState = [];
3042
+ this._filterState = [];
3043
+ this.ClearSelection();
3044
+ this.buildAgColumnDefs();
3045
+ this.cdr.detectChanges();
3046
+ }
3047
+ // ========================================
3048
+ // State Persistence
3049
+ // ========================================
3050
+ scheduleStateSave() {
3051
+ if (!this._stateKey)
3052
+ return;
3053
+ this.statesSaveSubject.next();
3054
+ }
3055
+ async loadPersistedState() {
3056
+ // TODO: Load state from User Settings entity
3057
+ }
3058
+ // ========================================
3059
+ // Toolbar Click Handlers
3060
+ // ========================================
3061
+ onAddClick() {
3062
+ // Emit legacy events for backward compatibility
3063
+ this.AddRequested.emit();
3064
+ this.NewButtonClick.emit();
3065
+ // Emit navigation events based on CreateRecordMode
3066
+ if (this._entityInfo) {
3067
+ if (this._createRecordMode === 'Dialog') {
3068
+ this.NewRecordDialogRequested.emit({
3069
+ entityInfo: this._entityInfo,
3070
+ defaultValues: this._newRecordValues
3071
+ });
3072
+ }
3073
+ else {
3074
+ this.NewRecordTabRequested.emit({
3075
+ entityInfo: this._entityInfo,
3076
+ defaultValues: this._newRecordValues
3077
+ });
3078
+ }
3079
+ }
3080
+ }
3081
+ onDeleteClick() {
3082
+ const selectedRows = this.GetSelectedRows();
3083
+ if (selectedRows.length > 0) {
3084
+ this.DeleteRequested.emit(selectedRows);
3085
+ this.DeleteButtonClick.emit(selectedRows);
3086
+ }
3087
+ }
3088
+ onExportClick() {
3089
+ this.ExportRequested.emit();
3090
+ this.ExportButtonClick.emit();
3091
+ // Show the export dialog
3092
+ this.showExportDialogForCurrentData();
3093
+ }
3094
+ /**
3095
+ * Shows the export dialog for the current grid data
3096
+ */
3097
+ showExportDialogForCurrentData() {
3098
+ const data = this.getExportData();
3099
+ const columns = this.getExportColumns();
3100
+ const fileName = this.getDefaultExportFileName();
3101
+ this.exportDialogConfig = {
3102
+ data,
3103
+ columns,
3104
+ defaultFileName: fileName,
3105
+ availableFormats: ['excel', 'csv', 'json'],
3106
+ defaultFormat: 'excel',
3107
+ showSamplingOptions: true,
3108
+ defaultSamplingMode: 'all',
3109
+ dialogTitle: `Export ${this._entityInfo?.Name || 'Data'}`
3110
+ };
3111
+ this.showExportDialog = true;
3112
+ this.cdr.detectChanges();
3113
+ }
3114
+ /**
3115
+ * Handle export dialog close
3116
+ */
3117
+ onExportDialogClosed(result) {
3118
+ this.showExportDialog = false;
3119
+ this.exportDialogConfig = null;
3120
+ this.cdr.detectChanges();
3121
+ }
3122
+ /**
3123
+ * Export grid data directly without showing dialog.
3124
+ * Use this for programmatic export with specific options.
3125
+ * @param options Export options (format, sampling, etc.)
3126
+ * @param download If true, automatically downloads the file. Default true.
3127
+ * @returns Export result with data buffer and metadata
3128
+ */
3129
+ async Export(options, download = true) {
3130
+ const data = this.getExportData();
3131
+ const columns = this.getExportColumns();
3132
+ const fileName = options?.fileName || this.getDefaultExportFileName();
3133
+ const exportOptions = {
3134
+ format: 'excel',
3135
+ fileName,
3136
+ columns,
3137
+ includeHeaders: true,
3138
+ ...options
3139
+ };
3140
+ const result = await this.exportService.export(data, exportOptions);
3141
+ if (result.success && download) {
3142
+ this.exportService.downloadResult(result);
3143
+ }
3144
+ return result;
3145
+ }
3146
+ /**
3147
+ * Export to Excel format directly (convenience method)
3148
+ * @param download If true, automatically downloads the file. Default true.
3149
+ */
3150
+ async ExportToExcel(download = true) {
3151
+ return this.Export({ format: 'excel' }, download);
3152
+ }
3153
+ /**
3154
+ * Export to CSV format directly (convenience method)
3155
+ * @param download If true, automatically downloads the file. Default true.
3156
+ */
3157
+ async ExportToCSV(download = true) {
3158
+ return this.Export({ format: 'csv' }, download);
3159
+ }
3160
+ /**
3161
+ * Export to JSON format directly (convenience method)
3162
+ * @param download If true, automatically downloads the file. Default true.
3163
+ */
3164
+ async ExportToJSON(download = true) {
3165
+ return this.Export({ format: 'json' }, download);
3166
+ }
3167
+ /**
3168
+ * Get the current grid data formatted for export
3169
+ */
3170
+ getExportData() {
3171
+ // Convert BaseEntity[] to plain objects for export
3172
+ return this.rowData.map(row => {
3173
+ if (row instanceof BaseEntity) {
3174
+ return row.GetAll();
3175
+ }
3176
+ return row;
3177
+ });
3178
+ }
3179
+ /**
3180
+ * Get column definitions for export based on current grid columns
3181
+ */
3182
+ getExportColumns() {
3183
+ if (!this._entityInfo) {
3184
+ // Fallback: use AG Grid column definitions
3185
+ return this.agColumnDefs
3186
+ .filter(col => col.field && !col.hide)
3187
+ .map(col => ({
3188
+ name: col.field,
3189
+ displayName: (col.headerName || col.field)
3190
+ }));
3191
+ }
3192
+ // Use entity field info for better column metadata
3193
+ return this._columns
3194
+ .filter(col => col.visible !== false)
3195
+ .map(col => {
3196
+ const field = this._entityInfo?.Fields.find(f => f.Name === col.field);
3197
+ return {
3198
+ name: col.field,
3199
+ displayName: col.title || field?.DisplayName || col.field,
3200
+ dataType: this.mapFieldTypeToExportType(field?.Type),
3201
+ width: typeof col.width === 'number' ? col.width : undefined
3202
+ };
3203
+ });
3204
+ }
3205
+ /**
3206
+ * Map MemberJunction field types to export column types
3207
+ */
3208
+ mapFieldTypeToExportType(fieldType) {
3209
+ if (!fieldType)
3210
+ return 'string';
3211
+ const type = fieldType.toLowerCase();
3212
+ if (type.includes('int') || type.includes('decimal') || type.includes('float') || type.includes('numeric')) {
3213
+ return 'number';
3214
+ }
3215
+ if (type.includes('date') || type.includes('time')) {
3216
+ return 'date';
3217
+ }
3218
+ if (type.includes('bit') || type.includes('bool')) {
3219
+ return 'boolean';
3220
+ }
3221
+ if (type.includes('money') || type.includes('currency')) {
3222
+ return 'currency';
3223
+ }
3224
+ return 'string';
3225
+ }
3226
+ /**
3227
+ * Generate default file name for export
3228
+ */
3229
+ getDefaultExportFileName() {
3230
+ const entityName = this._entityInfo?.Name || 'export';
3231
+ const timestamp = new Date().toISOString().slice(0, 10);
3232
+ return `${entityName}_${timestamp}`;
3233
+ }
3234
+ onRefreshClick() {
3235
+ this.RefreshButtonClick.emit();
3236
+ this.Refresh();
3237
+ }
3238
+ onCompareClick() {
3239
+ const selectedRows = this.GetSelectedRows();
3240
+ if (selectedRows.length >= 2) {
3241
+ // Emit legacy event for backward compatibility
3242
+ this.CompareButtonClick.emit(selectedRows);
3243
+ // Emit new structured event for Explorer dialogs
3244
+ if (this._entityInfo) {
3245
+ this.CompareRecordsRequested.emit({
3246
+ entityInfo: this._entityInfo,
3247
+ records: selectedRows
3248
+ });
3249
+ }
3250
+ }
3251
+ }
3252
+ onMergeClick() {
3253
+ const selectedRows = this.GetSelectedRows();
3254
+ if (selectedRows.length >= 2) {
3255
+ // Emit legacy event for backward compatibility
3256
+ this.MergeButtonClick.emit(selectedRows);
3257
+ // Emit new structured event for Explorer dialogs
3258
+ if (this._entityInfo) {
3259
+ this.MergeRecordsRequested.emit({
3260
+ entityInfo: this._entityInfo,
3261
+ records: selectedRows
3262
+ });
3263
+ }
3264
+ }
3265
+ }
3266
+ onAddToListClick() {
3267
+ const selectedRows = this.GetSelectedRows();
3268
+ if (selectedRows.length > 0) {
3269
+ // Emit legacy event for backward compatibility
3270
+ this.AddToListButtonClick.emit(selectedRows);
3271
+ // Emit new structured event with record IDs for list management
3272
+ if (this._entityInfo) {
3273
+ const recordIds = selectedRows.map(r => {
3274
+ return r.PrimaryKey?.ToConcatenatedString() || String(r.Get(this._keyField));
3275
+ });
3276
+ this.AddToListRequested.emit({
3277
+ entityInfo: this._entityInfo,
3278
+ records: selectedRows,
3279
+ recordIds
3280
+ });
3281
+ }
3282
+ }
3283
+ }
3284
+ onDuplicateSearchClick() {
3285
+ const selectedRows = this.GetSelectedRows();
3286
+ if (selectedRows.length >= 2) {
3287
+ // Emit legacy event for backward compatibility
3288
+ this.DuplicateSearchButtonClick.emit(selectedRows);
3289
+ // Emit new structured event for Explorer dialogs
3290
+ if (this._entityInfo) {
3291
+ this.DuplicateSearchRequested.emit({
3292
+ entityInfo: this._entityInfo,
3293
+ records: selectedRows
3294
+ });
3295
+ }
3296
+ }
3297
+ }
3298
+ onCommunicationClick() {
3299
+ const selectedRows = this.GetSelectedRows();
3300
+ if (selectedRows.length > 0) {
3301
+ // Emit legacy event for backward compatibility
3302
+ this.CommunicationButtonClick.emit(selectedRows);
3303
+ // Emit new structured event for Explorer dialogs
3304
+ if (this._entityInfo) {
3305
+ this.CommunicationRequested.emit({
3306
+ entityInfo: this._entityInfo,
3307
+ records: selectedRows,
3308
+ viewParams: this._params
3309
+ });
3310
+ }
3311
+ }
3312
+ }
3313
+ onColumnChooserClick() {
3314
+ // TODO: Implement column chooser dialog
3315
+ }
3316
+ /**
3317
+ * Handles entity action click from the overflow menu
3318
+ */
3319
+ onEntityActionClick(action) {
3320
+ if (!this._entityInfo)
3321
+ return;
3322
+ this.EntityActionRequested.emit({
3323
+ entityInfo: this._entityInfo,
3324
+ action,
3325
+ selectedRecords: this.GetSelectedRows()
3326
+ });
3327
+ }
3328
+ /**
3329
+ * Checks if an entity action is currently enabled based on selection requirements
3330
+ */
3331
+ isEntityActionEnabled(action) {
3332
+ if (!action.requiresSelection)
3333
+ return true;
3334
+ const selectedCount = this._selectedKeys.length;
3335
+ if (selectedCount === 0)
3336
+ return false;
3337
+ if (action.minSelectedRecords && selectedCount < action.minSelectedRecords)
3338
+ return false;
3339
+ if (action.maxSelectedRecords && selectedCount > action.maxSelectedRecords)
3340
+ return false;
3341
+ return true;
3342
+ }
3343
+ // ========================================
3344
+ // Overflow Menu Methods
3345
+ // ========================================
3346
+ toggleOverflowMenu() {
3347
+ this.showOverflowMenu = !this.showOverflowMenu;
3348
+ this.cdr.detectChanges();
3349
+ // Add click outside listener when menu is open
3350
+ if (this.showOverflowMenu) {
3351
+ setTimeout(() => {
3352
+ document.addEventListener('click', this.handleOutsideClick);
3353
+ }, 0);
3354
+ }
3355
+ else {
3356
+ document.removeEventListener('click', this.handleOutsideClick);
3357
+ }
3358
+ }
3359
+ closeOverflowMenu() {
3360
+ this.showOverflowMenu = false;
3361
+ document.removeEventListener('click', this.handleOutsideClick);
3362
+ this.cdr.detectChanges();
3363
+ }
3364
+ handleOutsideClick = () => {
3365
+ this.closeOverflowMenu();
3366
+ };
3367
+ // Overflow menu visibility helpers
3368
+ get hasOverflowMenuItems() {
3369
+ return this.showExportInOverflow ||
3370
+ this.showColumnChooserInOverflow ||
3371
+ (this._showEntityActionButtons && this._entityActions.length > 0) ||
3372
+ this.hasSelectionDependentOverflowActions;
3373
+ }
3374
+ get showExportInOverflow() {
3375
+ // Export is in overflow when it's not shown as a primary button
3376
+ return !this.ShowExportButton && !!this.ToolbarConfig.showExport;
3377
+ }
3378
+ get showColumnChooserInOverflow() {
3379
+ return this.AllowColumnToggle && !this.ToolbarConfig.showColumnChooser;
3380
+ }
3381
+ get hasSelectionDependentOverflowActions() {
3382
+ return this.showCommunicationInOverflow;
3383
+ }
3384
+ get showCommunicationInOverflow() {
3385
+ // Communication is in overflow when it's not shown as a primary button
3386
+ return !this.ShowCommunicationButton && this.HasSelection;
3387
+ }
3388
+ // ========================================
3389
+ // Toolbar Button State Helpers
3390
+ // ========================================
3391
+ get HasSelection() {
3392
+ return this._selectedKeys.length > 0;
3393
+ }
3394
+ get HasMultipleSelection() {
3395
+ return this._selectedKeys.length >= 2;
3396
+ }
3397
+ // ========================================
3398
+ // CSS Helpers
3399
+ // ========================================
3400
+ get gridContainerClasses() {
3401
+ const classes = ['mj-grid-container'];
3402
+ classes.push(`grid-lines-${this._gridLines}`);
3403
+ if (this._striped)
3404
+ classes.push('grid-striped');
3405
+ // Add visual config classes
3406
+ const vc = this.effectiveVisualConfig;
3407
+ classes.push(`header-style-${vc.headerStyle}`);
3408
+ if (vc.headerShadow)
3409
+ classes.push('header-shadow');
3410
+ if (vc.alternateRows)
3411
+ classes.push(`alternate-rows-${vc.alternateRowContrast}`);
3412
+ if (vc.hoverTransitions)
3413
+ classes.push('hover-transitions');
3414
+ classes.push(`cell-padding-${vc.cellPadding}`);
3415
+ if (vc.checkboxStyle !== 'default')
3416
+ classes.push(`checkbox-style-${vc.checkboxStyle}`);
3417
+ return classes;
3418
+ }
3419
+ /**
3420
+ * Apply visual configuration by setting CSS custom properties
3421
+ */
3422
+ applyVisualConfig() {
3423
+ const vc = this.effectiveVisualConfig;
3424
+ const el = this.elementRef.nativeElement;
3425
+ // Set CSS custom properties for dynamic values
3426
+ if (vc.headerBackground) {
3427
+ el.style.setProperty('--grid-header-bg', vc.headerBackground);
3428
+ }
3429
+ if (vc.headerTextColor) {
3430
+ el.style.setProperty('--grid-header-text', vc.headerTextColor);
3431
+ }
3432
+ if (vc.selectionIndicatorColor) {
3433
+ el.style.setProperty('--grid-selection-indicator-color', vc.selectionIndicatorColor);
3434
+ }
3435
+ if (vc.selectionIndicatorWidth) {
3436
+ el.style.setProperty('--grid-selection-indicator-width', `${vc.selectionIndicatorWidth}px`);
3437
+ }
3438
+ if (vc.selectionBackground) {
3439
+ el.style.setProperty('--grid-row-selected-bg', vc.selectionBackground);
3440
+ }
3441
+ if (vc.checkboxColor) {
3442
+ el.style.setProperty('--grid-checkbox-color', vc.checkboxColor);
3443
+ }
3444
+ if (vc.borderRadius !== undefined) {
3445
+ el.style.setProperty('--grid-border-radius', `${vc.borderRadius}px`);
3446
+ }
3447
+ if (vc.accentColor) {
3448
+ el.style.setProperty('--grid-accent-color', vc.accentColor);
3449
+ el.style.setProperty('--grid-sort-indicator-color', vc.accentColor);
3450
+ }
3451
+ if (vc.hoverTransitionDuration) {
3452
+ el.style.setProperty('--grid-hover-transition', `${vc.hoverTransitionDuration}ms`);
3453
+ }
3454
+ // Rebuild column defs if formatting options changed
3455
+ if (this._entityInfo) {
3456
+ this.buildAgColumnDefs();
3457
+ }
3458
+ }
3459
+ get gridHeightStyle() {
3460
+ if (typeof this._height === 'number') {
3461
+ return `${this._height}px`;
3462
+ }
3463
+ return this._height === 'auto' ? '100%' : 'fit-content';
3464
+ }
3465
+ // ========================================
3466
+ // Toolbar Button Helpers
3467
+ // ========================================
3468
+ isButtonVisible(button) {
3469
+ if (button.visible === undefined)
3470
+ return true;
3471
+ if (typeof button.visible === 'boolean')
3472
+ return button.visible;
3473
+ if (typeof button.visible === 'function')
3474
+ return button.visible();
3475
+ return true;
3476
+ }
3477
+ isButtonDisabled(button) {
3478
+ if (button.disabled === undefined)
3479
+ return false;
3480
+ if (typeof button.disabled === 'boolean')
3481
+ return button.disabled;
3482
+ if (typeof button.disabled === 'function')
3483
+ return button.disabled();
3484
+ return false;
3485
+ }
3486
+ onToolbarButtonClick(button) {
3487
+ if (button.onClick) {
3488
+ button.onClick();
3489
+ }
3490
+ }
3491
+ // ========================================
3492
+ // Public Getters
3493
+ // ========================================
3494
+ get VisibleRows() {
3495
+ return Array.from(this._rowDataMap.values());
3496
+ }
3497
+ get VisibleColumnStates() {
3498
+ return this._columnStates
3499
+ .filter(c => c.visible)
3500
+ .sort((a, b) => a.order - b.order);
3501
+ }
3502
+ static ɵfac = function EntityDataGridComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EntityDataGridComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i1.ExportService)); };
3503
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EntityDataGridComponent, selectors: [["mj-entity-data-grid"]], viewQuery: function EntityDataGridComponent_Query(rf, ctx) { if (rf & 1) {
3504
+ i0.ɵɵviewQuery(_c0, 5);
3505
+ } if (rf & 2) {
3506
+ let _t;
3507
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.gridContainer = _t.first);
3508
+ } }, inputs: { Params: "Params", AllowLoad: "AllowLoad", AutoRefreshOnParamsChange: "AutoRefreshOnParamsChange", PaginationMode: "PaginationMode", PageSize: "PageSize", CacheBlockSize: "CacheBlockSize", MaxBlocksInCache: "MaxBlocksInCache", Data: "Data", Columns: "Columns", GridState: "GridState", AllowColumnReorder: "AllowColumnReorder", AllowColumnResize: "AllowColumnResize", AllowColumnToggle: "AllowColumnToggle", ShowHeader: "ShowHeader", AllowSorting: "AllowSorting", AllowMultiSort: "AllowMultiSort", ServerSideSorting: "ServerSideSorting", AllowColumnFilters: "AllowColumnFilters", ShowSearch: "ShowSearch", SelectionMode: "SelectionMode", SelectedKeys: "SelectedKeys", KeyField: "KeyField", EditMode: "EditMode", AllowAdd: "AllowAdd", AllowDelete: "AllowDelete", Height: "Height", RowHeight: "RowHeight", VirtualScroll: "VirtualScroll", ShowRowNumbers: "ShowRowNumbers", Striped: "Striped", GridLines: "GridLines", VisualConfig: "VisualConfig", ShowToolbar: "ShowToolbar", ToolbarConfig: "ToolbarConfig", StateKey: "StateKey", AutoPersistState: "AutoPersistState", StatePersistDebounce: "StatePersistDebounce", RefreshDebounce: "RefreshDebounce", FilterText: "FilterText", ShowNewButton: "ShowNewButton", ShowRefreshButton: "ShowRefreshButton", ShowExportButton: "ShowExportButton", ShowDeleteButton: "ShowDeleteButton", ShowCompareButton: "ShowCompareButton", ShowMergeButton: "ShowMergeButton", ShowAddToListButton: "ShowAddToListButton", ShowDuplicateSearchButton: "ShowDuplicateSearchButton", ShowCommunicationButton: "ShowCommunicationButton", AutoNavigate: "AutoNavigate", NavigateOnDoubleClick: "NavigateOnDoubleClick", CreateRecordMode: "CreateRecordMode", NewRecordValues: "NewRecordValues", ShowEntityActionButtons: "ShowEntityActionButtons", EntityActions: "EntityActions" }, outputs: { BeforeRowSelect: "BeforeRowSelect", AfterRowSelect: "AfterRowSelect", BeforeRowDeselect: "BeforeRowDeselect", AfterRowDeselect: "AfterRowDeselect", SelectionChange: "SelectionChange", BeforeRowClick: "BeforeRowClick", AfterRowClick: "AfterRowClick", BeforeRowDoubleClick: "BeforeRowDoubleClick", AfterRowDoubleClick: "AfterRowDoubleClick", BeforeCellEdit: "BeforeCellEdit", AfterCellEditBegin: "AfterCellEditBegin", BeforeCellEditCommit: "BeforeCellEditCommit", AfterCellEditCommit: "AfterCellEditCommit", BeforeCellEditCancel: "BeforeCellEditCancel", AfterCellEditCancel: "AfterCellEditCancel", BeforeRowSave: "BeforeRowSave", AfterRowSave: "AfterRowSave", BeforeRowDelete: "BeforeRowDelete", AfterRowDelete: "AfterRowDelete", BeforeDataLoad: "BeforeDataLoad", AfterDataLoad: "AfterDataLoad", BeforeDataRefresh: "BeforeDataRefresh", AfterDataRefresh: "AfterDataRefresh", BeforeSort: "BeforeSort", AfterSort: "AfterSort", BeforeColumnReorder: "BeforeColumnReorder", AfterColumnReorder: "AfterColumnReorder", BeforeColumnResize: "BeforeColumnResize", AfterColumnResize: "AfterColumnResize", BeforeColumnVisibilityChange: "BeforeColumnVisibilityChange", AfterColumnVisibilityChange: "AfterColumnVisibilityChange", GridStateChanged: "GridStateChanged", AddRequested: "AddRequested", DeleteRequested: "DeleteRequested", ExportRequested: "ExportRequested", NewButtonClick: "NewButtonClick", RefreshButtonClick: "RefreshButtonClick", ExportButtonClick: "ExportButtonClick", DeleteButtonClick: "DeleteButtonClick", CompareButtonClick: "CompareButtonClick", MergeButtonClick: "MergeButtonClick", AddToListButtonClick: "AddToListButtonClick", DuplicateSearchButtonClick: "DuplicateSearchButtonClick", CommunicationButtonClick: "CommunicationButtonClick", NavigationRequested: "NavigationRequested", NewRecordDialogRequested: "NewRecordDialogRequested", NewRecordTabRequested: "NewRecordTabRequested", CompareRecordsRequested: "CompareRecordsRequested", MergeRecordsRequested: "MergeRecordsRequested", CommunicationRequested: "CommunicationRequested", DuplicateSearchRequested: "DuplicateSearchRequested", AddToListRequested: "AddToListRequested", LoadEntityActionsRequested: "LoadEntityActionsRequested", EntityActionRequested: "EntityActionRequested" }, decls: 10, vars: 12, consts: [["gridContainer", ""], ["class", "mj-grid-toolbar", 4, "ngIf"], [1, "mj-grid-content"], ["class", "mj-grid-loading-overlay", 4, "ngIf"], ["class", "mj-grid-error", 4, "ngIf"], ["class", "mj-grid-empty", 4, "ngIf"], ["class", "mj-ag-grid ag-theme-alpine", 3, "theme", "columnDefs", "rowData", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "gridReady", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", 4, "ngIf"], ["class", "mj-ag-grid ag-theme-alpine", 3, "theme", "columnDefs", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowModelType", "cacheBlockSize", "maxBlocksInCache", "infiniteInitialRowCount", "cacheOverflowSize", "gridReady", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", 4, "ngIf"], [3, "closed", "visible", "config"], [1, "mj-grid-toolbar"], [1, "toolbar-left"], ["class", "toolbar-search", 4, "ngIf"], [4, "ngFor", "ngForOf"], [1, "toolbar-center"], ["class", "row-count", 4, "ngIf"], ["class", "selection-count", 4, "ngIf"], [1, "toolbar-right"], ["class", "toolbar-button", "title", "Create new record", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Refresh data", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Export to Excel", 3, "click", 4, "ngIf"], ["class", "toolbar-button toolbar-button-danger", "title", "Delete selected records", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Compare selected records", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Merge selected records", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Add selected records to a list", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Search for duplicate records", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Send message to selected records", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Add New", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Refresh", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button toolbar-button-danger", "title", "Delete Selected", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Export", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Column Chooser", 3, "click", 4, "ngIf"], ["class", "toolbar-overflow", 3, "click", 4, "ngIf"], [1, "toolbar-search"], [1, "fa-solid", "fa-search", "search-icon"], ["type", "text", 1, "search-input", 3, "input", "placeholder", "value"], ["class", "search-clear", 3, "click", 4, "ngIf"], [1, "search-clear", 3, "click"], [1, "fa-solid", "fa-times"], ["class", "toolbar-button", 3, "class", "disabled", "title", "click", 4, "ngIf"], [1, "toolbar-button", 3, "click", "disabled", "title"], [3, "class", 4, "ngIf"], [4, "ngIf"], [1, "row-count"], [1, "selection-count"], ["title", "Create new record", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "button-text"], ["title", "Refresh data", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-arrows-rotate"], ["title", "Export to Excel", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-file-excel"], ["title", "Delete selected records", 1, "toolbar-button", "toolbar-button-danger", 3, "click"], [1, "fa-solid", "fa-trash"], ["title", "Compare selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-code-compare"], ["title", "Merge selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-code-merge"], ["title", "Add selected records to a list", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-list-check"], ["title", "Search for duplicate records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-magnifying-glass-plus"], ["title", "Send message to selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-envelope"], ["title", "Add New", 1, "toolbar-button", 3, "click"], ["title", "Refresh", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-refresh"], ["title", "Delete Selected", 1, "toolbar-button", "toolbar-button-danger", 3, "click"], ["title", "Export", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-download"], ["title", "Column Chooser", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-columns"], [1, "toolbar-overflow", 3, "click"], ["title", "More actions", 1, "toolbar-button", "overflow-trigger", 3, "click"], [1, "fa-solid", "fa-ellipsis-vertical"], ["class", "overflow-menu", 4, "ngIf"], [1, "overflow-menu"], ["class", "overflow-item", 3, "click", 4, "ngIf"], ["class", "overflow-divider", 4, "ngIf"], [1, "overflow-item", 3, "click"], [1, "overflow-divider"], [1, "overflow-section-label"], ["class", "overflow-item", 3, "disabled", "click", 4, "ngFor", "ngForOf"], [1, "overflow-item", 3, "click", "disabled"], [1, "mj-grid-loading-overlay"], ["text", "Loading..."], [1, "mj-grid-error"], [1, "fa-solid", "fa-exclamation-triangle"], [1, "error-retry", 3, "click"], [1, "mj-grid-empty"], [1, "fa-solid", "fa-inbox"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "gridReady", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", "theme", "columnDefs", "rowData", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "gridReady", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", "theme", "columnDefs", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowModelType", "cacheBlockSize", "maxBlocksInCache", "infiniteInitialRowCount", "cacheOverflowSize"]], template: function EntityDataGridComponent_Template(rf, ctx) { if (rf & 1) {
3509
+ const _r1 = i0.ɵɵgetCurrentView();
3510
+ i0.ɵɵelementStart(0, "div", null, 0);
3511
+ i0.ɵɵtemplate(2, EntityDataGridComponent_div_2_Template, 24, 20, "div", 1);
3512
+ i0.ɵɵelementStart(3, "div", 2);
3513
+ i0.ɵɵtemplate(4, EntityDataGridComponent_div_4_Template, 2, 0, "div", 3)(5, EntityDataGridComponent_div_5_Template, 6, 1, "div", 4)(6, EntityDataGridComponent_div_6_Template, 4, 0, "div", 5)(7, EntityDataGridComponent_ag_grid_angular_7_Template, 1, 9, "ag-grid-angular", 6)(8, EntityDataGridComponent_ag_grid_angular_8_Template, 1, 13, "ag-grid-angular", 7);
3514
+ i0.ɵɵelementEnd()();
3515
+ i0.ɵɵelementStart(9, "mj-export-dialog", 8);
3516
+ i0.ɵɵlistener("closed", function EntityDataGridComponent_Template_mj_export_dialog_closed_9_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onExportDialogClosed($event)); });
3517
+ i0.ɵɵelementEnd();
3518
+ } if (rf & 2) {
3519
+ i0.ɵɵclassMap(ctx.gridContainerClasses.join(" "));
3520
+ i0.ɵɵstyleProp("height", ctx.gridHeightStyle);
3521
+ i0.ɵɵadvance(2);
3522
+ i0.ɵɵproperty("ngIf", ctx.ShowToolbar);
3523
+ i0.ɵɵadvance(2);
3524
+ i0.ɵɵproperty("ngIf", ctx.loading && ctx.rowData.length === 0);
3525
+ i0.ɵɵadvance();
3526
+ i0.ɵɵproperty("ngIf", ctx.errorMessage && !ctx.loading);
3527
+ i0.ɵɵadvance();
3528
+ i0.ɵɵproperty("ngIf", !ctx.loading && !ctx.errorMessage && ctx.rowData.length === 0);
3529
+ i0.ɵɵadvance();
3530
+ i0.ɵɵproperty("ngIf", !ctx.errorMessage && ctx.PaginationMode === "client" && (ctx.rowData.length > 0 || ctx.loading));
3531
+ i0.ɵɵadvance();
3532
+ i0.ɵɵproperty("ngIf", !ctx.errorMessage && ctx.PaginationMode === "infinite");
3533
+ i0.ɵɵadvance();
3534
+ i0.ɵɵproperty("visible", ctx.showExportDialog)("config", ctx.exportDialogConfig);
3535
+ } }, dependencies: [i2.NgForOf, i2.NgIf, i3.AgGridAngular, i4.LoadingComponent, i1.ExportDialogComponent], styles: ["\n\n\n\n\n[_nghost-%COMP%] {\n \n\n --grid-border-color: #e0e0e0;\n --grid-border-radius: 0px;\n --grid-background: #ffffff;\n\n \n\n --grid-header-bg: #fafafa;\n --grid-header-text: #333333;\n --grid-header-font-weight: 600;\n --grid-header-height: 40px;\n --grid-header-border-color: #e0e0e0;\n\n \n\n --grid-row-height: 40px;\n --grid-row-bg: #ffffff;\n --grid-row-bg-alt: #fafafa;\n --grid-row-hover-bg: #f5f5f5;\n --grid-row-selected-bg: #fff9e6;\n --grid-row-selected-hover-bg: #fff3cc;\n\n \n\n --grid-cell-padding: 8px 12px;\n --grid-cell-text: #333333;\n --grid-cell-border-color: #f0f0f0;\n\n \n\n --grid-checkbox-color: #2196F3;\n --grid-selection-indicator-color: #f9a825;\n\n \n\n --grid-edit-cell-bg: #ffffff;\n --grid-edit-cell-border: #2196F3;\n --grid-edit-cell-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n\n \n\n --grid-sort-indicator-color: #2196F3;\n\n \n\n --grid-toolbar-bg: #ffffff;\n --grid-toolbar-height: 48px;\n --grid-toolbar-border-color: #e0e0e0;\n\n \n\n --grid-loading-overlay-bg: rgba(255, 255, 255, 0.8);\n\n \n\n --grid-empty-text-color: #999999;\n --grid-empty-icon-color: #cccccc;\n\n display: block;\n height: 100%;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n}\n\n\n\n\n\n\n.mj-grid-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--grid-border-color);\n border-radius: var(--grid-border-radius);\n background: var(--grid-background);\n overflow: hidden;\n}\n\n\n\n\n\n\n.mj-grid-toolbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: var(--grid-toolbar-height);\n padding: 0 12px;\n background: var(--grid-toolbar-bg);\n border-bottom: 1px solid var(--grid-toolbar-border-color);\n gap: 12px;\n}\n\n.toolbar-left[_ngcontent-%COMP%], \n.toolbar-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-center[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #666;\n font-size: 13px;\n}\n\n.toolbar-search[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.search-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 10px;\n color: #999;\n font-size: 13px;\n}\n\n.search-input[_ngcontent-%COMP%] {\n padding: 6px 30px 6px 32px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n width: 200px;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n.search-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--grid-selection-indicator-color);\n box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);\n}\n\n.search-clear[_ngcontent-%COMP%] {\n position: absolute;\n right: 6px;\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: #999;\n font-size: 12px;\n}\n\n.search-clear[_ngcontent-%COMP%]:hover {\n color: #666;\n}\n\n.toolbar-button[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: #f5f5f5;\n border: 1px solid #ddd;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n color: #333;\n transition: background-color 0.2s, border-color 0.2s;\n}\n\n.toolbar-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #eee;\n border-color: #ccc;\n}\n\n.toolbar-button[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.toolbar-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n.toolbar-button-danger[_ngcontent-%COMP%] {\n color: #d32f2f;\n}\n\n.toolbar-button-danger[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #ffebee;\n border-color: #ffcdd2;\n}\n\n.row-count[_ngcontent-%COMP%], \n.selection-count[_ngcontent-%COMP%] {\n font-weight: 500;\n}\n\n\n\n\n\n\n.mj-grid-content[_ngcontent-%COMP%] {\n flex: 1;\n position: relative;\n overflow: hidden;\n}\n\n.mj-grid-scroll-container[_ngcontent-%COMP%] {\n height: 100%;\n overflow: auto;\n}\n\n\n\n\n\n\n.mj-grid-loading-overlay[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--grid-loading-overlay-bg);\n z-index: 10;\n}\n\n\n\n\n\n\n.mj-grid-error[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n color: #d32f2f;\n gap: 12px;\n}\n\n.mj-grid-error[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 32px;\n}\n\n.error-retry[_ngcontent-%COMP%] {\n padding: 8px 16px;\n background: #d32f2f;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n}\n\n.error-retry[_ngcontent-%COMP%]:hover {\n background: #c62828;\n}\n\n\n\n\n\n\n.mj-grid-empty[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: var(--grid-empty-text-color);\n gap: 12px;\n}\n\n.mj-grid-empty[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n color: var(--grid-empty-icon-color);\n}\n\n\n\n\n\n\n.mj-grid-header[_ngcontent-%COMP%] {\n display: flex;\n min-height: var(--grid-header-height);\n background: var(--grid-header-bg);\n border-bottom: 2px solid var(--grid-header-border-color);\n position: sticky;\n top: 0;\n z-index: 5;\n}\n\n.mj-grid-header-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n font-weight: var(--grid-header-font-weight);\n color: var(--grid-header-text);\n font-size: 13px;\n user-select: none;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right: 1px solid var(--grid-cell-border-color);\n flex-shrink: 0;\n}\n\n.mj-grid-header-cell[_ngcontent-%COMP%]:last-child {\n border-right: none;\n}\n\n.mj-grid-header-cell.sortable[_ngcontent-%COMP%] {\n cursor: pointer;\n}\n\n.mj-grid-header-cell.sortable[_ngcontent-%COMP%]:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n.header-text[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sort-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n margin-left: 6px;\n color: var(--grid-sort-indicator-color);\n}\n\n.sort-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n}\n\n.sort-index[_ngcontent-%COMP%] {\n font-size: 10px;\n margin-left: 2px;\n font-weight: normal;\n}\n\n\n\n\n\n\n.mj-grid-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: stretch;\n background: var(--grid-row-bg);\n transition: background-color 0.15s;\n cursor: default;\n}\n\n.mj-grid-row[_ngcontent-%COMP%]:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-alt[_ngcontent-%COMP%] {\n background: var(--grid-row-bg-alt);\n}\n\n.mj-grid-row.grid-row-alt[_ngcontent-%COMP%]:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-selected[_ngcontent-%COMP%] {\n background: var(--grid-row-selected-bg);\n}\n\n.mj-grid-row.grid-row-selected[_ngcontent-%COMP%]:hover {\n background: var(--grid-row-selected-hover-bg);\n}\n\n.mj-grid-row.grid-row-editing[_ngcontent-%COMP%] {\n background: #fffde7;\n}\n\n.mj-grid-row.grid-row-dirty[_ngcontent-%COMP%] {\n border-left: 3px solid #ff9800;\n}\n\n\n\n\n\n\n.mj-grid-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n color: var(--grid-cell-text);\n font-size: 13px;\n overflow: hidden;\n border-right: 1px solid transparent;\n flex-shrink: 0;\n}\n\n.mj-grid-cell[_ngcontent-%COMP%]:last-child {\n border-right: none;\n}\n\n\n\n.grid-lines-horizontal[_ngcontent-%COMP%] .mj-grid-row[_ngcontent-%COMP%] {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-vertical[_ngcontent-%COMP%] .mj-grid-cell[_ngcontent-%COMP%] {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both[_ngcontent-%COMP%] .mj-grid-row[_ngcontent-%COMP%] {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both[_ngcontent-%COMP%] .mj-grid-cell[_ngcontent-%COMP%] {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n\n\n.mj-grid-cell.align-left[_ngcontent-%COMP%] {\n justify-content: flex-start;\n}\n\n.mj-grid-cell.align-center[_ngcontent-%COMP%] {\n justify-content: center;\n}\n\n.mj-grid-cell.align-right[_ngcontent-%COMP%] {\n justify-content: flex-end;\n}\n\n.cell-content[_ngcontent-%COMP%] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n\n\n.row-number-cell[_ngcontent-%COMP%] {\n width: 50px;\n min-width: 50px;\n max-width: 50px;\n justify-content: center;\n color: #999;\n font-size: 12px;\n background: var(--grid-header-bg);\n}\n\n.checkbox-cell[_ngcontent-%COMP%] {\n width: 40px;\n min-width: 40px;\n max-width: 40px;\n justify-content: center;\n}\n\n.checkbox-cell[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n accent-color: var(--grid-checkbox-color);\n}\n\n\n\n\n\n\n.mj-grid-virtual-spacer[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n\n\n\n\n\n@media (max-width: 768px) {\n .mj-grid-toolbar[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n padding: 8px;\n }\n\n .toolbar-search[_ngcontent-%COMP%] {\n order: 3;\n width: 100%;\n margin-top: 8px;\n }\n\n .search-input[_ngcontent-%COMP%] {\n width: 100%;\n }\n\n .toolbar-center[_ngcontent-%COMP%] {\n order: 2;\n }\n\n \n\n .toolbar-button[_ngcontent-%COMP%] .button-text[_ngcontent-%COMP%] {\n display: none;\n }\n}\n\n\n\n\n\n\n.toolbar-button[_ngcontent-%COMP%] .button-text[_ngcontent-%COMP%] {\n font-size: 13px;\n}\n\n\n\n\n\n\n.toolbar-overflow[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.overflow-trigger[_ngcontent-%COMP%] {\n padding: 6px 8px !important;\n}\n\n.overflow-trigger[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n}\n\n.overflow-menu[_ngcontent-%COMP%] {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 4px;\n min-width: 220px;\n background: #ffffff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08);\n z-index: 1000;\n overflow: hidden;\n}\n\n.overflow-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 10px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n text-align: left;\n gap: 12px;\n transition: background-color 0.15s;\n}\n\n.overflow-item[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #f5f5f5;\n}\n\n.overflow-item[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.overflow-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 18px;\n font-size: 14px;\n color: #666;\n text-align: center;\n}\n\n.overflow-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.overflow-divider[_ngcontent-%COMP%] {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n\n.overflow-section-label[_ngcontent-%COMP%] {\n padding: 8px 16px 4px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: #999;\n letter-spacing: 0.5px;\n}\n\n\n\n.overflow-item.action-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #2196F3;\n}\n\n\n\n\n\n\n .highlight-match {\n background-color: #fff176;\n border-radius: 2px;\n padding: 0 1px;\n}\n\n\n\n\n\n\n.mj-ag-grid[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n}\n\n .ag-theme-alpine {\n \n\n --ag-row-hover-color: var(--grid-row-hover-bg);\n --ag-selected-row-background-color: var(--grid-row-selected-bg);\n --ag-header-background-color: var(--grid-header-bg);\n\n \n\n --ag-range-selection-background-color: rgba(249, 168, 37, 0.15);\n --ag-range-selection-border-color: #f9a825;\n\n \n\n --ag-borders: none;\n --ag-row-border-color: var(--grid-cell-border-color);\n}\n\n\n\n .ag-row-selected {\n box-shadow: inset 4px 0 0 0 #f9a825;\n}\n\n\n\n .ag-theme-alpine .ag-checkbox-input-wrapper {\n width: 18px;\n height: 18px;\n}\n\n .ag-theme-alpine .ag-checkbox-input-wrapper.ag-checked::after {\n color: var(--grid-checkbox-color, #2196F3);\n}\n\n\n\n .ag-theme-alpine .ag-row:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f0f7ff);\n}\n\n\n\n\n\n\n\n\n.header-style-flat[_ngcontent-%COMP%] .ag-header {\n background: var(--grid-header-bg, #fafafa);\n border-bottom: 1px solid var(--grid-header-border-color, #e0e0e0);\n}\n\n\n\n.header-style-elevated[_ngcontent-%COMP%] .ag-header {\n background: linear-gradient(180deg, #ffffff 0%, #f8f9fa 100%);\n border-bottom: 1px solid var(--grid-header-border-color, #e0e0e0);\n}\n\n.header-style-elevated.header-shadow[_ngcontent-%COMP%] .ag-header {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);\n}\n\n\n\n.header-style-gradient[_ngcontent-%COMP%] .ag-header {\n background: linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%);\n border-bottom: none;\n}\n\n.header-style-gradient.header-shadow[_ngcontent-%COMP%] .ag-header {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n\n\n\n.header-style-bold[_ngcontent-%COMP%] .ag-header {\n background: linear-gradient(180deg, #37474f 0%, #263238 100%);\n border-bottom: none;\n}\n\n.header-style-bold[_ngcontent-%COMP%] .ag-header-cell-text {\n color: #ffffff;\n font-weight: 600;\n}\n\n.header-style-bold[_ngcontent-%COMP%] .ag-header-icon {\n color: rgba(255, 255, 255, 0.7);\n}\n\n.header-style-bold[_ngcontent-%COMP%] .ag-header-cell:hover {\n background: rgba(255, 255, 255, 0.1);\n}\n\n\n\n .ag-header-cell-sorted-asc .ag-icon-asc, \n .ag-header-cell-sorted-desc .ag-icon-desc {\n color: var(--grid-accent-color, var(--grid-sort-indicator-color, #2196F3));\n}\n\n\n\n\n\n\n\n\n.alternate-rows-subtle[_ngcontent-%COMP%] .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.015);\n}\n\n.alternate-rows-subtle[_ngcontent-%COMP%] .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n\n\n.alternate-rows-medium[_ngcontent-%COMP%] .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.025);\n}\n\n.alternate-rows-medium[_ngcontent-%COMP%] .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n\n\n.alternate-rows-strong[_ngcontent-%COMP%] .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.alternate-rows-strong[_ngcontent-%COMP%] .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n\n\n\n\n\n.hover-transitions[_ngcontent-%COMP%] .ag-row {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n.hover-transitions[_ngcontent-%COMP%] .ag-cell {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n\n\n\n\n\n.cell-padding-compact[_ngcontent-%COMP%] .ag-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-compact[_ngcontent-%COMP%] .ag-header-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-normal[_ngcontent-%COMP%] .ag-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-normal[_ngcontent-%COMP%] .ag-header-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-comfortable[_ngcontent-%COMP%] .ag-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n.cell-padding-comfortable[_ngcontent-%COMP%] .ag-header-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n\n\n\n\n\n\n\n.checkbox-style-rounded[_ngcontent-%COMP%] .ag-checkbox-input-wrapper {\n border-radius: 4px;\n}\n\n.checkbox-style-rounded[_ngcontent-%COMP%] .ag-checkbox-input-wrapper::after {\n border-radius: 3px;\n}\n\n\n\n.checkbox-style-filled[_ngcontent-%COMP%] .ag-checkbox-input-wrapper.ag-checked {\n background-color: var(--grid-checkbox-color, #2196F3);\n border-color: var(--grid-checkbox-color, #2196F3);\n}\n\n.checkbox-style-filled[_ngcontent-%COMP%] .ag-checkbox-input-wrapper.ag-checked::after {\n color: #ffffff;\n}\n\n\n\n\n\n\n\n\n .cell-align-right {\n text-align: right;\n justify-content: flex-end;\n}\n\n .header-align-right .ag-header-cell-label {\n justify-content: flex-end;\n}\n\n\n\n .cell-empty {\n color: #bdbdbd;\n font-style: normal;\n}\n\n\n\n .cell-boolean-true {\n color: #43a047;\n font-size: 14px;\n}\n\n .cell-boolean-false {\n color: #bdbdbd;\n font-size: 14px;\n}\n\n\n\n .cell-link {\n color: var(--grid-accent-color, #2196F3);\n text-decoration: none;\n transition: color 0.15s;\n font-size: inherit;\n font-family: inherit;\n line-height: inherit;\n}\n\n .cell-link:hover {\n color: #1976D2;\n text-decoration: underline;\n}\n\n\n\n .cell-email {\n font-family: inherit;\n font-size: 13px;\n}\n\n\n\n .cell-url {\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n font-size: 13px;\n}\n\n\n\n .cell-phone {\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n}\n\n\n\n\n\n\n@keyframes _ngcontent-%COMP%_skeleton-shimmer {\n 0% {\n background-position: -200px 0;\n }\n 100% {\n background-position: calc(200px + 100%) 0;\n }\n}\n\n.skeleton-row[_ngcontent-%COMP%] {\n display: flex;\n height: 40px;\n align-items: center;\n padding: 0 12px;\n border-bottom: 1px solid var(--grid-cell-border-color, #f0f0f0);\n}\n\n.skeleton-cell[_ngcontent-%COMP%] {\n height: 16px;\n border-radius: 4px;\n background: linear-gradient(\n 90deg,\n #f0f0f0 0px,\n #e8e8e8 40px,\n #f0f0f0 80px\n );\n background-size: 200px 100%;\n animation: _ngcontent-%COMP%_skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-cell-short[_ngcontent-%COMP%] {\n width: 60px;\n}\n\n.skeleton-cell-medium[_ngcontent-%COMP%] {\n width: 120px;\n}\n\n.skeleton-cell-long[_ngcontent-%COMP%] {\n width: 180px;\n}\n\n\n\n .ag-header-select-all {\n margin-right: 0;\n}\n\n\n\n\n\n\n .ag-theme-alpine .ag-row {\n border-bottom: 1px solid var(--grid-cell-border-color, #f0f0f0);\n}\n\n .ag-theme-alpine .ag-row:last-child {\n border-bottom: none;\n}\n\n\n\n\n\n\n .ag-theme-alpine .ag-cell-focus {\n border: none !important;\n outline: none !important;\n}\n\n .ag-theme-alpine .ag-header-cell:focus {\n outline: 2px solid var(--grid-accent-color, #2196F3);\n outline-offset: -2px;\n}\n\n\n\n\n\n\n .ag-body-viewport::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n .ag-body-viewport::-webkit-scrollbar-track {\n background: #f5f5f5;\n border-radius: 4px;\n}\n\n .ag-body-viewport::-webkit-scrollbar-thumb {\n background: #c0c0c0;\n border-radius: 4px;\n}\n\n .ag-body-viewport::-webkit-scrollbar-thumb:hover {\n background: #a0a0a0;\n}\n\n\n\n\n\n\n .ag-theme-alpine .ag-pinned-left-cols-container {\n border-right: 2px solid var(--grid-border-color, #e0e0e0);\n}\n\n .ag-theme-alpine .ag-pinned-right-cols-container {\n border-left: 2px solid var(--grid-border-color, #e0e0e0);\n}\n\n\n\n\n\n\n .ag-theme-alpine .ag-header-cell {\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n color: #546e7a;\n}\n\n .ag-theme-alpine .ag-header-cell:hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n .ag-theme-alpine .ag-header-cell-sortable:hover .ag-header-cell-label {\n color: var(--grid-accent-color, #2196F3);\n}\n\n\n\n .ag-theme-alpine .ag-sort-indicator-icon {\n transition: transform 0.2s ease;\n}\n\n .ag-theme-alpine .ag-header-cell:hover .ag-sort-indicator-icon {\n transform: scale(1.1);\n}"], data: { animation: [
3536
+ trigger('fadeIn', [
3537
+ transition(':enter', [
3538
+ style({ opacity: 0, transform: 'translateY(-8px)' }),
3539
+ animate('150ms ease-out', style({ opacity: 1, transform: 'translateY(0)' }))
3540
+ ]),
3541
+ transition(':leave', [
3542
+ animate('100ms ease-in', style({ opacity: 0, transform: 'translateY(-8px)' }))
3543
+ ])
3544
+ ])
3545
+ ] } });
3546
+ }
3547
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityDataGridComponent, [{
3548
+ type: Component,
3549
+ args: [{ selector: 'mj-entity-data-grid', animations: [
3550
+ trigger('fadeIn', [
3551
+ transition(':enter', [
3552
+ style({ opacity: 0, transform: 'translateY(-8px)' }),
3553
+ animate('150ms ease-out', style({ opacity: 1, transform: 'translateY(0)' }))
3554
+ ]),
3555
+ transition(':leave', [
3556
+ animate('100ms ease-in', style({ opacity: 0, transform: 'translateY(-8px)' }))
3557
+ ])
3558
+ ])
3559
+ ], template: "<!-- Grid Container -->\n<div\n #gridContainer\n [class]=\"gridContainerClasses.join(' ')\"\n [style.height]=\"gridHeightStyle\">\n\n <!-- Toolbar -->\n <div *ngIf=\"ShowToolbar\" class=\"mj-grid-toolbar\">\n <div class=\"toolbar-left\">\n <!-- Search -->\n <div *ngIf=\"ShowSearch\" class=\"toolbar-search\">\n <i class=\"fa-solid fa-search search-icon\"></i>\n <input\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"ToolbarConfig.searchPlaceholder || 'Search...'\"\n [value]=\"FilterText\"\n (input)=\"FilterText = $any($event.target).value\" />\n <button\n *ngIf=\"FilterText\"\n class=\"search-clear\"\n (click)=\"FilterText = ''\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Custom Left Buttons -->\n <ng-container *ngFor=\"let button of ToolbarConfig.customButtons\">\n <button\n *ngIf=\"button.position !== 'right' && isButtonVisible(button)\"\n class=\"toolbar-button\"\n [class]=\"button.cssClass\"\n [disabled]=\"isButtonDisabled(button)\"\n [title]=\"button.tooltip || ''\"\n (click)=\"onToolbarButtonClick(button)\">\n <i *ngIf=\"button.icon\" [class]=\"button.icon\"></i>\n <span *ngIf=\"button.text\">{{ button.text }}</span>\n </button>\n </ng-container>\n </div>\n\n <div class=\"toolbar-center\">\n <!-- Row Count -->\n <span *ngIf=\"ToolbarConfig.showRowCount !== false\" class=\"row-count\">\n {{ totalRowCount }} {{ totalRowCount === 1 ? 'row' : 'rows' }}\n </span>\n\n <!-- Selection Count -->\n <span *ngIf=\"ToolbarConfig.showSelectionCount && SelectedKeys.length > 0\" class=\"selection-count\">\n ({{ SelectedKeys.length }} selected)\n </span>\n </div>\n\n <div class=\"toolbar-right\">\n <!-- New/Add Button (predefined) -->\n <button\n *ngIf=\"ShowNewButton\"\n class=\"toolbar-button\"\n title=\"Create new record\"\n (click)=\"onAddClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n <span class=\"button-text\">New</span>\n </button>\n\n <!-- Refresh Button (predefined) -->\n <button\n *ngIf=\"ShowRefreshButton\"\n class=\"toolbar-button\"\n title=\"Refresh data\"\n [disabled]=\"loading\"\n (click)=\"onRefreshClick()\">\n <i class=\"fa-solid fa-arrows-rotate\" [class.fa-spin]=\"loading\"></i>\n <span class=\"button-text\">Refresh</span>\n </button>\n\n <!-- Export Button (predefined) -->\n <button\n *ngIf=\"ShowExportButton\"\n class=\"toolbar-button\"\n title=\"Export to Excel\"\n (click)=\"onExportClick()\">\n <i class=\"fa-solid fa-file-excel\"></i>\n <span class=\"button-text\">Export</span>\n </button>\n\n <!-- Delete Button (predefined) -->\n <button\n *ngIf=\"ShowDeleteButton && HasSelection\"\n class=\"toolbar-button toolbar-button-danger\"\n title=\"Delete selected records\"\n (click)=\"onDeleteClick()\">\n <i class=\"fa-solid fa-trash\"></i>\n <span class=\"button-text\">Delete</span>\n </button>\n\n <!-- Compare Button (predefined) -->\n <button\n *ngIf=\"ShowCompareButton\"\n class=\"toolbar-button\"\n title=\"Compare selected records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onCompareClick()\">\n <i class=\"fa-solid fa-code-compare\"></i>\n <span class=\"button-text\">Compare</span>\n </button>\n\n <!-- Merge Button (predefined) -->\n <button\n *ngIf=\"ShowMergeButton\"\n class=\"toolbar-button\"\n title=\"Merge selected records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onMergeClick()\">\n <i class=\"fa-solid fa-code-merge\"></i>\n <span class=\"button-text\">Merge</span>\n </button>\n\n <!-- Add to List Button (predefined) -->\n <button\n *ngIf=\"ShowAddToListButton\"\n class=\"toolbar-button\"\n title=\"Add selected records to a list\"\n [disabled]=\"!HasSelection\"\n (click)=\"onAddToListClick()\">\n <i class=\"fa-solid fa-list-check\"></i>\n <span class=\"button-text\">Add to List</span>\n </button>\n\n <!-- Search for Duplicates Button (predefined) -->\n <button\n *ngIf=\"ShowDuplicateSearchButton\"\n class=\"toolbar-button\"\n title=\"Search for duplicate records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onDuplicateSearchClick()\">\n <i class=\"fa-solid fa-magnifying-glass-plus\"></i>\n <span class=\"button-text\">Find Duplicates</span>\n </button>\n\n <!-- Communication Button (predefined) -->\n <button\n *ngIf=\"ShowCommunicationButton\"\n class=\"toolbar-button\"\n title=\"Send message to selected records\"\n [disabled]=\"!HasSelection\"\n (click)=\"onCommunicationClick()\">\n <i class=\"fa-solid fa-envelope\"></i>\n <span class=\"button-text\">Send Message</span>\n </button>\n\n <!-- Legacy ToolbarConfig buttons -->\n <!-- Add Button (legacy) -->\n <button\n *ngIf=\"ToolbarConfig.showAdd && AllowAdd && !ShowNewButton\"\n class=\"toolbar-button\"\n title=\"Add New\"\n (click)=\"onAddClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n </button>\n\n <!-- Refresh Button (legacy) -->\n <button\n *ngIf=\"ToolbarConfig.showRefresh !== false && !ShowRefreshButton\"\n class=\"toolbar-button\"\n title=\"Refresh\"\n [disabled]=\"loading\"\n (click)=\"onRefreshClick()\">\n <i class=\"fa-solid fa-refresh\" [class.fa-spin]=\"loading\"></i>\n </button>\n\n <!-- Delete Button (legacy) -->\n <button\n *ngIf=\"ToolbarConfig.showDelete && AllowDelete && HasSelection && !ShowDeleteButton\"\n class=\"toolbar-button toolbar-button-danger\"\n title=\"Delete Selected\"\n (click)=\"onDeleteClick()\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n\n <!-- Export Button (legacy) -->\n <button\n *ngIf=\"ToolbarConfig.showExport && !ShowExportButton\"\n class=\"toolbar-button\"\n title=\"Export\"\n (click)=\"onExportClick()\">\n <i class=\"fa-solid fa-download\"></i>\n </button>\n\n <!-- Column Chooser Button -->\n <button\n *ngIf=\"ToolbarConfig.showColumnChooser && AllowColumnToggle\"\n class=\"toolbar-button\"\n title=\"Column Chooser\"\n (click)=\"onColumnChooserClick()\">\n <i class=\"fa-solid fa-columns\"></i>\n </button>\n\n <!-- Custom Right Buttons -->\n <ng-container *ngFor=\"let button of ToolbarConfig.customButtons\">\n <button\n *ngIf=\"button.position === 'right' && isButtonVisible(button)\"\n class=\"toolbar-button\"\n [class]=\"button.cssClass\"\n [disabled]=\"isButtonDisabled(button)\"\n [title]=\"button.tooltip || ''\"\n (click)=\"onToolbarButtonClick(button)\">\n <i *ngIf=\"button.icon\" [class]=\"button.icon\"></i>\n <span *ngIf=\"button.text\">{{ button.text }}</span>\n </button>\n </ng-container>\n\n <!-- Overflow Menu -->\n <div *ngIf=\"hasOverflowMenuItems\" class=\"toolbar-overflow\" (click)=\"$event.stopPropagation()\">\n <button\n class=\"toolbar-button overflow-trigger\"\n title=\"More actions\"\n (click)=\"toggleOverflowMenu()\">\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </button>\n\n <div *ngIf=\"showOverflowMenu\" class=\"overflow-menu\" [@fadeIn]>\n <!-- Export (if in overflow) -->\n <button *ngIf=\"showExportInOverflow\" class=\"overflow-item\" (click)=\"onExportClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-file-excel\"></i>\n <span>Export to Excel</span>\n </button>\n\n <!-- Entity Actions -->\n <ng-container *ngIf=\"ShowEntityActionButtons && EntityActions.length > 0\">\n <div class=\"overflow-divider\"></div>\n <div class=\"overflow-section-label\">Actions</div>\n <button\n *ngFor=\"let action of EntityActions\"\n class=\"overflow-item\"\n [disabled]=\"!isEntityActionEnabled(action)\"\n (click)=\"onEntityActionClick(action); closeOverflowMenu()\">\n <i [class]=\"action.icon || 'fa-solid fa-bolt'\"></i>\n <span>{{ action.name }}</span>\n </button>\n </ng-container>\n\n <!-- Column Chooser (if in overflow) -->\n <div *ngIf=\"showColumnChooserInOverflow\" class=\"overflow-divider\"></div>\n <button *ngIf=\"showColumnChooserInOverflow\" class=\"overflow-item\" (click)=\"onColumnChooserClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-columns\"></i>\n <span>Manage Columns</span>\n </button>\n\n <!-- Selection-dependent actions in overflow -->\n <ng-container *ngIf=\"HasSelection && hasSelectionDependentOverflowActions\">\n <div class=\"overflow-divider\"></div>\n <button *ngIf=\"showCommunicationInOverflow\" class=\"overflow-item\" (click)=\"onCommunicationClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-envelope\"></i>\n <span>Send Message</span>\n </button>\n </ng-container>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Grid Content -->\n <div class=\"mj-grid-content\">\n <!-- Loading Overlay -->\n <div *ngIf=\"loading && rowData.length === 0\" class=\"mj-grid-loading-overlay\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n\n <!-- Error State -->\n <div *ngIf=\"errorMessage && !loading\" class=\"mj-grid-error\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ errorMessage }}</span>\n <button class=\"error-retry\" (click)=\"Refresh()\">Retry</button>\n </div>\n\n <!-- Empty State -->\n <div *ngIf=\"!loading && !errorMessage && rowData.length === 0\" class=\"mj-grid-empty\">\n <i class=\"fa-solid fa-inbox\"></i>\n <span>No data to display</span>\n </div>\n\n <!-- AG Grid (Client-side mode) -->\n <ag-grid-angular\n *ngIf=\"!errorMessage && PaginationMode === 'client' && (rowData.length > 0 || loading)\"\n class=\"mj-ag-grid ag-theme-alpine\"\n [theme]=\"agGridTheme\"\n [columnDefs]=\"agColumnDefs\"\n [rowData]=\"rowData\"\n [defaultColDef]=\"defaultColDef\"\n [rowSelection]=\"agRowSelection\"\n [getRowId]=\"getRowId\"\n [suppressCellFocus]=\"true\"\n [rowHeight]=\"RowHeight\"\n [headerHeight]=\"ShowHeader ? undefined : 0\"\n (gridReady)=\"onGridReady($event)\"\n (rowClicked)=\"onAgRowClicked($event)\"\n (rowDoubleClicked)=\"onAgRowDoubleClicked($event)\"\n (sortChanged)=\"onAgSortChanged($event)\"\n (selectionChanged)=\"onAgSelectionChanged($event)\"\n (columnResized)=\"onAgColumnResized($event)\"\n (columnMoved)=\"onAgColumnMoved($event)\">\n </ag-grid-angular>\n\n <!-- AG Grid (Infinite Scroll mode) -->\n <ag-grid-angular\n *ngIf=\"!errorMessage && PaginationMode === 'infinite'\"\n class=\"mj-ag-grid ag-theme-alpine\"\n [theme]=\"agGridTheme\"\n [columnDefs]=\"agColumnDefs\"\n [defaultColDef]=\"defaultColDef\"\n [rowSelection]=\"agRowSelection\"\n [getRowId]=\"getRowId\"\n [suppressCellFocus]=\"true\"\n [rowHeight]=\"RowHeight\"\n [headerHeight]=\"ShowHeader ? undefined : 0\"\n [rowModelType]=\"'infinite'\"\n [cacheBlockSize]=\"CacheBlockSize\"\n [maxBlocksInCache]=\"MaxBlocksInCache\"\n [infiniteInitialRowCount]=\"1\"\n [cacheOverflowSize]=\"2\"\n (gridReady)=\"onGridReady($event)\"\n (rowClicked)=\"onAgRowClicked($event)\"\n (rowDoubleClicked)=\"onAgRowDoubleClicked($event)\"\n (sortChanged)=\"onAgSortChanged($event)\"\n (selectionChanged)=\"onAgSelectionChanged($event)\"\n (columnResized)=\"onAgColumnResized($event)\"\n (columnMoved)=\"onAgColumnMoved($event)\">\n </ag-grid-angular>\n </div>\n</div>\n\n<!-- Export Dialog -->\n<mj-export-dialog\n [visible]=\"showExportDialog\"\n [config]=\"exportDialogConfig\"\n (closed)=\"onExportDialogClosed($event)\">\n</mj-export-dialog>\n", styles: ["/* ========================================\n CSS Custom Properties (Theme Variables)\n ======================================== */\n\n:host {\n /* Grid container */\n --grid-border-color: #e0e0e0;\n --grid-border-radius: 0px;\n --grid-background: #ffffff;\n\n /* Header */\n --grid-header-bg: #fafafa;\n --grid-header-text: #333333;\n --grid-header-font-weight: 600;\n --grid-header-height: 40px;\n --grid-header-border-color: #e0e0e0;\n\n /* Rows */\n --grid-row-height: 40px;\n --grid-row-bg: #ffffff;\n --grid-row-bg-alt: #fafafa;\n --grid-row-hover-bg: #f5f5f5;\n --grid-row-selected-bg: #fff9e6;\n --grid-row-selected-hover-bg: #fff3cc;\n\n /* Cells */\n --grid-cell-padding: 8px 12px;\n --grid-cell-text: #333333;\n --grid-cell-border-color: #f0f0f0;\n\n /* Selection - mellow yellow to avoid conflict with blue hyperlinks */\n --grid-checkbox-color: #2196F3;\n --grid-selection-indicator-color: #f9a825;\n\n /* Editing */\n --grid-edit-cell-bg: #ffffff;\n --grid-edit-cell-border: #2196F3;\n --grid-edit-cell-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n\n /* Sorting */\n --grid-sort-indicator-color: #2196F3;\n\n /* Toolbar */\n --grid-toolbar-bg: #ffffff;\n --grid-toolbar-height: 48px;\n --grid-toolbar-border-color: #e0e0e0;\n\n /* Loading */\n --grid-loading-overlay-bg: rgba(255, 255, 255, 0.8);\n\n /* Empty state */\n --grid-empty-text-color: #999999;\n --grid-empty-icon-color: #cccccc;\n\n display: block;\n height: 100%;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n}\n\n/* ========================================\n Grid Container\n ======================================== */\n\n.mj-grid-container {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--grid-border-color);\n border-radius: var(--grid-border-radius);\n background: var(--grid-background);\n overflow: hidden;\n}\n\n/* ========================================\n Toolbar\n ======================================== */\n\n.mj-grid-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: var(--grid-toolbar-height);\n padding: 0 12px;\n background: var(--grid-toolbar-bg);\n border-bottom: 1px solid var(--grid-toolbar-border-color);\n gap: 12px;\n}\n\n.toolbar-left,\n.toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-center {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #666;\n font-size: 13px;\n}\n\n.toolbar-search {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.search-icon {\n position: absolute;\n left: 10px;\n color: #999;\n font-size: 13px;\n}\n\n.search-input {\n padding: 6px 30px 6px 32px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n width: 200px;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n.search-input:focus {\n outline: none;\n border-color: var(--grid-selection-indicator-color);\n box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);\n}\n\n.search-clear {\n position: absolute;\n right: 6px;\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: #999;\n font-size: 12px;\n}\n\n.search-clear:hover {\n color: #666;\n}\n\n.toolbar-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: #f5f5f5;\n border: 1px solid #ddd;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n color: #333;\n transition: background-color 0.2s, border-color 0.2s;\n}\n\n.toolbar-button:hover:not(:disabled) {\n background: #eee;\n border-color: #ccc;\n}\n\n.toolbar-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.toolbar-button i {\n font-size: 14px;\n}\n\n.toolbar-button-danger {\n color: #d32f2f;\n}\n\n.toolbar-button-danger:hover:not(:disabled) {\n background: #ffebee;\n border-color: #ffcdd2;\n}\n\n.row-count,\n.selection-count {\n font-weight: 500;\n}\n\n/* ========================================\n Grid Content\n ======================================== */\n\n.mj-grid-content {\n flex: 1;\n position: relative;\n overflow: hidden;\n}\n\n.mj-grid-scroll-container {\n height: 100%;\n overflow: auto;\n}\n\n/* ========================================\n Loading Overlay\n ======================================== */\n\n.mj-grid-loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--grid-loading-overlay-bg);\n z-index: 10;\n}\n\n/* ========================================\n Error State\n ======================================== */\n\n.mj-grid-error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n color: #d32f2f;\n gap: 12px;\n}\n\n.mj-grid-error i {\n font-size: 32px;\n}\n\n.error-retry {\n padding: 8px 16px;\n background: #d32f2f;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n}\n\n.error-retry:hover {\n background: #c62828;\n}\n\n/* ========================================\n Empty State\n ======================================== */\n\n.mj-grid-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: var(--grid-empty-text-color);\n gap: 12px;\n}\n\n.mj-grid-empty i {\n font-size: 48px;\n color: var(--grid-empty-icon-color);\n}\n\n/* ========================================\n Header Row\n ======================================== */\n\n.mj-grid-header {\n display: flex;\n min-height: var(--grid-header-height);\n background: var(--grid-header-bg);\n border-bottom: 2px solid var(--grid-header-border-color);\n position: sticky;\n top: 0;\n z-index: 5;\n}\n\n.mj-grid-header-cell {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n font-weight: var(--grid-header-font-weight);\n color: var(--grid-header-text);\n font-size: 13px;\n user-select: none;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right: 1px solid var(--grid-cell-border-color);\n flex-shrink: 0;\n}\n\n.mj-grid-header-cell:last-child {\n border-right: none;\n}\n\n.mj-grid-header-cell.sortable {\n cursor: pointer;\n}\n\n.mj-grid-header-cell.sortable:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n.header-text {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sort-indicator {\n display: flex;\n align-items: center;\n margin-left: 6px;\n color: var(--grid-sort-indicator-color);\n}\n\n.sort-indicator i {\n font-size: 12px;\n}\n\n.sort-index {\n font-size: 10px;\n margin-left: 2px;\n font-weight: normal;\n}\n\n/* ========================================\n Data Rows\n ======================================== */\n\n.mj-grid-row {\n display: flex;\n align-items: stretch;\n background: var(--grid-row-bg);\n transition: background-color 0.15s;\n cursor: default;\n}\n\n.mj-grid-row:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-alt {\n background: var(--grid-row-bg-alt);\n}\n\n.mj-grid-row.grid-row-alt:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-selected {\n background: var(--grid-row-selected-bg);\n}\n\n.mj-grid-row.grid-row-selected:hover {\n background: var(--grid-row-selected-hover-bg);\n}\n\n.mj-grid-row.grid-row-editing {\n background: #fffde7;\n}\n\n.mj-grid-row.grid-row-dirty {\n border-left: 3px solid #ff9800;\n}\n\n/* ========================================\n Data Cells\n ======================================== */\n\n.mj-grid-cell {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n color: var(--grid-cell-text);\n font-size: 13px;\n overflow: hidden;\n border-right: 1px solid transparent;\n flex-shrink: 0;\n}\n\n.mj-grid-cell:last-child {\n border-right: none;\n}\n\n/* Grid lines modes */\n.grid-lines-horizontal .mj-grid-row {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-vertical .mj-grid-cell {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both .mj-grid-row {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both .mj-grid-cell {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n/* Cell alignment */\n.mj-grid-cell.align-left {\n justify-content: flex-start;\n}\n\n.mj-grid-cell.align-center {\n justify-content: center;\n}\n\n.mj-grid-cell.align-right {\n justify-content: flex-end;\n}\n\n.cell-content {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Special cells */\n.row-number-cell {\n width: 50px;\n min-width: 50px;\n max-width: 50px;\n justify-content: center;\n color: #999;\n font-size: 12px;\n background: var(--grid-header-bg);\n}\n\n.checkbox-cell {\n width: 40px;\n min-width: 40px;\n max-width: 40px;\n justify-content: center;\n}\n\n.checkbox-cell input[type=\"checkbox\"] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n accent-color: var(--grid-checkbox-color);\n}\n\n/* ========================================\n Virtual Scrolling\n ======================================== */\n\n.mj-grid-virtual-spacer {\n flex-shrink: 0;\n}\n\n/* ========================================\n Responsive Adjustments\n ======================================== */\n\n@media (max-width: 768px) {\n .mj-grid-toolbar {\n flex-wrap: wrap;\n padding: 8px;\n }\n\n .toolbar-search {\n order: 3;\n width: 100%;\n margin-top: 8px;\n }\n\n .search-input {\n width: 100%;\n }\n\n .toolbar-center {\n order: 2;\n }\n\n /* Hide button text on mobile */\n .toolbar-button .button-text {\n display: none;\n }\n}\n\n/* ========================================\n Toolbar Button Text\n ======================================== */\n\n.toolbar-button .button-text {\n font-size: 13px;\n}\n\n/* ========================================\n Overflow Menu\n ======================================== */\n\n.toolbar-overflow {\n position: relative;\n}\n\n.overflow-trigger {\n padding: 6px 8px !important;\n}\n\n.overflow-trigger i {\n font-size: 16px;\n}\n\n.overflow-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 4px;\n min-width: 220px;\n background: #ffffff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08);\n z-index: 1000;\n overflow: hidden;\n}\n\n.overflow-item {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 10px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n text-align: left;\n gap: 12px;\n transition: background-color 0.15s;\n}\n\n.overflow-item:hover:not(:disabled) {\n background: #f5f5f5;\n}\n\n.overflow-item:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.overflow-item i {\n width: 18px;\n font-size: 14px;\n color: #666;\n text-align: center;\n}\n\n.overflow-item span {\n flex: 1;\n}\n\n.overflow-divider {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n\n.overflow-section-label {\n padding: 8px 16px 4px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: #999;\n letter-spacing: 0.5px;\n}\n\n/* Entity Actions submenu styling */\n.overflow-item.action-item i {\n color: #2196F3;\n}\n\n/* ========================================\n Highlight Matches\n ======================================== */\n\n::ng-deep .highlight-match {\n background-color: #fff176;\n border-radius: 2px;\n padding: 0 1px;\n}\n\n/* ========================================\n AG Grid Customizations\n ======================================== */\n\n.mj-ag-grid {\n width: 100%;\n height: 100%;\n}\n\n::ng-deep .ag-theme-alpine {\n /* Row colors */\n --ag-row-hover-color: var(--grid-row-hover-bg);\n --ag-selected-row-background-color: var(--grid-row-selected-bg);\n --ag-header-background-color: var(--grid-header-bg);\n\n /* Selection accent colors - mellow yellow */\n --ag-range-selection-background-color: rgba(249, 168, 37, 0.15);\n --ag-range-selection-border-color: #f9a825;\n\n /* Ensure borders are visible */\n --ag-borders: none;\n --ag-row-border-color: var(--grid-cell-border-color);\n}\n\n/* Selected row styling - left indicator bar only, background handled by AG Grid theme */\n::ng-deep .ag-row-selected {\n box-shadow: inset 4px 0 0 0 #f9a825;\n}\n\n/* Selection checkbox styling */\n::ng-deep .ag-theme-alpine .ag-checkbox-input-wrapper {\n width: 18px;\n height: 18px;\n}\n\n::ng-deep .ag-theme-alpine .ag-checkbox-input-wrapper.ag-checked::after {\n color: var(--grid-checkbox-color, #2196F3);\n}\n\n/* Row hover effect */\n::ng-deep .ag-theme-alpine .ag-row:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f0f7ff);\n}\n\n/* ========================================\n Visual Config: Header Styles\n ======================================== */\n\n/* Flat header (minimal) */\n.header-style-flat ::ng-deep .ag-header {\n background: var(--grid-header-bg, #fafafa);\n border-bottom: 1px solid var(--grid-header-border-color, #e0e0e0);\n}\n\n/* Elevated header (default - subtle shadow) */\n.header-style-elevated ::ng-deep .ag-header {\n background: linear-gradient(180deg, #ffffff 0%, #f8f9fa 100%);\n border-bottom: 1px solid var(--grid-header-border-color, #e0e0e0);\n}\n\n.header-style-elevated.header-shadow ::ng-deep .ag-header {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);\n}\n\n/* Gradient header (more prominent) */\n.header-style-gradient ::ng-deep .ag-header {\n background: linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%);\n border-bottom: none;\n}\n\n.header-style-gradient.header-shadow ::ng-deep .ag-header {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n\n/* Bold header (high contrast) */\n.header-style-bold ::ng-deep .ag-header {\n background: linear-gradient(180deg, #37474f 0%, #263238 100%);\n border-bottom: none;\n}\n\n.header-style-bold ::ng-deep .ag-header-cell-text {\n color: #ffffff;\n font-weight: 600;\n}\n\n.header-style-bold ::ng-deep .ag-header-icon {\n color: rgba(255, 255, 255, 0.7);\n}\n\n.header-style-bold ::ng-deep .ag-header-cell:hover {\n background: rgba(255, 255, 255, 0.1);\n}\n\n/* Header sort icons enhancement */\n::ng-deep .ag-header-cell-sorted-asc .ag-icon-asc,\n::ng-deep .ag-header-cell-sorted-desc .ag-icon-desc {\n color: var(--grid-accent-color, var(--grid-sort-indicator-color, #2196F3));\n}\n\n/* ========================================\n Visual Config: Zebra Striping\n ======================================== */\n\n/* Subtle contrast */\n.alternate-rows-subtle ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.015);\n}\n\n.alternate-rows-subtle ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n/* Medium contrast (default) */\n.alternate-rows-medium ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.025);\n}\n\n.alternate-rows-medium ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n/* Strong contrast */\n.alternate-rows-strong ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.alternate-rows-strong ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n/* ========================================\n Visual Config: Hover Transitions\n ======================================== */\n\n.hover-transitions ::ng-deep .ag-row {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n.hover-transitions ::ng-deep .ag-cell {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n/* ========================================\n Visual Config: Cell Padding\n ======================================== */\n\n.cell-padding-compact ::ng-deep .ag-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-compact ::ng-deep .ag-header-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-normal ::ng-deep .ag-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-normal ::ng-deep .ag-header-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-comfortable ::ng-deep .ag-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n.cell-padding-comfortable ::ng-deep .ag-header-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n/* ========================================\n Visual Config: Checkbox Styles\n ======================================== */\n\n/* Rounded checkbox */\n.checkbox-style-rounded ::ng-deep .ag-checkbox-input-wrapper {\n border-radius: 4px;\n}\n\n.checkbox-style-rounded ::ng-deep .ag-checkbox-input-wrapper::after {\n border-radius: 3px;\n}\n\n/* Filled checkbox */\n.checkbox-style-filled ::ng-deep .ag-checkbox-input-wrapper.ag-checked {\n background-color: var(--grid-checkbox-color, #2196F3);\n border-color: var(--grid-checkbox-color, #2196F3);\n}\n\n.checkbox-style-filled ::ng-deep .ag-checkbox-input-wrapper.ag-checked::after {\n color: #ffffff;\n}\n\n/* ========================================\n Cell Content Formatting\n ======================================== */\n\n/* Right-aligned cells (numbers) */\n::ng-deep .cell-align-right {\n text-align: right;\n justify-content: flex-end;\n}\n\n::ng-deep .header-align-right .ag-header-cell-label {\n justify-content: flex-end;\n}\n\n/* Empty cell placeholder */\n::ng-deep .cell-empty {\n color: #bdbdbd;\n font-style: normal;\n}\n\n/* Boolean icons */\n::ng-deep .cell-boolean-true {\n color: #43a047;\n font-size: 14px;\n}\n\n::ng-deep .cell-boolean-false {\n color: #bdbdbd;\n font-size: 14px;\n}\n\n/* Clickable links */\n::ng-deep .cell-link {\n color: var(--grid-accent-color, #2196F3);\n text-decoration: none;\n transition: color 0.15s;\n font-size: inherit;\n font-family: inherit;\n line-height: inherit;\n}\n\n::ng-deep .cell-link:hover {\n color: #1976D2;\n text-decoration: underline;\n}\n\n/* Email cells */\n::ng-deep .cell-email {\n font-family: inherit;\n font-size: 13px;\n}\n\n/* URL cells */\n::ng-deep .cell-url {\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n font-size: 13px;\n}\n\n/* Phone cells */\n::ng-deep .cell-phone {\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n}\n\n/* ========================================\n Skeleton Loading Animation\n ======================================== */\n\n@keyframes skeleton-shimmer {\n 0% {\n background-position: -200px 0;\n }\n 100% {\n background-position: calc(200px + 100%) 0;\n }\n}\n\n.skeleton-row {\n display: flex;\n height: 40px;\n align-items: center;\n padding: 0 12px;\n border-bottom: 1px solid var(--grid-cell-border-color, #f0f0f0);\n}\n\n.skeleton-cell {\n height: 16px;\n border-radius: 4px;\n background: linear-gradient(\n 90deg,\n #f0f0f0 0px,\n #e8e8e8 40px,\n #f0f0f0 80px\n );\n background-size: 200px 100%;\n animation: skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-cell-short {\n width: 60px;\n}\n\n.skeleton-cell-medium {\n width: 120px;\n}\n\n.skeleton-cell-long {\n width: 180px;\n}\n\n/* Selection checkbox column header */\n::ng-deep .ag-header-select-all {\n margin-right: 0;\n}\n\n/* ========================================\n Row Border Enhancement\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-row {\n border-bottom: 1px solid var(--grid-cell-border-color, #f0f0f0);\n}\n\n::ng-deep .ag-theme-alpine .ag-row:last-child {\n border-bottom: none;\n}\n\n/* ========================================\n Focus States\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-cell-focus {\n border: none !important;\n outline: none !important;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:focus {\n outline: 2px solid var(--grid-accent-color, #2196F3);\n outline-offset: -2px;\n}\n\n/* ========================================\n Scrollbar Styling\n ======================================== */\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-track {\n background: #f5f5f5;\n border-radius: 4px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-thumb {\n background: #c0c0c0;\n border-radius: 4px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-thumb:hover {\n background: #a0a0a0;\n}\n\n/* ========================================\n Pinned Column Styling\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-pinned-left-cols-container {\n border-right: 2px solid var(--grid-border-color, #e0e0e0);\n}\n\n::ng-deep .ag-theme-alpine .ag-pinned-right-cols-container {\n border-left: 2px solid var(--grid-border-color, #e0e0e0);\n}\n\n/* ========================================\n Header Cell Enhancements\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-header-cell {\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n color: #546e7a;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell-sortable:hover .ag-header-cell-label {\n color: var(--grid-accent-color, #2196F3);\n}\n\n/* Sort icon animation */\n::ng-deep .ag-theme-alpine .ag-sort-indicator-icon {\n transition: transform 0.2s ease;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:hover .ag-sort-indicator-icon {\n transform: scale(1.1);\n}\n"] }]
3560
+ }], () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i1.ExportService }], { Params: [{
3561
+ type: Input
3562
+ }], AllowLoad: [{
3563
+ type: Input
3564
+ }], AutoRefreshOnParamsChange: [{
3565
+ type: Input
3566
+ }], PaginationMode: [{
3567
+ type: Input
3568
+ }], PageSize: [{
3569
+ type: Input
3570
+ }], CacheBlockSize: [{
3571
+ type: Input
3572
+ }], MaxBlocksInCache: [{
3573
+ type: Input
3574
+ }], Data: [{
3575
+ type: Input
3576
+ }], Columns: [{
3577
+ type: Input
3578
+ }], GridState: [{
3579
+ type: Input
3580
+ }], AllowColumnReorder: [{
3581
+ type: Input
3582
+ }], AllowColumnResize: [{
3583
+ type: Input
3584
+ }], AllowColumnToggle: [{
3585
+ type: Input
3586
+ }], ShowHeader: [{
3587
+ type: Input
3588
+ }], AllowSorting: [{
3589
+ type: Input
3590
+ }], AllowMultiSort: [{
3591
+ type: Input
3592
+ }], ServerSideSorting: [{
3593
+ type: Input
3594
+ }], AllowColumnFilters: [{
3595
+ type: Input
3596
+ }], ShowSearch: [{
3597
+ type: Input
3598
+ }], SelectionMode: [{
3599
+ type: Input
3600
+ }], SelectedKeys: [{
3601
+ type: Input
3602
+ }], KeyField: [{
3603
+ type: Input
3604
+ }], EditMode: [{
3605
+ type: Input
3606
+ }], AllowAdd: [{
3607
+ type: Input
3608
+ }], AllowDelete: [{
3609
+ type: Input
3610
+ }], Height: [{
3611
+ type: Input
3612
+ }], RowHeight: [{
3613
+ type: Input
3614
+ }], VirtualScroll: [{
3615
+ type: Input
3616
+ }], ShowRowNumbers: [{
3617
+ type: Input
3618
+ }], Striped: [{
3619
+ type: Input
3620
+ }], GridLines: [{
3621
+ type: Input
3622
+ }], VisualConfig: [{
3623
+ type: Input
3624
+ }], ShowToolbar: [{
3625
+ type: Input
3626
+ }], ToolbarConfig: [{
3627
+ type: Input
3628
+ }], StateKey: [{
3629
+ type: Input
3630
+ }], AutoPersistState: [{
3631
+ type: Input
3632
+ }], StatePersistDebounce: [{
3633
+ type: Input
3634
+ }], RefreshDebounce: [{
3635
+ type: Input
3636
+ }], FilterText: [{
3637
+ type: Input
3638
+ }], ShowNewButton: [{
3639
+ type: Input
3640
+ }], ShowRefreshButton: [{
3641
+ type: Input
3642
+ }], ShowExportButton: [{
3643
+ type: Input
3644
+ }], ShowDeleteButton: [{
3645
+ type: Input
3646
+ }], ShowCompareButton: [{
3647
+ type: Input
3648
+ }], ShowMergeButton: [{
3649
+ type: Input
3650
+ }], ShowAddToListButton: [{
3651
+ type: Input
3652
+ }], ShowDuplicateSearchButton: [{
3653
+ type: Input
3654
+ }], ShowCommunicationButton: [{
3655
+ type: Input
3656
+ }], AutoNavigate: [{
3657
+ type: Input
3658
+ }], NavigateOnDoubleClick: [{
3659
+ type: Input
3660
+ }], CreateRecordMode: [{
3661
+ type: Input
3662
+ }], NewRecordValues: [{
3663
+ type: Input
3664
+ }], ShowEntityActionButtons: [{
3665
+ type: Input
3666
+ }], EntityActions: [{
3667
+ type: Input
3668
+ }], BeforeRowSelect: [{
3669
+ type: Output
3670
+ }], AfterRowSelect: [{
3671
+ type: Output
3672
+ }], BeforeRowDeselect: [{
3673
+ type: Output
3674
+ }], AfterRowDeselect: [{
3675
+ type: Output
3676
+ }], SelectionChange: [{
3677
+ type: Output
3678
+ }], BeforeRowClick: [{
3679
+ type: Output
3680
+ }], AfterRowClick: [{
3681
+ type: Output
3682
+ }], BeforeRowDoubleClick: [{
3683
+ type: Output
3684
+ }], AfterRowDoubleClick: [{
3685
+ type: Output
3686
+ }], BeforeCellEdit: [{
3687
+ type: Output
3688
+ }], AfterCellEditBegin: [{
3689
+ type: Output
3690
+ }], BeforeCellEditCommit: [{
3691
+ type: Output
3692
+ }], AfterCellEditCommit: [{
3693
+ type: Output
3694
+ }], BeforeCellEditCancel: [{
3695
+ type: Output
3696
+ }], AfterCellEditCancel: [{
3697
+ type: Output
3698
+ }], BeforeRowSave: [{
3699
+ type: Output
3700
+ }], AfterRowSave: [{
3701
+ type: Output
3702
+ }], BeforeRowDelete: [{
3703
+ type: Output
3704
+ }], AfterRowDelete: [{
3705
+ type: Output
3706
+ }], BeforeDataLoad: [{
3707
+ type: Output
3708
+ }], AfterDataLoad: [{
3709
+ type: Output
3710
+ }], BeforeDataRefresh: [{
3711
+ type: Output
3712
+ }], AfterDataRefresh: [{
3713
+ type: Output
3714
+ }], BeforeSort: [{
3715
+ type: Output
3716
+ }], AfterSort: [{
3717
+ type: Output
3718
+ }], BeforeColumnReorder: [{
3719
+ type: Output
3720
+ }], AfterColumnReorder: [{
3721
+ type: Output
3722
+ }], BeforeColumnResize: [{
3723
+ type: Output
3724
+ }], AfterColumnResize: [{
3725
+ type: Output
3726
+ }], BeforeColumnVisibilityChange: [{
3727
+ type: Output
3728
+ }], AfterColumnVisibilityChange: [{
3729
+ type: Output
3730
+ }], GridStateChanged: [{
3731
+ type: Output
3732
+ }], AddRequested: [{
3733
+ type: Output
3734
+ }], DeleteRequested: [{
3735
+ type: Output
3736
+ }], ExportRequested: [{
3737
+ type: Output
3738
+ }], NewButtonClick: [{
3739
+ type: Output
3740
+ }], RefreshButtonClick: [{
3741
+ type: Output
3742
+ }], ExportButtonClick: [{
3743
+ type: Output
3744
+ }], DeleteButtonClick: [{
3745
+ type: Output
3746
+ }], CompareButtonClick: [{
3747
+ type: Output
3748
+ }], MergeButtonClick: [{
3749
+ type: Output
3750
+ }], AddToListButtonClick: [{
3751
+ type: Output
3752
+ }], DuplicateSearchButtonClick: [{
3753
+ type: Output
3754
+ }], CommunicationButtonClick: [{
3755
+ type: Output
3756
+ }], NavigationRequested: [{
3757
+ type: Output
3758
+ }], NewRecordDialogRequested: [{
3759
+ type: Output
3760
+ }], NewRecordTabRequested: [{
3761
+ type: Output
3762
+ }], CompareRecordsRequested: [{
3763
+ type: Output
3764
+ }], MergeRecordsRequested: [{
3765
+ type: Output
3766
+ }], CommunicationRequested: [{
3767
+ type: Output
3768
+ }], DuplicateSearchRequested: [{
3769
+ type: Output
3770
+ }], AddToListRequested: [{
3771
+ type: Output
3772
+ }], LoadEntityActionsRequested: [{
3773
+ type: Output
3774
+ }], EntityActionRequested: [{
3775
+ type: Output
3776
+ }], gridContainer: [{
3777
+ type: ViewChild,
3778
+ args: ['gridContainer']
3779
+ }] }); })();
3780
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityDataGridComponent, { className: "EntityDataGridComponent" }); })();
3781
+ //# sourceMappingURL=entity-data-grid.component.js.map