@memberjunction/ng-entity-viewer 2.131.0 → 2.133.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 (37) hide show
  1. package/README.md +317 -124
  2. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +792 -0
  3. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -0
  4. package/dist/lib/entity-data-grid/entity-data-grid.component.js +3778 -0
  5. package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -0
  6. package/dist/lib/entity-data-grid/events/grid-events.d.ts +398 -0
  7. package/dist/lib/entity-data-grid/events/grid-events.d.ts.map +1 -0
  8. package/dist/lib/entity-data-grid/events/grid-events.js +556 -0
  9. package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -0
  10. package/dist/lib/entity-data-grid/models/grid-types.d.ts +437 -0
  11. package/dist/lib/entity-data-grid/models/grid-types.d.ts.map +1 -0
  12. package/dist/lib/entity-data-grid/models/grid-types.js +37 -0
  13. package/dist/lib/entity-data-grid/models/grid-types.js.map +1 -0
  14. package/dist/lib/entity-viewer/entity-viewer.component.d.ts +92 -2
  15. package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
  16. package/dist/lib/entity-viewer/entity-viewer.component.js +255 -92
  17. package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
  18. package/dist/lib/types.d.ts +14 -31
  19. package/dist/lib/types.d.ts.map +1 -1
  20. package/dist/lib/types.js.map +1 -1
  21. package/dist/lib/view-config-panel/view-config-panel.component.d.ts +363 -0
  22. package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -0
  23. package/dist/lib/view-config-panel/view-config-panel.component.js +2006 -0
  24. package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -0
  25. package/dist/module.d.ts +16 -13
  26. package/dist/module.d.ts.map +1 -1
  27. package/dist/module.js +24 -14
  28. package/dist/module.js.map +1 -1
  29. package/dist/public-api.d.ts +4 -1
  30. package/dist/public-api.d.ts.map +1 -1
  31. package/dist/public-api.js +6 -1
  32. package/dist/public-api.js.map +1 -1
  33. package/package.json +10 -6
  34. package/dist/lib/entity-grid/entity-grid.component.d.ts +0 -216
  35. package/dist/lib/entity-grid/entity-grid.component.d.ts.map +0 -1
  36. package/dist/lib/entity-grid/entity-grid.component.js +0 -676
  37. package/dist/lib/entity-grid/entity-grid.component.js.map +0 -1
