@memberjunction/ng-entity-viewer 5.40.1 → 5.41.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.
@@ -7,8 +7,8 @@ import { HighlightUtil } from '../utils/highlight.util';
7
7
  import * as i0 from "@angular/core";
8
8
  import * as i1 from "@memberjunction/ng-shared-generic";
9
9
  import * as i2 from "../pill/pill.component";
10
- function _forTrack0($index, $item) { return this.getRecordTrackId($item, $index); }
11
- const _forTrack1 = ($index, $item) => $item.name;
10
+ const _forTrack0 = ($index, $item) => $item.trackId;
11
+ const _forTrack1 = ($index, $item) => $item.field.name;
12
12
  function EntityCardsComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
13
13
  i0.ɵɵelementStart(0, "div", 1);
14
14
  i0.ɵɵelement(1, "mj-loading", 3);
@@ -19,48 +19,41 @@ function EntityCardsComponent_Conditional_2_For_2_Case_2_Template(rf, ctx) { if
19
19
  i0.ɵɵelement(1, "img", 19);
20
20
  i0.ɵɵelementEnd();
21
21
  } if (rf & 2) {
22
- const record_r2 = i0.ɵɵnextContext().$implicit;
23
- const ctx_r2 = i0.ɵɵnextContext(2);
22
+ const vm_r2 = i0.ɵɵnextContext().$implicit;
24
23
  i0.ɵɵadvance();
25
- i0.ɵɵproperty("src", ctx_r2.getThumbnailUrl(record_r2), i0.ɵɵsanitizeUrl);
24
+ i0.ɵɵproperty("src", vm_r2.thumbnailUrl, i0.ɵɵsanitizeUrl);
26
25
  } }
27
26
  function EntityCardsComponent_Conditional_2_For_2_Case_3_Template(rf, ctx) { if (rf & 1) {
28
27
  i0.ɵɵelementStart(0, "div", 20);
29
28
  i0.ɵɵelement(1, "i");
30
29
  i0.ɵɵelementEnd();
31
30
  } if (rf & 2) {
32
- const record_r2 = i0.ɵɵnextContext().$implicit;
33
- const ctx_r2 = i0.ɵɵnextContext(2);
34
- i0.ɵɵstyleProp("background-color", ctx_r2.getRecordColor(record_r2));
31
+ const vm_r2 = i0.ɵɵnextContext().$implicit;
32
+ i0.ɵɵstyleProp("background-color", vm_r2.color);
35
33
  i0.ɵɵadvance();
36
- i0.ɵɵclassMap(ctx_r2.getThumbnailUrl(record_r2));
34
+ i0.ɵɵclassMap(vm_r2.thumbnailUrl);
37
35
  } }
38
36
  function EntityCardsComponent_Conditional_2_For_2_Case_4_Template(rf, ctx) { if (rf & 1) {
39
37
  i0.ɵɵelementStart(0, "div", 20);
40
38
  i0.ɵɵtext(1);
41
39
  i0.ɵɵelementEnd();
42
40
  } if (rf & 2) {
43
- const record_r2 = i0.ɵɵnextContext().$implicit;
44
- const ctx_r2 = i0.ɵɵnextContext(2);
45
- i0.ɵɵstyleProp("background-color", ctx_r2.getRecordColor(record_r2));
41
+ const vm_r2 = i0.ɵɵnextContext().$implicit;
42
+ i0.ɵɵstyleProp("background-color", vm_r2.color);
46
43
  i0.ɵɵadvance();
47
- i0.ɵɵtextInterpolate1(" ", ctx_r2.getInitials(record_r2), " ");
44
+ i0.ɵɵtextInterpolate1(" ", vm_r2.initials, " ");
48
45
  } }
49
46
  function EntityCardsComponent_Conditional_2_For_2_Conditional_7_Conditional_0_Template(rf, ctx) { if (rf & 1) {
50
47
  i0.ɵɵelement(0, "mj-pill", 21);
51
48
  } if (rf & 2) {
52
- const subField_r4 = i0.ɵɵnextContext();
53
- const record_r2 = i0.ɵɵnextContext().$implicit;
54
- const ctx_r2 = i0.ɵɵnextContext(2);
55
- i0.ɵɵproperty("value", ctx_r2.getFieldValue(record_r2, subField_r4));
49
+ const vm_r2 = i0.ɵɵnextContext(2).$implicit;
50
+ i0.ɵɵproperty("value", vm_r2.subtitleValue);
56
51
  } }
57
52
  function EntityCardsComponent_Conditional_2_For_2_Conditional_7_Conditional_1_Template(rf, ctx) { if (rf & 1) {
58
53
  i0.ɵɵelement(0, "span", 22);
59
54
  } if (rf & 2) {
60
- const subField_r4 = i0.ɵɵnextContext();
61
- const record_r2 = i0.ɵɵnextContext().$implicit;
62
- const ctx_r2 = i0.ɵɵnextContext(2);
63
- i0.ɵɵproperty("innerHTML", ctx_r2.highlightMatch(ctx_r2.getFieldValue(record_r2, subField_r4)), i0.ɵɵsanitizeHtml);
55
+ const vm_r2 = i0.ɵɵnextContext(2).$implicit;
56
+ i0.ɵɵproperty("innerHTML", vm_r2.highlightedSubtitle, i0.ɵɵsanitizeHtml);
64
57
  } }
