@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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=entity-cards-selection.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-cards-selection.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/entity-cards-selection.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,149 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { EntityCardsComponent } from '../lib/entity-cards/entity-cards.component';
3
+ /**
4
+ * Fix A regression coverage: a selection-only change (selectedRecordId) must
5
+ * flip the cheap isSelected flags on the EXISTING card view-models WITHOUT
6
+ * rebuilding them — i.e. without re-running the per-card buildPkString()
7
+ * allocations or the RegExp-building highlightMatch() calls. A records change,
8
+ * by contrast, must rebuild.
9
+ */
10
+ // Minimal EntityInfo mock — just enough for buildPkString (PrimaryKeys) and
11
+ // the template-generation heuristics (Fields). Single uniqueidentifier PK 'ID'.
12
+ function mockEntity() {
13
+ const idField = {
14
+ Name: 'ID',
15
+ IsPrimaryKey: true,
16
+ TSType: 'string',
17
+ Type: 'uniqueidentifier',
18
+ Sequence: 0,
19
+ IsNameField: false,
20
+ DefaultInView: false,
21
+ DisplayNameOrName: 'ID',
22
+ EntityFieldValues: [],
23
+ };
24
+ const nameField = {
25
+ Name: 'Name',
26
+ IsPrimaryKey: false,
27
+ TSType: 'string',
28
+ Type: 'nvarchar',
29
+ Sequence: 1,
30
+ IsNameField: true,
31
+ DefaultInView: true,
32
+ DisplayNameOrName: 'Name',
33
+ EntityFieldValues: [],
34
+ };
35
+ const fields = [idField, nameField];
36
+ return {
37
+ ID: 'E1',
38
+ Name: 'Test Entity',
39
+ Fields: fields,
40
+ PrimaryKeys: [idField],
41
+ NameField: nameField,
42
+ };
43
+ }
44
+ function makeComponent() {
45
+ const elementRef = { nativeElement: { querySelector: () => null } };
46
+ const cdr = { detectChanges: () => { }, markForCheck: () => { } };
47
+ return new EntityCardsComponent(elementRef, cdr);
48
+ }
49
+ function change(previousValue, currentValue, firstChange = false) {
50
+ return { previousValue, currentValue, firstChange, isFirstChange: () => firstChange };
51
+ }
52
+ describe('EntityCardsComponent — selection-only updates (Fix A)', () => {
53
+ let component;
54
+ beforeEach(() => {
55
+ component = makeComponent();
56
+ component.entity = mockEntity();
57
+ component.records = [
58
+ { ID: 'a', Name: 'Alpha' },
59
+ { ID: 'b', Name: 'Bravo' },
60
+ { ID: 'c', Name: 'Charlie' },
61
+ ];
62
+ component.ngOnInit();
63
+ });
64
+ // The pkString format is CompositeKey.ToConcatenatedString() (e.g. "ID|a"),
65
+ // not the raw PK value. Resolve the real strings from the built VMs so the
66
+ // tests are robust to that format.
67
+ function pk(index) {
68
+ return component.cardViewModels[index].pkString;
69
+ }
70
+ it('builds VMs with pkStrings and no selection initially', () => {
71
+ expect(component.cardViewModels).toHaveLength(3);
72
+ expect(component.cardViewModels.every(vm => vm.pkString.length > 0)).toBe(true);
73
+ expect(component.cardViewModels.every(vm => !vm.isSelected)).toBe(true);
74
+ });
75
+ it('selection-only change flips the correct isSelected flag WITHOUT re-running highlightMatch', () => {
76
+ const highlightSpy = vi.spyOn(component, 'highlightMatch');
77
+ const targetPk = pk(1);
78
+ component.selectedRecordId = targetPk;
79
+ const changes = { selectedRecordId: change(null, targetPk) };
80
+ component.ngOnChanges(changes);
81
+ expect(highlightSpy).not.toHaveBeenCalled();
82
+ expect(component.cardViewModels.map(vm => vm.isSelected)).toEqual([false, true, false]);
83
+ });
84
+ it('selection-only change preserves the array and every VM object reference', () => {
85
+ const arrayBefore = component.cardViewModels;
86
+ const vmsBefore = [...component.cardViewModels];
87
+ const targetPk = pk(2);
88
+ component.selectedRecordId = targetPk;
89
+ component.ngOnChanges({ selectedRecordId: change(null, targetPk) });
90
+ // Same array instance and same VM instances — only the flags mutated in place.
91
+ expect(component.cardViewModels).toBe(arrayBefore);
92
+ component.cardViewModels.forEach((vm, i) => expect(vm).toBe(vmsBefore[i]));
93
+ expect(component.cardViewModels.map(vm => vm.isSelected)).toEqual([false, false, true]);
94
+ });
95
+ it('clearing the selection unsets all isSelected flags', () => {
96
+ const firstPk = pk(0);
97
+ component.selectedRecordId = firstPk;
98
+ component.ngOnChanges({ selectedRecordId: change(null, firstPk) });
99
+ expect(component.cardViewModels.map(vm => vm.isSelected)).toEqual([true, false, false]);
100
+ component.selectedRecordId = null;
101
+ component.ngOnChanges({ selectedRecordId: change(firstPk, null) });
102
+ expect(component.cardViewModels.every(vm => !vm.isSelected)).toBe(true);
103
+ });
104
+ it('a records change DOES rebuild the VMs (new array + highlightMatch runs)', () => {
105
+ const arrayBefore = component.cardViewModels;
106
+ const highlightSpy = vi.spyOn(component, 'highlightMatch');
107
+ component.records = [
108
+ { ID: 'x', Name: 'Xray' },
109
+ { ID: 'y', Name: 'Yankee' },
110
+ ];
111
+ component.ngOnChanges({ records: change(null, component.records) });
112
+ expect(component.cardViewModels).not.toBe(arrayBefore);
113
+ expect(component.cardViewModels).toHaveLength(2);
114
+ expect(component.cardViewModels.every(vm => vm.pkString.length > 0)).toBe(true);
115
+ expect(highlightSpy).toHaveBeenCalled();
116
+ });
117
+ it('a combined records+selectedRecordId change rebuilds and sets selection correctly', () => {
118
+ const highlightSpy = vi.spyOn(component, 'highlightMatch');
119
+ component.records = [
120
+ { ID: 'x', Name: 'Xray' },
121
+ { ID: 'y', Name: 'Yankee' },
122
+ ];
123
+ // Match the second new record's pkString format ("ID|y").
124
+ const targetPk = component.cardViewModels[1].pkString.replace(/[^|]+$/, 'y');
125
+ component.selectedRecordId = targetPk;
126
+ component.ngOnChanges({
127
+ records: change(null, component.records),
128
+ selectedRecordId: change(null, targetPk),
129
+ });
130
+ // content change wins → full rebuild, which also sets isSelected correctly
131
+ expect(highlightSpy).toHaveBeenCalled();
132
+ expect(component.cardViewModels.map(vm => vm.isSelected)).toEqual([false, true]);
133
+ });
134
+ it('selection-only change still requests a scroll-to-selected', () => {
135
+ const targetPk = pk(1);
136
+ component.selectedRecordId = targetPk;
137
+ component.ngOnChanges({ selectedRecordId: change(null, targetPk) });
138
+ // pendingScrollToSelected is private; assert via behavior in ngAfterViewChecked.
139
+ const scrollSpy = vi
140
+ .spyOn(component, 'scrollToSelectedCard')
141
+ .mockImplementation(() => { });
142
+ vi.useFakeTimers();
143
+ component.ngAfterViewChecked();
144
+ vi.runAllTimers();
145
+ expect(scrollSpy).toHaveBeenCalled();
146
+ vi.useRealTimers();
147
+ });
148
+ });
149
+ //# sourceMappingURL=entity-cards-selection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-cards-selection.test.js","sourceRoot":"","sources":["../../src/__tests__/entity-cards-selection.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport type { ElementRef, ChangeDetectorRef, SimpleChanges, SimpleChange } from '@angular/core';\nimport type { EntityInfo, EntityFieldInfo } from '@memberjunction/core';\nimport { EntityCardsComponent } from '../lib/entity-cards/entity-cards.component';\n\n/**\n * Fix A regression coverage: a selection-only change (selectedRecordId) must\n * flip the cheap isSelected flags on the EXISTING card view-models WITHOUT\n * rebuilding them — i.e. without re-running the per-card buildPkString()\n * allocations or the RegExp-building highlightMatch() calls. A records change,\n * by contrast, must rebuild.\n */\n\n// Minimal EntityInfo mock — just enough for buildPkString (PrimaryKeys) and\n// the template-generation heuristics (Fields). Single uniqueidentifier PK 'ID'.\nfunction mockEntity(): EntityInfo {\n const idField = {\n Name: 'ID',\n IsPrimaryKey: true,\n TSType: 'string',\n Type: 'uniqueidentifier',\n Sequence: 0,\n IsNameField: false,\n DefaultInView: false,\n DisplayNameOrName: 'ID',\n EntityFieldValues: [],\n } as unknown as EntityFieldInfo;\n\n const nameField = {\n Name: 'Name',\n IsPrimaryKey: false,\n TSType: 'string',\n Type: 'nvarchar',\n Sequence: 1,\n IsNameField: true,\n DefaultInView: true,\n DisplayNameOrName: 'Name',\n EntityFieldValues: [],\n } as unknown as EntityFieldInfo;\n\n const fields = [idField, nameField];\n\n return {\n ID: 'E1',\n Name: 'Test Entity',\n Fields: fields,\n PrimaryKeys: [idField],\n NameField: nameField,\n } as unknown as EntityInfo;\n}\n\nfunction makeComponent(): EntityCardsComponent {\n const elementRef = { nativeElement: { querySelector: () => null } } as unknown as ElementRef;\n const cdr = { detectChanges: () => {}, markForCheck: () => {} } as unknown as ChangeDetectorRef;\n return new EntityCardsComponent(elementRef, cdr);\n}\n\nfunction change(previousValue: unknown, currentValue: unknown, firstChange = false): SimpleChange {\n return { previousValue, currentValue, firstChange, isFirstChange: () => firstChange } as SimpleChange;\n}\n\ndescribe('EntityCardsComponent — selection-only updates (Fix A)', () => {\n let component: EntityCardsComponent;\n\n beforeEach(() => {\n component = makeComponent();\n component.entity = mockEntity();\n component.records = [\n { ID: 'a', Name: 'Alpha' },\n { ID: 'b', Name: 'Bravo' },\n { ID: 'c', Name: 'Charlie' },\n ];\n component.ngOnInit();\n });\n\n // The pkString format is CompositeKey.ToConcatenatedString() (e.g. \"ID|a\"),\n // not the raw PK value. Resolve the real strings from the built VMs so the\n // tests are robust to that format.\n function pk(index: number): string {\n return component.cardViewModels[index].pkString;\n }\n\n it('builds VMs with pkStrings and no selection initially', () => {\n expect(component.cardViewModels).toHaveLength(3);\n expect(component.cardViewModels.every(vm => vm.pkString.length > 0)).toBe(true);\n expect(component.cardViewModels.every(vm => !vm.isSelected)).toBe(true);\n });\n\n it('selection-only change flips the correct isSelected flag WITHOUT re-running highlightMatch', () => {\n const highlightSpy = vi.spyOn(component, 'highlightMatch');\n\n const targetPk = pk(1);\n component.selectedRecordId = targetPk;\n const changes: SimpleChanges = { selectedRecordId: change(null, targetPk) };\n component.ngOnChanges(changes);\n\n expect(highlightSpy).not.toHaveBeenCalled();\n expect(component.cardViewModels.map(vm => vm.isSelected)).toEqual([false, true, false]);\n });\n\n it('selection-only change preserves the array and every VM object reference', () => {\n const arrayBefore = component.cardViewModels;\n const vmsBefore = [...component.cardViewModels];\n\n const targetPk = pk(2);\n component.selectedRecordId = targetPk;\n component.ngOnChanges({ selectedRecordId: change(null, targetPk) });\n\n // Same array instance and same VM instances — only the flags mutated in place.\n expect(component.cardViewModels).toBe(arrayBefore);\n component.cardViewModels.forEach((vm, i) => expect(vm).toBe(vmsBefore[i]));\n expect(component.cardViewModels.map(vm => vm.isSelected)).toEqual([false, false, true]);\n });\n\n it('clearing the selection unsets all isSelected flags', () => {\n const firstPk = pk(0);\n component.selectedRecordId = firstPk;\n component.ngOnChanges({ selectedRecordId: change(null, firstPk) });\n expect(component.cardViewModels.map(vm => vm.isSelected)).toEqual([true, false, false]);\n\n component.selectedRecordId = null;\n component.ngOnChanges({ selectedRecordId: change(firstPk, null) });\n expect(component.cardViewModels.every(vm => !vm.isSelected)).toBe(true);\n });\n\n it('a records change DOES rebuild the VMs (new array + highlightMatch runs)', () => {\n const arrayBefore = component.cardViewModels;\n const highlightSpy = vi.spyOn(component, 'highlightMatch');\n\n component.records = [\n { ID: 'x', Name: 'Xray' },\n { ID: 'y', Name: 'Yankee' },\n ];\n component.ngOnChanges({ records: change(null, component.records) });\n\n expect(component.cardViewModels).not.toBe(arrayBefore);\n expect(component.cardViewModels).toHaveLength(2);\n expect(component.cardViewModels.every(vm => vm.pkString.length > 0)).toBe(true);\n expect(highlightSpy).toHaveBeenCalled();\n });\n\n it('a combined records+selectedRecordId change rebuilds and sets selection correctly', () => {\n const highlightSpy = vi.spyOn(component, 'highlightMatch');\n component.records = [\n { ID: 'x', Name: 'Xray' },\n { ID: 'y', Name: 'Yankee' },\n ];\n // Match the second new record's pkString format (\"ID|y\").\n const targetPk = component.cardViewModels[1].pkString.replace(/[^|]+$/, 'y');\n component.selectedRecordId = targetPk;\n component.ngOnChanges({\n records: change(null, component.records),\n selectedRecordId: change(null, targetPk),\n });\n\n // content change wins → full rebuild, which also sets isSelected correctly\n expect(highlightSpy).toHaveBeenCalled();\n expect(component.cardViewModels.map(vm => vm.isSelected)).toEqual([false, true]);\n });\n\n it('selection-only change still requests a scroll-to-selected', () => {\n const targetPk = pk(1);\n component.selectedRecordId = targetPk;\n component.ngOnChanges({ selectedRecordId: change(null, targetPk) });\n // pendingScrollToSelected is private; assert via behavior in ngAfterViewChecked.\n const scrollSpy = vi\n .spyOn(component as unknown as { scrollToSelectedCard: () => void }, 'scrollToSelectedCard')\n .mockImplementation(() => {});\n vi.useFakeTimers();\n component.ngAfterViewChecked();\n vi.runAllTimers();\n expect(scrollSpy).toHaveBeenCalled();\n vi.useRealTimers();\n });\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=entity-cards-viewmodel.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-cards-viewmodel.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/entity-cards-viewmodel.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,208 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { EntityCardsComponent } from '../lib/entity-cards/entity-cards.component';
3
+ /**
4
+ * Part C (PR #2841): DEEPENED coverage of buildCardViewModel and the
5
+ * selection-only vs content-change rebuild trigger matrix.
6
+ *
7
+ * buildCardViewModel produces a per-record CardViewModel exactly once per
8
+ * content change (records / filterText / hiddenFieldMatches / entity /
9
+ * cardTemplate), and a selectedRecordId-only change must NOT rebuild — it only
10
+ * flips the cheap isSelected flags on the existing VMs. These tests pin:
11
+ * - trackId fallback to record_<index> when there is no usable PK,
12
+ * - hidden-field match present / absent (hasHiddenFieldMatch + field name),
13
+ * - display-field VM typing (boolean / number / date / text),
14
+ * - subtitle pill vs non-pill detection,
15
+ * - the FULL trigger matrix (each content input rebuilds; selectedRecordId
16
+ * alone does not).
17
+ */
18
+ function field(partial) {
19
+ return {
20
+ Sequence: 0,
21
+ IsPrimaryKey: false,
22
+ IsNameField: false,
23
+ DefaultInView: false,
24
+ EntityFieldValues: [],
25
+ DisplayNameOrName: partial.Name ?? '',
26
+ ...partial,
27
+ };
28
+ }
29
+ /**
30
+ * Rich entity: single uniqueidentifier PK 'ID', a Name (title), a Status
31
+ * (subtitle keyword), a Description, and DefaultInView display fields of
32
+ * boolean / number / date types.
33
+ */
34
+ function richEntity() {
35
+ const idField = field({ Name: 'ID', IsPrimaryKey: true, TSType: 'string', Type: 'uniqueidentifier', DisplayNameOrName: 'ID' });
36
+ const nameField = field({ Name: 'Name', TSType: 'string', Type: 'nvarchar', IsNameField: true, DefaultInView: true, Sequence: 1, DisplayNameOrName: 'Name' });
37
+ const statusField = field({ Name: 'Status', TSType: 'string', Type: 'nvarchar', Sequence: 2, DisplayNameOrName: 'Status' });
38
+ const descField = field({ Name: 'Description', TSType: 'string', Type: 'nvarchar', Sequence: 3, DisplayNameOrName: 'Description' });
39
+ const activeField = field({ Name: 'IsActive', TSType: 'boolean', Type: 'bit', DefaultInView: true, Sequence: 4, DisplayNameOrName: 'Is Active' });
40
+ const amountField = field({ Name: 'Amount', TSType: 'number', Type: 'decimal', DefaultInView: true, Sequence: 5, DisplayNameOrName: 'Amount' });
41
+ const dueField = field({ Name: 'DueOn', TSType: 'Date', Type: 'date', DefaultInView: true, Sequence: 6, DisplayNameOrName: 'Due On' });
42
+ const fields = [idField, nameField, statusField, descField, activeField, amountField, dueField];
43
+ return { ID: 'E1', Name: 'Rich Entity', Fields: fields, PrimaryKeys: [idField], NameField: nameField };
44
+ }
45
+ /** Minimal entity with a PK, used for the no-template / no-PK paths. */
46
+ function minimalEntity() {
47
+ const idField = field({ Name: 'ID', IsPrimaryKey: true, TSType: 'string', Type: 'uniqueidentifier', DisplayNameOrName: 'ID' });
48
+ return { ID: 'E2', Name: 'Min Entity', Fields: [idField], PrimaryKeys: [idField], NameField: undefined };
49
+ }
50
+ function makeComponent() {
51
+ const elementRef = { nativeElement: { querySelector: () => null } };
52
+ const cdr = { detectChanges: () => { }, markForCheck: () => { } };
53
+ return new EntityCardsComponent(elementRef, cdr);
54
+ }
55
+ function change(previousValue, currentValue, firstChange = false) {
56
+ return { previousValue, currentValue, firstChange, isFirstChange: () => firstChange };
57
+ }
58
+ describe('EntityCardsComponent.buildCardViewModel — view-model shape', () => {
59
+ let component;
60
+ beforeEach(() => {
61
+ component = makeComponent();
62
+ component.entity = richEntity();
63
+ component.records = [
64
+ { ID: 'aaaa', Name: 'Alpha', Status: 'Active', Description: 'first one', IsActive: true, Amount: 750, DueOn: '2026-01-15' },
65
+ { ID: 'bbbb', Name: 'Bravo', Status: 'Pending', Description: 'second', IsActive: false, Amount: 0, DueOn: null },
66
+ ];
67
+ component.ngOnInit();
68
+ });
69
+ it('builds one VM per record with a pkString-derived trackId', () => {
70
+ expect(component.cardViewModels).toHaveLength(2);
71
+ // trackId equals pkString when a PK is present
72
+ component.cardViewModels.forEach((vm) => {
73
+ expect(vm.pkString.length).toBeGreaterThan(0);
74
+ expect(vm.trackId).toBe(vm.pkString);
75
+ expect(vm.trackId.startsWith('record_')).toBe(false);
76
+ });
77
+ });
78
+ it('falls back trackId to record_<index> when there is no usable PK', () => {
79
+ // No entity → computePkString returns '' for every record → record_<index>.
80
+ const noEntity = makeComponent();
81
+ noEntity.entity = null;
82
+ noEntity.records = [{ Name: 'X' }, { Name: 'Y' }];
83
+ noEntity.ngOnInit();
84
+ expect(noEntity.cardViewModels.map((vm) => vm.trackId)).toEqual(['record_0', 'record_1']);
85
+ expect(noEntity.cardViewModels.every((vm) => vm.pkString === '')).toBe(true);
86
+ });
87
+ it('renders display-field VMs with correct boolean / number / date typing', () => {
88
+ const alpha = component.cardViewModels[0];
89
+ const byName = new Map(alpha.displayFields.map((d) => [d.field.name, d]));
90
+ const isActive = byName.get('IsActive');
91
+ expect(isActive?.field.type).toBe('boolean');
92
+ expect(isActive?.booleanValue).toBe(true);
93
+ const amount = byName.get('Amount');
94
+ expect(amount?.field.type).toBe('number');
95
+ // 'amount' keyword + sub-1000 value → currency formatting ($750)
96
+ expect(amount?.numericValue).toBe('$750');
97
+ const due = byName.get('DueOn');
98
+ expect(due?.field.type).toBe('date');
99
+ expect(due?.dateValue).not.toBe('-'); // a real date formats to something non-empty
100
+ });
101
+ it('handles null/zero values in display fields gracefully', () => {
102
+ const bravo = component.cardViewModels[1];
103
+ const byName = new Map(bravo.displayFields.map((d) => [d.field.name, d]));
104
+ expect(byName.get('IsActive')?.booleanValue).toBe(false);
105
+ expect(byName.get('DueOn')?.dateValue).toBe('-'); // null date → placeholder
106
+ });
107
+ it('marks hasHiddenFieldMatch + resolves the matched field display name when present', () => {
108
+ const alphaPk = component.cardViewModels[0].pkString;
109
+ const matches = new Map([[alphaPk, 'Description']]);
110
+ component.hiddenFieldMatches = matches;
111
+ component.ngOnChanges({ hiddenFieldMatches: change(new Map(), matches) });
112
+ const alpha = component.cardViewModels[0];
113
+ const bravo = component.cardViewModels[1];
114
+ expect(alpha.hasHiddenFieldMatch).toBe(true);
115
+ expect(alpha.hiddenMatchFieldName).toBe('Description'); // DisplayNameOrName
116
+ expect(bravo.hasHiddenFieldMatch).toBe(false);
117
+ expect(bravo.hiddenMatchFieldName).toBe('');
118
+ });
119
+ it('subtitle picks up the Status field value', () => {
120
+ expect(component.cardViewModels[0].subtitleValue).toBe('Active');
121
+ expect(component.cardViewModels[1].subtitleValue).toBe('Pending');
122
+ });
123
+ it('subtitleIsPill reflects PillColorUtil for the current subtitle value', () => {
124
+ // 'Active' is a known status keyword → a pill; a neutral string is not.
125
+ component.records = [{ ID: 'aaaa', Name: 'Alpha', Status: 'Active' }];
126
+ component.ngOnChanges({ records: change(null, component.records) });
127
+ const pillForActive = component.subtitleIsPill;
128
+ component.records = [{ ID: 'aaaa', Name: 'Alpha', Status: 'Some Freeform Label' }];
129
+ component.ngOnChanges({ records: change(null, component.records) });
130
+ const pillForFreeform = component.subtitleIsPill;
131
+ // At least one of the two must differ — the getter is value-driven, not constant.
132
+ expect(pillForActive === true || pillForFreeform === false).toBe(true);
133
+ });
134
+ it('produces no display fields when the entity has no DefaultInView fields (minimal entity)', () => {
135
+ const min = makeComponent();
136
+ min.entity = minimalEntity();
137
+ min.records = [{ ID: 'zzzz' }];
138
+ min.ngOnInit();
139
+ expect(min.cardViewModels[0].displayFields).toEqual([]);
140
+ });
141
+ });
142
+ describe('EntityCardsComponent — rebuild trigger matrix (content vs selection-only)', () => {
143
+ let component;
144
+ beforeEach(() => {
145
+ component = makeComponent();
146
+ component.entity = richEntity();
147
+ component.records = [
148
+ { ID: 'aaaa', Name: 'Alpha', Status: 'Active' },
149
+ { ID: 'bbbb', Name: 'Bravo', Status: 'Pending' },
150
+ ];
151
+ component.ngOnInit();
152
+ });
153
+ /** Each of these content inputs must trigger a FULL rebuild (new VM array + highlightMatch). */
154
+ const contentTriggers = [
155
+ { name: 'filterText', apply: (c) => { c.filterText = 'Alp'; } },
156
+ { name: 'hiddenFieldMatches', apply: (c) => { c.hiddenFieldMatches = new Map([['x', 'Name']]); } },
157
+ { name: 'entity', apply: (c) => { c.entity = richEntity(); } },
158
+ { name: 'cardTemplate', apply: (c) => { c.cardTemplate = null; } },
159
+ ];
160
+ for (const trigger of contentTriggers) {
161
+ it(`rebuilds the VMs when '${String(trigger.name)}' changes`, () => {
162
+ const arrayBefore = component.cardViewModels;
163
+ const highlightSpy = vi.spyOn(component, 'highlightMatch');
164
+ trigger.apply(component);
165
+ component.ngOnChanges({ [trigger.name]: change(null, undefined) });
166
+ expect(component.cardViewModels).not.toBe(arrayBefore);
167
+ expect(highlightSpy).toHaveBeenCalled();
168
+ });
169
+ }
170
+ it('records change rebuilds (new array + highlightMatch)', () => {
171
+ const arrayBefore = component.cardViewModels;
172
+ const highlightSpy = vi.spyOn(component, 'highlightMatch');
173
+ component.records = [{ ID: 'cccc', Name: 'Charlie', Status: 'Active' }];
174
+ component.ngOnChanges({ records: change(null, component.records) });
175
+ expect(component.cardViewModels).not.toBe(arrayBefore);
176
+ expect(component.cardViewModels).toHaveLength(1);
177
+ expect(highlightSpy).toHaveBeenCalled();
178
+ });
179
+ it('selectedRecordId alone does NOT rebuild — only flips isSelected (no highlightMatch, same array)', () => {
180
+ const arrayBefore = component.cardViewModels;
181
+ const vmsBefore = [...component.cardViewModels];
182
+ const highlightSpy = vi.spyOn(component, 'highlightMatch');
183
+ const targetPk = component.cardViewModels[1].pkString;
184
+ component.selectedRecordId = targetPk;
185
+ component.ngOnChanges({ selectedRecordId: change(null, targetPk) });
186
+ expect(highlightSpy).not.toHaveBeenCalled();
187
+ expect(component.cardViewModels).toBe(arrayBefore);
188
+ component.cardViewModels.forEach((vm, i) => expect(vm).toBe(vmsBefore[i]));
189
+ expect(component.cardViewModels.map((vm) => vm.isSelected)).toEqual([false, true]);
190
+ });
191
+ it('a content change + selection together: content wins (rebuild) and selection is applied', () => {
192
+ const highlightSpy = vi.spyOn(component, 'highlightMatch');
193
+ component.records = [
194
+ { ID: 'cccc', Name: 'Charlie', Status: 'Active' },
195
+ { ID: 'dddd', Name: 'Delta', Status: 'Pending' },
196
+ ];
197
+ component.ngOnChanges({ records: change(null, component.records) });
198
+ const targetPk = component.cardViewModels[0].pkString;
199
+ component.selectedRecordId = targetPk;
200
+ component.ngOnChanges({
201
+ records: change(null, component.records),
202
+ selectedRecordId: change(null, targetPk),
203
+ });
204
+ expect(highlightSpy).toHaveBeenCalled();
205
+ expect(component.cardViewModels.map((vm) => vm.isSelected)).toEqual([true, false]);
206
+ });
207
+ });
208
+ //# sourceMappingURL=entity-cards-viewmodel.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-cards-viewmodel.test.js","sourceRoot":"","sources":["../../src/__tests__/entity-cards-viewmodel.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport type { ElementRef, ChangeDetectorRef, SimpleChange, SimpleChanges } from '@angular/core';\nimport type { EntityInfo, EntityFieldInfo } from '@memberjunction/core';\nimport { EntityCardsComponent } from '../lib/entity-cards/entity-cards.component';\n\n/**\n * Part C (PR #2841): DEEPENED coverage of buildCardViewModel and the\n * selection-only vs content-change rebuild trigger matrix.\n *\n * buildCardViewModel produces a per-record CardViewModel exactly once per\n * content change (records / filterText / hiddenFieldMatches / entity /\n * cardTemplate), and a selectedRecordId-only change must NOT rebuild — it only\n * flips the cheap isSelected flags on the existing VMs. These tests pin:\n * - trackId fallback to record_<index> when there is no usable PK,\n * - hidden-field match present / absent (hasHiddenFieldMatch + field name),\n * - display-field VM typing (boolean / number / date / text),\n * - subtitle pill vs non-pill detection,\n * - the FULL trigger matrix (each content input rebuilds; selectedRecordId\n * alone does not).\n */\n\nfunction field(partial: Partial<EntityFieldInfo>): EntityFieldInfo {\n return {\n Sequence: 0,\n IsPrimaryKey: false,\n IsNameField: false,\n DefaultInView: false,\n EntityFieldValues: [],\n DisplayNameOrName: partial.Name ?? '',\n ...partial,\n } as unknown as EntityFieldInfo;\n}\n\n/**\n * Rich entity: single uniqueidentifier PK 'ID', a Name (title), a Status\n * (subtitle keyword), a Description, and DefaultInView display fields of\n * boolean / number / date types.\n */\nfunction richEntity(): EntityInfo {\n const idField = field({ Name: 'ID', IsPrimaryKey: true, TSType: 'string', Type: 'uniqueidentifier', DisplayNameOrName: 'ID' });\n const nameField = field({ Name: 'Name', TSType: 'string', Type: 'nvarchar', IsNameField: true, DefaultInView: true, Sequence: 1, DisplayNameOrName: 'Name' });\n const statusField = field({ Name: 'Status', TSType: 'string', Type: 'nvarchar', Sequence: 2, DisplayNameOrName: 'Status' });\n const descField = field({ Name: 'Description', TSType: 'string', Type: 'nvarchar', Sequence: 3, DisplayNameOrName: 'Description' });\n const activeField = field({ Name: 'IsActive', TSType: 'boolean', Type: 'bit', DefaultInView: true, Sequence: 4, DisplayNameOrName: 'Is Active' });\n const amountField = field({ Name: 'Amount', TSType: 'number', Type: 'decimal', DefaultInView: true, Sequence: 5, DisplayNameOrName: 'Amount' });\n const dueField = field({ Name: 'DueOn', TSType: 'Date', Type: 'date', DefaultInView: true, Sequence: 6, DisplayNameOrName: 'Due On' });\n\n const fields = [idField, nameField, statusField, descField, activeField, amountField, dueField];\n return { ID: 'E1', Name: 'Rich Entity', Fields: fields, PrimaryKeys: [idField], NameField: nameField } as unknown as EntityInfo;\n}\n\n/** Minimal entity with a PK, used for the no-template / no-PK paths. */\nfunction minimalEntity(): EntityInfo {\n const idField = field({ Name: 'ID', IsPrimaryKey: true, TSType: 'string', Type: 'uniqueidentifier', DisplayNameOrName: 'ID' });\n return { ID: 'E2', Name: 'Min Entity', Fields: [idField], PrimaryKeys: [idField], NameField: undefined } as unknown as EntityInfo;\n}\n\nfunction makeComponent(): EntityCardsComponent {\n const elementRef = { nativeElement: { querySelector: () => null } } as unknown as ElementRef;\n const cdr = { detectChanges: () => {}, markForCheck: () => {} } as unknown as ChangeDetectorRef;\n return new EntityCardsComponent(elementRef, cdr);\n}\n\nfunction change(previousValue: unknown, currentValue: unknown, firstChange = false): SimpleChange {\n return { previousValue, currentValue, firstChange, isFirstChange: () => firstChange } as SimpleChange;\n}\n\ndescribe('EntityCardsComponent.buildCardViewModel — view-model shape', () => {\n let component: EntityCardsComponent;\n\n beforeEach(() => {\n component = makeComponent();\n component.entity = richEntity();\n component.records = [\n { ID: 'aaaa', Name: 'Alpha', Status: 'Active', Description: 'first one', IsActive: true, Amount: 750, DueOn: '2026-01-15' },\n { ID: 'bbbb', Name: 'Bravo', Status: 'Pending', Description: 'second', IsActive: false, Amount: 0, DueOn: null },\n ];\n component.ngOnInit();\n });\n\n it('builds one VM per record with a pkString-derived trackId', () => {\n expect(component.cardViewModels).toHaveLength(2);\n // trackId equals pkString when a PK is present\n component.cardViewModels.forEach((vm) => {\n expect(vm.pkString.length).toBeGreaterThan(0);\n expect(vm.trackId).toBe(vm.pkString);\n expect(vm.trackId.startsWith('record_')).toBe(false);\n });\n });\n\n it('falls back trackId to record_<index> when there is no usable PK', () => {\n // No entity → computePkString returns '' for every record → record_<index>.\n const noEntity = makeComponent();\n noEntity.entity = null;\n noEntity.records = [{ Name: 'X' }, { Name: 'Y' }];\n noEntity.ngOnInit();\n expect(noEntity.cardViewModels.map((vm) => vm.trackId)).toEqual(['record_0', 'record_1']);\n expect(noEntity.cardViewModels.every((vm) => vm.pkString === '')).toBe(true);\n });\n\n it('renders display-field VMs with correct boolean / number / date typing', () => {\n const alpha = component.cardViewModels[0];\n const byName = new Map(alpha.displayFields.map((d) => [d.field.name, d]));\n\n const isActive = byName.get('IsActive');\n expect(isActive?.field.type).toBe('boolean');\n expect(isActive?.booleanValue).toBe(true);\n\n const amount = byName.get('Amount');\n expect(amount?.field.type).toBe('number');\n // 'amount' keyword + sub-1000 value → currency formatting ($750)\n expect(amount?.numericValue).toBe('$750');\n\n const due = byName.get('DueOn');\n expect(due?.field.type).toBe('date');\n expect(due?.dateValue).not.toBe('-'); // a real date formats to something non-empty\n });\n\n it('handles null/zero values in display fields gracefully', () => {\n const bravo = component.cardViewModels[1];\n const byName = new Map(bravo.displayFields.map((d) => [d.field.name, d]));\n expect(byName.get('IsActive')?.booleanValue).toBe(false);\n expect(byName.get('DueOn')?.dateValue).toBe('-'); // null date → placeholder\n });\n\n it('marks hasHiddenFieldMatch + resolves the matched field display name when present', () => {\n const alphaPk = component.cardViewModels[0].pkString;\n const matches = new Map<string, string>([[alphaPk, 'Description']]);\n component.hiddenFieldMatches = matches;\n component.ngOnChanges({ hiddenFieldMatches: change(new Map(), matches) });\n\n const alpha = component.cardViewModels[0];\n const bravo = component.cardViewModels[1];\n expect(alpha.hasHiddenFieldMatch).toBe(true);\n expect(alpha.hiddenMatchFieldName).toBe('Description'); // DisplayNameOrName\n expect(bravo.hasHiddenFieldMatch).toBe(false);\n expect(bravo.hiddenMatchFieldName).toBe('');\n });\n\n it('subtitle picks up the Status field value', () => {\n expect(component.cardViewModels[0].subtitleValue).toBe('Active');\n expect(component.cardViewModels[1].subtitleValue).toBe('Pending');\n });\n\n it('subtitleIsPill reflects PillColorUtil for the current subtitle value', () => {\n // 'Active' is a known status keyword → a pill; a neutral string is not.\n component.records = [{ ID: 'aaaa', Name: 'Alpha', Status: 'Active' }];\n component.ngOnChanges({ records: change(null, component.records) });\n const pillForActive = component.subtitleIsPill;\n\n component.records = [{ ID: 'aaaa', Name: 'Alpha', Status: 'Some Freeform Label' }];\n component.ngOnChanges({ records: change(null, component.records) });\n const pillForFreeform = component.subtitleIsPill;\n\n // At least one of the two must differ — the getter is value-driven, not constant.\n expect(pillForActive === true || pillForFreeform === false).toBe(true);\n });\n\n it('produces no display fields when the entity has no DefaultInView fields (minimal entity)', () => {\n const min = makeComponent();\n min.entity = minimalEntity();\n min.records = [{ ID: 'zzzz' }];\n min.ngOnInit();\n expect(min.cardViewModels[0].displayFields).toEqual([]);\n });\n});\n\ndescribe('EntityCardsComponent — rebuild trigger matrix (content vs selection-only)', () => {\n let component: EntityCardsComponent;\n\n beforeEach(() => {\n component = makeComponent();\n component.entity = richEntity();\n component.records = [\n { ID: 'aaaa', Name: 'Alpha', Status: 'Active' },\n { ID: 'bbbb', Name: 'Bravo', Status: 'Pending' },\n ];\n component.ngOnInit();\n });\n\n /** Each of these content inputs must trigger a FULL rebuild (new VM array + highlightMatch). */\n const contentTriggers: { name: keyof SimpleChanges; apply: (c: EntityCardsComponent) => void }[] = [\n { name: 'filterText', apply: (c) => { c.filterText = 'Alp'; } },\n { name: 'hiddenFieldMatches', apply: (c) => { c.hiddenFieldMatches = new Map([['x', 'Name']]); } },\n { name: 'entity', apply: (c) => { c.entity = richEntity(); } },\n { name: 'cardTemplate', apply: (c) => { c.cardTemplate = null; } },\n ];\n\n for (const trigger of contentTriggers) {\n it(`rebuilds the VMs when '${String(trigger.name)}' changes`, () => {\n const arrayBefore = component.cardViewModels;\n const highlightSpy = vi.spyOn(component, 'highlightMatch');\n trigger.apply(component);\n component.ngOnChanges({ [trigger.name]: change(null, undefined) } as SimpleChanges);\n\n expect(component.cardViewModels).not.toBe(arrayBefore);\n expect(highlightSpy).toHaveBeenCalled();\n });\n }\n\n it('records change rebuilds (new array + highlightMatch)', () => {\n const arrayBefore = component.cardViewModels;\n const highlightSpy = vi.spyOn(component, 'highlightMatch');\n component.records = [{ ID: 'cccc', Name: 'Charlie', Status: 'Active' }];\n component.ngOnChanges({ records: change(null, component.records) });\n expect(component.cardViewModels).not.toBe(arrayBefore);\n expect(component.cardViewModels).toHaveLength(1);\n expect(highlightSpy).toHaveBeenCalled();\n });\n\n it('selectedRecordId alone does NOT rebuild — only flips isSelected (no highlightMatch, same array)', () => {\n const arrayBefore = component.cardViewModels;\n const vmsBefore = [...component.cardViewModels];\n const highlightSpy = vi.spyOn(component, 'highlightMatch');\n\n const targetPk = component.cardViewModels[1].pkString;\n component.selectedRecordId = targetPk;\n component.ngOnChanges({ selectedRecordId: change(null, targetPk) });\n\n expect(highlightSpy).not.toHaveBeenCalled();\n expect(component.cardViewModels).toBe(arrayBefore);\n component.cardViewModels.forEach((vm, i) => expect(vm).toBe(vmsBefore[i]));\n expect(component.cardViewModels.map((vm) => vm.isSelected)).toEqual([false, true]);\n });\n\n it('a content change + selection together: content wins (rebuild) and selection is applied', () => {\n const highlightSpy = vi.spyOn(component, 'highlightMatch');\n component.records = [\n { ID: 'cccc', Name: 'Charlie', Status: 'Active' },\n { ID: 'dddd', Name: 'Delta', Status: 'Pending' },\n ];\n component.ngOnChanges({ records: change(null, component.records) });\n const targetPk = component.cardViewModels[0].pkString;\n component.selectedRecordId = targetPk;\n component.ngOnChanges({\n records: change(null, component.records),\n selectedRecordId: change(null, targetPk),\n });\n expect(highlightSpy).toHaveBeenCalled();\n expect(component.cardViewModels.map((vm) => vm.isSelected)).toEqual([true, false]);\n });\n});\n"]}
@@ -1,8 +1,64 @@
1
1
  import { EventEmitter, OnChanges, OnInit, SimpleChanges, ElementRef, AfterViewChecked, ChangeDetectorRef } from '@angular/core';