@@ -0,0 +1,3778 @@
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)("rowMultiSelectWithClick", ctx_r2.SelectionMode === "multiple");
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)("rowMultiSelectWithClick", ctx_r2.SelectionMode === "multiple")("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.flushPendingPersistence();
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
+ */
1381
+ flushPendingPersistence() {
1382
+ // Flush pending view state
1383
+ if (this._pendingViewStateToPersist && this._viewEntity) {
1384
+ this.persistGridStateToView(this._pendingViewStateToPersist);
1385
+ this._pendingViewStateToPersist = null;
1386
+ }
1387
+ // Flush pending user defaults
1388
+ if (this._pendingUserDefaultsToPersist && this._entityInfo) {
1389
+ this.persistUserDefaultGridState(this._pendingUserDefaultsToPersist);
1390
+ this._pendingUserDefaultsToPersist = null;
1391
+ }
1392
+ }
1393
+ // ========================================
1394
+ // Setup Methods
1395
+ // ========================================
1396
+ setupRefreshDebounce() {
1397
+ this.refreshSubject.pipe(debounceTime(this._refreshDebounce), takeUntil(this.destroy$)).subscribe(() => {
1398
+ this.loadData(true);
1399
+ });
1400
+ }
1401
+ setupStatePersistDebounce() {
1402
+ this.statePersistSubject.pipe(debounceTime(this._statePersistDebounce), takeUntil(this.destroy$)).subscribe((state) => {
1403
+ this.persistGridStateToView(state);
1404
+ });
1405
+ }
1406
+ setupUserDefaultsPersistDebounce() {
1407
+ this.userDefaultsPersistSubject.pipe(debounceTime(this._statePersistDebounce), takeUntil(this.destroy$)).subscribe((state) => {
1408
+ this.persistUserDefaultGridState(state);
1409
+ });
1410
+ }
1411
+ /**
1412
+ * Called when Params input changes. Handles loading view entity metadata
1413
+ * and triggering data load if allowed.
1414
+ */
1415
+ async onParamsChanged() {
1416
+ if (!this._params) {
1417
+ // Params cleared - reset view entity
1418
+ this._viewEntity = null;
1419
+ return;
1420
+ }
1421
+ // Suppress persistence during view transition to prevent old view's state
1422
+ // from being saved to the new view's storage
1423
+ this._suppressPersist = true;
1424
+ // Reset dirty flag - no changes have been made to the new view yet
1425
+ this._isGridStateDirty = false;
1426
+ // Reset internal grid state when params change - this ensures we don't
1427
+ // carry over column/sort settings from a previous view when switching views
1428
+ this._gridState = null;
1429
+ this._sortState = [];
1430
+ try {
1431
+ // If using a stored view, load the view entity first
1432
+ if (this._params.ViewEntity) {
1433
+ // ViewEntity was provided directly
1434
+ this._viewEntity = this._params.ViewEntity;
1435
+ this._entityInfo = this._viewEntity.ViewEntityInfo;
1436
+ this.applyViewEntitySettings();
1437
+ }
1438
+ else if (this._params.ViewID) {
1439
+ // Load view entity by ID from engine
1440
+ const cachedView = this.getViewFromEngine(this._params.ViewID);
1441
+ if (cachedView) {
1442
+ this._viewEntity = cachedView;
1443
+ }
1444
+ else {
1445
+ // View not in cache - use ViewInfo (which also uses engine)
1446
+ this._viewEntity = await ViewInfo.GetViewEntity(this._params.ViewID);
1447
+ }
1448
+ this._entityInfo = this._viewEntity.ViewEntityInfo;
1449
+ this.applyViewEntitySettings();
1450
+ }
1451
+ else if (this._params.ViewName) {
1452
+ // Load view entity by name from engine
1453
+ const cachedView = this.getViewFromEngineByName(this._params.ViewName);
1454
+ if (cachedView) {
1455
+ this._viewEntity = cachedView;
1456
+ }
1457
+ else {
1458
+ // View not in cache - use ViewInfo (which also uses engine)
1459
+ this._viewEntity = await ViewInfo.GetViewEntityByName(this._params.ViewName);
1460
+ }
1461
+ this._entityInfo = this._viewEntity.ViewEntityInfo;
1462
+ this.applyViewEntitySettings();
1463
+ }
1464
+ else if (this._params.EntityName) {
1465
+ // Dynamic view - just get entity metadata
1466
+ this._viewEntity = null;
1467
+ const md = new Metadata();
1468
+ this._entityInfo = md.Entities.find(e => e.Name === this._params.EntityName) || null;
1469
+ // Reset columns to force regeneration from metadata when switching to dynamic view
1470
+ // This ensures we don't carry over column config from a previous saved view
1471
+ this._columns = [];
1472
+ // For dynamic views, try to load user's saved defaults
1473
+ if (this._entityInfo && this._autoPersistState) {
1474
+ this.loadUserDefaultGridState(this._entityInfo.Name);
1475
+ }
1476
+ }
1477
+ // Generate columns if not already set
1478
+ if (this._columns.length === 0 && this._entityInfo) {
1479
+ this.generateColumnsFromMetadata();
1480
+ }
1481
+ // Rebuild AG Grid column definitions to reflect the new view's settings
1482
+ this.buildAgColumnDefs();
1483
+ // Load data if auto-refresh is enabled
1484
+ if (this._autoRefreshOnParamsChange) {
1485
+ await this.loadData(false);
1486
+ }
1487
+ }
1488
+ catch (error) {
1489
+ this.errorMessage = error instanceof Error ? error.message : 'Failed to load view';
1490
+ this.cdr.detectChanges();
1491
+ }
1492
+ finally {
1493
+ // Re-enable persistence now that the new view is fully loaded
1494
+ this._suppressPersist = false;
1495
+ }
1496
+ }
1497
+ /**
1498
+ * Gets a view from the UserViewEngine cache by ID.
1499
+ * Returns undefined if not found or engine not initialized.
1500
+ */
1501
+ getViewFromEngine(viewId) {
1502
+ try {
1503
+ return UserViewEngine.Instance.GetViewById(viewId);
1504
+ }
1505
+ catch {
1506
+ // Engine may not be initialized yet
1507
+ return undefined;
1508
+ }
1509
+ }
1510
+ /**
1511
+ * Gets a view from the UserViewEngine cache by name.
1512
+ * Returns undefined if not found or engine not initialized.
1513
+ */
1514
+ getViewFromEngineByName(viewName) {
1515
+ try {
1516
+ return UserViewEngine.Instance.GetViewByName(viewName);
1517
+ }
1518
+ catch {
1519
+ // Engine may not be initialized yet
1520
+ return undefined;
1521
+ }
1522
+ }
1523
+ /**
1524
+ * Loads user's saved grid state defaults for a dynamic view.
1525
+ * Uses UserInfoEngine to retrieve settings stored with key format: "default-view-setting/{entityName}"
1526
+ */
1527
+ loadUserDefaultGridState(entityName) {
1528
+ try {
1529
+ const settingKey = `default-view-setting/${entityName}`;
1530
+ const savedState = UserInfoEngine.Instance.GetSetting(settingKey);
1531
+ if (savedState) {
1532
+ const gridState = JSON.parse(savedState);
1533
+ // Only apply if not already set via props (props take precedence)
1534
+ if (!this._gridState && gridState.columnSettings?.length) {
1535
+ this._gridState = {
1536
+ columnSettings: gridState.columnSettings,
1537
+ sortSettings: gridState.sortSettings || []
1538
+ };
1539
+ }
1540
+ // Apply sort state if not already set
1541
+ if (this._sortState.length === 0 && gridState.sortSettings?.length) {
1542
+ this._sortState = gridState.sortSettings.map((s, index) => ({
1543
+ field: s.field,
1544
+ direction: s.dir,
1545
+ index
1546
+ }));
1547
+ }
1548
+ }
1549
+ }
1550
+ catch (error) {
1551
+ console.error('[entity-data-grid] Failed to load user default grid state:', error);
1552
+ }
1553
+ }
1554
+ /**
1555
+ * Persists the current grid state as user defaults for a dynamic view.
1556
+ * Uses UserInfoEngine to store settings with key format: "default-view-setting/{entityName}"
1557
+ */
1558
+ async persistUserDefaultGridState(state) {
1559
+ if (!this._entityInfo) {
1560
+ this._pendingUserDefaultsToPersist = null;
1561
+ return;
1562
+ }
1563
+ try {
1564
+ const settingKey = `default-view-setting/${this._entityInfo.Name}`;
1565
+ const gridStateJson = {
1566
+ columnSettings: state.columnSettings,
1567
+ sortSettings: state.sortSettings
1568
+ };
1569
+ await UserInfoEngine.Instance.SetSetting(settingKey, JSON.stringify(gridStateJson));
1570
+ // Clear pending state and reset dirty flag after successful save
1571
+ this._pendingUserDefaultsToPersist = null;
1572
+ this._isGridStateDirty = false;
1573
+ }
1574
+ catch (error) {
1575
+ console.error('[entity-data-grid] Failed to persist user default grid state:', error);
1576
+ }
1577
+ }
1578
+ /**
1579
+ * Applies settings from a loaded ViewEntity (column configuration, sort state, etc.)
1580
+ *
1581
+ * Precedence rules (highest to lowest):
1582
+ * 1. Props passed directly (gridState, orderBy, etc.) - always take precedence
1583
+ * 2. Settings from ViewEntity.GridState (persisted column/sort configuration)
1584
+ * 3. Settings from ViewEntity.SortInfo (legacy sort configuration)
1585
+ * 4. Auto-generated defaults from entity metadata
1586
+ */
1587
+ applyViewEntitySettings() {
1588
+ if (!this._viewEntity)
1589
+ return;
1590
+ // Only apply grid state from view entity if not already set via props
1591
+ // (gridState input takes precedence)
1592
+ if (!this._gridState && this._viewEntity.GridState) {
1593
+ try {
1594
+ const gridState = JSON.parse(this._viewEntity.GridState);
1595
+ if (gridState.columnSettings?.length) {
1596
+ this._gridState = {
1597
+ columnSettings: gridState.columnSettings,
1598
+ sortSettings: gridState.sortSettings || []
1599
+ };
1600
+ }
1601
+ }
1602
+ catch (e) {
1603
+ console.warn('Failed to parse view GridState:', e);
1604
+ }
1605
+ }
1606
+ // Only apply sort state if:
1607
+ // 1. No explicit OrderBy in Params
1608
+ // 2. No sort already set via gridState (either prop or from view)
1609
+ const hasExplicitOrderBy = this._params?.OrderBy && this._params.OrderBy.trim().length > 0;
1610
+ const hasSortFromGridState = this._gridState?.sortSettings && this._gridState.sortSettings.length > 0;
1611
+ if (!hasExplicitOrderBy && !hasSortFromGridState && this._sortState.length === 0) {
1612
+ const sortInfo = this._viewEntity.ViewSortInfo;
1613
+ if (sortInfo?.length) {
1614
+ this._sortState = sortInfo.map((s, index) => ({
1615
+ field: s.field,
1616
+ direction: s.direction?.toLowerCase() === 'desc' ? 'desc' : 'asc',
1617
+ index
1618
+ }));
1619
+ }
1620
+ }
1621
+ }
1622
+ onGridStateChanged() {
1623
+ if (this._gridState && this._entityInfo) {
1624
+ this.buildAgColumnDefs();
1625
+ // Update AG Grid with new column definitions to apply header styles
1626
+ if (this.gridApi) {
1627
+ this.gridApi.setGridOption('columnDefs', this.agColumnDefs);
1628
+ // Refresh header to apply new header styles
1629
+ this.gridApi.refreshHeader();
1630
+ }
1631
+ // Apply sort if present
1632
+ if (this._gridState.sortSettings?.length && this.gridApi) {
1633
+ const sortSetting = this._gridState.sortSettings[0];
1634
+ this._sortState = [{
1635
+ field: sortSetting.field,
1636
+ direction: sortSetting.dir,
1637
+ index: 0
1638
+ }];
1639
+ this.applySortStateToGrid();
1640
+ }
1641
+ }
1642
+ }
1643
+ onFilterTextChanged() {
1644
+ // Rebuild column defs to update cell renderers with new filter text
1645
+ this.buildAgColumnDefs();
1646
+ // Update AG Grid with new column definitions and refresh cells
1647
+ if (this.gridApi) {
1648
+ // Update the column definitions in AG Grid
1649
+ this.gridApi.setGridOption('columnDefs', this.agColumnDefs);
1650
+ // Force refresh all cells to apply new highlighting
1651
+ this.gridApi.refreshCells({ force: true });
1652
+ }
1653
+ }
1654
+ generateColumnsFromMetadata() {
1655
+ if (!this._entityInfo)
1656
+ return;
1657
+ this._columns = this._entityInfo.Fields
1658
+ .filter(f => this.shouldShowField(f))
1659
+ .map(field => ({
1660
+ field: field.Name,
1661
+ title: field.DisplayName || field.Name,
1662
+ type: this.mapFieldTypeToGridType(field.Type),
1663
+ sortable: true,
1664
+ filterable: true,
1665
+ visible: true,
1666
+ width: this.estimateColumnWidth(field)
1667
+ }));
1668
+ this.initializeColumnStates();
1669
+ this.buildAgColumnDefs();
1670
+ }
1671
+ shouldShowField(field) {
1672
+ if (field.Name.startsWith('__mj_'))
1673
+ return false;
1674
+ if (field.IsPrimaryKey && field.SQLFullType?.toLowerCase() === 'uniqueidentifier') {
1675
+ return false;
1676
+ }
1677
+ if (field.DefaultInView === true)
1678
+ return true;
1679
+ if (field.Length > 500)
1680
+ return false;
1681
+ return true;
1682
+ }
1683
+ estimateColumnWidth(field) {
1684
+ const fieldNameLower = field.Name.toLowerCase();
1685
+ const displayNameLower = (field.DisplayName || field.Name).toLowerCase();
1686
+ // Fixed-width types
1687
+ if (field.TSType === 'boolean')
1688
+ return 80;
1689
+ if (field.TSType === 'Date')
1690
+ return 120;
1691
+ // Numeric fields - compact
1692
+ if (field.TSType === 'number') {
1693
+ if (fieldNameLower.includes('year') || fieldNameLower.includes('age'))
1694
+ return 80;
1695
+ if (fieldNameLower.includes('amount') || fieldNameLower.includes('price') || fieldNameLower.includes('total'))
1696
+ return 120;
1697
+ return 100;
1698
+ }
1699
+ // ID fields - compact
1700
+ if (fieldNameLower.endsWith('id') && field.Length <= 50)
1701
+ return 80;
1702
+ // Email - needs more space
1703
+ if (fieldNameLower.includes('email'))
1704
+ return 220;
1705
+ // Phone numbers
1706
+ if (fieldNameLower.includes('phone') || fieldNameLower.includes('mobile') || fieldNameLower.includes('fax'))
1707
+ return 130;
1708
+ // Name fields - medium width
1709
+ if (fieldNameLower.includes('name') || fieldNameLower.includes('title')) {
1710
+ if (fieldNameLower === 'firstname' || fieldNameLower === 'lastname' || fieldNameLower === 'first name' || fieldNameLower === 'last name')
1711
+ return 120;
1712
+ return 160;
1713
+ }
1714
+ // Location fields
1715
+ if (fieldNameLower.includes('city'))
1716
+ return 120;
1717
+ if (fieldNameLower.includes('state') || fieldNameLower.includes('country'))
1718
+ return 100;
1719
+ if (fieldNameLower.includes('zip') || fieldNameLower.includes('postal'))
1720
+ return 90;
1721
+ if (fieldNameLower.includes('address'))
1722
+ return 200;
1723
+ // Date-like strings
1724
+ if (fieldNameLower.includes('date') || fieldNameLower.includes('time'))
1725
+ return 120;
1726
+ // Status/Type fields - usually short values
1727
+ if (fieldNameLower.includes('status') || fieldNameLower.includes('type') || fieldNameLower.includes('category'))
1728
+ return 110;
1729
+ // Code/abbreviation fields
1730
+ if (fieldNameLower.includes('code') || fieldNameLower.includes('abbr'))
1731
+ return 100;
1732
+ // Long text fields - limit width, they'll truncate
1733
+ if (field.Length > 500)
1734
+ return 150;
1735
+ if (field.Length > 200)
1736
+ return 180;
1737
+ // Default: estimate based on field length but with tighter bounds
1738
+ const estimatedChars = Math.min(field.Length, 50);
1739
+ const charWidth = 7;
1740
+ const padding = 24;
1741
+ return Math.min(Math.max(estimatedChars * charWidth / 2 + padding, 80), 200);
1742
+ }
1743
+ mapFieldTypeToGridType(fieldType) {
1744
+ switch (fieldType.toLowerCase()) {
1745
+ case 'int':
1746
+ case 'bigint':
1747
+ case 'smallint':
1748
+ case 'tinyint':
1749
+ case 'decimal':
1750
+ case 'numeric':
1751
+ case 'float':
1752
+ case 'real':
1753
+ case 'money':
1754
+ case 'smallmoney':
1755
+ return 'number';
1756
+ case 'bit':
1757
+ return 'boolean';
1758
+ case 'date':
1759
+ return 'date';
1760
+ case 'datetime':
1761
+ case 'datetime2':
1762
+ case 'smalldatetime':
1763
+ case 'datetimeoffset':
1764
+ return 'datetime';
1765
+ default:
1766
+ return 'string';
1767
+ }
1768
+ }
1769
+ initializeColumnStates() {
1770
+ this._columnStates = this._columns.map((config, index) => ({
1771
+ config,
1772
+ computedWidth: typeof config.width === 'number' ? config.width : 150,
1773
+ sortDirection: config.sortDirection || 'none',
1774
+ sortIndex: config.sortIndex || 0,
1775
+ filterValue: undefined,
1776
+ visible: config.visible !== false,
1777
+ order: index
1778
+ }));
1779
+ }
1780
+ // ========================================
1781
+ // AG Grid Column Building
1782
+ // ========================================
1783
+ buildAgColumnDefs() {
1784
+ if (this._gridState?.columnSettings?.length && this._entityInfo) {
1785
+ this.agColumnDefs = this.buildAgColumnDefsFromGridState(this._gridState.columnSettings);
1786
+ }
1787
+ else if (this._columns.length > 0) {
1788
+ this.agColumnDefs = this._columns.map(col => this.mapColumnConfigToColDef(col));
1789
+ }
1790
+ else if (this._entityInfo) {
1791
+ this.agColumnDefs = this.generateAgColumnDefs(this._entityInfo);
1792
+ }
1793
+ else {
1794
+ this.agColumnDefs = [];
1795
+ }
1796
+ // Add row number column if enabled
1797
+ if (this._showRowNumbers && this.agColumnDefs.length > 0) {
1798
+ this.agColumnDefs.unshift({
1799
+ headerName: '#',
1800
+ field: '__rowNumber',
1801
+ width: 60,
1802
+ minWidth: 50,
1803
+ maxWidth: 80,
1804
+ sortable: false,
1805
+ resizable: false,
1806
+ valueGetter: (params) => params.node ? params.node.rowIndex + 1 : ''
1807
+ });
1808
+ }
1809
+ }
1810
+ buildAgColumnDefsFromGridState(columnSettings) {
1811
+ if (!this._entityInfo)
1812
+ return [];
1813
+ const sortedColumns = [...columnSettings].sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0));
1814
+ const cols = [];
1815
+ for (const colConfig of sortedColumns) {
1816
+ if (colConfig.hidden)
1817
+ continue;
1818
+ const field = this._entityInfo.Fields.find(f => f.Name.toLowerCase() === colConfig.Name.toLowerCase());
1819
+ if (!field)
1820
+ continue;
1821
+ const colDef = {
1822
+ field: field.Name,
1823
+ // Use userDisplayName if set, otherwise fall back to DisplayName or field name
1824
+ headerName: colConfig.userDisplayName || colConfig.DisplayName || field.DisplayNameOrName,
1825
+ width: colConfig.width || this.estimateColumnWidth(field),
1826
+ sortable: this._allowSorting,
1827
+ resizable: this._allowColumnResize
1828
+ };
1829
+ // Add type-specific formatters with optional custom format
1830
+ this.applyFieldFormatter(colDef, field, colConfig.format);
1831
+ cols.push(colDef);
1832
+ }
1833
+ return cols;
1834
+ }
1835
+ mapColumnConfigToColDef(col) {
1836
+ const colDef = {
1837
+ field: col.field,
1838
+ headerName: col.title || col.field,
1839
+ width: typeof col.width === 'number' ? col.width : undefined,
1840
+ minWidth: col.minWidth,
1841
+ maxWidth: col.maxWidth,
1842
+ sortable: col.sortable !== false && this._allowSorting,
1843
+ filter: false,
1844
+ resizable: col.resizable !== false && this._allowColumnResize,
1845
+ hide: col.visible === false
1846
+ };
1847
+ // Apply field formatter for highlighting and type-specific formatting
1848
+ // if we have entity metadata for this field
1849
+ if (this._entityInfo) {
1850
+ const field = this._entityInfo.Fields.find(f => f.Name.toLowerCase() === col.field.toLowerCase());
1851
+ if (field) {
1852
+ this.applyFieldFormatter(colDef, field);
1853
+ }
1854
+ }
1855
+ return colDef;
1856
+ }
1857
+ generateAgColumnDefs(entity) {
1858
+ const cols = [];
1859
+ const visibleFields = entity.Fields.filter(f => this.shouldShowField(f));
1860
+ for (const field of visibleFields) {
1861
+ const colDef = {
1862
+ field: field.Name,
1863
+ headerName: field.DisplayNameOrName,
1864
+ width: this.estimateColumnWidth(field),
1865
+ sortable: this._allowSorting,
1866
+ resizable: this._allowColumnResize
1867
+ };
1868
+ this.applyFieldFormatter(colDef, field);
1869
+ cols.push(colDef);
1870
+ }
1871
+ return cols;
1872
+ }
1873
+ applyFieldFormatter(colDef, field, customFormat) {
1874
+ // Store type info for use in cell renderer
1875
+ const fieldType = field.TSType;
1876
+ const fieldNameLower = field.Name.toLowerCase();
1877
+ const extendedType = field.ExtendedType?.toLowerCase() || '';
1878
+ const vc = this.effectiveVisualConfig;
1879
+ // Determine special field types using ExtendedType metadata first, then field name patterns as fallback
1880
+ const isCurrency = customFormat?.type === 'currency' ||
1881
+ fieldNameLower.includes('amount') ||
1882
+ fieldNameLower.includes('price') ||
1883
+ fieldNameLower.includes('cost') ||
1884
+ fieldNameLower.includes('total');
1885
+ // Use ExtendedType='Email' from metadata, fallback to field name pattern
1886
+ const isEmail = extendedType === 'email' ||
1887
+ (!extendedType && fieldNameLower.includes('email'));
1888
+ // Use ExtendedType='URL' from metadata, fallback to field name pattern
1889
+ const isUrl = extendedType === 'url' ||
1890
+ (!extendedType && (fieldNameLower.includes('url') ||
1891
+ fieldNameLower.includes('website') ||
1892
+ fieldNameLower.includes('link')));
1893
+ // Use ExtendedType='Tel' from metadata, fallback to field name pattern
1894
+ const isPhone = extendedType === 'tel' ||
1895
+ (!extendedType && (fieldNameLower.includes('phone') ||
1896
+ fieldNameLower.includes('mobile') ||
1897
+ fieldNameLower.includes('fax')));
1898
+ // Apply alignment - use custom format alignment if provided, otherwise default to right for numbers
1899
+ const customAlign = customFormat?.align;
1900
+ if (customAlign) {
1901
+ colDef.cellClass = `cell-align-${customAlign}`;
1902
+ colDef.headerClass = `header-align-${customAlign}`;
1903
+ }
1904
+ else if (vc.rightAlignNumbers && (fieldType === 'number' || isCurrency)) {
1905
+ colDef.cellClass = 'cell-align-right';
1906
+ colDef.headerClass = 'header-align-right';
1907
+ }
1908
+ // Apply custom header style if provided
1909
+ if (customFormat?.headerStyle) {
1910
+ const headerStyle = this.buildCssStyle(customFormat.headerStyle);
1911
+ if (headerStyle) {
1912
+ colDef.headerClass = (colDef.headerClass || '') + ' custom-header-style';
1913
+ // AG Grid uses headerStyle for inline styles
1914
+ colDef.headerStyle = this.buildStyleObject(customFormat.headerStyle);
1915
+ }
1916
+ }
1917
+ // Use cellRenderer for highlighting support and enhanced formatting
1918
+ colDef.cellRenderer = (params) => {
1919
+ if (params.value === null || params.value === undefined) {
1920
+ return '<span class="cell-empty">—</span>';
1921
+ }
1922
+ let displayValue = '';
1923
+ let extraClass = '';
1924
+ let inlineStyle = '';
1925
+ let isHtmlContent = false; // Track if displayValue contains HTML (icons, links)
1926
+ // Build inline style from custom cell style
1927
+ if (customFormat?.cellStyle) {
1928
+ inlineStyle = this.buildCssStyle(customFormat.cellStyle);
1929
+ }
1930
+ // Check if custom format is provided and has a non-auto type
1931
+ const useCustomFormat = customFormat && customFormat.type && customFormat.type !== 'auto';
1932
+ if (useCustomFormat) {
1933
+ // Use custom formatting
1934
+ displayValue = this.formatValueWithCustomFormat(params.value, customFormat);
1935
+ // Check if formatCustomBoolean returned HTML (icon or checkbox)
1936
+ if (customFormat.type === 'boolean' &&
1937
+ (customFormat.booleanDisplay === 'icon' || customFormat.booleanDisplay === 'checkbox')) {
1938
+ isHtmlContent = true;
1939
+ }
1940
+ }
1941
+ else {
1942
+ // Use default formatting based on field type
1943
+ // Boolean formatting
1944
+ if (fieldType === 'boolean') {
1945
+ if (vc.booleanIcons) {
1946
+ const iconClass = params.value
1947
+ ? 'fa-solid fa-check cell-boolean-true'
1948
+ : 'fa-solid fa-xmark cell-boolean-false';
1949
+ displayValue = `<i class="${iconClass}"></i>`;
1950
+ isHtmlContent = true;
1951
+ }
1952
+ else {
1953
+ displayValue = params.value ? 'Yes' : 'No';
1954
+ }
1955
+ }
1956
+ // Date formatting
1957
+ else if (fieldType === 'Date') {
1958
+ const date = params.value instanceof Date ? params.value : new Date(params.value);
1959
+ if (isNaN(date.getTime())) {
1960
+ displayValue = String(params.value);
1961
+ }
1962
+ else if (vc.friendlyDates) {
1963
+ displayValue = date.toLocaleDateString(undefined, {
1964
+ month: 'short',
1965
+ day: 'numeric',
1966
+ year: 'numeric'
1967
+ });
1968
+ }
1969
+ else {
1970
+ displayValue = date.toISOString().split('T')[0];
1971
+ }
1972
+ }
1973
+ // Currency formatting
1974
+ else if (fieldType === 'number' && isCurrency) {
1975
+ const num = Number(params.value);
1976
+ displayValue = isNaN(num) ? String(params.value) : `$${num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
1977
+ }
1978
+ // Regular number formatting
1979
+ else if (fieldType === 'number') {
1980
+ const num = Number(params.value);
1981
+ displayValue = isNaN(num) ? String(params.value) : num.toLocaleString();
1982
+ }
1983
+ // Email formatting
1984
+ else if (isEmail && vc.clickableEmails) {
1985
+ const email = String(params.value);
1986
+ const escapedEmail = HighlightUtil.escapeHtml(email);
1987
+ if (this._filterText) {
1988
+ const highlighted = HighlightUtil.highlight(email, this._filterText, true);
1989
+ return this.wrapWithStyle(`<a href="mailto:${escapedEmail}" class="cell-link cell-email" onclick="event.stopPropagation()">${highlighted}</a>`, inlineStyle);
1990
+ }
1991
+ return this.wrapWithStyle(`<a href="mailto:${escapedEmail}" class="cell-link cell-email" onclick="event.stopPropagation()">${escapedEmail}</a>`, inlineStyle);
1992
+ }
1993
+ // URL formatting
1994
+ else if (isUrl && vc.clickableUrls) {
1995
+ let url = String(params.value);
1996
+ if (url && !url.startsWith('http')) {
1997
+ url = 'https://' + url;
1998
+ }
1999
+ const displayUrl = url.replace(/^https?:\/\//, '').replace(/\/$/, '');
2000
+ const escapedDisplay = HighlightUtil.escapeHtml(displayUrl);
2001
+ if (this._filterText) {
2002
+ const highlighted = HighlightUtil.highlight(displayUrl, this._filterText, true);
2003
+ return this.wrapWithStyle(`<a href="${HighlightUtil.escapeHtml(url)}" target="_blank" class="cell-link cell-url" onclick="event.stopPropagation()">${highlighted}</a>`, inlineStyle);
2004
+ }
2005
+ return this.wrapWithStyle(`<a href="${HighlightUtil.escapeHtml(url)}" target="_blank" class="cell-link cell-url" onclick="event.stopPropagation()">${escapedDisplay}</a>`, inlineStyle);
2006
+ }
2007
+ // Phone formatting
2008
+ else if (isPhone) {
2009
+ displayValue = String(params.value);
2010
+ extraClass = 'cell-phone';
2011
+ }
2012
+ // Default string formatting
2013
+ else {
2014
+ displayValue = String(params.value);
2015
+ }
2016
+ }
2017
+ // Build the span with optional style and class
2018
+ const styleAttr = inlineStyle ? ` style="${inlineStyle}"` : '';
2019
+ const classAttr = extraClass ? ` class="${extraClass}"` : '';
2020
+ // Handle content that's already HTML (icons, etc.)
2021
+ if (isHtmlContent) {
2022
+ if (inlineStyle) {
2023
+ return `<span${styleAttr}>${displayValue}</span>`;
2024
+ }
2025
+ return displayValue;
2026
+ }
2027
+ // Apply highlighting if filterText is set
2028
+ if (this._filterText && displayValue) {
2029
+ return `<span${classAttr}${styleAttr}>${HighlightUtil.highlight(displayValue, this._filterText, true)}</span>`;
2030
+ }
2031
+ return `<span${classAttr}${styleAttr}>${HighlightUtil.escapeHtml(displayValue)}</span>`;
2032
+ };
2033
+ }
2034
+ /**
2035
+ * Helper to wrap content with an inline style span
2036
+ */
2037
+ wrapWithStyle(content, style) {
2038
+ if (!style)
2039
+ return content;
2040
+ return `<span style="${style}">${content}</span>`;
2041
+ }
2042
+ /**
2043
+ * Build a CSS style string from a ColumnTextStyle object
2044
+ */
2045
+ buildCssStyle(style) {
2046
+ const parts = [];
2047
+ if (style.bold)
2048
+ parts.push('font-weight: bold');
2049
+ if (style.italic)
2050
+ parts.push('font-style: italic');
2051
+ if (style.underline)
2052
+ parts.push('text-decoration: underline');
2053
+ if (style.color)
2054
+ parts.push(`color: ${style.color}`);
2055
+ if (style.backgroundColor)
2056
+ parts.push(`background-color: ${style.backgroundColor}`);
2057
+ return parts.join('; ');
2058
+ }
2059
+ /**
2060
+ * Build a style object for AG Grid from a ColumnTextStyle object
2061
+ */
2062
+ buildStyleObject(style) {
2063
+ const obj = {};
2064
+ if (style.bold)
2065
+ obj['fontWeight'] = 'bold';
2066
+ if (style.italic)
2067
+ obj['fontStyle'] = 'italic';
2068
+ if (style.underline)
2069
+ obj['textDecoration'] = 'underline';
2070
+ if (style.color)
2071
+ obj['color'] = style.color;
2072
+ if (style.backgroundColor)
2073
+ obj['backgroundColor'] = style.backgroundColor;
2074
+ return obj;
2075
+ }
2076
+ /**
2077
+ * Format a value using custom ColumnFormat settings
2078
+ */
2079
+ formatValueWithCustomFormat(value, format) {
2080
+ if (value == null)
2081
+ return '—';
2082
+ switch (format.type) {
2083
+ case 'number':
2084
+ return this.formatCustomNumber(value, format);
2085
+ case 'currency':
2086
+ return this.formatCustomCurrency(value, format);
2087
+ case 'percent':
2088
+ return this.formatCustomPercent(value, format);
2089
+ case 'date':
2090
+ case 'datetime':
2091
+ return this.formatCustomDate(value, format);
2092
+ case 'boolean':
2093
+ return this.formatCustomBoolean(value, format);
2094
+ default:
2095
+ return String(value);
2096
+ }
2097
+ }
2098
+ formatCustomNumber(value, format) {
2099
+ const num = Number(value);
2100
+ if (isNaN(num))
2101
+ return String(value);
2102
+ const options = {
2103
+ minimumFractionDigits: format.decimals ?? 0,
2104
+ maximumFractionDigits: format.decimals ?? 0,
2105
+ useGrouping: format.thousandsSeparator ?? true
2106
+ };
2107
+ return new Intl.NumberFormat('en-US', options).format(num);
2108
+ }
2109
+ formatCustomCurrency(value, format) {
2110
+ const num = Number(value);
2111
+ if (isNaN(num))
2112
+ return String(value);
2113
+ const options = {
2114
+ style: 'currency',
2115
+ currency: format.currencyCode || 'USD',
2116
+ minimumFractionDigits: format.decimals ?? 2,
2117
+ maximumFractionDigits: format.decimals ?? 2
2118
+ };
2119
+ return new Intl.NumberFormat('en-US', options).format(num);
2120
+ }
2121
+ formatCustomPercent(value, format) {
2122
+ const num = Number(value);
2123
+ if (isNaN(num))
2124
+ return String(value);
2125
+ const options = {
2126
+ style: 'percent',
2127
+ minimumFractionDigits: format.decimals ?? 0,
2128
+ maximumFractionDigits: format.decimals ?? 0
2129
+ };
2130
+ // Assume value is already a percentage (e.g., 50 = 50%), divide by 100
2131
+ return new Intl.NumberFormat('en-US', options).format(num / 100);
2132
+ }
2133
+ formatCustomDate(value, format) {
2134
+ const date = value instanceof Date ? value : new Date(value);
2135
+ if (isNaN(date.getTime()))
2136
+ return String(value);
2137
+ // Parse format string - check for weekday variants
2138
+ const formatStr = format.dateFormat || 'medium';
2139
+ const includeWeekday = formatStr.includes('-weekday');
2140
+ const baseFormat = formatStr.replace('-weekday', '');
2141
+ let options;
2142
+ // Intl.DateTimeFormat doesn't allow combining dateStyle with weekday
2143
+ // So we must use individual components when weekday is requested
2144
+ if (includeWeekday) {
2145
+ if (baseFormat === 'short') {
2146
+ options = { weekday: 'short', month: 'numeric', day: 'numeric', year: '2-digit' };
2147
+ }
2148
+ else if (baseFormat === 'long') {
2149
+ options = { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' };
2150
+ }
2151
+ else {
2152
+ // medium
2153
+ options = { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' };
2154
+ }
2155
+ if (format.type === 'datetime') {
2156
+ options.hour = 'numeric';
2157
+ options.minute = '2-digit';
2158
+ }
2159
+ }
2160
+ else {
2161
+ // No weekday - can use dateStyle shorthand
2162
+ options = {
2163
+ dateStyle: baseFormat === 'short' ? 'short' : baseFormat === 'long' ? 'long' : 'medium'
2164
+ };
2165
+ if (format.type === 'datetime') {
2166
+ options.timeStyle = 'short';
2167
+ }
2168
+ }
2169
+ return new Intl.DateTimeFormat('en-US', options).format(date);
2170
+ }
2171
+ formatCustomBoolean(value, format) {
2172
+ if (format.booleanDisplay === 'icon') {
2173
+ const iconClass = value
2174
+ ? 'fa-solid fa-check cell-boolean-true'
2175
+ : 'fa-solid fa-xmark cell-boolean-false';
2176
+ return `<i class="${iconClass}"></i>`;
2177
+ }
2178
+ if (format.booleanDisplay === 'checkbox') {
2179
+ return value
2180
+ ? '<i class="fa-regular fa-square-check cell-boolean-true"></i>'
2181
+ : '<i class="fa-regular fa-square cell-boolean-false"></i>';
2182
+ }
2183
+ return value ? (format.trueLabel || 'Yes') : (format.falseLabel || 'No');
2184
+ }
2185
+ // ========================================
2186
+ // AG Grid Row Selection Configuration
2187
+ // ========================================
2188
+ updateAgRowSelection() {
2189
+ switch (this._selectionMode) {
2190
+ case 'none':
2191
+ this.agRowSelection = { mode: 'singleRow', enableClickSelection: false, checkboxes: false };
2192
+ break;
2193
+ case 'single':
2194
+ this.agRowSelection = { mode: 'singleRow', enableClickSelection: true, checkboxes: false };
2195
+ break;
2196
+ case 'multiple':
2197
+ this.agRowSelection = { mode: 'multiRow', enableClickSelection: true, checkboxes: false };
2198
+ break;
2199
+ case 'checkbox':
2200
+ this.agRowSelection = {
2201
+ mode: 'multiRow',
2202
+ enableClickSelection: true,
2203
+ checkboxes: true,
2204
+ headerCheckbox: true
2205
+ };
2206
+ break;
2207
+ }
2208
+ // Update the grid if it's already initialized
2209
+ if (this.gridApi) {
2210
+ this.gridApi.setGridOption('rowSelection', this.agRowSelection);
2211
+ }
2212
+ }
2213
+ // ========================================
2214
+ // Data Loading
2215
+ // ========================================
2216
+ async loadData(isAutoRefresh = false) {
2217
+ if (this._useExternalData) {
2218
+ this.processData();
2219
+ return;
2220
+ }
2221
+ // Check if we have a valid data source via Params
2222
+ const hasDataSource = this._params && (this._params.ViewID ||
2223
+ this._params.ViewName ||
2224
+ this._params.ViewEntity ||
2225
+ this._params.EntityName);
2226
+ if (!hasDataSource) {
2227
+ return;
2228
+ }
2229
+ // Check AllowLoad for deferred loading
2230
+ if (!this._allowLoad) {
2231
+ return;
2232
+ }
2233
+ // For infinite scroll mode, setup or refresh the datasource
2234
+ if (this._paginationMode === 'infinite') {
2235
+ if (this.gridApi) {
2236
+ if (!this.infiniteDatasource) {
2237
+ this.setupInfiniteScroll();
2238
+ }
2239
+ else {
2240
+ this.refreshInfiniteCache();
2241
+ }
2242
+ }
2243
+ // In infinite mode, the datasource handles loading, so we're done
2244
+ return;
2245
+ }
2246
+ // If a load is already in progress, return the existing promise
2247
+ // This prevents redundant API calls when loadData is called multiple times
2248
+ if (this._loadDataPromise) {
2249
+ return this._loadDataPromise;
2250
+ }
2251
+ // Wrap the actual loading logic in a promise we can track
2252
+ this._loadDataPromise = this.executeLoadData(isAutoRefresh);
2253
+ try {
2254
+ await this._loadDataPromise;
2255
+ }
2256
+ finally {
2257
+ this._loadDataPromise = null;
2258
+ }
2259
+ }
2260
+ /**
2261
+ * Internal method that performs the actual data loading.
2262
+ * Called by loadData() which handles promise deduplication.
2263
+ */
2264
+ async executeLoadData(isAutoRefresh) {
2265
+ // Client-side mode - load all data upfront
2266
+ const beforeRefreshEvent = new BeforeDataRefreshEventArgs(this, isAutoRefresh);
2267
+ this.BeforeDataRefresh.emit(beforeRefreshEvent);
2268
+ if (beforeRefreshEvent.cancel) {
2269
+ return;
2270
+ }
2271
+ // Build the RunViewParams - prefer new Params input over legacy inputs
2272
+ const runViewParams = this.buildRunViewParams();
2273
+ // Create GridRunViewParams for events (backward compatibility)
2274
+ const gridParams = {
2275
+ entityName: runViewParams.EntityName || this._entityInfo?.Name || '',
2276
+ extraFilter: runViewParams.ExtraFilter || '',
2277
+ orderBy: runViewParams.OrderBy || '',
2278
+ maxRows: runViewParams.MaxRows || 0,
2279
+ fields: runViewParams.Fields,
2280
+ searchString: runViewParams.UserSearchString || ''
2281
+ };
2282
+ const beforeLoadEvent = new BeforeDataLoadEventArgs(this, gridParams);
2283
+ this.BeforeDataLoad.emit(beforeLoadEvent);
2284
+ if (beforeLoadEvent.cancel) {
2285
+ return;
2286
+ }
2287
+ // Apply any modifications from beforeLoadEvent
2288
+ if (beforeLoadEvent.modifiedParams) {
2289
+ if (beforeLoadEvent.modifiedParams.extraFilter) {
2290
+ runViewParams.ExtraFilter = beforeLoadEvent.modifiedParams.extraFilter;
2291
+ }
2292
+ if (beforeLoadEvent.modifiedParams.orderBy) {
2293
+ runViewParams.OrderBy = beforeLoadEvent.modifiedParams.orderBy;
2294
+ }
2295
+ if (beforeLoadEvent.modifiedParams.maxRows) {
2296
+ runViewParams.MaxRows = beforeLoadEvent.modifiedParams.maxRows;
2297
+ }
2298
+ if (beforeLoadEvent.modifiedParams.fields) {
2299
+ runViewParams.Fields = beforeLoadEvent.modifiedParams.fields;
2300
+ }
2301
+ if (beforeLoadEvent.modifiedParams.searchString) {
2302
+ runViewParams.UserSearchString = beforeLoadEvent.modifiedParams.searchString;
2303
+ }
2304
+ }
2305
+ this.loading = true;
2306
+ this.errorMessage = '';
2307
+ this.cdr.detectChanges();
2308
+ const startTime = performance.now();
2309
+ try {
2310
+ const rv = new RunView();
2311
+ const result = await rv.RunView({
2312
+ ...runViewParams,
2313
+ ResultType: 'entity_object'
2314
+ });
2315
+ const loadTimeMs = performance.now() - startTime;
2316
+ if (result.Success) {
2317
+ this._allData = result.Results || [];
2318
+ this.totalRowCount = result.TotalRowCount || this._allData.length;
2319
+ this.processData();
2320
+ // Reapply sort state to grid after data load to maintain visual indicators
2321
+ // Use Promise.resolve() to defer until after Angular's change detection cycle
2322
+ // has completed and AG Grid has processed the new row data
2323
+ if (this._sortState.length > 0) {
2324
+ Promise.resolve().then(() => {
2325
+ this.applySortStateToGrid();
2326
+ });
2327
+ }
2328
+ const afterLoadEvent = new AfterDataLoadEventArgs(this, gridParams, true, this.totalRowCount, this._allData.length, loadTimeMs);
2329
+ this.AfterDataLoad.emit(afterLoadEvent);
2330
+ const afterRefreshEvent = new AfterDataRefreshEventArgs(this, true, this.totalRowCount, loadTimeMs);
2331
+ this.AfterDataRefresh.emit(afterRefreshEvent);
2332
+ }
2333
+ else {
2334
+ this.errorMessage = result.ErrorMessage || 'Failed to load data';
2335
+ const afterLoadEvent = new AfterDataLoadEventArgs(this, gridParams, false, 0, 0, loadTimeMs, this.errorMessage);
2336
+ this.AfterDataLoad.emit(afterLoadEvent);
2337
+ }
2338
+ }
2339
+ catch (error) {
2340
+ const loadTimeMs = performance.now() - startTime;
2341
+ this.errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
2342
+ const afterLoadEvent = new AfterDataLoadEventArgs(this, gridParams, false, 0, 0, loadTimeMs, this.errorMessage);
2343
+ this.AfterDataLoad.emit(afterLoadEvent);
2344
+ }
2345
+ finally {
2346
+ this.loading = false;
2347
+ this.cdr.detectChanges();
2348
+ }
2349
+ }
2350
+ /**
2351
+ * Builds RunViewParams from the Params input.
2352
+ * Requires Params to be set - the grid needs a data source configured.
2353
+ */
2354
+ buildRunViewParams() {
2355
+ if (!this._params) {
2356
+ throw new Error('Params must be set before loading data');
2357
+ }
2358
+ const params = { ...this._params };
2359
+ // Apply sort state (may be modified by user interactions)
2360
+ const orderBy = this.buildOrderByClause();
2361
+ if (orderBy) {
2362
+ params.OrderBy = orderBy;
2363
+ }
2364
+ return params;
2365
+ }
2366
+ buildOrderByClause() {
2367
+ if (this._sortState.length > 0) {
2368
+ return this._sortState
2369
+ .sort((a, b) => a.index - b.index)
2370
+ .map(s => `${s.field} ${s.direction.toUpperCase()}`)
2371
+ .join(', ');
2372
+ }
2373
+ // Fall back to OrderBy from Params if set
2374
+ return this._params?.OrderBy || '';
2375
+ }
2376
+ // ========================================
2377
+ // Infinite Scroll Data Source
2378
+ // ========================================
2379
+ /**
2380
+ * Creates and returns an AG Grid IDatasource for infinite scroll mode.
2381
+ * This datasource fetches data in pages from the server as the user scrolls.
2382
+ */
2383
+ createInfiniteDatasource() {
2384
+ return {
2385
+ getRows: async (params) => {
2386
+ const startRow = params.startRow;
2387
+ const endRow = params.endRow;
2388
+ const blockSize = endRow - startRow;
2389
+ try {
2390
+ // Build params with pagination
2391
+ const runViewParams = this.buildRunViewParams();
2392
+ runViewParams.StartRow = startRow;
2393
+ runViewParams.MaxRows = blockSize;
2394
+ const rv = new RunView();
2395
+ const result = await rv.RunView({
2396
+ ...runViewParams,
2397
+ ResultType: 'entity_object'
2398
+ });
2399
+ if (result.Success) {
2400
+ const entities = result.Results || [];
2401
+ // Process entities into row data
2402
+ const rowData = entities.map((entity, index) => {
2403
+ return this.entityToRowData(entity, startRow + index);
2404
+ });
2405
+ // Update total row count
2406
+ this.totalRowCount = result.TotalRowCount || 0;
2407
+ this.cdr.detectChanges();
2408
+ // Determine if we've reached the last row
2409
+ const lastRow = result.TotalRowCount != null && startRow + entities.length >= result.TotalRowCount
2410
+ ? result.TotalRowCount
2411
+ : undefined;
2412
+ params.successCallback(rowData, lastRow);
2413
+ }
2414
+ else {
2415
+ this.errorMessage = result.ErrorMessage || 'Failed to load data';
2416
+ this.cdr.detectChanges();
2417
+ params.failCallback();
2418
+ }
2419
+ }
2420
+ catch (error) {
2421
+ this.errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
2422
+ this.cdr.detectChanges();
2423
+ params.failCallback();
2424
+ }
2425
+ }
2426
+ };
2427
+ }
2428
+ /**
2429
+ * Converts a BaseEntity to AG Grid row data format.
2430
+ * This is used by both client and infinite scroll modes for consistent data formatting.
2431
+ */
2432
+ entityToRowData(entity, index) {
2433
+ const key = this.getRowKey(entity);
2434
+ // Store in row data map for later retrieval
2435
+ const rowData = {
2436
+ key,
2437
+ index,
2438
+ entity,
2439
+ selected: this._selectedKeys.includes(key),
2440
+ editing: this._editingRowKey === key,
2441
+ dirty: this._pendingChanges.some(c => c.rowKey === key),
2442
+ cssClasses: this.computeRowClasses(index, entity)
2443
+ };
2444
+ this._rowDataMap.set(key, rowData);
2445
+ // Build AG Grid row data
2446
+ const row = {
2447
+ __pk: key
2448
+ };
2449
+ if (this._entityInfo) {
2450
+ for (const field of this._entityInfo.Fields) {
2451
+ row[field.Name] = entity.Get(field.Name);
2452
+ }
2453
+ }
2454
+ return row;
2455
+ }
2456
+ /**
2457
+ * Sets up the grid for infinite scroll mode.
2458
+ * Called when PaginationMode is 'infinite'.
2459
+ */
2460
+ setupInfiniteScroll() {
2461
+ if (!this.gridApi)
2462
+ return;
2463
+ // Create datasource
2464
+ this.infiniteDatasource = this.createInfiniteDatasource();
2465
+ // Set datasource on grid
2466
+ this.gridApi.setGridOption('datasource', this.infiniteDatasource);
2467
+ }
2468
+ /**
2469
+ * Refreshes the infinite scroll cache.
2470
+ * Call this when data source parameters change.
2471
+ */
2472
+ refreshInfiniteCache() {
2473
+ if (!this.gridApi || this._paginationMode !== 'infinite')
2474
+ return;
2475
+ // Clear the row data map since we're refreshing
2476
+ this._rowDataMap.clear();
2477
+ // Purge cache and refresh
2478
+ this.gridApi.refreshInfiniteCache();
2479
+ // Reapply sort state to grid after refresh to maintain visual indicators
2480
+ // Use Promise.resolve() to defer until after AG Grid has processed the cache refresh
2481
+ if (this._sortState.length > 0) {
2482
+ Promise.resolve().then(() => {
2483
+ this.applySortStateToGrid();
2484
+ });
2485
+ }
2486
+ }
2487
+ processData() {
2488
+ this._rowDataMap.clear();
2489
+ const dataSource = this._useExternalData ? this._data : this._allData;
2490
+ this.rowData = dataSource.map((entity, index) => {
2491
+ const key = this.getRowKey(entity);
2492
+ const rowData = {
2493
+ key,
2494
+ index,
2495
+ entity,
2496
+ selected: this._selectedKeys.includes(key),
2497
+ editing: this._editingRowKey === key,
2498
+ dirty: this._pendingChanges.some(c => c.rowKey === key),
2499
+ cssClasses: this.computeRowClasses(index, entity)
2500
+ };
2501
+ this._rowDataMap.set(key, rowData);
2502
+ // Build AG Grid row data
2503
+ const row = {
2504
+ __pk: key
2505
+ };
2506
+ if (this._entityInfo) {
2507
+ for (const field of this._entityInfo.Fields) {
2508
+ row[field.Name] = entity.Get(field.Name);
2509
+ }
2510
+ }
2511
+ return row;
2512
+ });
2513
+ this.cdr.detectChanges();
2514
+ }
2515
+ getRowKey(entity) {
2516
+ // Use composite key if available
2517
+ if (entity.PrimaryKey) {
2518
+ return entity.PrimaryKey.ToConcatenatedString();
2519
+ }
2520
+ const keyValue = entity.Get(this._keyField);
2521
+ return keyValue != null ? String(keyValue) : '';
2522
+ }
2523
+ computeRowClasses(index, _entity) {
2524
+ const classes = [];
2525
+ if (this._striped && index % 2 === 1) {
2526
+ classes.push('grid-row-alt');
2527
+ }
2528
+ return classes;
2529
+ }
2530
+ // ========================================
2531
+ // AG Grid Event Handlers
2532
+ // ========================================
2533
+ onGridReady(event) {
2534
+ this.gridApi = event.api;
2535
+ this.updateSelection();
2536
+ if (this._sortState.length > 0) {
2537
+ this.applySortStateToGrid();
2538
+ }
2539
+ // Smart column sizing: use our estimated widths but ensure grid fills available space
2540
+ // without making columns excessively wide
2541
+ this.autoSizeColumnsSmartly(event.api);
2542
+ // Setup infinite scroll if in that mode and we have params
2543
+ if (this._paginationMode === 'infinite' && this._allowLoad && this._params) {
2544
+ this.setupInfiniteScroll();
2545
+ }
2546
+ }
2547
+ /**
2548
+ * Smart column auto-sizing that respects our width estimates
2549
+ * while ensuring the grid fills available space appropriately
2550
+ */
2551
+ autoSizeColumnsSmartly(api) {
2552
+ // Get total estimated width from our column definitions
2553
+ const totalEstimatedWidth = this.agColumnDefs.reduce((sum, col) => sum + (col.width || 150), 0);
2554
+ // Get available width from the grid container
2555
+ const gridElement = this.elementRef.nativeElement.querySelector('.mj-ag-grid');
2556
+ const availableWidth = gridElement?.clientWidth || 0;
2557
+ if (availableWidth > 0 && totalEstimatedWidth < availableWidth) {
2558
+ // If our columns are narrower than available space, stretch proportionally
2559
+ // but cap individual column growth to prevent excessive widths
2560
+ const ratio = availableWidth / totalEstimatedWidth;
2561
+ const maxRatio = 1.5; // Don't let columns grow more than 50%
2562
+ if (ratio <= maxRatio) {
2563
+ // Moderate stretch - use sizeColumnsToFit
2564
+ api.sizeColumnsToFit();
2565
+ }
2566
+ else {
2567
+ // Too much stretch - just use our estimated widths
2568
+ // The grid will have some empty space on the right, which is fine
2569
+ }
2570
+ }
2571
+ // If our columns are wider than available space, let the user scroll horizontally
2572
+ // (AG Grid handles this automatically)
2573
+ }
2574
+ onAgRowClicked(event) {
2575
+ if (!this._entityInfo || !event.data)
2576
+ return;
2577
+ const pkString = event.data['__pk'];
2578
+ const rowData = this._rowDataMap.get(pkString);
2579
+ if (!rowData)
2580
+ return;
2581
+ // Fire before click event
2582
+ const mouseEvent = event.event;
2583
+ const beforeEvent = new BeforeRowClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
2584
+ this.BeforeRowClick.emit(beforeEvent);
2585
+ if (beforeEvent.cancel)
2586
+ return;
2587
+ // Fire after click event
2588
+ const afterEvent = new AfterRowClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
2589
+ this.AfterRowClick.emit(afterEvent);
2590
+ // Auto-navigate on single click (if enabled and not navigating on double-click only)
2591
+ if (this._autoNavigate && !this._navigateOnDoubleClick && this._editMode === 'none') {
2592
+ this.emitNavigationRequest(rowData.entity, pkString);
2593
+ }
2594
+ }
2595
+ onAgRowDoubleClicked(event) {
2596
+ if (!this._entityInfo || !event.data)
2597
+ return;
2598
+ const pkString = event.data['__pk'];
2599
+ const rowData = this._rowDataMap.get(pkString);
2600
+ if (!rowData)
2601
+ return;
2602
+ // Fire before double-click event
2603
+ const mouseEvent = event.event;
2604
+ const beforeEvent = new BeforeRowDoubleClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
2605
+ this.BeforeRowDoubleClick.emit(beforeEvent);
2606
+ if (beforeEvent.cancel)
2607
+ return;
2608
+ // Fire after double-click event
2609
+ const afterEvent = new AfterRowDoubleClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
2610
+ this.AfterRowDoubleClick.emit(afterEvent);
2611
+ // Auto-navigate on double-click (if enabled)
2612
+ if (this._autoNavigate && this._navigateOnDoubleClick && this._editMode === 'none') {
2613
+ this.emitNavigationRequest(rowData.entity, pkString);
2614
+ }
2615
+ }
2616
+ /**
2617
+ * Emits a navigation request for the given entity record.
2618
+ */
2619
+ emitNavigationRequest(entity, compositeKey) {
2620
+ if (!this._entityInfo)
2621
+ return;
2622
+ this.NavigationRequested.emit({
2623
+ entityInfo: this._entityInfo,
2624
+ record: entity,
2625
+ compositeKey
2626
+ });
2627
+ }
2628
+ onAgSortChanged(event) {
2629
+ if (this.suppressSortEvents)
2630
+ return;
2631
+ const sortModel = event.api.getColumnState()
2632
+ .filter(col => col.sort)
2633
+ .map(col => ({
2634
+ field: col.colId,
2635
+ direction: col.sort,
2636
+ index: col.sortIndex ?? 0
2637
+ }));
2638
+ if (sortModel.length > 0) {
2639
+ // Find the column config
2640
+ const column = this._columnStates.find(c => c.config.field === sortModel[0].field);
2641
+ if (column) {
2642
+ // Fire before sort event
2643
+ const beforeEvent = new BeforeSortEventArgs(this, column.config, sortModel[0].direction, sortModel.length > 1, this._sortState);
2644
+ this.BeforeSort.emit(beforeEvent);
2645
+ if (beforeEvent.cancel) {
2646
+ // Revert to previous sort state
2647
+ this.applySortStateToGrid();
2648
+ return;
2649
+ }
2650
+ }
2651
+ this._sortState = sortModel;
2652
+ if (column) {
2653
+ // Fire after sort event
2654
+ const afterEvent = new AfterSortEventArgs(this, column.config, sortModel[0].direction, this._sortState);
2655
+ this.AfterSort.emit(afterEvent);
2656
+ }
2657
+ // User changed sort - mark as dirty and emit grid state changed
2658
+ this._isGridStateDirty = true;
2659
+ this.emitGridStateChanged('sort');
2660
+ // Determine if we need to reload data from server or can sort client-side
2661
+ // Client-side sorting is only possible if we have ALL the data loaded
2662
+ if (this._serverSideSorting && !this._useExternalData) {
2663
+ this.loadData(true);
2664
+ }
2665
+ else if (this._useExternalData) {
2666
+ // Using external data - check if we have all data or just a page
2667
+ // If totalRowCount > current data length, we only have partial data and parent must handle sorting
2668
+ // Parent receives afterSort event and can reload with new sort order
2669
+ // If we have all data, AG Grid handles client-side sorting automatically
2670
+ }
2671
+ }
2672
+ else {
2673
+ this._sortState = [];
2674
+ // User cleared sort - mark as dirty and emit grid state changed
2675
+ this._isGridStateDirty = true;
2676
+ this.emitGridStateChanged('sort');
2677
+ }
2678
+ }
2679
+ onAgSelectionChanged(event) {
2680
+ const selectedNodes = event.api.getSelectedNodes();
2681
+ const previousSelection = [...this._selectedKeys];
2682
+ const newSelection = selectedNodes.map(node => node.data['__pk']);
2683
+ // Find newly selected rows
2684
+ const addedKeys = newSelection.filter(k => !previousSelection.includes(k));
2685
+ const removedKeys = previousSelection.filter(k => !newSelection.includes(k));
2686
+ // Handle deselections
2687
+ for (const key of removedKeys) {
2688
+ const rowData = this._rowDataMap.get(key);
2689
+ if (rowData) {
2690
+ const beforeEvent = new BeforeRowDeselectEventArgs(this, rowData.entity, rowData.index, key, this._selectedKeys);
2691
+ this.BeforeRowDeselect.emit(beforeEvent);
2692
+ // Note: Can't cancel AG Grid's selection, but we emit the event
2693
+ }
2694
+ }
2695
+ // Handle selections
2696
+ for (const key of addedKeys) {
2697
+ const rowData = this._rowDataMap.get(key);
2698
+ if (rowData) {
2699
+ const beforeEvent = new BeforeRowSelectEventArgs(this, rowData.entity, rowData.index, key, addedKeys.length > 1, this._selectedKeys);
2700
+ this.BeforeRowSelect.emit(beforeEvent);
2701
+ // Note: Can't cancel AG Grid's selection, but we emit the event
2702
+ }
2703
+ }
2704
+ this._selectedKeys = newSelection;
2705
+ this.updateRowSelectionState();
2706
+ // Fire after events
2707
+ for (const key of removedKeys) {
2708
+ const rowData = this._rowDataMap.get(key);
2709
+ if (rowData) {
2710
+ const afterEvent = new AfterRowDeselectEventArgs(this, rowData.entity, rowData.index, key, this._selectedKeys, previousSelection);
2711
+ this.AfterRowDeselect.emit(afterEvent);
2712
+ }
2713
+ }
2714
+ for (const key of addedKeys) {
2715
+ const rowData = this._rowDataMap.get(key);
2716
+ if (rowData) {
2717
+ const afterEvent = new AfterRowSelectEventArgs(this, rowData.entity, rowData.index, key, addedKeys.length > 1, this._selectedKeys, previousSelection);
2718
+ this.AfterRowSelect.emit(afterEvent);
2719
+ }
2720
+ }
2721
+ this.SelectionChange.emit(this._selectedKeys);
2722
+ }
2723
+ onAgColumnResized(event) {
2724
+ if (event.finished && event.source !== 'api') {
2725
+ // User manually resized a column - mark as dirty
2726
+ this._isGridStateDirty = true;
2727
+ this.emitGridStateChanged('columns');
2728
+ }
2729
+ }
2730
+ onAgColumnMoved(event) {
2731
+ if (event.finished && event.source !== 'api') {
2732
+ // User manually moved a column - mark as dirty
2733
+ this._isGridStateDirty = true;
2734
+ this.emitGridStateChanged('columns');
2735
+ }
2736
+ }
2737
+ // ========================================
2738
+ // Grid State Management
2739
+ // ========================================
2740
+ emitGridStateChanged(changeType) {
2741
+ if (!this.gridApi || !this._entityInfo)
2742
+ return;
2743
+ // Only emit and persist if we're dirty (user made actual changes)
2744
+ // This prevents emitting stale state during view transitions
2745
+ if (!this._isGridStateDirty) {
2746
+ return;
2747
+ }
2748
+ const currentState = this.buildCurrentGridState();
2749
+ // Emit the event for external consumers
2750
+ this.GridStateChanged.emit({
2751
+ gridState: currentState,
2752
+ changeType
2753
+ });
2754
+ // Schedule auto-persist if enabled, but NOT during view transitions
2755
+ // (suppressPersist prevents old view's state from being saved to new view)
2756
+ if (this._autoPersistState && !this._suppressPersist && this.canEditCurrentView) {
2757
+ if (!this.IsDynamicView && this._viewEntity) {
2758
+ // Stored view - persist to UserView.GridState (debounced)
2759
+ // Track pending state for flush on destroy
2760
+ this._pendingViewStateToPersist = currentState;
2761
+ this.statePersistSubject.next(currentState);
2762
+ }
2763
+ else if (this.IsDynamicView) {
2764
+ // Dynamic view - persist to User Settings as defaults (debounced via same subject)
2765
+ // Track pending state for flush on destroy
2766
+ this._pendingUserDefaultsToPersist = currentState;
2767
+ this.userDefaultsPersistSubject.next(currentState);
2768
+ }
2769
+ }
2770
+ }
2771
+ /**
2772
+ * Persists the grid state to the UserView entity.
2773
+ * Only saves if the user has edit permission on the view.
2774
+ */
2775
+ async persistGridStateToView(state) {
2776
+ if (!this._viewEntity || this.isSavingState) {
2777
+ return;
2778
+ }
2779
+ // Check permission before saving
2780
+ if (!this._viewEntity.UserCanEdit) {
2781
+ this._pendingViewStateToPersist = null; // Clear pending since we can't save anyway
2782
+ return;
2783
+ }
2784
+ try {
2785
+ this.isSavingState = true;
2786
+ this.pendingStateToSave = state;
2787
+ // Build the grid state JSON matching ViewGridState format
2788
+ const gridStateJson = {
2789
+ columnSettings: state.columnSettings,
2790
+ sortSettings: state.sortSettings
2791
+ };
2792
+ // Update the view entity's GridState
2793
+ this._viewEntity.GridState = JSON.stringify(gridStateJson);
2794
+ // Update SortState separately if changed
2795
+ if (state.sortSettings?.length) {
2796
+ this._viewEntity.SortState = JSON.stringify(state.sortSettings.map(s => ({
2797
+ field: s.field,
2798
+ dir: s.dir
2799
+ })));
2800
+ }
2801
+ // Save the view entity
2802
+ const success = await this._viewEntity.Save();
2803
+ if (!success) {
2804
+ console.warn('[entity-data-grid] Failed to save view state:', this._viewEntity.LatestResult?.Message);
2805
+ }
2806
+ else {
2807
+ // Clear pending state and reset dirty flag after successful save
2808
+ this._pendingViewStateToPersist = null;
2809
+ this._isGridStateDirty = false;
2810
+ }
2811
+ }
2812
+ catch (error) {
2813
+ console.error('[entity-data-grid] Error persisting grid state:', error);
2814
+ }
2815
+ finally {
2816
+ this.isSavingState = false;
2817
+ this.pendingStateToSave = null;
2818
+ }
2819
+ }
2820
+ buildCurrentGridState() {
2821
+ if (!this.gridApi || !this._entityInfo || !this._entityInfo.Fields) {
2822
+ return { columnSettings: [], sortSettings: [] };
2823
+ }
2824
+ const columnState = this.gridApi.getColumnState();
2825
+ if (!columnState) {
2826
+ return { columnSettings: [], sortSettings: [] };
2827
+ }
2828
+ // Build lookup maps for existing custom properties to preserve them
2829
+ // AG Grid's column state doesn't track our custom format/userDisplayName properties,
2830
+ // so we need to carry them over from the current gridState
2831
+ const existingFormatsByName = new Map();
2832
+ const existingUserDisplayNames = new Map();
2833
+ if (this._gridState?.columnSettings) {
2834
+ for (const col of this._gridState.columnSettings) {
2835
+ const keyLower = col.Name.toLowerCase();
2836
+ if (col.format) {
2837
+ existingFormatsByName.set(keyLower, col.format);
2838
+ }
2839
+ if (col.userDisplayName) {
2840
+ existingUserDisplayNames.set(keyLower, col.userDisplayName);
2841
+ }
2842
+ }
2843
+ }
2844
+ const columnSettings = [];
2845
+ const sortSettings = [];
2846
+ for (let i = 0; i < columnState.length; i++) {
2847
+ const col = columnState[i];
2848
+ if (col.colId === '__rowNumber')
2849
+ continue; // Skip row number column
2850
+ const field = this._entityInfo.Fields.find(f => f.Name === col.colId);
2851
+ if (field) {
2852
+ const keyLower = field.Name.toLowerCase();
2853
+ const colConfig = {
2854
+ ID: field.ID,
2855
+ Name: field.Name,
2856
+ DisplayName: field.DisplayNameOrName,
2857
+ hidden: col.hide ?? false,
2858
+ width: col.width ?? undefined,
2859
+ orderIndex: i
2860
+ };
2861
+ // Preserve format from existing gridState if present
2862
+ const existingFormat = existingFormatsByName.get(keyLower);
2863
+ if (existingFormat) {
2864
+ colConfig.format = existingFormat;
2865
+ }
2866
+ // Preserve userDisplayName from existing gridState if present
2867
+ const existingUserDisplayName = existingUserDisplayNames.get(keyLower);
2868
+ if (existingUserDisplayName) {
2869
+ colConfig.userDisplayName = existingUserDisplayName;
2870
+ }
2871
+ columnSettings.push(colConfig);
2872
+ }
2873
+ if (col.sort) {
2874
+ sortSettings.push({
2875
+ field: col.colId,
2876
+ dir: col.sort
2877
+ });
2878
+ }
2879
+ }
2880
+ return { columnSettings, sortSettings };
2881
+ }
2882
+ applySortStateToGrid() {
2883
+ if (!this.gridApi || this._sortState.length === 0)
2884
+ return;
2885
+ const currentColumnState = this.gridApi.getColumnState();
2886
+ if (!currentColumnState)
2887
+ return;
2888
+ this.suppressSortEvents = true;
2889
+ try {
2890
+ const columnState = currentColumnState.map(col => {
2891
+ const sort = this._sortState.find(s => s.field === col.colId);
2892
+ return {
2893
+ ...col,
2894
+ sort: sort ? sort.direction : null,
2895
+ sortIndex: sort ? sort.index : null
2896
+ };
2897
+ });
2898
+ this.gridApi.applyColumnState({ state: columnState });
2899
+ }
2900
+ finally {
2901
+ this.suppressSortEvents = false;
2902
+ }
2903
+ }
2904
+ updateSelection() {
2905
+ if (!this.gridApi || this._selectedKeys.length === 0) {
2906
+ this.gridApi?.deselectAll();
2907
+ return;
2908
+ }
2909
+ for (const key of this._selectedKeys) {
2910
+ const node = this.gridApi.getRowNode(key);
2911
+ if (node) {
2912
+ node.setSelected(true);
2913
+ }
2914
+ }
2915
+ }
2916
+ updateRowSelectionState() {
2917
+ for (const rowData of this._rowDataMap.values()) {
2918
+ rowData.selected = this._selectedKeys.includes(rowData.key);
2919
+ }
2920
+ this.cdr.detectChanges();
2921
+ }
2922
+ // ========================================
2923
+ // Selection Public Methods
2924
+ // ========================================
2925
+ SelectRows(keys, additive = false) {
2926
+ if (!this.gridApi)
2927
+ return;
2928
+ if (!additive) {
2929
+ this.gridApi.deselectAll();
2930
+ }
2931
+ for (const key of keys) {
2932
+ const node = this.gridApi.getRowNode(key);
2933
+ if (node) {
2934
+ node.setSelected(true, !additive);
2935
+ }
2936
+ }
2937
+ }
2938
+ DeselectRows(keys) {
2939
+ if (!this.gridApi)
2940
+ return;
2941
+ for (const key of keys) {
2942
+ const node = this.gridApi.getRowNode(key);
2943
+ if (node) {
2944
+ node.setSelected(false);
2945
+ }
2946
+ }
2947
+ }
2948
+ SelectAll() {
2949
+ this.gridApi?.selectAll();
2950
+ }
2951
+ ClearSelection() {
2952
+ this.gridApi?.deselectAll();
2953
+ }
2954
+ GetSelectedRows() {
2955
+ return this._selectedKeys
2956
+ .map(key => this._rowDataMap.get(key)?.entity)
2957
+ .filter((e) => e !== undefined);
2958
+ }
2959
+ IsRowSelected(key) {
2960
+ return this._selectedKeys.includes(key);
2961
+ }
2962
+ /**
2963
+ * Scrolls the grid to make the specified row visible.
2964
+ * @param key The primary key of the row to scroll to
2965
+ * @param position Where to position the row: 'top', 'middle', or 'bottom'
2966
+ */
2967
+ EnsureRowVisible(key, position = 'middle') {
2968
+ if (!this.gridApi)
2969
+ return;
2970
+ const node = this.gridApi.getRowNode(key);
2971
+ if (node) {
2972
+ this.gridApi.ensureNodeVisible(node, position);
2973
+ }
2974
+ }
2975
+ // ========================================
2976
+ // Public Methods
2977
+ // ========================================
2978
+ async Refresh() {
2979
+ await this.loadData(false);
2980
+ }
2981
+ Clear() {
2982
+ this._allData = [];
2983
+ this._data = [];
2984
+ this._rowDataMap.clear();
2985
+ this._selectedKeys = [];
2986
+ this.rowData = [];
2987
+ this.totalRowCount = 0;
2988
+ this.cdr.detectChanges();
2989
+ }
2990
+ GetData() {
2991
+ return this._useExternalData ? [...this._data] : [...this._allData];
2992
+ }
2993
+ GetRowByKey(key) {
2994
+ return this._rowDataMap.get(key)?.entity;
2995
+ }
2996
+ GetRowByIndex(index) {
2997
+ const dataSource = this._useExternalData ? this._data : this._allData;
2998
+ return dataSource[index];
2999
+ }
3000
+ GetState() {
3001
+ return {
3002
+ columns: this._columnStates.map(c => ({
3003
+ field: c.config.field,
3004
+ width: c.computedWidth,
3005
+ visible: c.visible,
3006
+ order: c.order
3007
+ })),
3008
+ sort: [...this._sortState],
3009
+ filters: [...this._filterState],
3010
+ selection: [...this._selectedKeys]
3011
+ };
3012
+ }
3013
+ SetState(state) {
3014
+ for (const colState of state.columns) {
3015
+ const column = this._columnStates.find(c => c.config.field === colState.field);
3016
+ if (column) {
3017
+ column.computedWidth = colState.width;
3018
+ column.visible = colState.visible;
3019
+ column.order = colState.order;
3020
+ }
3021
+ }
3022
+ this._sortState = [...state.sort];
3023
+ for (const sort of this._sortState) {
3024
+ const column = this._columnStates.find(c => c.config.field === sort.field);
3025
+ if (column) {
3026
+ column.sortDirection = sort.direction;
3027
+ column.sortIndex = sort.index;
3028
+ }
3029
+ }
3030
+ this._filterState = [...state.filters];
3031
+ this._selectedKeys = [...state.selection];
3032
+ this.updateRowSelectionState();
3033
+ this.buildAgColumnDefs();
3034
+ this.cdr.detectChanges();
3035
+ }
3036
+ ResetState() {
3037
+ this.initializeColumnStates();
3038
+ this._sortState = [];
3039
+ this._filterState = [];
3040
+ this.ClearSelection();
3041
+ this.buildAgColumnDefs();
3042
+ this.cdr.detectChanges();
3043
+ }
3044
+ // ========================================
3045
+ // State Persistence
3046
+ // ========================================
3047
+ scheduleStateSave() {
3048
+ if (!this._stateKey)
3049
+ return;
3050
+ this.statesSaveSubject.next();
3051
+ }
3052
+ async loadPersistedState() {
3053
+ // TODO: Load state from User Settings entity
3054
+ }
3055
+ // ========================================
3056
+ // Toolbar Click Handlers
3057
+ // ========================================
3058
+ onAddClick() {
3059
+ // Emit legacy events for backward compatibility
3060
+ this.AddRequested.emit();
3061
+ this.NewButtonClick.emit();
3062
+ // Emit navigation events based on CreateRecordMode
3063
+ if (this._entityInfo) {
3064
+ if (this._createRecordMode === 'Dialog') {
3065
+ this.NewRecordDialogRequested.emit({
3066
+ entityInfo: this._entityInfo,
3067
+ defaultValues: this._newRecordValues
3068
+ });
3069
+ }
3070
+ else {
3071
+ this.NewRecordTabRequested.emit({
3072
+ entityInfo: this._entityInfo,
3073
+ defaultValues: this._newRecordValues
3074
+ });
3075
+ }
3076
+ }
3077
+ }
3078
+ onDeleteClick() {
3079
+ const selectedRows = this.GetSelectedRows();
3080
+ if (selectedRows.length > 0) {
3081
+ this.DeleteRequested.emit(selectedRows);
3082
+ this.DeleteButtonClick.emit(selectedRows);
3083
+ }
3084
+ }
3085
+ onExportClick() {
3086
+ this.ExportRequested.emit();
3087
+ this.ExportButtonClick.emit();
3088
+ // Show the export dialog
3089
+ this.showExportDialogForCurrentData();
3090
+ }
3091
+ /**
3092
+ * Shows the export dialog for the current grid data
3093
+ */
3094
+ showExportDialogForCurrentData() {
3095
+ const data = this.getExportData();
3096
+ const columns = this.getExportColumns();
3097
+ const fileName = this.getDefaultExportFileName();
3098
+ this.exportDialogConfig = {
3099
+ data,
3100
+ columns,
3101
+ defaultFileName: fileName,
3102
+ availableFormats: ['excel', 'csv', 'json'],
3103
+ defaultFormat: 'excel',
3104
+ showSamplingOptions: true,
3105
+ defaultSamplingMode: 'all',
3106
+ dialogTitle: `Export ${this._entityInfo?.Name || 'Data'}`
3107
+ };
3108
+ this.showExportDialog = true;
3109
+ this.cdr.detectChanges();
3110
+ }
3111
+ /**
3112
+ * Handle export dialog close
3113
+ */
3114
+ onExportDialogClosed(result) {
3115
+ this.showExportDialog = false;
3116
+ this.exportDialogConfig = null;
3117
+ this.cdr.detectChanges();
3118
+ }
3119
+ /**
3120
+ * Export grid data directly without showing dialog.
3121
+ * Use this for programmatic export with specific options.
3122
+ * @param options Export options (format, sampling, etc.)
3123
+ * @param download If true, automatically downloads the file. Default true.
3124
+ * @returns Export result with data buffer and metadata
3125
+ */
3126
+ async Export(options, download = true) {
3127
+ const data = this.getExportData();
3128
+ const columns = this.getExportColumns();
3129
+ const fileName = options?.fileName || this.getDefaultExportFileName();
3130
+ const exportOptions = {
3131
+ format: 'excel',
3132
+ fileName,
3133
+ columns,
3134
+ includeHeaders: true,
3135
+ ...options
3136
+ };
3137
+ const result = await this.exportService.export(data, exportOptions);
3138
+ if (result.success && download) {
3139
+ this.exportService.downloadResult(result);
3140
+ }
3141
+ return result;
3142
+ }
3143
+ /**
3144
+ * Export to Excel format directly (convenience method)
3145
+ * @param download If true, automatically downloads the file. Default true.
3146
+ */
3147
+ async ExportToExcel(download = true) {
3148
+ return this.Export({ format: 'excel' }, download);
3149
+ }
3150
+ /**
3151
+ * Export to CSV format directly (convenience method)
3152
+ * @param download If true, automatically downloads the file. Default true.
3153
+ */
3154
+ async ExportToCSV(download = true) {
3155
+ return this.Export({ format: 'csv' }, download);
3156
+ }
3157
+ /**
3158
+ * Export to JSON format directly (convenience method)
3159
+ * @param download If true, automatically downloads the file. Default true.
3160
+ */
3161
+ async ExportToJSON(download = true) {
3162
+ return this.Export({ format: 'json' }, download);
3163
+ }
3164
+ /**
3165
+ * Get the current grid data formatted for export
3166
+ */
3167
+ getExportData() {
3168
+ // Convert BaseEntity[] to plain objects for export
3169
+ return this.rowData.map(row => {
3170
+ if (row instanceof BaseEntity) {
3171
+ return row.GetAll();
3172
+ }
3173
+ return row;
3174
+ });
3175
+ }
3176
+ /**
3177
+ * Get column definitions for export based on current grid columns
3178
+ */
3179
+ getExportColumns() {
3180
+ if (!this._entityInfo) {
3181
+ // Fallback: use AG Grid column definitions
3182
+ return this.agColumnDefs
3183
+ .filter(col => col.field && !col.hide)
3184
+ .map(col => ({
3185
+ name: col.field,
3186
+ displayName: (col.headerName || col.field)
3187
+ }));
3188
+ }
3189
+ // Use entity field info for better column metadata
3190
+ return this._columns
3191
+ .filter(col => col.visible !== false)
3192
+ .map(col => {
3193
+ const field = this._entityInfo?.Fields.find(f => f.Name === col.field);
3194
+ return {
3195
+ name: col.field,
3196
+ displayName: col.title || field?.DisplayName || col.field,
3197
+ dataType: this.mapFieldTypeToExportType(field?.Type),
3198
+ width: typeof col.width === 'number' ? col.width : undefined
3199
+ };
3200
+ });
3201
+ }
3202
+ /**
3203
+ * Map MemberJunction field types to export column types
3204
+ */
3205
+ mapFieldTypeToExportType(fieldType) {
3206
+ if (!fieldType)
3207
+ return 'string';
3208
+ const type = fieldType.toLowerCase();
3209
+ if (type.includes('int') || type.includes('decimal') || type.includes('float') || type.includes('numeric')) {
3210
+ return 'number';
3211
+ }
3212
+ if (type.includes('date') || type.includes('time')) {
3213
+ return 'date';
3214
+ }
3215
+ if (type.includes('bit') || type.includes('bool')) {
3216
+ return 'boolean';
3217
+ }
3218
+ if (type.includes('money') || type.includes('currency')) {
3219
+ return 'currency';
3220
+ }
3221
+ return 'string';
3222
+ }
3223
+ /**
3224
+ * Generate default file name for export
3225
+ */
3226
+ getDefaultExportFileName() {
3227
+ const entityName = this._entityInfo?.Name || 'export';
3228
+ const timestamp = new Date().toISOString().slice(0, 10);
3229
+ return `${entityName}_${timestamp}`;
3230
+ }
3231
+ onRefreshClick() {
3232
+ this.RefreshButtonClick.emit();
3233
+ this.Refresh();
3234
+ }
3235
+ onCompareClick() {
3236
+ const selectedRows = this.GetSelectedRows();
3237
+ if (selectedRows.length >= 2) {
3238
+ // Emit legacy event for backward compatibility
3239
+ this.CompareButtonClick.emit(selectedRows);
3240
+ // Emit new structured event for Explorer dialogs
3241
+ if (this._entityInfo) {
3242
+ this.CompareRecordsRequested.emit({
3243
+ entityInfo: this._entityInfo,
3244
+ records: selectedRows
3245
+ });
3246
+ }
3247
+ }
3248
+ }
3249
+ onMergeClick() {
3250
+ const selectedRows = this.GetSelectedRows();
3251
+ if (selectedRows.length >= 2) {
3252
+ // Emit legacy event for backward compatibility
3253
+ this.MergeButtonClick.emit(selectedRows);
3254
+ // Emit new structured event for Explorer dialogs
3255
+ if (this._entityInfo) {
3256
+ this.MergeRecordsRequested.emit({
3257
+ entityInfo: this._entityInfo,
3258
+ records: selectedRows
3259
+ });
3260
+ }
3261
+ }
3262
+ }
3263
+ onAddToListClick() {
3264
+ const selectedRows = this.GetSelectedRows();
3265
+ if (selectedRows.length > 0) {
3266
+ // Emit legacy event for backward compatibility
3267
+ this.AddToListButtonClick.emit(selectedRows);
3268
+ // Emit new structured event with record IDs for list management
3269
+ if (this._entityInfo) {
3270
+ const recordIds = selectedRows.map(r => {
3271
+ return r.PrimaryKey?.ToConcatenatedString() || String(r.Get(this._keyField));
3272
+ });
3273
+ this.AddToListRequested.emit({
3274
+ entityInfo: this._entityInfo,
3275
+ records: selectedRows,
3276
+ recordIds
3277
+ });
3278
+ }
3279
+ }
3280
+ }
3281
+ onDuplicateSearchClick() {
3282
+ const selectedRows = this.GetSelectedRows();
3283
+ if (selectedRows.length >= 2) {
3284
+ // Emit legacy event for backward compatibility
3285
+ this.DuplicateSearchButtonClick.emit(selectedRows);
3286
+ // Emit new structured event for Explorer dialogs
3287
+ if (this._entityInfo) {
3288
+ this.DuplicateSearchRequested.emit({
3289
+ entityInfo: this._entityInfo,
3290
+ records: selectedRows
3291
+ });
3292
+ }
3293
+ }
3294
+ }
3295
+ onCommunicationClick() {
3296
+ const selectedRows = this.GetSelectedRows();
3297
+ if (selectedRows.length > 0) {
3298
+ // Emit legacy event for backward compatibility
3299
+ this.CommunicationButtonClick.emit(selectedRows);
3300
+ // Emit new structured event for Explorer dialogs
3301
+ if (this._entityInfo) {
3302
+ this.CommunicationRequested.emit({
3303
+ entityInfo: this._entityInfo,
3304
+ records: selectedRows,
3305
+ viewParams: this._params
3306
+ });
3307
+ }
3308
+ }
3309
+ }
3310
+ onColumnChooserClick() {
3311
+ // TODO: Implement column chooser dialog
3312
+ }
3313
+ /**
3314
+ * Handles entity action click from the overflow menu
3315
+ */
3316
+ onEntityActionClick(action) {
3317
+ if (!this._entityInfo)
3318
+ return;
3319
+ this.EntityActionRequested.emit({
3320
+ entityInfo: this._entityInfo,
3321
+ action,
3322
+ selectedRecords: this.GetSelectedRows()
3323
+ });
3324
+ }
3325
+ /**
3326
+ * Checks if an entity action is currently enabled based on selection requirements
3327
+ */
3328
+ isEntityActionEnabled(action) {
3329
+ if (!action.requiresSelection)
3330
+ return true;
3331
+ const selectedCount = this._selectedKeys.length;
3332
+ if (selectedCount === 0)
3333
+ return false;
3334
+ if (action.minSelectedRecords && selectedCount < action.minSelectedRecords)
3335
+ return false;
3336
+ if (action.maxSelectedRecords && selectedCount > action.maxSelectedRecords)
3337
+ return false;
3338
+ return true;
3339
+ }
3340
+ // ========================================
3341
+ // Overflow Menu Methods
3342
+ // ========================================
3343
+ toggleOverflowMenu() {
3344
+ this.showOverflowMenu = !this.showOverflowMenu;
3345
+ this.cdr.detectChanges();
3346
+ // Add click outside listener when menu is open
3347
+ if (this.showOverflowMenu) {
3348
+ setTimeout(() => {
3349
+ document.addEventListener('click', this.handleOutsideClick);
3350
+ }, 0);
3351
+ }
3352
+ else {
3353
+ document.removeEventListener('click', this.handleOutsideClick);
3354
+ }
3355
+ }
3356
+ closeOverflowMenu() {
3357
+ this.showOverflowMenu = false;
3358
+ document.removeEventListener('click', this.handleOutsideClick);
3359
+ this.cdr.detectChanges();
3360
+ }
3361
+ handleOutsideClick = () => {
3362
+ this.closeOverflowMenu();
3363
+ };
3364
+ // Overflow menu visibility helpers
3365
+ get hasOverflowMenuItems() {
3366
+ return this.showExportInOverflow ||
3367
+ this.showColumnChooserInOverflow ||
3368
+ (this._showEntityActionButtons && this._entityActions.length > 0) ||
3369
+ this.hasSelectionDependentOverflowActions;
3370
+ }
3371
+ get showExportInOverflow() {
3372
+ // Export is in overflow when it's not shown as a primary button
3373
+ return !this.ShowExportButton && !!this.ToolbarConfig.showExport;
3374
+ }
3375
+ get showColumnChooserInOverflow() {
3376
+ return this.AllowColumnToggle && !this.ToolbarConfig.showColumnChooser;
3377
+ }
3378
+ get hasSelectionDependentOverflowActions() {
3379
+ return this.showCommunicationInOverflow;
3380
+ }
3381
+ get showCommunicationInOverflow() {
3382
+ // Communication is in overflow when it's not shown as a primary button
3383
+ return !this.ShowCommunicationButton && this.HasSelection;
3384
+ }
3385
+ // ========================================
3386
+ // Toolbar Button State Helpers
3387
+ // ========================================
3388
+ get HasSelection() {
3389
+ return this._selectedKeys.length > 0;
3390
+ }
3391
+ get HasMultipleSelection() {
3392
+ return this._selectedKeys.length >= 2;
3393
+ }
3394
+ // ========================================
3395
+ // CSS Helpers
3396
+ // ========================================
3397
+ get gridContainerClasses() {
3398
+ const classes = ['mj-grid-container'];
3399
+ classes.push(`grid-lines-${this._gridLines}`);
3400
+ if (this._striped)
3401
+ classes.push('grid-striped');
3402
+ // Add visual config classes
3403
+ const vc = this.effectiveVisualConfig;
3404
+ classes.push(`header-style-${vc.headerStyle}`);
3405
+ if (vc.headerShadow)
3406
+ classes.push('header-shadow');
3407
+ if (vc.alternateRows)
3408
+ classes.push(`alternate-rows-${vc.alternateRowContrast}`);
3409
+ if (vc.hoverTransitions)
3410
+ classes.push('hover-transitions');
3411
+ classes.push(`cell-padding-${vc.cellPadding}`);
3412
+ if (vc.checkboxStyle !== 'default')
3413
+ classes.push(`checkbox-style-${vc.checkboxStyle}`);
3414
+ return classes;
3415
+ }
3416
+ /**
3417
+ * Apply visual configuration by setting CSS custom properties
3418
+ */
3419
+ applyVisualConfig() {
3420
+ const vc = this.effectiveVisualConfig;
3421
+ const el = this.elementRef.nativeElement;
3422
+ // Set CSS custom properties for dynamic values
3423
+ if (vc.headerBackground) {
3424
+ el.style.setProperty('--grid-header-bg', vc.headerBackground);
3425
+ }
3426
+ if (vc.headerTextColor) {
3427
+ el.style.setProperty('--grid-header-text', vc.headerTextColor);
3428
+ }
3429
+ if (vc.selectionIndicatorColor) {
3430
+ el.style.setProperty('--grid-selection-indicator-color', vc.selectionIndicatorColor);
3431
+ }
3432
+ if (vc.selectionIndicatorWidth) {
3433
+ el.style.setProperty('--grid-selection-indicator-width', `${vc.selectionIndicatorWidth}px`);
3434
+ }
3435
+ if (vc.selectionBackground) {
3436
+ el.style.setProperty('--grid-row-selected-bg', vc.selectionBackground);
3437
+ }
3438
+ if (vc.checkboxColor) {
3439
+ el.style.setProperty('--grid-checkbox-color', vc.checkboxColor);
3440
+ }
3441
+ if (vc.borderRadius !== undefined) {
3442
+ el.style.setProperty('--grid-border-radius', `${vc.borderRadius}px`);
3443
+ }
3444
+ if (vc.accentColor) {
3445
+ el.style.setProperty('--grid-accent-color', vc.accentColor);
3446
+ el.style.setProperty('--grid-sort-indicator-color', vc.accentColor);
3447
+ }
3448
+ if (vc.hoverTransitionDuration) {
3449
+ el.style.setProperty('--grid-hover-transition', `${vc.hoverTransitionDuration}ms`);
3450
+ }
3451
+ // Rebuild column defs if formatting options changed
3452
+ if (this._entityInfo) {
3453
+ this.buildAgColumnDefs();
3454
+ }
3455
+ }
3456
+ get gridHeightStyle() {
3457
+ if (typeof this._height === 'number') {
3458
+ return `${this._height}px`;
3459
+ }
3460
+ return this._height === 'auto' ? '100%' : 'fit-content';
3461
+ }
3462
+ // ========================================
3463
+ // Toolbar Button Helpers
3464
+ // ========================================
3465
+ isButtonVisible(button) {
3466
+ if (button.visible === undefined)
3467
+ return true;
3468
+ if (typeof button.visible === 'boolean')
3469
+ return button.visible;
3470
+ if (typeof button.visible === 'function')
3471
+ return button.visible();
3472
+ return true;
3473
+ }
3474
+ isButtonDisabled(button) {
3475
+ if (button.disabled === undefined)
3476
+ return false;
3477
+ if (typeof button.disabled === 'boolean')
3478
+ return button.disabled;
3479
+ if (typeof button.disabled === 'function')
3480
+ return button.disabled();
3481
+ return false;
3482
+ }
3483
+ onToolbarButtonClick(button) {
3484
+ if (button.onClick) {
3485
+ button.onClick();
3486
+ }
3487
+ }
3488
+ // ========================================
3489
+ // Public Getters
3490
+ // ========================================
3491
+ get VisibleRows() {
3492
+ return Array.from(this._rowDataMap.values());
3493
+ }
3494
+ get VisibleColumnStates() {
3495
+ return this._columnStates
3496
+ .filter(c => c.visible)
3497
+ .sort((a, b) => a.order - b.order);
3498
+ }
3499
+ static ɵfac = function EntityDataGridComponent_Factory(t) { return new (t || EntityDataGridComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i1.ExportService)); };
3500
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EntityDataGridComponent, selectors: [["mj-entity-data-grid"]], viewQuery: function EntityDataGridComponent_Query(rf, ctx) { if (rf & 1) {
3501
+ i0.ɵɵviewQuery(_c0, 5);
3502
+ } if (rf & 2) {
3503
+ let _t;
3504
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.gridContainer = _t.first);
3505
+ } }, 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", "rowMultiSelectWithClick", "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", "rowMultiSelectWithClick", "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", "rowMultiSelectWithClick"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "gridReady", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", "theme", "columnDefs", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowMultiSelectWithClick", "rowModelType", "cacheBlockSize", "maxBlocksInCache", "infiniteInitialRowCount", "cacheOverflowSize"]], template: function EntityDataGridComponent_Template(rf, ctx) { if (rf & 1) {
3506
+ const _r1 = i0.ɵɵgetCurrentView();
3507
+ i0.ɵɵelementStart(0, "div", null, 0);
3508
+ i0.ɵɵtemplate(2, EntityDataGridComponent_div_2_Template, 24, 20, "div", 1);
3509
+ i0.ɵɵelementStart(3, "div", 2);
3510
+ 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, 10, "ag-grid-angular", 6)(8, EntityDataGridComponent_ag_grid_angular_8_Template, 1, 14, "ag-grid-angular", 7);
3511
+ i0.ɵɵelementEnd()();
3512
+ i0.ɵɵelementStart(9, "mj-export-dialog", 8);
3513
+ i0.ɵɵlistener("closed", function EntityDataGridComponent_Template_mj_export_dialog_closed_9_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onExportDialogClosed($event)); });
3514
+ i0.ɵɵelementEnd();
3515
+ } if (rf & 2) {
3516
+ i0.ɵɵclassMap(ctx.gridContainerClasses.join(" "));
3517
+ i0.ɵɵstyleProp("height", ctx.gridHeightStyle);
3518
+ i0.ɵɵadvance(2);
3519
+ i0.ɵɵproperty("ngIf", ctx.ShowToolbar);
3520
+ i0.ɵɵadvance(2);
3521
+ i0.ɵɵproperty("ngIf", ctx.loading && ctx.rowData.length === 0);
3522
+ i0.ɵɵadvance();
3523
+ i0.ɵɵproperty("ngIf", ctx.errorMessage && !ctx.loading);
3524
+ i0.ɵɵadvance();
3525
+ i0.ɵɵproperty("ngIf", !ctx.loading && !ctx.errorMessage && ctx.rowData.length === 0);
3526
+ i0.ɵɵadvance();
3527
+ i0.ɵɵproperty("ngIf", !ctx.errorMessage && ctx.PaginationMode === "client" && (ctx.rowData.length > 0 || ctx.loading));
3528
+ i0.ɵɵadvance();
3529
+ i0.ɵɵproperty("ngIf", !ctx.errorMessage && ctx.PaginationMode === "infinite");
3530
+ i0.ɵɵadvance();
3531
+ i0.ɵɵproperty("visible", ctx.showExportDialog)("config", ctx.exportDialogConfig);
3532
+ } }, 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: [
3533
+ trigger('fadeIn', [
3534
+ transition(':enter', [
3535
+ style({ opacity: 0, transform: 'translateY(-8px)' }),
3536
+ animate('150ms ease-out', style({ opacity: 1, transform: 'translateY(0)' }))
3537
+ ]),
3538
+ transition(':leave', [
3539
+ animate('100ms ease-in', style({ opacity: 0, transform: 'translateY(-8px)' }))
3540
+ ])
3541
+ ])
3542
+ ] } });
3543
+ }
3544
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityDataGridComponent, [{
3545
+ type: Component,
3546
+ args: [{ selector: 'mj-entity-data-grid', animations: [
3547
+ trigger('fadeIn', [
3548
+ transition(':enter', [
3549
+ style({ opacity: 0, transform: 'translateY(-8px)' }),
3550
+ animate('150ms ease-out', style({ opacity: 1, transform: 'translateY(0)' }))
3551
+ ]),
3552
+ transition(':leave', [
3553
+ animate('100ms ease-in', style({ opacity: 0, transform: 'translateY(-8px)' }))
3554
+ ])
3555
+ ])
3556
+ ], 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 [rowMultiSelectWithClick]=\"SelectionMode === 'multiple'\"\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 [rowMultiSelectWithClick]=\"SelectionMode === 'multiple'\"\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"] }]
3557
+ }], () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i1.ExportService }], { Params: [{
3558
+ type: Input
3559
+ }], AllowLoad: [{
3560
+ type: Input
3561
+ }], AutoRefreshOnParamsChange: [{
3562
+ type: Input
3563
+ }], PaginationMode: [{
3564
+ type: Input
3565
+ }], PageSize: [{
3566
+ type: Input
3567
+ }], CacheBlockSize: [{
3568
+ type: Input
3569
+ }], MaxBlocksInCache: [{
3570
+ type: Input
3571
+ }], Data: [{
3572
+ type: Input
3573
+ }], Columns: [{
3574
+ type: Input
3575
+ }], GridState: [{
3576
+ type: Input
3577
+ }], AllowColumnReorder: [{
3578
+ type: Input
3579
+ }], AllowColumnResize: [{
3580
+ type: Input
3581
+ }], AllowColumnToggle: [{
3582
+ type: Input
3583
+ }], ShowHeader: [{
3584
+ type: Input
3585
+ }], AllowSorting: [{
3586
+ type: Input
3587
+ }], AllowMultiSort: [{
3588
+ type: Input
3589
+ }], ServerSideSorting: [{
3590
+ type: Input
3591
+ }], AllowColumnFilters: [{
3592
+ type: Input
3593
+ }], ShowSearch: [{
3594
+ type: Input
3595
+ }], SelectionMode: [{
3596
+ type: Input
3597
+ }], SelectedKeys: [{
3598
+ type: Input
3599
+ }], KeyField: [{
3600
+ type: Input
3601
+ }], EditMode: [{
3602
+ type: Input
3603
+ }], AllowAdd: [{
3604
+ type: Input
3605
+ }], AllowDelete: [{
3606
+ type: Input
3607
+ }], Height: [{
3608
+ type: Input
3609
+ }], RowHeight: [{
3610
+ type: Input
3611
+ }], VirtualScroll: [{
3612
+ type: Input
3613
+ }], ShowRowNumbers: [{
3614
+ type: Input
3615
+ }], Striped: [{
3616
+ type: Input
3617
+ }], GridLines: [{
3618
+ type: Input
3619
+ }], VisualConfig: [{
3620
+ type: Input
3621
+ }], ShowToolbar: [{
3622
+ type: Input
3623
+ }], ToolbarConfig: [{
3624
+ type: Input
3625
+ }], StateKey: [{
3626
+ type: Input
3627
+ }], AutoPersistState: [{
3628
+ type: Input
3629
+ }], StatePersistDebounce: [{
3630
+ type: Input
3631
+ }], RefreshDebounce: [{
3632
+ type: Input
3633
+ }], FilterText: [{
3634
+ type: Input
3635
+ }], ShowNewButton: [{
3636
+ type: Input
3637
+ }], ShowRefreshButton: [{
3638
+ type: Input
3639
+ }], ShowExportButton: [{
3640
+ type: Input
3641
+ }], ShowDeleteButton: [{
3642
+ type: Input
3643
+ }], ShowCompareButton: [{
3644
+ type: Input
3645
+ }], ShowMergeButton: [{
3646
+ type: Input
3647
+ }], ShowAddToListButton: [{
3648
+ type: Input
3649
+ }], ShowDuplicateSearchButton: [{
3650
+ type: Input
3651
+ }], ShowCommunicationButton: [{
3652
+ type: Input
3653
+ }], AutoNavigate: [{
3654
+ type: Input
3655
+ }], NavigateOnDoubleClick: [{
3656
+ type: Input
3657
+ }], CreateRecordMode: [{
3658
+ type: Input
3659
+ }], NewRecordValues: [{
3660
+ type: Input
3661
+ }], ShowEntityActionButtons: [{
3662
+ type: Input
3663
+ }], EntityActions: [{
3664
+ type: Input
3665
+ }], BeforeRowSelect: [{
3666
+ type: Output
3667
+ }], AfterRowSelect: [{
3668
+ type: Output
3669
+ }], BeforeRowDeselect: [{
3670
+ type: Output
3671
+ }], AfterRowDeselect: [{
3672
+ type: Output
3673
+ }], SelectionChange: [{
3674
+ type: Output
3675
+ }], BeforeRowClick: [{
3676
+ type: Output
3677
+ }], AfterRowClick: [{
3678
+ type: Output
3679
+ }], BeforeRowDoubleClick: [{
3680
+ type: Output
3681
+ }], AfterRowDoubleClick: [{
3682
+ type: Output
3683
+ }], BeforeCellEdit: [{
3684
+ type: Output
3685
+ }], AfterCellEditBegin: [{
3686
+ type: Output
3687
+ }], BeforeCellEditCommit: [{
3688
+ type: Output
3689
+ }], AfterCellEditCommit: [{
3690
+ type: Output
3691
+ }], BeforeCellEditCancel: [{
3692
+ type: Output
3693
+ }], AfterCellEditCancel: [{
3694
+ type: Output
3695
+ }], BeforeRowSave: [{
3696
+ type: Output
3697
+ }], AfterRowSave: [{
3698
+ type: Output
3699
+ }], BeforeRowDelete: [{
3700
+ type: Output
3701
+ }], AfterRowDelete: [{
3702
+ type: Output
3703
+ }], BeforeDataLoad: [{
3704
+ type: Output
3705
+ }], AfterDataLoad: [{
3706
+ type: Output
3707
+ }], BeforeDataRefresh: [{
3708
+ type: Output
3709
+ }], AfterDataRefresh: [{
3710
+ type: Output
3711
+ }], BeforeSort: [{
3712
+ type: Output
3713
+ }], AfterSort: [{
3714
+ type: Output
3715
+ }], BeforeColumnReorder: [{
3716
+ type: Output
3717
+ }], AfterColumnReorder: [{
3718
+ type: Output
3719
+ }], BeforeColumnResize: [{
3720
+ type: Output
3721
+ }], AfterColumnResize: [{
3722
+ type: Output
3723
+ }], BeforeColumnVisibilityChange: [{
3724
+ type: Output
3725
+ }], AfterColumnVisibilityChange: [{
3726
+ type: Output
3727
+ }], GridStateChanged: [{
3728
+ type: Output
3729
+ }], AddRequested: [{
3730
+ type: Output
3731
+ }], DeleteRequested: [{
3732
+ type: Output
3733
+ }], ExportRequested: [{
3734
+ type: Output
3735
+ }], NewButtonClick: [{
3736
+ type: Output
3737
+ }], RefreshButtonClick: [{
3738
+ type: Output
3739
+ }], ExportButtonClick: [{
3740
+ type: Output
3741
+ }], DeleteButtonClick: [{
3742
+ type: Output
3743
+ }], CompareButtonClick: [{
3744
+ type: Output
3745
+ }], MergeButtonClick: [{
3746
+ type: Output
3747
+ }], AddToListButtonClick: [{
3748
+ type: Output
3749
+ }], DuplicateSearchButtonClick: [{
3750
+ type: Output
3751
+ }], CommunicationButtonClick: [{
3752
+ type: Output
3753
+ }], NavigationRequested: [{
3754
+ type: Output
3755
+ }], NewRecordDialogRequested: [{
3756
+ type: Output
3757
+ }], NewRecordTabRequested: [{
3758
+ type: Output
3759
+ }], CompareRecordsRequested: [{
3760
+ type: Output
3761
+ }], MergeRecordsRequested: [{
3762
+ type: Output
3763
+ }], CommunicationRequested: [{
3764
+ type: Output
3765
+ }], DuplicateSearchRequested: [{
3766
+ type: Output
3767
+ }], AddToListRequested: [{
3768
+ type: Output
3769
+ }], LoadEntityActionsRequested: [{
3770
+ type: Output
3771
+ }], EntityActionRequested: [{
3772
+ type: Output
3773
+ }], gridContainer: [{
3774
+ type: ViewChild,
3775
+ args: ['gridContainer']
3776
+ }] }); })();
3777
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityDataGridComponent, { className: "EntityDataGridComponent", filePath: "src/lib/entity-data-grid/entity-data-grid.component.ts", lineNumber: 150 }); })();
3778
+ //# sourceMappingURL=entity-data-grid.component.js.map