65
58
  function EntityCardsComponent_Conditional_2_For_2_Conditional_7_Template(rf, ctx) { if (rf & 1) {
66
59
  i0.ɵɵconditionalCreate(0, EntityCardsComponent_Conditional_2_For_2_Conditional_7_Conditional_0_Template, 1, 1, "mj-pill", 21)(1, EntityCardsComponent_Conditional_2_For_2_Conditional_7_Conditional_1_Template, 1, 1, "span", 22);
@@ -71,9 +64,8 @@ function EntityCardsComponent_Conditional_2_For_2_Conditional_7_Template(rf, ctx
71
64
  function EntityCardsComponent_Conditional_2_For_2_Conditional_11_Template(rf, ctx) { if (rf & 1) {
72
65
  i0.ɵɵelement(0, "p", 15);
73
66
  } if (rf & 2) {
74
- const record_r2 = i0.ɵɵnextContext().$implicit;
75
- const ctx_r2 = i0.ɵɵnextContext(2);
76
- i0.ɵɵproperty("innerHTML", ctx_r2.highlightMatch(ctx_r2.getTextValue(record_r2, ctx, 100)), i0.ɵɵsanitizeHtml);
67
+ const vm_r2 = i0.ɵɵnextContext().$implicit;
68
+ i0.ɵɵproperty("innerHTML", vm_r2.highlightedDescription, i0.ɵɵsanitizeHtml);
77
69
  } }
78
70
  function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_1_Template(rf, ctx) { if (rf & 1) {
79
71
  i0.ɵɵelement(0, "i", 25);
@@ -81,12 +73,10 @@ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_1_Te
81
73
  i0.ɵɵtext(2);
82
74
  i0.ɵɵelementEnd();
83
75
  } if (rf & 2) {
84
- const field_r5 = i0.ɵɵnextContext().$implicit;
85
- const record_r2 = i0.ɵɵnextContext(2).$implicit;
86
- const ctx_r2 = i0.ɵɵnextContext(2);
87
- i0.ɵɵclassProp("fa-solid", true)("fa-check", ctx_r2.getBooleanValue(record_r2, field_r5.name))("fa-minus", !ctx_r2.getBooleanValue(record_r2, field_r5.name))("bool-true", ctx_r2.getBooleanValue(record_r2, field_r5.name))("bool-false", !ctx_r2.getBooleanValue(record_r2, field_r5.name));
76
+ const df_r4 = i0.ɵɵnextContext().$implicit;
77
+ i0.ɵɵclassProp("fa-solid", true)("fa-check", df_r4.booleanValue)("fa-minus", !df_r4.booleanValue)("bool-true", df_r4.booleanValue)("bool-false", !df_r4.booleanValue);
88
78
  i0.ɵɵadvance(2);
89
- i0.ɵɵtextInterpolate(field_r5.label);
79
+ i0.ɵɵtextInterpolate(df_r4.field.label);
90
80
  } }
91
81
  function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_2_Template(rf, ctx) { if (rf & 1) {
92
82
  i0.ɵɵelementStart(0, "span", 27);
@@ -96,13 +86,11 @@ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_2_Te
96
86
  i0.ɵɵtext(3);
97
87
  i0.ɵɵelementEnd();
98
88
  } if (rf & 2) {
99
- const field_r5 = i0.ɵɵnextContext().$implicit;
100
- const record_r2 = i0.ɵɵnextContext(2).$implicit;
101
- const ctx_r2 = i0.ɵɵnextContext(2);
89
+ const df_r4 = i0.ɵɵnextContext().$implicit;
102
90
  i0.ɵɵadvance();
103
- i0.ɵɵtextInterpolate(ctx_r2.getNumericValue(record_r2, field_r5.name));
91
+ i0.ɵɵtextInterpolate(df_r4.numericValue);
104
92
  i0.ɵɵadvance(2);
105
- i0.ɵɵtextInterpolate(field_r5.label);
93
+ i0.ɵɵtextInterpolate(df_r4.field.label);
106
94
  } }
107
95
  function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_3_Template(rf, ctx) { if (rf & 1) {
108
96
  i0.ɵɵelementStart(0, "span", 28);
@@ -112,13 +100,11 @@ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_3_Te
112
100
  i0.ɵɵtext(3);
113
101
  i0.ɵɵelementEnd();
114
102
  } if (rf & 2) {
115
- const field_r5 = i0.ɵɵnextContext().$implicit;
116
- const record_r2 = i0.ɵɵnextContext(2).$implicit;
117
- const ctx_r2 = i0.ɵɵnextContext(2);
103
+ const df_r4 = i0.ɵɵnextContext().$implicit;
118
104
  i0.ɵɵadvance();
119
- i0.ɵɵtextInterpolate(ctx_r2.getDateValue(record_r2, field_r5.name));
105
+ i0.ɵɵtextInterpolate(df_r4.dateValue);
120
106
  i0.ɵɵadvance(2);
121
- i0.ɵɵtextInterpolate(field_r5.label);
107
+ i0.ɵɵtextInterpolate(df_r4.field.label);
122
108
  } }
123
109
  function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_4_Template(rf, ctx) { if (rf & 1) {
124
110
  i0.ɵɵelementStart(0, "span", 26);
@@ -126,13 +112,11 @@ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_4_Te
126
112
  i0.ɵɵelementEnd();
127
113
  i0.ɵɵelement(2, "span", 29);
128
114
  } if (rf & 2) {
129
- const field_r5 = i0.ɵɵnextContext().$implicit;
130
- const record_r2 = i0.ɵɵnextContext(2).$implicit;
131
- const ctx_r2 = i0.ɵɵnextContext(2);
115
+ const df_r4 = i0.ɵɵnextContext().$implicit;
132
116
  i0.ɵɵadvance();
133
- i0.ɵɵtextInterpolate(field_r5.label);
117
+ i0.ɵɵtextInterpolate(df_r4.field.label);
134
118
  i0.ɵɵadvance();
135
- i0.ɵɵproperty("innerHTML", ctx_r2.highlightMatch(ctx_r2.getTextValue(record_r2, field_r5.name, 40)), i0.ɵɵsanitizeHtml);
119
+ i0.ɵɵproperty("innerHTML", df_r4.highlightedText, i0.ɵɵsanitizeHtml);
136
120
  } }
137
121
  function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Template(rf, ctx) { if (rf & 1) {
138
122
  i0.ɵɵelementStart(0, "div", 24);
@@ -140,29 +124,28 @@ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Template(
140
124
  i0.ɵɵelementEnd();
141
125
  } if (rf & 2) {
142
126
  let tmp_24_0;
143
- const field_r5 = ctx.$implicit;
144
- i0.ɵɵclassProp("field-boolean", field_r5.type === "boolean")("field-text", field_r5.type === "text");
127
+ const df_r4 = ctx.$implicit;
128
+ i0.ɵɵclassProp("field-boolean", df_r4.field.type === "boolean")("field-text", df_r4.field.type === "text");
145
129
  i0.ɵɵadvance();
146
- i0.ɵɵconditional((tmp_24_0 = field_r5.type) === "boolean" ? 1 : tmp_24_0 === "number" ? 2 : tmp_24_0 === "date" ? 3 : 4);
130
+ i0.ɵɵconditional((tmp_24_0 = df_r4.field.type) === "boolean" ? 1 : tmp_24_0 === "number" ? 2 : tmp_24_0 === "date" ? 3 : 4);
147
131
  } }
148
132
  function EntityCardsComponent_Conditional_2_For_2_Conditional_12_Template(rf, ctx) { if (rf & 1) {
149
133
  i0.ɵɵelementStart(0, "div", 16);
150
134
  i0.ɵɵrepeaterCreate(1, EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Template, 5, 5, "div", 23, _forTrack1);
151
135
  i0.ɵɵelementEnd();
152
136
  } if (rf & 2) {
153
- const ctx_r2 = i0.ɵɵnextContext(3);
137
+ const vm_r2 = i0.ɵɵnextContext().$implicit;
154
138
  i0.ɵɵadvance();
155
- i0.ɵɵrepeater(ctx_r2.effectiveTemplate.displayFields);
139
+ i0.ɵɵrepeater(vm_r2.displayFields);
156
140
  } }
157
141
  function EntityCardsComponent_Conditional_2_For_2_Conditional_13_Template(rf, ctx) { if (rf & 1) {
158
142
  i0.ɵɵelementStart(0, "div", 17);
159
143
  i0.ɵɵelement(1, "mj-pill", 21);
160
144
  i0.ɵɵelementEnd();
161
145
  } if (rf & 2) {
162
- const record_r2 = i0.ɵɵnextContext().$implicit;
163
- const ctx_r2 = i0.ɵɵnextContext(2);
146
+ const vm_r2 = i0.ɵɵnextContext().$implicit;
164
147
  i0.ɵɵadvance();
165
- i0.ɵɵproperty("value", ctx_r2.getFieldValue(record_r2, ctx));
148
+ i0.ɵɵproperty("value", vm_r2.badgeValue);
166
149
  } }
167
150
  function EntityCardsComponent_Conditional_2_For_2_Conditional_14_Template(rf, ctx) { if (rf & 1) {
168
151
  i0.ɵɵelementStart(0, "div", 18);
@@ -171,16 +154,15 @@ function EntityCardsComponent_Conditional_2_For_2_Conditional_14_Template(rf, ct
171
154
  i0.ɵɵtext(3);
172
155
  i0.ɵɵelementEnd()();
173
156
  } if (rf & 2) {
174
- const record_r2 = i0.ɵɵnextContext().$implicit;
175
- const ctx_r2 = i0.ɵɵnextContext(2);
176
- i0.ɵɵproperty("title", "Matched in: " + ctx_r2.getHiddenMatchFieldName(record_r2));
157
+ const vm_r2 = i0.ɵɵnextContext().$implicit;
158
+ i0.ɵɵproperty("title", "Matched in: " + vm_r2.hiddenMatchFieldName);
177
159
  i0.ɵɵadvance(3);
178
- i0.ɵɵtextInterpolate(ctx_r2.getHiddenMatchFieldName(record_r2));
160
+ i0.ɵɵtextInterpolate(vm_r2.hiddenMatchFieldName);
179
161
  } }
180
162
  function EntityCardsComponent_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1) {
181
163
  const _r1 = i0.ɵɵgetCurrentView();
182
164
  i0.ɵɵelementStart(0, "div", 6);
183
- i0.ɵɵlistener("click", function EntityCardsComponent_Conditional_2_For_2_Template_div_click_0_listener() { const record_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onCardClick(record_r2)); });
165
+ i0.ɵɵlistener("click", function EntityCardsComponent_Conditional_2_For_2_Template_div_click_0_listener() { const vm_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onCardClick(vm_r2.record)); });
184
166
  i0.ɵɵelementStart(1, "div", 7);
185
167
  i0.ɵɵconditionalCreate(2, EntityCardsComponent_Conditional_2_For_2_Case_2_Template, 2, 1, "div", 8)(3, EntityCardsComponent_Conditional_2_For_2_Case_3_Template, 2, 4, "div", 9)(4, EntityCardsComponent_Conditional_2_For_2_Case_4_Template, 2, 3, "div", 9);
186
168
  i0.ɵɵelementStart(5, "div", 10);
@@ -188,7 +170,7 @@ function EntityCardsComponent_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1
188
170
  i0.ɵɵconditionalCreate(7, EntityCardsComponent_Conditional_2_For_2_Conditional_7_Template, 2, 1);
189
171
  i0.ɵɵelementEnd();
190
172
  i0.ɵɵelementStart(8, "button", 12);
191
- i0.ɵɵlistener("click", function EntityCardsComponent_Conditional_2_For_2_Template_button_click_8_listener($event) { const record_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onOpenClick($event, record_r2)); });
173
+ i0.ɵɵlistener("click", function EntityCardsComponent_Conditional_2_For_2_Template_button_click_8_listener($event) { const vm_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onOpenClick($event, vm_r2.record)); });
192
174
  i0.ɵɵelement(9, "i", 13);
193
175
  i0.ɵɵelementEnd()();
194
176
  i0.ɵɵelementStart(10, "div", 14);
@@ -200,26 +182,23 @@ function EntityCardsComponent_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1
200
182
  i0.ɵɵelementEnd();
201
183
  } if (rf & 2) {
202
184
  let tmp_12_0;
203
- let tmp_14_0;
204
- let tmp_15_0;
205
- let tmp_17_0;
206
- const record_r2 = ctx.$implicit;
185
+ const vm_r2 = ctx.$implicit;
207
186
  const ctx_r2 = i0.ɵɵnextContext(2);
208
- i0.ɵɵclassProp("selected", ctx_r2.isSelected(record_r2));
187
+ i0.ɵɵclassProp("selected", vm_r2.isSelected);
209
188
  i0.ɵɵadvance(2);
210
- i0.ɵɵconditional((tmp_12_0 = ctx_r2.getThumbnailType(record_r2)) === "image" ? 2 : tmp_12_0 === "icon" ? 3 : 4);
189
+ i0.ɵɵconditional((tmp_12_0 = vm_r2.thumbnailType) === "image" ? 2 : tmp_12_0 === "icon" ? 3 : 4);
211
190
  i0.ɵɵadvance(4);
212
- i0.ɵɵproperty("innerHTML", ctx_r2.highlightMatch(ctx_r2.getCombinedTitle(record_r2)), i0.ɵɵsanitizeHtml);
191
+ i0.ɵɵproperty("innerHTML", vm_r2.highlightedTitle, i0.ɵɵsanitizeHtml);
213
192
  i0.ɵɵadvance();
214
- i0.ɵɵconditional((tmp_14_0 = ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.subtitleField) ? 7 : -1, tmp_14_0);
193
+ i0.ɵɵconditional((ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.subtitleField) ? 7 : -1);
215
194
  i0.ɵɵadvance(4);
216
- i0.ɵɵconditional((tmp_15_0 = ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.descriptionField) ? 11 : -1, tmp_15_0);
195
+ i0.ɵɵconditional((ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.descriptionField) ? 11 : -1);
217
196
  i0.ɵɵadvance();
218
- i0.ɵɵconditional(ctx_r2.effectiveTemplate && ctx_r2.effectiveTemplate.displayFields.length > 0 ? 12 : -1);
197
+ i0.ɵɵconditional(vm_r2.displayFields.length > 0 ? 12 : -1);
219
198
  i0.ɵɵadvance();
220
- i0.ɵɵconditional((tmp_17_0 = ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.badgeField) ? 13 : -1, tmp_17_0);
199
+ i0.ɵɵconditional((ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.badgeField) ? 13 : -1);
221
200
  i0.ɵɵadvance();
222
- i0.ɵɵconditional(ctx_r2.hasHiddenFieldMatch(record_r2) ? 14 : -1);
201
+ i0.ɵɵconditional(vm_r2.hasHiddenFieldMatch ? 14 : -1);
223
202
  } }