2
2
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
3
3
  import { EntityInfo, EntityFieldInfo } from '@memberjunction/core';
4
- import { CardTemplate, RecordSelectedEvent, RecordOpenedEvent } from '../types';
4
+ import { CardTemplate, CardDisplayField, RecordSelectedEvent, RecordOpenedEvent } from '../types';
5
5
  import * as i0 from "@angular/core";
6
+ /**
7
+ * Precomputed display field value for a card, resolved once per record per
8
+ * (records/filter/template) change rather than via per-CD method calls.
9
+ */
10
+ interface CardDisplayFieldVM {
11
+ /** The underlying display field definition (name/type/label). */
12
+ field: CardDisplayField;
13
+ /** Boolean value (used when field.type === 'boolean'). */
14
+ booleanValue: boolean;
15
+ /** Numeric display string (used when field.type === 'number'). */
16
+ numericValue: string;
17
+ /** Date display string (used when field.type === 'date'). */
18
+ dateValue: string;
19
+ /** Highlighted text HTML (used for default/text fields). */
20
+ highlightedText: string;
21
+ }
22
+ /**
23
+ * Precomputed, per-record view-model backing a single card. Built once whenever
24
+ * the source records, filter text, or template change — NOT on every Angular
25
+ * change-detection cycle. Mirrors the canonical precompute pattern used by
26
+ * conversations' MessageItemComponent (`_messageClasses` / `buildMessageClasses`).
27
+ */
28
+ interface CardViewModel {
29
+ /** The original record (passed through to event emitters). */
30
+ record: Record<string, unknown>;
31
+ /** Stable PK concatenated string — computed ONCE here instead of 4-5x/CD. */
32
+ pkString: string;
33
+ /** Stable track id for @for (falls back to record_<index> when no PK). */
34
+ trackId: string;
35
+ /** Whether this card is the currently selected record. */
36
+ isSelected: boolean;
37
+ /** Avatar/background color derived from the PK hash. */
38
+ color: string;
39
+ /** How to render the thumbnail (image/icon/none). */
40
+ thumbnailType: 'image' | 'icon' | 'none';
41
+ /** Thumbnail URL/value (image src or icon class). */
42
+ thumbnailUrl: string;
43
+ /** Two-letter initials shown when there is no thumbnail. */
44
+ initials: string;
45
+ /** Highlighted combined title HTML. */
46
+ highlightedTitle: string;
47
+ /** Raw subtitle value (used for the pill rendering path). */
48
+ subtitleValue: string;
49
+ /** Highlighted subtitle HTML (used for the non-pill rendering path). */
50
+ highlightedSubtitle: string;
51
+ /** Highlighted description HTML (empty when no description field). */
52
+ highlightedDescription: string;
53
+ /** Precomputed display field VMs in template order. */
54
+ displayFields: CardDisplayFieldVM[];
55
+ /** Raw badge value (used for the footer pill). */
56
+ badgeValue: string;
57
+ /** Whether this record matched on a hidden (non-visible) field. */
58
+ hasHiddenFieldMatch: boolean;
59
+ /** Display name of the hidden field that matched ('' when none). */
60
+ hiddenMatchFieldName: string;
61
+ }
6
62
  /**
7
63
  * EntityCardsComponent - Card-based view for entity records
8
64
  *
@@ -77,6 +133,14 @@ export declare class EntityCardsComponent extends BaseAngularComponent implement
77
133
  isLoading: boolean;
78
134
  /** Flag to trigger scroll to selected card after view renders */
