@memberjunction/ng-entity-card 0.0.1 → 5.24.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.
- package/dist/lib/entity-card.component.d.ts +171 -0
- package/dist/lib/entity-card.component.d.ts.map +1 -0
- package/dist/lib/entity-card.component.js +582 -0
- package/dist/lib/entity-card.component.js.map +1 -0
- package/dist/lib/entity-card.types.d.ts +109 -0
- package/dist/lib/entity-card.types.d.ts.map +1 -0
- package/dist/lib/entity-card.types.js +258 -0
- package/dist/lib/entity-card.types.js.map +1 -0
- package/dist/public-api.d.ts +8 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +13 -0
- package/dist/public-api.js.map +1 -0
- package/package.json +32 -7
- package/README.md +0 -45
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef, inject } from '@angular/core';
|
|
2
|
+
import { Metadata } from '@memberjunction/core';
|
|
3
|
+
import { GenerateCardTemplate, GenerateCardTemplateFromMetadata, CombineTitleFields, FormatKeyAsLabel, } from './entity-card.types';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
const _forTrack0 = ($index, $item) => $item.Name;
|
|
6
|
+
function MJEntityCardComponent_Conditional_1_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
7
|
+
i0.ɵɵdomElementStart(0, "div", 10);
|
|
8
|
+
i0.ɵɵdomElement(1, "img", 12);
|
|
9
|
+
i0.ɵɵdomElementEnd();
|
|
10
|
+
} if (rf & 2) {
|
|
11
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
12
|
+
i0.ɵɵadvance();
|
|
13
|
+
i0.ɵɵdomProperty("src", ctx_r0.ThumbnailValue, i0.ɵɵsanitizeUrl);
|
|
14
|
+
} }
|
|
15
|
+
function MJEntityCardComponent_Conditional_1_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
16
|
+
i0.ɵɵdomElementStart(0, "div", 13);
|
|
17
|
+
i0.ɵɵdomElement(1, "i");
|
|
18
|
+
i0.ɵɵdomElementEnd();
|
|
19
|
+
} if (rf & 2) {
|
|
20
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
21
|
+
i0.ɵɵstyleProp("background", ctx_r0.AvatarColor);
|
|
22
|
+
i0.ɵɵadvance();
|
|
23
|
+
i0.ɵɵclassMap(ctx_r0.ThumbnailValue);
|
|
24
|
+
} }
|
|
25
|
+
function MJEntityCardComponent_Conditional_1_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
26
|
+
i0.ɵɵdomElementStart(0, "div", 13);
|
|
27
|
+
i0.ɵɵdomElement(1, "i");
|
|
28
|
+
i0.ɵɵdomElementEnd();
|
|
29
|
+
} if (rf & 2) {
|
|
30
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
31
|
+
i0.ɵɵstyleProp("background", ctx_r0.AvatarColor);
|
|
32
|
+
i0.ɵɵadvance();
|
|
33
|
+
i0.ɵɵclassMap(ctx_r0.EntityIcon);
|
|
34
|
+
} }
|
|
35
|
+
function MJEntityCardComponent_Conditional_1_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
36
|
+
i0.ɵɵdomElementStart(0, "div", 13);
|
|
37
|
+
i0.ɵɵtext(1);
|
|
38
|
+
i0.ɵɵdomElementEnd();
|
|
39
|
+
} if (rf & 2) {
|
|
40
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
41
|
+
i0.ɵɵstyleProp("background", ctx_r0.AvatarColor);
|
|
42
|
+
i0.ɵɵadvance();
|
|
43
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r0.Initials, " ");
|
|
44
|
+
} }
|
|
45
|
+
function MJEntityCardComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
46
|
+
i0.ɵɵdomElementStart(0, "div", 1);
|
|
47
|
+
i0.ɵɵconditionalCreate(1, MJEntityCardComponent_Conditional_1_Conditional_1_Template, 2, 1, "div", 10)(2, MJEntityCardComponent_Conditional_1_Conditional_2_Template, 2, 4, "div", 11)(3, MJEntityCardComponent_Conditional_1_Conditional_3_Template, 2, 4, "div", 11)(4, MJEntityCardComponent_Conditional_1_Conditional_4_Template, 2, 3, "div", 11);
|
|
48
|
+
i0.ɵɵdomElementEnd();
|
|
49
|
+
} if (rf & 2) {
|
|
50
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
51
|
+
i0.ɵɵadvance();
|
|
52
|
+
i0.ɵɵconditional(ctx_r0.HasThumbnail && ctx_r0.ThumbnailIsImage ? 1 : ctx_r0.HasThumbnail && ctx_r0.ThumbnailIsIcon ? 2 : ctx_r0.EntityIcon ? 3 : 4);
|
|
53
|
+
} }
|
|
54
|
+
function MJEntityCardComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
55
|
+
i0.ɵɵdomElementStart(0, "div", 5);
|
|
56
|
+
i0.ɵɵtext(1);
|
|
57
|
+
i0.ɵɵdomElementEnd();
|
|
58
|
+
} if (rf & 2) {
|
|
59
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
60
|
+
i0.ɵɵadvance();
|
|
61
|
+
i0.ɵɵtextInterpolate(ctx_r0.EntityDisplayName);
|
|
62
|
+
} }
|
|
63
|
+
function MJEntityCardComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
64
|
+
i0.ɵɵdomElementStart(0, "div", 6);
|
|
65
|
+
i0.ɵɵtext(1);
|
|
66
|
+
i0.ɵɵdomElementEnd();
|
|
67
|
+
} if (rf & 2) {
|
|
68
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
69
|
+
i0.ɵɵadvance();
|
|
70
|
+
i0.ɵɵtextInterpolate(ctx_r0.Subtitle);
|
|
71
|
+
} }
|
|
72
|
+
function MJEntityCardComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
73
|
+
i0.ɵɵdomElementStart(0, "div", 7);
|
|
74
|
+
i0.ɵɵtext(1);
|
|
75
|
+
i0.ɵɵdomElementEnd();
|
|
76
|
+
} if (rf & 2) {
|
|
77
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
78
|
+
i0.ɵɵadvance();
|
|
79
|
+
i0.ɵɵtextInterpolate(ctx_r0.Description);
|
|
80
|
+
} }
|
|
81
|
+
function MJEntityCardComponent_Conditional_9_For_2_Conditional_0_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
82
|
+
i0.ɵɵdomElementStart(0, "span", 16);
|
|
83
|
+
i0.ɵɵtext(1);
|
|
84
|
+
i0.ɵɵdomElementEnd();
|
|
85
|
+
} if (rf & 2) {
|
|
86
|
+
const field_r2 = i0.ɵɵnextContext(2).$implicit;
|
|
87
|
+
i0.ɵɵadvance();
|
|
88
|
+
i0.ɵɵtextInterpolate(field_r2.Label);
|
|
89
|
+
} }
|
|
90
|
+
function MJEntityCardComponent_Conditional_9_For_2_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
91
|
+
i0.ɵɵdomElementStart(0, "div", 15);
|
|
92
|
+
i0.ɵɵconditionalCreate(1, MJEntityCardComponent_Conditional_9_For_2_Conditional_0_Conditional_1_Template, 2, 1, "span", 16);
|
|
93
|
+
i0.ɵɵdomElementStart(2, "span", 17);
|
|
94
|
+
i0.ɵɵtext(3);
|
|
95
|
+
i0.ɵɵdomElementEnd()();
|
|
96
|
+
} if (rf & 2) {
|
|
97
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
98
|
+
i0.ɵɵadvance();
|
|
99
|
+
i0.ɵɵconditional(ctx_r0.ShouldShowLabels ? 1 : -1);
|
|
100
|
+
i0.ɵɵadvance(2);
|
|
101
|
+
i0.ɵɵtextInterpolate(ctx);
|
|
102
|
+
} }
|
|
103
|
+
function MJEntityCardComponent_Conditional_9_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
104
|
+
i0.ɵɵconditionalCreate(0, MJEntityCardComponent_Conditional_9_For_2_Conditional_0_Template, 4, 2, "div", 15);
|
|
105
|
+
} if (rf & 2) {
|
|
106
|
+
let tmp_11_0;
|
|
107
|
+
const field_r2 = ctx.$implicit;
|
|
108
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
109
|
+
i0.ɵɵconditional((tmp_11_0 = ctx_r0.GetFieldValue(field_r2)) ? 0 : -1, tmp_11_0);
|
|
110
|
+
} }
|
|
111
|
+
function MJEntityCardComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
112
|
+
i0.ɵɵdomElementStart(0, "div", 14);
|
|
113
|
+
i0.ɵɵrepeaterCreate(1, MJEntityCardComponent_Conditional_9_For_2_Template, 1, 1, null, null, _forTrack0);
|
|
114
|
+
i0.ɵɵdomElementEnd();
|
|
115
|
+
} if (rf & 2) {
|
|
116
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
117
|
+
i0.ɵɵclassProp("mj-ec-fields--labeled", ctx_r0.ShouldShowLabels);
|
|
118
|
+
i0.ɵɵadvance();
|
|
119
|
+
i0.ɵɵrepeater(ctx_r0.DisplayFields);
|
|
120
|
+
} }
|
|
121
|
+
function MJEntityCardComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
|
|
122
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
123
|
+
i0.ɵɵdomElementStart(0, "button", 18);
|
|
124
|
+
i0.ɵɵdomListener("click", function MJEntityCardComponent_Conditional_10_Template_button_click_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnOpenClick($event)); });
|
|
125
|
+
i0.ɵɵdomElement(1, "i", 19);
|
|
126
|
+
i0.ɵɵdomElementEnd();
|
|
127
|
+
} }
|
|
128
|
+
/**
|
|
129
|
+
* `MJEntityCardComponent` — A generic, metadata-driven entity record card.
|
|
130
|
+
*
|
|
131
|
+
* Renders a single entity record as a visual card with automatically prioritized
|
|
132
|
+
* fields based on MemberJunction entity metadata (`IsNameField`, `DefaultInView`,
|
|
133
|
+
* `Sequence`). Designed to work with both full entity records and sparse vector
|
|
134
|
+
* metadata from the Knowledge Hub.
|
|
135
|
+
*
|
|
136
|
+
* ## Variants
|
|
137
|
+
* - `compact` — Tooltip-sized. Title + 2-3 key fields. No avatar.
|
|
138
|
+
* - `card` — Grid card. Avatar + title + subtitle + display fields + badge.
|
|
139
|
+
* - `detail` — Full panel. All available fields with labels.
|
|
140
|
+
*
|
|
141
|
+
* ## Field Resolution
|
|
142
|
+
* 1. **Title**: All `IsNameField` fields combined in Sequence order
|
|
143
|
+
* 2. **Primary fields**: `DefaultInView=true` sorted by Sequence
|
|
144
|
+
* 3. **Fallback**: First 5 non-PK/FK fields when no metadata flags are set
|
|
145
|
+
*
|
|
146
|
+
* ## Events
|
|
147
|
+
* All interactions follow the `BeforeXXX` / `AfterXXX` cancelable event pattern.
|
|
148
|
+
* Set `event.Cancel = true` in a `BeforeXXX` handler to prevent the default action.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```html
|
|
152
|
+
* <!-- From entity name + vector metadata -->
|
|
153
|
+
* <mj-entity-card
|
|
154
|
+
* [EntityName]="'Members'"
|
|
155
|
+
* [Record]="vectorMetadata"
|
|
156
|
+
* Variant="card"
|
|
157
|
+
* (CardClicked)="onCardClick($event)">
|
|
158
|
+
* </mj-entity-card>
|
|
159
|
+
*
|
|
160
|
+
* <!-- From EntityInfo + full record -->
|
|
161
|
+
* <mj-entity-card
|
|
162
|
+
* [Entity]="entityInfo"
|
|
163
|
+
* [Record]="record.GetAll()"
|
|
164
|
+
* Variant="detail"
|
|
165
|
+
* [ShowOpenButton]="true"
|
|
166
|
+
* (OpenRequested)="onOpen($event)">
|
|
167
|
+
* </mj-entity-card>
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* @selector mj-entity-card
|
|
171
|
+
*/
|
|
172
|
+
export class MJEntityCardComponent {
|
|
173
|
+
cdr = inject(ChangeDetectorRef);
|
|
174
|
+
// ================================================================
|
|
175
|
+
// Data Inputs
|
|
176
|
+
// ================================================================
|
|
177
|
+
/**
|
|
178
|
+
* The entity name (e.g., "Members"). Used to look up `EntityInfo` from
|
|
179
|
+
* the MJ Metadata cache. Mutually exclusive with `Entity` — if both are
|
|
180
|
+
* provided, `Entity` takes precedence.
|
|
181
|
+
*/
|
|
182
|
+
EntityName = null;
|
|
183
|
+
/**
|
|
184
|
+
* Pre-resolved EntityInfo object. Takes precedence over `EntityName`.
|
|
185
|
+
* Use this when you already have the EntityInfo to avoid a redundant lookup.
|
|
186
|
+
*/
|
|
187
|
+
Entity = null;
|
|
188
|
+
/**
|
|
189
|
+
* The record data as a plain key-value object. Works with both full entity
|
|
190
|
+
* records (`record.GetAll()`) and sparse vector metadata.
|
|
191
|
+
*
|
|
192
|
+
* Fields not present in the record are gracefully skipped in the display.
|
|
193
|
+
*/
|
|
194
|
+
Record = {};
|
|
195
|
+
/**
|
|
196
|
+
* Visual variant controlling size, layout, and detail level.
|
|
197
|
+
* @default 'card'
|
|
198
|
+
*/
|
|
199
|
+
Variant = 'card';
|
|
200
|
+
// ================================================================
|
|
201
|
+
// Customization Inputs
|
|
202
|
+
// ================================================================
|
|
203
|
+
/**
|
|
204
|
+
* Custom card template. When provided, overrides the auto-generated template.
|
|
205
|
+
* Use `GenerateCardTemplate()` from the types module to build one programmatically.
|
|
206
|
+
*/
|
|
207
|
+
Template = null;
|
|
208
|
+
/**
|
|
209
|
+
* Maximum number of display fields in the card body.
|
|
210
|
+
* Only applies when using the auto-generated template.
|
|
211
|
+
* @default 4 for 'card', 2 for 'compact', unlimited for 'detail'
|
|
212
|
+
*/
|
|
213
|
+
MaxDisplayFields = null;
|
|
214
|
+
/**
|
|
215
|
+
* Whether to show the entity icon/avatar.
|
|
216
|
+
* @default true for 'card', false for 'compact', true for 'detail'
|
|
217
|
+
*/
|
|
218
|
+
ShowAvatar = null;
|
|
219
|
+
/**
|
|
220
|
+
* Whether to show the "Open Record" button.
|
|
221
|
+
* @default false
|
|
222
|
+
*/
|
|
223
|
+
ShowOpenButton = false;
|
|
224
|
+
/**
|
|
225
|
+
* Whether to show field labels alongside values.
|
|
226
|
+
* @default true for 'detail', false for 'compact', true for 'card'
|
|
227
|
+
*/
|
|
228
|
+
ShowFieldLabels = null;
|
|
229
|
+
/**
|
|
230
|
+
* CSS class to apply to the outer container for custom styling.
|
|
231
|
+
*/
|
|
232
|
+
CssClass = '';
|
|
233
|
+
// ================================================================
|
|
234
|
+
// Events — BeforeXXX / AfterXXX Pattern
|
|
235
|
+
// ================================================================
|
|
236
|
+
/**
|
|
237
|
+
* Fires before the card is clicked. Set `Cancel = true` to prevent
|
|
238
|
+
* the `CardClicked` event from firing.
|
|
239
|
+
*/
|
|
240
|
+
BeforeCardClick = new EventEmitter();
|
|
241
|
+
/** Fires after the card is clicked (if not canceled). */
|
|
242
|
+
CardClicked = new EventEmitter();
|
|
243
|
+
/**
|
|
244
|
+
* Fires before the "Open Record" button is clicked. Set `Cancel = true`
|
|
245
|
+
* to prevent the `OpenRequested` event from firing.
|
|
246
|
+
*/
|
|
247
|
+
BeforeOpen = new EventEmitter();
|
|
248
|
+
/** Fires when the "Open Record" button is clicked (if not canceled). */
|
|
249
|
+
OpenRequested = new EventEmitter();
|
|
250
|
+
// ================================================================
|
|
251
|
+
// Computed State
|
|
252
|
+
// ================================================================
|
|
253
|
+
/** The resolved EntityInfo (from Entity input or EntityName lookup) */
|
|
254
|
+
ResolvedEntity = null;
|
|
255
|
+
/** The effective card template (custom or auto-generated) */
|
|
256
|
+
EffectiveTemplate = null;
|
|
257
|
+
/** The combined title string */
|
|
258
|
+
Title = '';
|
|
259
|
+
/** The entity icon class (from vector metadata or entity info) */
|
|
260
|
+
EntityIcon = '';
|
|
261
|
+
/** Resolved entity display name */
|
|
262
|
+
EntityDisplayName = '';
|
|
263
|
+
// ================================================================
|
|
264
|
+
// Lifecycle
|
|
265
|
+
// ================================================================
|
|
266
|
+
ngOnChanges(changes) {
|
|
267
|
+
if (changes['Entity'] || changes['EntityName'] || changes['Record'] || changes['Template'] || changes['Variant']) {
|
|
268
|
+
this.resolve();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// ================================================================
|
|
272
|
+
// Event Handlers
|
|
273
|
+
// ================================================================
|
|
274
|
+
/** @internal Handle card click with cancelable before/after pattern */
|
|
275
|
+
OnCardClick() {
|
|
276
|
+
const payload = this.BuildEventPayload();
|
|
277
|
+
const cancelable = { Data: payload, Cancel: false };
|
|
278
|
+
this.BeforeCardClick.emit(cancelable);
|
|
279
|
+
if (!cancelable.Cancel) {
|
|
280
|
+
this.CardClicked.emit(payload);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** @internal Handle open button click with cancelable before/after pattern */
|
|
284
|
+
OnOpenClick(event) {
|
|
285
|
+
event.stopPropagation();
|
|
286
|
+
const payload = this.BuildEventPayload();
|
|
287
|
+
const cancelable = { Data: payload, Cancel: false };
|
|
288
|
+
this.BeforeOpen.emit(cancelable);
|
|
289
|
+
if (!cancelable.Cancel) {
|
|
290
|
+
this.OpenRequested.emit(payload);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// ================================================================
|
|
294
|
+
// Display Helpers (used in template)
|
|
295
|
+
// ================================================================
|
|
296
|
+
/** Get the resolved display fields, respecting maxFields for variant */
|
|
297
|
+
get DisplayFields() {
|
|
298
|
+
if (!this.EffectiveTemplate)
|
|
299
|
+
return [];
|
|
300
|
+
const max = this.ResolvedMaxFields;
|
|
301
|
+
return this.EffectiveTemplate.DisplayFields.slice(0, max);
|
|
302
|
+
}
|
|
303
|
+
/** Whether to show the avatar based on variant and input */
|
|
304
|
+
get ShouldShowAvatar() {
|
|
305
|
+
if (this.ShowAvatar != null)
|
|
306
|
+
return this.ShowAvatar;
|
|
307
|
+
return this.Variant !== 'compact';
|
|
308
|
+
}
|
|
309
|
+
/** Whether to show field labels based on variant and input */
|
|
310
|
+
get ShouldShowLabels() {
|
|
311
|
+
if (this.ShowFieldLabels != null)
|
|
312
|
+
return this.ShowFieldLabels;
|
|
313
|
+
return this.Variant !== 'compact';
|
|
314
|
+
}
|
|
315
|
+
/** Get formatted value for a display field */
|
|
316
|
+
GetFieldValue(field) {
|
|
317
|
+
const value = this.Record[field.Name];
|
|
318
|
+
if (value == null || String(value).trim() === '')
|
|
319
|
+
return '';
|
|
320
|
+
return this.FormatValue(value, field);
|
|
321
|
+
}
|
|
322
|
+
/** Get the label for a field */
|
|
323
|
+
GetFieldLabel(fieldName) {
|
|
324
|
+
return this.EffectiveTemplate?.FieldLabels[fieldName] ?? FormatKeyAsLabel(fieldName);
|
|
325
|
+
}
|
|
326
|
+
/** Get initials from the title for avatar fallback */
|
|
327
|
+
get Initials() {
|
|
328
|
+
if (!this.Title || this.Title === 'Unknown')
|
|
329
|
+
return '?';
|
|
330
|
+
const words = this.Title.split(/\s+/).filter(w => w.length > 0);
|
|
331
|
+
if (words.length === 1)
|
|
332
|
+
return words[0].substring(0, 2).toUpperCase();
|
|
333
|
+
return (words[0][0] + words[words.length - 1][0]).toUpperCase();
|
|
334
|
+
}
|
|
335
|
+
/** Get a deterministic color for the avatar based on the record */
|
|
336
|
+
get AvatarColor() {
|
|
337
|
+
const colors = [
|
|
338
|
+
'var(--mj-brand-primary)', 'var(--mj-status-success)',
|
|
339
|
+
'var(--mj-status-warning)', 'var(--mj-status-info)',
|
|
340
|
+
];
|
|
341
|
+
let hash = 0;
|
|
342
|
+
const key = this.Title + (this.Record['RecordID'] ?? '');
|
|
343
|
+
for (let i = 0; i < key.length; i++) {
|
|
344
|
+
hash = key.charCodeAt(i) + ((hash << 5) - hash);
|
|
345
|
+
}
|
|
346
|
+
return colors[Math.abs(hash) % colors.length];
|
|
347
|
+
}
|
|
348
|
+
/** Get the subtitle value */
|
|
349
|
+
get Subtitle() {
|
|
350
|
+
if (!this.EffectiveTemplate?.SubtitleField)
|
|
351
|
+
return '';
|
|
352
|
+
const v = this.Record[this.EffectiveTemplate.SubtitleField];
|
|
353
|
+
return v != null ? String(v) : '';
|
|
354
|
+
}
|
|
355
|
+
/** Get the description value */
|
|
356
|
+
get Description() {
|
|
357
|
+
if (!this.EffectiveTemplate?.DescriptionField)
|
|
358
|
+
return '';
|
|
359
|
+
const v = this.Record[this.EffectiveTemplate.DescriptionField];
|
|
360
|
+
if (v == null)
|
|
361
|
+
return '';
|
|
362
|
+
const s = String(v);
|
|
363
|
+
return s.length > 120 ? s.substring(0, 120) + '...' : s;
|
|
364
|
+
}
|
|
365
|
+
/** Check if a thumbnail is available */
|
|
366
|
+
get HasThumbnail() {
|
|
367
|
+
if (!this.EffectiveTemplate?.ThumbnailFields)
|
|
368
|
+
return false;
|
|
369
|
+
return this.EffectiveTemplate.ThumbnailFields.some(f => {
|
|
370
|
+
const v = this.Record[f];
|
|
371
|
+
return v != null && String(v).trim() !== '';
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
/** Get the thumbnail URL or icon class */
|
|
375
|
+
get ThumbnailValue() {
|
|
376
|
+
if (!this.EffectiveTemplate?.ThumbnailFields)
|
|
377
|
+
return '';
|
|
378
|
+
for (const f of this.EffectiveTemplate.ThumbnailFields) {
|
|
379
|
+
const v = this.Record[f];
|
|
380
|
+
if (v != null && String(v).trim() !== '')
|
|
381
|
+
return String(v);
|
|
382
|
+
}
|
|
383
|
+
return '';
|
|
384
|
+
}
|
|
385
|
+
/** Whether the thumbnail is an image URL */
|
|
386
|
+
get ThumbnailIsImage() {
|
|
387
|
+
const v = this.ThumbnailValue;
|
|
388
|
+
return v.startsWith('http://') || v.startsWith('https://') || v.startsWith('data:image/');
|
|
389
|
+
}
|
|
390
|
+
/** Whether the thumbnail is an icon class */
|
|
391
|
+
get ThumbnailIsIcon() {
|
|
392
|
+
const v = this.ThumbnailValue.toLowerCase();
|
|
393
|
+
return v.startsWith('fa-') || v.startsWith('fa ') || v.startsWith('fas ') || v.startsWith('far ');
|
|
394
|
+
}
|
|
395
|
+
// ================================================================
|
|
396
|
+
// Private Methods
|
|
397
|
+
// ================================================================
|
|
398
|
+
get ResolvedMaxFields() {
|
|
399
|
+
if (this.MaxDisplayFields != null)
|
|
400
|
+
return this.MaxDisplayFields;
|
|
401
|
+
switch (this.Variant) {
|
|
402
|
+
case 'compact': return 3;
|
|
403
|
+
case 'card': return 4;
|
|
404
|
+
case 'detail': return 999;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
resolve() {
|
|
408
|
+
this.ResolvedEntity = this.resolveEntity();
|
|
409
|
+
this.EffectiveTemplate = this.resolveTemplate();
|
|
410
|
+
this.Title = this.resolveTitle();
|
|
411
|
+
this.EntityIcon = this.resolveEntityIcon();
|
|
412
|
+
this.EntityDisplayName = this.resolveEntityDisplayName();
|
|
413
|
+
this.cdr.markForCheck();
|
|
414
|
+
}
|
|
415
|
+
resolveEntity() {
|
|
416
|
+
if (this.Entity)
|
|
417
|
+
return this.Entity;
|
|
418
|
+
if (!this.EntityName)
|
|
419
|
+
return null;
|
|
420
|
+
try {
|
|
421
|
+
const md = new Metadata();
|
|
422
|
+
return md.Entities.find(e => e.Name === this.EntityName) ?? null;
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
resolveTemplate() {
|
|
429
|
+
if (this.Template)
|
|
430
|
+
return this.Template;
|
|
431
|
+
const entityName = this.Entity?.Name ?? this.EntityName;
|
|
432
|
+
if (!entityName)
|
|
433
|
+
return null;
|
|
434
|
+
const metadataKeys = Object.keys(this.Record);
|
|
435
|
+
if (this.ResolvedEntity) {
|
|
436
|
+
const full = GenerateCardTemplate(this.ResolvedEntity, this.ResolvedMaxFields);
|
|
437
|
+
// Filter to fields present in the record
|
|
438
|
+
const keySet = new Set(metadataKeys);
|
|
439
|
+
return {
|
|
440
|
+
...full,
|
|
441
|
+
TitleFields: full.TitleFields.filter(f => keySet.has(f)),
|
|
442
|
+
DisplayFields: full.DisplayFields.filter(f => keySet.has(f.Name)),
|
|
443
|
+
SubtitleField: full.SubtitleField && keySet.has(full.SubtitleField) ? full.SubtitleField : null,
|
|
444
|
+
DescriptionField: full.DescriptionField && keySet.has(full.DescriptionField) ? full.DescriptionField : null,
|
|
445
|
+
ThumbnailFields: full.ThumbnailFields.filter(f => keySet.has(f)),
|
|
446
|
+
BadgeField: full.BadgeField && keySet.has(full.BadgeField) ? full.BadgeField : null,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return GenerateCardTemplateFromMetadata(entityName, metadataKeys, this.ResolvedMaxFields);
|
|
450
|
+
}
|
|
451
|
+
resolveTitle() {
|
|
452
|
+
if (!this.EffectiveTemplate || this.EffectiveTemplate.TitleFields.length === 0) {
|
|
453
|
+
return String(this.Record['Name'] ?? this.Record['Title'] ?? 'Unknown');
|
|
454
|
+
}
|
|
455
|
+
return CombineTitleFields(this.Record, this.EffectiveTemplate.TitleFields);
|
|
456
|
+
}
|
|
457
|
+
resolveEntityIcon() {
|
|
458
|
+
// From vector metadata first
|
|
459
|
+
const metaIcon = this.Record['EntityIcon'];
|
|
460
|
+
if (metaIcon && typeof metaIcon === 'string')
|
|
461
|
+
return metaIcon;
|
|
462
|
+
// From entity info
|
|
463
|
+
if (this.ResolvedEntity?.Icon)
|
|
464
|
+
return this.ResolvedEntity.Icon;
|
|
465
|
+
return 'fa-solid fa-table';
|
|
466
|
+
}
|
|
467
|
+
resolveEntityDisplayName() {
|
|
468
|
+
const metaEntity = this.Record['Entity'];
|
|
469
|
+
if (metaEntity && typeof metaEntity === 'string')
|
|
470
|
+
return metaEntity;
|
|
471
|
+
return this.ResolvedEntity?.Name ?? this.EntityName ?? '';
|
|
472
|
+
}
|
|
473
|
+
FormatValue(value, field) {
|
|
474
|
+
switch (field.Type) {
|
|
475
|
+
case 'number': {
|
|
476
|
+
const num = Number(value);
|
|
477
|
+
if (isNaN(num))
|
|
478
|
+
return String(value);
|
|
479
|
+
if (num >= 1000000)
|
|
480
|
+
return `${(num / 1000000).toFixed(1)}M`;
|
|
481
|
+
if (num >= 1000)
|
|
482
|
+
return `${(num / 1000).toFixed(1)}K`;
|
|
483
|
+
const nameLower = field.Name.toLowerCase();
|
|
484
|
+
if (['amount', 'price', 'cost', 'value', 'revenue', 'total'].some(k => nameLower.includes(k))) {
|
|
485
|
+
return `$${num.toLocaleString()}`;
|
|
486
|
+
}
|
|
487
|
+
return num.toLocaleString();
|
|
488
|
+
}
|
|
489
|
+
case 'boolean':
|
|
490
|
+
return value ? 'Yes' : 'No';
|
|
491
|
+
case 'date': {
|
|
492
|
+
try {
|
|
493
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
494
|
+
if (isNaN(d.getTime()))
|
|
495
|
+
return String(value);
|
|
496
|
+
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return String(value);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
default: {
|
|
503
|
+
const s = String(value);
|
|
504
|
+
return s.length > 60 ? s.substring(0, 60) + '...' : s;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
BuildEventPayload() {
|
|
509
|
+
return {
|
|
510
|
+
EntityName: this.EntityDisplayName,
|
|
511
|
+
Record: this.Record,
|
|
512
|
+
RecordID: String(this.Record['RecordID'] ?? this.Record['ID'] ?? ''),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
static ɵfac = function MJEntityCardComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJEntityCardComponent)(); };
|
|
516
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJEntityCardComponent, selectors: [["mj-entity-card"]], inputs: { EntityName: "EntityName", Entity: "Entity", Record: "Record", Variant: "Variant", Template: "Template", MaxDisplayFields: "MaxDisplayFields", ShowAvatar: "ShowAvatar", ShowOpenButton: "ShowOpenButton", ShowFieldLabels: "ShowFieldLabels", CssClass: "CssClass" }, outputs: { BeforeCardClick: "BeforeCardClick", CardClicked: "CardClicked", BeforeOpen: "BeforeOpen", OpenRequested: "OpenRequested" }, features: [i0.ɵɵNgOnChangesFeature], decls: 11, vars: 9, consts: [[1, "mj-entity-card", 3, "click"], [1, "mj-ec-avatar-area"], [1, "mj-ec-content"], [1, "mj-ec-header"], [1, "mj-ec-title"], [1, "mj-ec-entity"], [1, "mj-ec-subtitle"], [1, "mj-ec-description"], [1, "mj-ec-fields", 3, "mj-ec-fields--labeled"], ["title", "Open record", 1, "mj-ec-open-btn"], [1, "mj-ec-thumbnail"], [1, "mj-ec-avatar", 3, "background"], ["alt", "", 3, "src"], [1, "mj-ec-avatar"], [1, "mj-ec-fields"], [1, "mj-ec-field"], [1, "mj-ec-field-label"], [1, "mj-ec-field-value"], ["title", "Open record", 1, "mj-ec-open-btn", 3, "click"], [1, "fa-solid", "fa-external-link-alt"]], template: function MJEntityCardComponent_Template(rf, ctx) { if (rf & 1) {
|
|
517
|
+
i0.ɵɵdomElementStart(0, "div", 0);
|
|
518
|
+
i0.ɵɵdomListener("click", function MJEntityCardComponent_Template_div_click_0_listener() { return ctx.OnCardClick(); });
|
|
519
|
+
i0.ɵɵconditionalCreate(1, MJEntityCardComponent_Conditional_1_Template, 5, 1, "div", 1);
|
|
520
|
+
i0.ɵɵdomElementStart(2, "div", 2)(3, "div", 3)(4, "div", 4);
|
|
521
|
+
i0.ɵɵtext(5);
|
|
522
|
+
i0.ɵɵdomElementEnd();
|
|
523
|
+
i0.ɵɵconditionalCreate(6, MJEntityCardComponent_Conditional_6_Template, 2, 1, "div", 5);
|
|
524
|
+
i0.ɵɵdomElementEnd();
|
|
525
|
+
i0.ɵɵconditionalCreate(7, MJEntityCardComponent_Conditional_7_Template, 2, 1, "div", 6);
|
|
526
|
+
i0.ɵɵconditionalCreate(8, MJEntityCardComponent_Conditional_8_Template, 2, 1, "div", 7);
|
|
527
|
+
i0.ɵɵconditionalCreate(9, MJEntityCardComponent_Conditional_9_Template, 3, 2, "div", 8);
|
|
528
|
+
i0.ɵɵdomElementEnd();
|
|
529
|
+
i0.ɵɵconditionalCreate(10, MJEntityCardComponent_Conditional_10_Template, 2, 0, "button", 9);
|
|
530
|
+
i0.ɵɵdomElementEnd();
|
|
531
|
+
} if (rf & 2) {
|
|
532
|
+
i0.ɵɵclassMap("mj-entity-card--" + ctx.Variant + " " + ctx.CssClass);
|
|
533
|
+
i0.ɵɵadvance();
|
|
534
|
+
i0.ɵɵconditional(ctx.ShouldShowAvatar ? 1 : -1);
|
|
535
|
+
i0.ɵɵadvance(4);
|
|
536
|
+
i0.ɵɵtextInterpolate(ctx.Title);
|
|
537
|
+
i0.ɵɵadvance();
|
|
538
|
+
i0.ɵɵconditional(ctx.EntityDisplayName && ctx.Variant !== "compact" ? 6 : -1);
|
|
539
|
+
i0.ɵɵadvance();
|
|
540
|
+
i0.ɵɵconditional(ctx.Subtitle && ctx.Variant !== "compact" ? 7 : -1);
|
|
541
|
+
i0.ɵɵadvance();
|
|
542
|
+
i0.ɵɵconditional(ctx.Description && ctx.Variant === "detail" ? 8 : -1);
|
|
543
|
+
i0.ɵɵadvance();
|
|
544
|
+
i0.ɵɵconditional(ctx.DisplayFields.length > 0 ? 9 : -1);
|
|
545
|
+
i0.ɵɵadvance();
|
|
546
|
+
i0.ɵɵconditional(ctx.ShowOpenButton ? 10 : -1);
|
|
547
|
+
} }, styles: ["\n\n\n\n\n.mj-entity-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 12px;\n cursor: pointer;\n transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;\n position: relative;\n}\n\n.mj-entity-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n}\n\n\n\n\n.mj-entity-card--compact[_ngcontent-%COMP%] {\n padding: 8px 10px;\n gap: 8px;\n border-radius: 6px;\n}\n\n.mj-entity-card--compact[_ngcontent-%COMP%] .mj-ec-title[_ngcontent-%COMP%] {\n font-size: 0.85rem;\n}\n\n.mj-entity-card--compact[_ngcontent-%COMP%] .mj-ec-fields[_ngcontent-%COMP%] {\n gap: 4px;\n}\n\n.mj-entity-card--compact[_ngcontent-%COMP%] .mj-ec-field-value[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n}\n\n\n\n\n.mj-entity-card--card[_ngcontent-%COMP%] {\n padding: 14px;\n}\n\n\n\n\n.mj-entity-card--detail[_ngcontent-%COMP%] {\n padding: 16px;\n flex-direction: column;\n gap: 14px;\n cursor: default;\n}\n\n.mj-entity-card--detail[_ngcontent-%COMP%] .mj-ec-avatar-area[_ngcontent-%COMP%] {\n align-self: flex-start;\n}\n\n.mj-entity-card--detail[_ngcontent-%COMP%] .mj-ec-content[_ngcontent-%COMP%] {\n width: 100%;\n}\n\n.mj-entity-card--detail[_ngcontent-%COMP%] .mj-ec-fields[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 8px;\n}\n\n.mj-entity-card--detail[_ngcontent-%COMP%] .mj-ec-field[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n padding: 6px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.mj-entity-card--detail[_ngcontent-%COMP%] .mj-ec-field[_ngcontent-%COMP%]:last-child {\n border-bottom: none;\n}\n\n.mj-entity-card--detail[_ngcontent-%COMP%] .mj-ec-field-label[_ngcontent-%COMP%] {\n font-weight: 600;\n color: var(--mj-text-secondary);\n min-width: 120px;\n flex-shrink: 0;\n}\n\n.mj-entity-card--detail[_ngcontent-%COMP%] .mj-ec-field-value[_ngcontent-%COMP%] {\n text-align: right;\n word-break: break-word;\n}\n\n\n\n\n.mj-ec-avatar-area[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n.mj-ec-avatar[_ngcontent-%COMP%] {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-inverse, white);\n font-size: 0.8rem;\n font-weight: 700;\n letter-spacing: 0.5px;\n}\n\n.mj-ec-thumbnail[_ngcontent-%COMP%] {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.mj-ec-thumbnail[_ngcontent-%COMP%] img[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n\n\n\n.mj-ec-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.mj-ec-header[_ngcontent-%COMP%] {\n margin-bottom: 4px;\n}\n\n.mj-ec-title[_ngcontent-%COMP%] {\n font-size: 0.95rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mj-ec-entity[_ngcontent-%COMP%] {\n font-size: 0.72rem;\n color: var(--mj-brand-primary);\n font-weight: 500;\n margin-top: 1px;\n}\n\n.mj-ec-subtitle[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n color: var(--mj-text-secondary);\n margin-bottom: 6px;\n}\n\n.mj-ec-description[_ngcontent-%COMP%] {\n font-size: 0.82rem;\n color: var(--mj-text-secondary);\n line-height: 1.45;\n margin-bottom: 8px;\n}\n\n\n\n\n.mj-ec-fields[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px 12px;\n margin-top: 6px;\n}\n\n.mj-ec-fields--labeled[_ngcontent-%COMP%] .mj-ec-field[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 1px;\n}\n\n.mj-ec-field[_ngcontent-%COMP%] {\n display: flex;\n align-items: baseline;\n gap: 4px;\n}\n\n.mj-ec-field-label[_ngcontent-%COMP%] {\n font-size: 0.68rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.mj-ec-field-value[_ngcontent-%COMP%] {\n font-size: 0.82rem;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 200px;\n}\n\n\n\n\n.mj-ec-open-btn[_ngcontent-%COMP%] {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 26px;\n height: 26px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n background: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;\n font-size: 0.75rem;\n}\n\n.mj-entity-card[_ngcontent-%COMP%]:hover .mj-ec-open-btn[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n.mj-ec-open-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}"], changeDetection: 0 });
|
|
548
|
+
}
|
|
549
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJEntityCardComponent, [{
|
|
550
|
+
type: Component,
|
|
551
|
+
args: [{ standalone: true, selector: 'mj-entity-card', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"mj-entity-card\"\n [class]=\"'mj-entity-card--' + Variant + ' ' + CssClass\"\n (click)=\"OnCardClick()\">\n\n <!-- Avatar / Thumbnail -->\n @if (ShouldShowAvatar) {\n <div class=\"mj-ec-avatar-area\">\n @if (HasThumbnail && ThumbnailIsImage) {\n <div class=\"mj-ec-thumbnail\">\n <img [src]=\"ThumbnailValue\" alt=\"\" />\n </div>\n } @else if (HasThumbnail && ThumbnailIsIcon) {\n <div class=\"mj-ec-avatar\" [style.background]=\"AvatarColor\">\n <i [class]=\"ThumbnailValue\"></i>\n </div>\n } @else if (EntityIcon) {\n <div class=\"mj-ec-avatar\" [style.background]=\"AvatarColor\">\n <i [class]=\"EntityIcon\"></i>\n </div>\n } @else {\n <div class=\"mj-ec-avatar\" [style.background]=\"AvatarColor\">\n {{ Initials }}\n </div>\n }\n </div>\n }\n\n <!-- Content -->\n <div class=\"mj-ec-content\">\n <!-- Header: Title + Entity -->\n <div class=\"mj-ec-header\">\n <div class=\"mj-ec-title\">{{ Title }}</div>\n @if (EntityDisplayName && Variant !== 'compact') {\n <div class=\"mj-ec-entity\">{{ EntityDisplayName }}</div>\n }\n </div>\n\n <!-- Subtitle -->\n @if (Subtitle && Variant !== 'compact') {\n <div class=\"mj-ec-subtitle\">{{ Subtitle }}</div>\n }\n\n <!-- Description (detail variant only) -->\n @if (Description && Variant === 'detail') {\n <div class=\"mj-ec-description\">{{ Description }}</div>\n }\n\n <!-- Display Fields -->\n @if (DisplayFields.length > 0) {\n <div class=\"mj-ec-fields\" [class.mj-ec-fields--labeled]=\"ShouldShowLabels\">\n @for (field of DisplayFields; track field.Name) {\n @if (GetFieldValue(field); as value) {\n <div class=\"mj-ec-field\">\n @if (ShouldShowLabels) {\n <span class=\"mj-ec-field-label\">{{ field.Label }}</span>\n }\n <span class=\"mj-ec-field-value\">{{ value }}</span>\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Open Button -->\n @if (ShowOpenButton) {\n <button class=\"mj-ec-open-btn\" (click)=\"OnOpenClick($event)\" title=\"Open record\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n </button>\n }\n</div>\n", styles: ["/* ================================================================\n MJ Entity Card \u2014 Design Token Compliant\n ================================================================ */\n\n.mj-entity-card {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 12px;\n cursor: pointer;\n transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;\n position: relative;\n}\n\n.mj-entity-card:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n}\n\n/* \u2500\u2500 Compact variant \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mj-entity-card--compact {\n padding: 8px 10px;\n gap: 8px;\n border-radius: 6px;\n}\n\n.mj-entity-card--compact .mj-ec-title {\n font-size: 0.85rem;\n}\n\n.mj-entity-card--compact .mj-ec-fields {\n gap: 4px;\n}\n\n.mj-entity-card--compact .mj-ec-field-value {\n font-size: 0.75rem;\n}\n\n/* \u2500\u2500 Card variant (default) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mj-entity-card--card {\n padding: 14px;\n}\n\n/* \u2500\u2500 Detail variant \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mj-entity-card--detail {\n padding: 16px;\n flex-direction: column;\n gap: 14px;\n cursor: default;\n}\n\n.mj-entity-card--detail .mj-ec-avatar-area {\n align-self: flex-start;\n}\n\n.mj-entity-card--detail .mj-ec-content {\n width: 100%;\n}\n\n.mj-entity-card--detail .mj-ec-fields {\n flex-direction: column;\n gap: 8px;\n}\n\n.mj-entity-card--detail .mj-ec-field {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n padding: 6px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.mj-entity-card--detail .mj-ec-field:last-child {\n border-bottom: none;\n}\n\n.mj-entity-card--detail .mj-ec-field-label {\n font-weight: 600;\n color: var(--mj-text-secondary);\n min-width: 120px;\n flex-shrink: 0;\n}\n\n.mj-entity-card--detail .mj-ec-field-value {\n text-align: right;\n word-break: break-word;\n}\n\n/* \u2500\u2500 Avatar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mj-ec-avatar-area {\n flex-shrink: 0;\n}\n\n.mj-ec-avatar {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-inverse, white);\n font-size: 0.8rem;\n font-weight: 700;\n letter-spacing: 0.5px;\n}\n\n.mj-ec-thumbnail {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.mj-ec-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n/* \u2500\u2500 Content \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mj-ec-content {\n flex: 1;\n min-width: 0;\n}\n\n.mj-ec-header {\n margin-bottom: 4px;\n}\n\n.mj-ec-title {\n font-size: 0.95rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mj-ec-entity {\n font-size: 0.72rem;\n color: var(--mj-brand-primary);\n font-weight: 500;\n margin-top: 1px;\n}\n\n.mj-ec-subtitle {\n font-size: 0.8rem;\n color: var(--mj-text-secondary);\n margin-bottom: 6px;\n}\n\n.mj-ec-description {\n font-size: 0.82rem;\n color: var(--mj-text-secondary);\n line-height: 1.45;\n margin-bottom: 8px;\n}\n\n/* \u2500\u2500 Display Fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mj-ec-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 6px 12px;\n margin-top: 6px;\n}\n\n.mj-ec-fields--labeled .mj-ec-field {\n flex-direction: column;\n gap: 1px;\n}\n\n.mj-ec-field {\n display: flex;\n align-items: baseline;\n gap: 4px;\n}\n\n.mj-ec-field-label {\n font-size: 0.68rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.mj-ec-field-value {\n font-size: 0.82rem;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 200px;\n}\n\n/* \u2500\u2500 Open Button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mj-ec-open-btn {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 26px;\n height: 26px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n background: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;\n font-size: 0.75rem;\n}\n\n.mj-entity-card:hover .mj-ec-open-btn {\n opacity: 1;\n}\n\n.mj-ec-open-btn:hover {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n"] }]
|
|
552
|
+
}], null, { EntityName: [{
|
|
553
|
+
type: Input
|
|
554
|
+
}], Entity: [{
|
|
555
|
+
type: Input
|
|
556
|
+
}], Record: [{
|
|
557
|
+
type: Input
|
|
558
|
+
}], Variant: [{
|
|
559
|
+
type: Input
|
|
560
|
+
}], Template: [{
|
|
561
|
+
type: Input
|
|
562
|
+
}], MaxDisplayFields: [{
|
|
563
|
+
type: Input
|
|
564
|
+
}], ShowAvatar: [{
|
|
565
|
+
type: Input
|
|
566
|
+
}], ShowOpenButton: [{
|
|
567
|
+
type: Input
|
|
568
|
+
}], ShowFieldLabels: [{
|
|
569
|
+
type: Input
|
|
570
|
+
}], CssClass: [{
|
|
571
|
+
type: Input
|
|
572
|
+
}], BeforeCardClick: [{
|
|
573
|
+
type: Output
|
|
574
|
+
}], CardClicked: [{
|
|
575
|
+
type: Output
|
|
576
|
+
}], BeforeOpen: [{
|
|
577
|
+
type: Output
|
|
578
|
+
}], OpenRequested: [{
|
|
579
|
+
type: Output
|
|
580
|
+
}] }); })();
|
|
581
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJEntityCardComponent, { className: "MJEntityCardComponent", filePath: "lib/entity-card.component.ts", lineNumber: 64 }); })();
|
|
582
|
+
//# sourceMappingURL=entity-card.component.js.map
|