224
203
  function EntityCardsComponent_Conditional_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
225
204
  i0.ɵɵelementStart(0, "div", 5);
@@ -230,13 +209,13 @@ function EntityCardsComponent_Conditional_2_Conditional_3_Template(rf, ctx) { if
230
209
  } }
231
210
  function EntityCardsComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
232
211
  i0.ɵɵelementStart(0, "div", 2);
233
- i0.ɵɵrepeaterCreate(1, EntityCardsComponent_Conditional_2_For_2_Template, 15, 9, "div", 4, _forTrack0, true);
212
+ i0.ɵɵrepeaterCreate(1, EntityCardsComponent_Conditional_2_For_2_Template, 15, 9, "div", 4, _forTrack0);
234
213
  i0.ɵɵconditionalCreate(3, EntityCardsComponent_Conditional_2_Conditional_3_Template, 4, 0, "div", 5);
235
214
  i0.ɵɵelementEnd();
236
215
  } if (rf & 2) {
237
216
  const ctx_r2 = i0.ɵɵnextContext();
238
217
  i0.ɵɵadvance();
239
- i0.ɵɵrepeater(ctx_r2.effectiveRecords);
218
+ i0.ɵɵrepeater(ctx_r2.cardViewModels);
240
219
  i0.ɵɵadvance(2);
241
220
  i0.ɵɵconditional(ctx_r2.effectiveRecords.length === 0 ? 3 : -1);
242
221
  } }