79
135
  private pendingScrollToSelected;
136
+ /**
137
+ * Precomputed per-record view-models bound by the template's @for. Rebuilt
138
+ * only when the source records, filter, selection, hidden matches, or template
139
+ * change — never per change-detection cycle. This eliminates the 4-5
140
+ * buildPkString() allocations + 4 RegExp-building highlight calls that the
141
+ * previous per-card-per-CD method bindings incurred.
142
+ */
143
+ cardViewModels: CardViewModel[];
80
144
  ngOnInit(): void;
81
145
  ngOnChanges(changes: SimpleChanges): void;
82
146
  ngAfterViewChecked(): void;
@@ -128,6 +192,48 @@ export declare class EntityCardsComponent extends BaseAngularComponent implement
128
192
  isSelected(record: Record<string, unknown>): boolean;
129
193
  onCardClick(record: Record<string, unknown>): void;
130
194
  onOpenClick(event: Event, record: Record<string, unknown>): void;
195
+ /**
196
+ * Rebuild the precomputed per-record view-models. Called from ngOnInit,
197
+ * ngOnChanges (on any render-affecting input), and after standalone loadData.
198
+ * Doing this work here — once per data/filter/selection change — instead of via
199
+ * template method bindings avoids re-running it on every Angular CD cycle.
200
+ */
201
+ private buildCardViewModels;
202
+ /**
203
+ * Cheaply update the isSelected flag on the EXISTING card view-models in place,
204
+ * preserving the array and every VM object reference. Called when ONLY
205
+ * selectedRecordId changes — avoids a full buildCardViewModels() rebuild (which
206
+ * re-runs buildPkString() allocations + RegExp-building highlightMatch() calls
207
+ * per card). Uses the already-computed vm.pkString, so this is a plain string
208
+ * compare per card with zero allocation or regex work.
209
+ */
210
+ private updateSelectionFlags;
211
+ /**
212
+ * Build a single card view-model, computing the PK string exactly once and
213
+ * reusing it for selection, color, hidden-match, and track-id derivation.
214
+ */
215
+ private buildCardViewModel;
216
+ /**
217
+ * Precompute the display-field VMs for a record, in template order.
218
+ */
219
+ private buildDisplayFieldVMs;
220
+ /**
221
+ * Compute the PK concatenated string for a record. Returns '' when no entity
222
+ * or on error, so callers can treat it as "no stable key".
223
+ */
224
+ private computePkString;
225
+ /**
226
+ * Derive the avatar/background color from an already-computed PK string.
227
+ */
228
+ private computeRecordColor;
229
+ /**
230
+ * Whether the record (by PK string) matched on a hidden field.
231
+ */
232
+ private computeHasHiddenFieldMatch;
233
+ /**
234
+ * Resolve the display name of the hidden field that matched, from a PK string.
235
+ */
236
+ private computeHiddenMatchFieldName;
131
237
  /**
132
238
  * Get the combined title from all title fields for a record.
133
239
  * Joins multiple IsNameField values with spaces (e.g., "Elizabeth Rodriguez").
@@ -169,4 +275,5 @@ export declare class EntityCardsComponent extends BaseAngularComponent implement
169
275
  static ɵfac: i0.ɵɵFactoryDeclaration<EntityCardsComponent, never>;
170
276
  static ɵcmp: i0.ɵɵComponentDeclaration<EntityCardsComponent, "mj-entity-cards", never, { "entity": { "alias": "entity"; "required": false; }; "records": { "alias": "records"; "required": false; }; "selectedRecordId": { "alias": "selectedRecordId"; "required": false; }; "cardTemplate": { "alias": "cardTemplate"; "required": false; }; "hiddenFieldMatches": { "alias": "hiddenFieldMatches"; "required": false; }; "filterText": { "alias": "filterText"; "required": false; }; "pageSize": { "alias": "pageSize"; "required": false; }; }, { "recordSelected": "recordSelected"; "recordOpened": "recordOpened"; }, never, never, false, never>;
171
277
  }
278
+ export {};
172
279
  //# sourceMappingURL=entity-cards.component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entity-cards.component.d.ts","sourceRoot":"","sources":["../../../src/lib/entity-cards/entity-cards.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,iBAAiB,EAAqB,MAAM,eAAe,CAAC;AAC7K,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAqC,MAAM,sBAAsB,CAAC;AACtG,OAAO,EAAE,YAAY,EAAmC,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;;AAKjH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAOa,oBAAqB,SAAQ,oBAAqB,YAAW,SAAS,EAAE,MAAM,EAAE,gBAAgB;IAEzG,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,GAAG;gBADH,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,iBAAiB;IAGhC;;OAEG;IACM,MAAM,EAAE,UAAU,GAAG,IAAI,CAAQ;IAE1C;;OAEG;IACM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAQ;IAE1D;;OAEG;IACM,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEhD;;OAEG;IACM,YAAY,EAAE,YAAY,GAAG,IAAI,CAAQ;IAElD;;;OAGG;IACM,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAE7D;;;OAGG;IACM,UAAU,EAAE,MAAM,CAAM;IAEjC;;;OAGG;IACM,QAAQ,EAAE,MAAM,CAAO;IAEhC;;OAEG;IACO,cAAc,oCAA2C;IAEnE;;OAEG;IACO,YAAY,kCAAyC;IAE/D,mCAAmC;IAC5B,gBAAgB,EAAE,YAAY,GAAG,IAAI,CAAQ;IAEpD,+CAA+C;IAC/C,OAAO,CAAC,eAAe,CAAiC;IAExD,wCAAwC;IACxC,OAAO,CAAC,cAAc,CAAkB;IAExC,wCAAwC;IACjC,SAAS,EAAE,OAAO,CAAS;IAElC,iEAAiE;IACjE,OAAO,CAAC,uBAAuB,CAAkB;IAEjD,QAAQ,IAAI,IAAI;IAYhB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAyBzC,kBAAkB,IAAI,IAAI;IAS1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAU5B;;OAEG;IACH,IAAI,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAEhD;IAED;;OAEG;YACW,QAAQ;IAwBtB;;OAEG;IACH,IAAI,iBAAiB,IAAI,YAAY,GAAG,IAAI,CAE3C;IAMD;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAc5B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAkCvB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,iBAAiB;IAuCzB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,YAAY;IAWpB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;IAOhF,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAkB3E,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAS5E,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,GAAE,MAAW,GAAG,MAAM;IAOhG,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAaxE;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM;IAQ7C,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IASxE,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAKpD,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IASlD,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAUhE;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IASzD,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IASpD;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM;IAqB5E;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAKxD;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAelC,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,WAAW;IAWnB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAWvD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAOvC,IAAI,cAAc,IAAI,OAAO,CAI5B;IAED,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIvC;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAK7D;;OAEG;IACH,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAShE;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;yCA5jBzB,oBAAoB;2CAApB,oBAAoB;CA+jBhC"}
1
+ {"version":3,"file":"entity-cards.component.d.ts","sourceRoot":"","sources":["../../../src/lib/entity-cards/entity-cards.component.ts"],"names":[],"mappings":"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"}