@@ -318,6 +297,14 @@ export class EntityCardsComponent extends BaseAngularComponent {
318
297
  isLoading = false;
319
298
  /** Flag to trigger scroll to selected card after view renders */
320
299
  pendingScrollToSelected = false;
300
+ /**
301
+ * Precomputed per-record view-models bound by the template's @for. Rebuilt
302
+ * only when the source records, filter, selection, hidden matches, or template
303
+ * change — never per change-detection cycle. This eliminates the 4-5
304
+ * buildPkString() allocations + 4 RegExp-building highlight calls that the
305
+ * previous per-card-per-CD method bindings incurred.
306
+ */
307
+ cardViewModels = [];
321
308
  ngOnInit() {
322
309
  this.standaloneMode = this.records === null;
323
310
  if (this.entity?.Fields && !this.effectiveTemplate) {
@@ -326,6 +313,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
326
313
  if (this.standaloneMode && this.entity) {
327
314
  this.loadData();
328
315
  }
316
+ this.buildCardViewModels();
329
317
  }
330
318
  ngOnChanges(changes) {
331
319
  if (changes['entity'] && this.entity?.Fields) {
@@ -348,6 +336,23 @@ export class EntityCardsComponent extends BaseAngularComponent {
348
336
  if (changes['selectedRecordId'] && this.selectedRecordId) {
349
337
  this.pendingScrollToSelected = true;
350
338
  }
339
+ // Rebuild the precomputed VMs whenever any input that affects card *content*
340
+ // changes. cardTemplate is included because effectiveTemplate derives from it.
341
+ // A selection-only change must NOT trigger a full rebuild — that would re-run
342
+ // the expensive per-card buildPkString()/CompositeKey allocations and the 4
343
+ // RegExp-building highlightMatch() calls for every card on every selection
344
+ // click (O(n) heavy work). Instead we only flip the cheap isSelected flags.
345
+ const contentChanged = !!(changes['records'] || changes['filterText'] ||
346
+ changes['hiddenFieldMatches'] || changes['entity'] || changes['cardTemplate']);
347
+ if (contentChanged) {
348
+ // Full rebuild also recomputes isSelected correctly (buildCardViewModel does).
349
+ this.buildCardViewModels();
350
+ }
351
+ else if (changes['selectedRecordId']) {
352
+ // Selection-only change: just update the isSelected flags on the existing
353
+ // VMs (string compare per card — no rebuild, no regex, no allocation).
354
+ this.updateSelectionFlags();
355
+ }
351
356
  }
352
357
  ngAfterViewChecked() {
353
358
  if (this.pendingScrollToSelected && this.selectedRecordId) {
@@ -392,6 +397,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
392
397
  });
393
398
  if (result.Success) {
394
399
  this.internalRecords = result.Results;
400
+ this.buildCardViewModels();
395
401
  }
396
402
  }
397
403
  catch (error) {
@@ -652,6 +658,125 @@ export class EntityCardsComponent extends BaseAngularComponent {
652
658
  compositeKey: buildCompositeKey(record, this.entity)
653
659
  });
654
660
  }
661
+ // ========================================
662
+ // PRECOMPUTED VIEW-MODELS (perf)
663
+ // ========================================
664
+ /**
665
+ * Rebuild the precomputed per-record view-models. Called from ngOnInit,
666
+ * ngOnChanges (on any render-affecting input), and after standalone loadData.
667
+ * Doing this work here — once per data/filter/selection change — instead of via
668
+ * template method bindings avoids re-running it on every Angular CD cycle.
669
+ */
670
+ buildCardViewModels() {
671
+ const records = this.effectiveRecords;
672
+ this.cardViewModels = records.map((record, index) => this.buildCardViewModel(record, index));
673
+ }
674
+ /**
675
+ * Cheaply update the isSelected flag on the EXISTING card view-models in place,
676
+ * preserving the array and every VM object reference. Called when ONLY
677
+ * selectedRecordId changes — avoids a full buildCardViewModels() rebuild (which
678
+ * re-runs buildPkString() allocations + RegExp-building highlightMatch() calls
679
+ * per card). Uses the already-computed vm.pkString, so this is a plain string
680
+ * compare per card with zero allocation or regex work.
681
+ */
682
+ updateSelectionFlags() {
683
+ for (const vm of this.cardViewModels) {
684
+ vm.isSelected = vm.pkString.length > 0 && vm.pkString === this.selectedRecordId;
685
+ }
686
+ }
687
+ /**
688
+ * Build a single card view-model, computing the PK string exactly once and
689
+ * reusing it for selection, color, hidden-match, and track-id derivation.
690
+ */
691
+ buildCardViewModel(record, index) {
692
+ const pkString = this.computePkString(record);
693
+ const trackId = pkString && pkString.trim().length > 0 ? pkString : `record_${index}`;
694
+ const template = this.effectiveTemplate;
695
+ const subtitleField = template?.subtitleField ?? null;
696
+ const subtitleValue = subtitleField ? this.getFieldValue(record, subtitleField) : '';
697
+ const descriptionField = template?.descriptionField ?? null;
698
+ return {
699
+ record,
700
+ pkString,
701
+ trackId,
702
+ isSelected: pkString.length > 0 && pkString === this.selectedRecordId,
703
+ color: this.computeRecordColor(pkString),
704
+ thumbnailType: this.getThumbnailType(record),
705
+ thumbnailUrl: this.getThumbnailUrl(record),
706
+ initials: this.getInitials(record),
707
+ highlightedTitle: this.highlightMatch(this.getCombinedTitle(record)),
708
+ subtitleValue,
709
+ highlightedSubtitle: this.highlightMatch(subtitleValue),
710
+ highlightedDescription: descriptionField
711
+ ? this.highlightMatch(this.getTextValue(record, descriptionField, 100))
712
+ : '',
713
+ displayFields: this.buildDisplayFieldVMs(record, template),
714
+ badgeValue: template?.badgeField ? this.getFieldValue(record, template.badgeField) : '',
715
+ hasHiddenFieldMatch: this.computeHasHiddenFieldMatch(pkString),
716
+ hiddenMatchFieldName: this.computeHiddenMatchFieldName(pkString)
717
+ };
718
+ }
719
+ /**
720
+ * Precompute the display-field VMs for a record, in template order.
721
+ */
722
+ buildDisplayFieldVMs(record, template) {
723
+ if (!template || template.displayFields.length === 0)
724
+ return [];
725
+ return template.displayFields.map(field => ({
726
+ field,
727
+ booleanValue: this.getBooleanValue(record, field.name),
728
+ numericValue: this.getNumericValue(record, field.name),
729
+ dateValue: this.getDateValue(record, field.name),
730
+ highlightedText: this.highlightMatch(this.getTextValue(record, field.name, 40))
731
+ }));
732
+ }
733
+ /**
734
+ * Compute the PK concatenated string for a record. Returns '' when no entity
735
+ * or on error, so callers can treat it as "no stable key".
736
+ */
737
+ computePkString(record) {
738
+ if (!this.entity)
739
+ return '';
740
+ try {
741
+ return buildPkString(record, this.entity);
742
+ }
743
+ catch {
744
+ return '';
745
+ }
746
+ }
747
+ /**
748
+ * Derive the avatar/background color from an already-computed PK string.
749
+ */
750
+ computeRecordColor(pkString) {
751
+ if (!this.entity || !pkString)
752
+ return 'var(--mj-brand-primary)';
753
+ const colors = ['var(--mj-brand-primary)', 'var(--mj-status-success)', 'var(--mj-status-warning)', 'var(--mj-brand-primary)', 'var(--mj-status-error)', 'var(--mj-brand-primary)', 'var(--mj-text-muted)', 'var(--mj-text-secondary)'];
754
+ let hash = 0;
755
+ for (let i = 0; i < pkString.length; i++) {
756
+ hash = pkString.charCodeAt(i) + ((hash << 5) - hash);
757
+ }
758
+ return colors[Math.abs(hash) % colors.length];
759
+ }
760
+ /**
761
+ * Whether the record (by PK string) matched on a hidden field.
762
+ */
763
+ computeHasHiddenFieldMatch(pkString) {
764
+ if (!this.entity || !pkString)
765
+ return false;
766
+ return this.hiddenFieldMatches.has(pkString);
767
+ }
768
+ /**
769
+ * Resolve the display name of the hidden field that matched, from a PK string.
770
+ */
771
+ computeHiddenMatchFieldName(pkString) {
772
+ if (!this.entity || !pkString)
773
+ return '';
774
+ const fieldName = this.hiddenFieldMatches.get(pkString);
775
+ if (!fieldName)
776
+ return '';
777
+ const field = this.entity.Fields.find(f => f.Name === fieldName);
778
+ return field ? field.DisplayNameOrName : fieldName;
779
+ }
655
780
  /**
656
781
  * Get the combined title from all title fields for a record.
657
782
  * Joins multiple IsNameField values with spaces (e.g., "Elizabeth Rodriguez").
@@ -744,15 +869,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
744
869
  return false;
745
870
  }
746
871
  getRecordColor(record) {
747
- if (!this.entity)
748
- return 'var(--mj-brand-primary)';
749
- const colors = ['var(--mj-brand-primary)', 'var(--mj-status-success)', 'var(--mj-status-warning)', 'var(--mj-brand-primary)', 'var(--mj-status-error)', 'var(--mj-brand-primary)', 'var(--mj-text-muted)', 'var(--mj-text-secondary)'];
750
- const pk = buildPkString(record, this.entity);
751
- let hash = 0;
752
- for (let i = 0; i < pk.length; i++) {
753
- hash = pk.charCodeAt(i) + ((hash << 5) - hash);
754
- }
755
- return colors[Math.abs(hash) % colors.length];
872
+ return this.computeRecordColor(this.computePkString(record));
756
873
  }
757
874
  isEnumField(fieldName) {
758
875
  if (!this.entity)
@@ -811,7 +928,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
811
928
  }
812
929
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityCardsComponent, [{
813
930
  type: Component,
814
- args: [{ standalone: false, selector: 'mj-entity-cards', encapsulation: ViewEncapsulation.None, template: "<div class=\"cards-view-wrapper\">\n @if (isLoading && effectiveRecords.length === 0) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading records...\"></mj-loading>\n </div>\n } @else {\n <div class=\"cards-container\">\n @for (record of effectiveRecords; track getRecordTrackId(record, $index)) {\n <div\n class=\"data-card\"\n [class.selected]=\"isSelected(record)\"\n (click)=\"onCardClick(record)\">\n\n <!-- Card Header with Avatar/Icon -->\n <div class=\"card-header\">\n @switch (getThumbnailType(record)) {\n @case ('image') {\n <div class=\"card-thumbnail\">\n <img [src]=\"getThumbnailUrl(record)\" alt=\"\" />\n </div>\n }\n @case ('icon') {\n <div class=\"card-avatar\" [style.background-color]=\"getRecordColor(record)\">\n <i [class]=\"getThumbnailUrl(record)\"></i>\n </div>\n }\n @default {\n <div class=\"card-avatar\" [style.background-color]=\"getRecordColor(record)\">\n {{ getInitials(record) }}\n </div>\n }\n }\n\n <div class=\"card-header-content\">\n <h3 class=\"card-title\" [innerHTML]=\"highlightMatch(getCombinedTitle(record))\"></h3>\n @if (effectiveTemplate?.subtitleField; as subField) {\n @if (subtitleIsPill) {\n <mj-pill [value]=\"getFieldValue(record, subField)\"></mj-pill>\n } @else {\n <span class=\"card-subtitle\" [innerHTML]=\"highlightMatch(getFieldValue(record, subField))\"></span>\n }\n }\n </div>\n\n <button class=\"card-open-btn\" (click)=\"onOpenClick($event, record)\" title=\"Open Record\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n </button>\n </div>\n\n <!-- Card Body -->\n <div class=\"card-body\">\n @if (effectiveTemplate?.descriptionField; as descField) {\n <p class=\"card-description\" [innerHTML]=\"highlightMatch(getTextValue(record, descField, 100))\"></p>\n }\n\n <!-- Display Fields -->\n @if (effectiveTemplate && effectiveTemplate.displayFields.length > 0) {\n <div class=\"card-fields\">\n @for (field of effectiveTemplate.displayFields; track field.name) {\n <div class=\"field-item\" [class.field-boolean]=\"field.type === 'boolean'\" [class.field-text]=\"field.type === 'text'\">\n @switch (field.type) {\n @case ('boolean') {\n <i class=\"field-icon\" [class.fa-solid]=\"true\"\n [class.fa-check]=\"getBooleanValue(record, field.name)\"\n [class.fa-minus]=\"!getBooleanValue(record, field.name)\"\n [class.bool-true]=\"getBooleanValue(record, field.name)\"\n [class.bool-false]=\"!getBooleanValue(record, field.name)\"></i>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @case ('number') {\n <span class=\"field-value\">{{ getNumericValue(record, field.name) }}</span>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @case ('date') {\n <span class=\"field-value field-date\">{{ getDateValue(record, field.name) }}</span>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @default {\n <span class=\"field-label\">{{ field.label }}</span>\n <span class=\"field-text-value\" [innerHTML]=\"highlightMatch(getTextValue(record, field.name, 40))\"></span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Card Footer with Badge -->\n @if (effectiveTemplate?.badgeField; as badgeField) {\n <div class=\"card-footer\">\n <mj-pill [value]=\"getFieldValue(record, badgeField)\"></mj-pill>\n </div>\n }\n\n <!-- Hidden field match indicator -->\n @if (hasHiddenFieldMatch(record)) {\n <div class=\"hidden-match-indicator\" [title]=\"'Matched in: ' + getHiddenMatchFieldName(record)\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <span>{{ getHiddenMatchFieldName(record) }}</span>\n </div>\n }\n </div>\n }\n\n @if (effectiveRecords.length === 0) {\n <div class=\"no-results\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>No records to display</p>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["/* Scoped to mj-entity-cards to prevent style leakage with ViewEncapsulation.None */\nmj-entity-cards .cards-view-wrapper {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\nmj-entity-cards .cards-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\nmj-entity-cards .data-card {\n background: var(--mj-bg-surface);\n border-radius: 12px;\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n/* Hover state for unselected cards */\nmj-entity-cards .data-card:hover:not(.selected) {\n border-color: var(--mj-border-strong);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\nmj-entity-cards .data-card:hover .card-open-btn {\n opacity: 1;\n}\n\n/* Selected state - subtle but clear */\nmj-entity-cards .data-card.selected {\n border-color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .data-card.selected .card-title {\n color: var(--mj-brand-primary-hover);\n}\n\n/* Selected + hover state - same as regular hover */\nmj-entity-cards .data-card.selected:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n\nmj-entity-cards .card-header {\n display: flex;\n align-items: flex-start;\n padding: 16px;\n gap: 12px;\n}\n\nmj-entity-cards .card-thumbnail {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n overflow: hidden;\n flex-shrink: 0;\n}\nmj-entity-cards .card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\nmj-entity-cards .card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-inverse);\n flex-shrink: 0;\n}\n\nmj-entity-cards .card-header-content {\n flex: 1;\n min-width: 0;\n}\n\nmj-entity-cards .card-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\nmj-entity-cards .card-subtitle {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\nmj-entity-cards .card-open-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n opacity: 0;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\nmj-entity-cards .card-open-btn:hover {\n background: var(--mj-bg-surface-card);\n color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .card-body {\n padding: 0 16px 16px 16px;\n}\n\nmj-entity-cards .card-description {\n margin: 0 0 12px 0;\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n/* Card Display Fields */\nmj-entity-cards .card-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\nmj-entity-cards .field-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 60px;\n}\n\nmj-entity-cards .field-item .field-value {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\nmj-entity-cards .field-item .field-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Boolean fields */\nmj-entity-cards .field-item.field-boolean {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: auto;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon {\n font-size: 14px;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-true {\n color: var(--mj-status-success);\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-false {\n color: var(--mj-text-muted);\n}\n\nmj-entity-cards .field-item.field-boolean .field-label {\n font-size: 12px;\n text-transform: none;\n color: var(--mj-text-secondary);\n}\n\n/* Text fields */\nmj-entity-cards .field-item.field-text {\n flex: 1 1 100%;\n max-width: 100%;\n}\n\nmj-entity-cards .field-item .field-text-value {\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.4;\n word-break: break-word;\n}\n\n/* Date fields */\nmj-entity-cards .field-item .field-date {\n font-size: 13px;\n font-weight: 500;\n}\n\nmj-entity-cards .card-footer {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n/* Highlight matches - scoped to work with innerHTML */\nmj-entity-cards .highlight-match {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-radius: 2px;\n}\n\n/* Pill spacing when used as subtitle */\nmj-entity-cards .card-header-content mj-pill {\n display: block;\n margin-top: 4px;\n}\n\n/* Hidden field match indicator */\nmj-entity-cards .hidden-match-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: var(--mj-status-warning-bg);\n border-top: 1px solid var(--mj-status-warning-border);\n font-size: 11px;\n color: var(--mj-status-warning);\n}\n\nmj-entity-cards .hidden-match-indicator i {\n font-size: 10px;\n}\n\nmj-entity-cards .hidden-match-indicator span {\n font-weight: 500;\n}\n\n/* Loading state */\nmj-entity-cards .loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n padding: 40px;\n}\n\n/* No results state */\nmj-entity-cards .no-results {\n grid-column: 1 / -1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: var(--mj-text-muted);\n text-align: center;\n}\nmj-entity-cards .no-results i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\nmj-entity-cards .no-results p {\n margin: 0;\n font-size: 14px;\n}\n"] }]
931
+ args: [{ standalone: false, selector: 'mj-entity-cards', encapsulation: ViewEncapsulation.None, template: "<div class=\"cards-view-wrapper\">\n @if (isLoading && effectiveRecords.length === 0) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading records...\"></mj-loading>\n </div>\n } @else {\n <div class=\"cards-container\">\n @for (vm of cardViewModels; track vm.trackId) {\n <div\n class=\"data-card\"\n [class.selected]=\"vm.isSelected\"\n (click)=\"onCardClick(vm.record)\">\n\n <!-- Card Header with Avatar/Icon -->\n <div class=\"card-header\">\n @switch (vm.thumbnailType) {\n @case ('image') {\n <div class=\"card-thumbnail\">\n <img [src]=\"vm.thumbnailUrl\" alt=\"\" />\n </div>\n }\n @case ('icon') {\n <div class=\"card-avatar\" [style.background-color]=\"vm.color\">\n <i [class]=\"vm.thumbnailUrl\"></i>\n </div>\n }\n @default {\n <div class=\"card-avatar\" [style.background-color]=\"vm.color\">\n {{ vm.initials }}\n </div>\n }\n }\n\n <div class=\"card-header-content\">\n <h3 class=\"card-title\" [innerHTML]=\"vm.highlightedTitle\"></h3>\n @if (effectiveTemplate?.subtitleField) {\n @if (subtitleIsPill) {\n <mj-pill [value]=\"vm.subtitleValue\"></mj-pill>\n } @else {\n <span class=\"card-subtitle\" [innerHTML]=\"vm.highlightedSubtitle\"></span>\n }\n }\n </div>\n\n <button class=\"card-open-btn\" (click)=\"onOpenClick($event, vm.record)\" title=\"Open Record\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n </button>\n </div>\n\n <!-- Card Body -->\n <div class=\"card-body\">\n @if (effectiveTemplate?.descriptionField) {\n <p class=\"card-description\" [innerHTML]=\"vm.highlightedDescription\"></p>\n }\n\n <!-- Display Fields -->\n @if (vm.displayFields.length > 0) {\n <div class=\"card-fields\">\n @for (df of vm.displayFields; track df.field.name) {\n <div class=\"field-item\" [class.field-boolean]=\"df.field.type === 'boolean'\" [class.field-text]=\"df.field.type === 'text'\">\n @switch (df.field.type) {\n @case ('boolean') {\n <i class=\"field-icon\" [class.fa-solid]=\"true\"\n [class.fa-check]=\"df.booleanValue\"\n [class.fa-minus]=\"!df.booleanValue\"\n [class.bool-true]=\"df.booleanValue\"\n [class.bool-false]=\"!df.booleanValue\"></i>\n <span class=\"field-label\">{{ df.field.label }}</span>\n }\n @case ('number') {\n <span class=\"field-value\">{{ df.numericValue }}</span>\n <span class=\"field-label\">{{ df.field.label }}</span>\n }\n @case ('date') {\n <span class=\"field-value field-date\">{{ df.dateValue }}</span>\n <span class=\"field-label\">{{ df.field.label }}</span>\n }\n @default {\n <span class=\"field-label\">{{ df.field.label }}</span>\n <span class=\"field-text-value\" [innerHTML]=\"df.highlightedText\"></span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Card Footer with Badge -->\n @if (effectiveTemplate?.badgeField) {\n <div class=\"card-footer\">\n <mj-pill [value]=\"vm.badgeValue\"></mj-pill>\n </div>\n }\n\n <!-- Hidden field match indicator -->\n @if (vm.hasHiddenFieldMatch) {\n <div class=\"hidden-match-indicator\" [title]=\"'Matched in: ' + vm.hiddenMatchFieldName\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <span>{{ vm.hiddenMatchFieldName }}</span>\n </div>\n }\n </div>\n }\n\n @if (effectiveRecords.length === 0) {\n <div class=\"no-results\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>No records to display</p>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["/* Scoped to mj-entity-cards to prevent style leakage with ViewEncapsulation.None */\nmj-entity-cards .cards-view-wrapper {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\nmj-entity-cards .cards-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\nmj-entity-cards .data-card {\n background: var(--mj-bg-surface);\n border-radius: 12px;\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n/* Hover state for unselected cards */\nmj-entity-cards .data-card:hover:not(.selected) {\n border-color: var(--mj-border-strong);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\nmj-entity-cards .data-card:hover .card-open-btn {\n opacity: 1;\n}\n\n/* Selected state - subtle but clear */\nmj-entity-cards .data-card.selected {\n border-color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .data-card.selected .card-title {\n color: var(--mj-brand-primary-hover);\n}\n\n/* Selected + hover state - same as regular hover */\nmj-entity-cards .data-card.selected:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n\nmj-entity-cards .card-header {\n display: flex;\n align-items: flex-start;\n padding: 16px;\n gap: 12px;\n}\n\nmj-entity-cards .card-thumbnail {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n overflow: hidden;\n flex-shrink: 0;\n}\nmj-entity-cards .card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\nmj-entity-cards .card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-inverse);\n flex-shrink: 0;\n}\n\nmj-entity-cards .card-header-content {\n flex: 1;\n min-width: 0;\n}\n\nmj-entity-cards .card-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\nmj-entity-cards .card-subtitle {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\nmj-entity-cards .card-open-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n opacity: 0;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\nmj-entity-cards .card-open-btn:hover {\n background: var(--mj-bg-surface-card);\n color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .card-body {\n padding: 0 16px 16px 16px;\n}\n\nmj-entity-cards .card-description {\n margin: 0 0 12px 0;\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n/* Card Display Fields */\nmj-entity-cards .card-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\nmj-entity-cards .field-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 60px;\n}\n\nmj-entity-cards .field-item .field-value {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\nmj-entity-cards .field-item .field-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Boolean fields */\nmj-entity-cards .field-item.field-boolean {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: auto;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon {\n font-size: 14px;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-true {\n color: var(--mj-status-success);\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-false {\n color: var(--mj-text-muted);\n}\n\nmj-entity-cards .field-item.field-boolean .field-label {\n font-size: 12px;\n text-transform: none;\n color: var(--mj-text-secondary);\n}\n\n/* Text fields */\nmj-entity-cards .field-item.field-text {\n flex: 1 1 100%;\n max-width: 100%;\n}\n\nmj-entity-cards .field-item .field-text-value {\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.4;\n word-break: break-word;\n}\n\n/* Date fields */\nmj-entity-cards .field-item .field-date {\n font-size: 13px;\n font-weight: 500;\n}\n\nmj-entity-cards .card-footer {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n/* Highlight matches - scoped to work with innerHTML */\nmj-entity-cards .highlight-match {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-radius: 2px;\n}\n\n/* Pill spacing when used as subtitle */\nmj-entity-cards .card-header-content mj-pill {\n display: block;\n margin-top: 4px;\n}\n\n/* Hidden field match indicator */\nmj-entity-cards .hidden-match-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: var(--mj-status-warning-bg);\n border-top: 1px solid var(--mj-status-warning-border);\n font-size: 11px;\n color: var(--mj-status-warning);\n}\n\nmj-entity-cards .hidden-match-indicator i {\n font-size: 10px;\n}\n\nmj-entity-cards .hidden-match-indicator span {\n font-weight: 500;\n}\n\n/* Loading state */\nmj-entity-cards .loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n padding: 40px;\n}\n\n/* No results state */\nmj-entity-cards .no-results {\n grid-column: 1 / -1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: var(--mj-text-muted);\n text-align: center;\n}\nmj-entity-cards .no-results i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\nmj-entity-cards .no-results p {\n margin: 0;\n font-size: 14px;\n}\n"] }]
815
932
  }], () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], { entity: [{
816
933
  type: Input
817
934
  }], records: [{
@@ -831,5 +948,5 @@ export class EntityCardsComponent extends BaseAngularComponent {
831
948
  }], recordOpened: [{
832
949
  type: Output
833
950
  }] }); })();
834
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityCardsComponent, { className: "EntityCardsComponent", filePath: "src/lib/entity-cards/entity-cards.component.ts", lineNumber: 37 }); })();
951
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityCardsComponent, { className: "EntityCardsComponent", filePath: "src/lib/entity-cards/entity-cards.component.ts", lineNumber: 95 }); })();
835
952
  //# sourceMappingURL=entity-cards.component.js.map