@memberjunction/ng-entity-viewer 6.1.4 → 6.2.0-edge.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts +18 -2
  2. package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts.map +1 -1
  3. package/dist/lib/aggregate-panel/aggregate-panel.component.js +47 -18
  4. package/dist/lib/aggregate-panel/aggregate-panel.component.js.map +1 -1
  5. package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts +22 -0
  6. package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts.map +1 -1
  7. package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js +57 -13
  8. package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js.map +1 -1
  9. package/dist/lib/entity-cards/entity-cards.component.d.ts +68 -5
  10. package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
  11. package/dist/lib/entity-cards/entity-cards.component.js +213 -73
  12. package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
  13. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +156 -14
  14. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
  15. package/dist/lib/entity-data-grid/entity-data-grid.component.js +432 -138
  16. package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
  17. package/dist/lib/entity-data-grid/events/grid-events.js +67 -67
  18. package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -1
  19. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts +112 -8
  20. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts.map +1 -1
  21. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +255 -69
  22. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
  23. package/dist/lib/entity-viewer/entity-viewer.component.d.ts +11 -0
  24. package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
  25. package/dist/lib/entity-viewer/entity-viewer.component.js +45 -8
  26. package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
  27. package/dist/lib/pill/pill.component.d.ts +13 -3
  28. package/dist/lib/pill/pill.component.d.ts.map +1 -1
  29. package/dist/lib/pill/pill.component.js +34 -10
  30. package/dist/lib/pill/pill.component.js.map +1 -1
  31. package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts +29 -3
  32. package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts.map +1 -1
  33. package/dist/lib/quick-save-dialog/quick-save-dialog.component.js +173 -114
  34. package/dist/lib/quick-save-dialog/quick-save-dialog.component.js.map +1 -1
  35. package/dist/lib/recycle-bin/recycle-bin.component.d.ts +4 -0
  36. package/dist/lib/recycle-bin/recycle-bin.component.d.ts.map +1 -1
  37. package/dist/lib/recycle-bin/recycle-bin.component.js +10 -2
  38. package/dist/lib/recycle-bin/recycle-bin.component.js.map +1 -1
  39. package/dist/lib/utils/aggregate-field.util.d.ts +21 -0
  40. package/dist/lib/utils/aggregate-field.util.d.ts.map +1 -0
  41. package/dist/lib/utils/aggregate-field.util.js +40 -0
  42. package/dist/lib/utils/aggregate-field.util.js.map +1 -0
  43. package/dist/lib/utils/highlight.util.d.ts +6 -0
  44. package/dist/lib/utils/highlight.util.d.ts.map +1 -1
  45. package/dist/lib/utils/highlight.util.js +23 -11
  46. package/dist/lib/utils/highlight.util.js.map +1 -1
  47. package/dist/lib/utils/record.util.d.ts +10 -0
  48. package/dist/lib/utils/record.util.d.ts.map +1 -1
  49. package/dist/lib/utils/record.util.js +20 -4
  50. package/dist/lib/utils/record.util.js.map +1 -1
  51. package/dist/lib/view-config-panel/view-config-panel.component.d.ts +22 -2
  52. package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -1
  53. package/dist/lib/view-config-panel/view-config-panel.component.js +54 -14
  54. package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
  55. package/dist/lib/view-selector/view-selector.component.js +2 -2
  56. package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts +15 -3
  57. package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts.map +1 -1
  58. package/dist/lib/view-types/renderers/cards-view-renderer.component.js +33 -5
  59. package/dist/lib/view-types/renderers/cards-view-renderer.component.js.map +1 -1
  60. package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts +160 -5
  61. package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts.map +1 -1
  62. package/dist/lib/view-types/renderers/grid-view-renderer.component.js +576 -36
  63. package/dist/lib/view-types/renderers/grid-view-renderer.component.js.map +1 -1
  64. package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts +16 -2
  65. package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts.map +1 -1
  66. package/dist/lib/view-types/renderers/map-view-renderer.component.js +37 -9
  67. package/dist/lib/view-types/renderers/map-view-renderer.component.js.map +1 -1
  68. package/dist/lib/view-types/view-type.contracts.js.map +1 -1
  69. package/dist/lib/view-workspace/view-workspace.component.d.ts +235 -24
  70. package/dist/lib/view-workspace/view-workspace.component.d.ts.map +1 -1
  71. package/dist/lib/view-workspace/view-workspace.component.js +600 -174
  72. package/dist/lib/view-workspace/view-workspace.component.js.map +1 -1
  73. package/dist/module.d.ts +5 -4
  74. package/dist/module.d.ts.map +1 -1
  75. package/dist/module.js +8 -1
  76. package/dist/module.js.map +1 -1
  77. package/dist/public-api.d.ts +1 -0
  78. package/dist/public-api.d.ts.map +1 -1
  79. package/dist/public-api.js +1 -0
  80. package/dist/public-api.js.map +1 -1
  81. package/package.json +21 -20
@@ -1,7 +1,7 @@
1
1
  import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
2
2
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
3
- import { EntityFieldValueListType, RunView, CoerceImageSrc } from '@memberjunction/core';
4
- import { buildCompositeKey, buildPkString, computeFieldsList } from '../utils/record.util';
3
+ import { EntityFieldValueListType, RunView, CoerceImageSrc, IsDateOnlySQLType, FormatDateOnly } from '@memberjunction/core';
4
+ import { BuildCompositeKey, BuildPkString, ComputeFieldsList } from '../utils/record.util';
5
5
  import { PillColorUtil } from '../pill/pill.component';
6
6
  import { HighlightUtil } from '../utils/highlight.util';
7
7
  import * as i0 from "@angular/core";
@@ -203,11 +203,14 @@ function EntityCardsComponent_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1
203
203
  } }
204
204
  function EntityCardsComponent_Conditional_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
205
205
  i0.ɵɵelement(0, "mj-empty-state", 5);
206
+ } if (rf & 2) {
207
+ const ctx_r2 = i0.ɵɵnextContext(2);
208
+ i0.ɵɵproperty("Title", ctx_r2.entity ? "No " + ctx_r2.entity.DisplayNamePlural + " to display" : "No records to display");
206
209
  } }
207
210
  function EntityCardsComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
208
211
  i0.ɵɵelementStart(0, "div", 2);
209
212
  i0.ɵɵrepeaterCreate(1, EntityCardsComponent_Conditional_2_For_2_Template, 15, 9, "div", 4, _forTrack0);
210
- i0.ɵɵconditionalCreate(3, EntityCardsComponent_Conditional_2_Conditional_3_Template, 1, 0, "mj-empty-state", 5);
213
+ i0.ɵɵconditionalCreate(3, EntityCardsComponent_Conditional_2_Conditional_3_Template, 1, 1, "mj-empty-state", 5);
211
214
  i0.ɵɵelementEnd();
212
215
  } if (rf & 2) {
213
216
  const ctx_r2 = i0.ɵɵnextContext();
@@ -260,12 +263,28 @@ export class EntityCardsComponent extends BaseAngularComponent {
260
263
  /**
261
264
  * Custom card template (optional - auto-generated if not provided)
262
265
  */
263
- cardTemplate = null;
266
+ CardTemplate = null;
267
+ /** @deprecated Use {@link CardTemplate}. */
268
+ set cardTemplate(value) {
269
+ this.CardTemplate = value;
270
+ }
271
+ /** @deprecated Use {@link CardTemplate}. */
272
+ get cardTemplate() {
273
+ return this.CardTemplate;
274
+ }
264
275
  /**
265
276
  * Map of record IDs to hidden field names that matched the filter
266
277
  * Used to display an indicator when a match occurred in a non-visible field
267
278
  */
268
- hiddenFieldMatches = new Map();
279
+ HiddenFieldMatches = new Map();
280
+ /** @deprecated Use {@link HiddenFieldMatches}. */
281
+ set hiddenFieldMatches(value) {
282
+ this.HiddenFieldMatches = value;
283
+ }
284
+ /** @deprecated Use {@link HiddenFieldMatches}. */
285
+ get hiddenFieldMatches() {
286
+ return this.HiddenFieldMatches;
287
+ }
269
288
  /**
270
289
  * Current filter text for highlighting matches
271
290
  * Supports SQL-style % wildcards
@@ -285,7 +304,15 @@ export class EntityCardsComponent extends BaseAngularComponent {
285
304
  */
286
305
  recordOpened = new EventEmitter();
287
306
  /** Auto-generated card template */
288
- autoCardTemplate = null;
307
+ AutoCardTemplate = null;
308
+ /** @deprecated Use {@link AutoCardTemplate}. */
309
+ get autoCardTemplate() {
310
+ return this.AutoCardTemplate;
311
+ }
312
+ /** @deprecated Use {@link AutoCardTemplate}. */
313
+ set autoCardTemplate(value) {
314
+ this.AutoCardTemplate = value;
315
+ }
289
316
  /** Internal records when loading standalone */
290
317
  internalRecords = [];
291
318
  /** Track if we're in standalone mode */
@@ -301,11 +328,19 @@ export class EntityCardsComponent extends BaseAngularComponent {
301
328
  * buildPkString() allocations + 4 RegExp-building highlight calls that the
302
329
  * previous per-card-per-CD method bindings incurred.
303
330
  */
304
- cardViewModels = [];
331
+ CardViewModels = [];
332
+ /** @deprecated Use {@link CardViewModels}. */
333
+ get cardViewModels() {
334
+ return this.CardViewModels;
335
+ }
336
+ /** @deprecated Use {@link CardViewModels}. */
337
+ set cardViewModels(value) {
338
+ this.CardViewModels = value;
339
+ }
305
340
  ngOnInit() {
306
341
  this.standaloneMode = this.records === null;
307
- if (this.entity?.Fields && !this.effectiveTemplate) {
308
- this.autoCardTemplate = this.generateCardTemplate(this.entity);
342
+ if (this.entity?.Fields && !this.EffectiveTemplate) {
343
+ this.AutoCardTemplate = this.generateCardTemplate(this.entity);
309
344
  }
310
345
  if (this.standaloneMode && this.entity) {
311
346
  this.loadData();
@@ -314,10 +349,10 @@ export class EntityCardsComponent extends BaseAngularComponent {
314
349
  }
315
350
  ngOnChanges(changes) {
316
351
  if (changes['entity'] && this.entity?.Fields) {
317
- this.autoCardTemplate = this.generateCardTemplate(this.entity);
352
+ this.AutoCardTemplate = this.generateCardTemplate(this.entity);
318
353
  }
319
354
  else if (changes['entity'] && !this.entity) {
320
- this.autoCardTemplate = null;
355
+ this.AutoCardTemplate = null;
321
356
  }
322
357
  if (changes['entity'] && this.standaloneMode && this.entity) {
323
358
  this.loadData();
@@ -374,9 +409,13 @@ export class EntityCardsComponent extends BaseAngularComponent {
374
409
  /**
375
410
  * Get effective records (external or internal)
376
411
  */
377
- get effectiveRecords() {
412
+ get EffectiveRecords() {
378
413
  return this.records ?? this.internalRecords;
379
414
  }
415
+ /** @deprecated Use {@link EffectiveRecords}. */
416
+ get effectiveRecords() {
417
+ return this.EffectiveRecords;
418
+ }
380
419
  /**
381
420
  * Load data in standalone mode
382
421
  */
@@ -389,7 +428,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
389
428
  const result = await rv.RunView({
390
429
  EntityName: this.entity.Name,
391
430
  ResultType: 'simple',
392
- Fields: computeFieldsList(this.entity),
431
+ Fields: ComputeFieldsList(this.entity),
393
432
  MaxRows: this.pageSize
394
433
  });
395
434
  if (result.Success) {
@@ -407,8 +446,12 @@ export class EntityCardsComponent extends BaseAngularComponent {
407
446
  /**
408
447
  * Get the effective card template (custom or auto-generated)
409
448
  */
449
+ get EffectiveTemplate() {
450
+ return this.CardTemplate || this.AutoCardTemplate;
451
+ }
452
+ /** @deprecated Use {@link EffectiveTemplate}. */
410
453
  get effectiveTemplate() {
411
- return this.cardTemplate || this.autoCardTemplate;
454
+ return this.EffectiveTemplate;
412
455
  }
413
456
  // ========================================
414
457
  // TEMPLATE GENERATION
@@ -491,7 +534,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
491
534
  displayFields.push({
492
535
  name: field.Name,
493
536
  type: this.getFieldType(field),
494
- label: this.getFieldLabel(field)
537
+ label: this.GetFieldLabel(field)
495
538
  });
496
539
  }
497
540
  if (displayFields.length >= 2)
@@ -506,7 +549,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
506
549
  displayFields.push({
507
550
  name: field.Name,
508
551
  type: this.getFieldType(field),
509
- label: this.getFieldLabel(field)
552
+ label: this.GetFieldLabel(field)
510
553
  });
511
554
  }
512
555
  }
@@ -547,7 +590,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
547
590
  // ========================================
548
591
  // VALUE FORMATTING
549
592
  // ========================================
550
- getFieldValue(record, fieldName) {
593
+ GetFieldValue(record, fieldName) {
551
594
  if (!fieldName)
552
595
  return '';
553
596
  const value = record[fieldName];
@@ -555,7 +598,11 @@ export class EntityCardsComponent extends BaseAngularComponent {
555
598
  return '';
556
599
  return String(value);
557
600
  }
558
- getNumericValue(record, fieldName) {
601
+ /** @deprecated Use {@link GetFieldValue}. */
602
+ getFieldValue(record, fieldName) {
603
+ return this.GetFieldValue(record, fieldName);
604
+ }
605
+ GetNumericValue(record, fieldName) {
559
606
  const value = record[fieldName];
560
607
  if (value === null || value === undefined)
561
608
  return '-';
@@ -572,7 +619,11 @@ export class EntityCardsComponent extends BaseAngularComponent {
572
619
  }
573
620
  return num.toLocaleString();
574
621
  }
575
- getBooleanValue(record, fieldName) {
622
+ /** @deprecated Use {@link GetNumericValue}. */
623
+ getNumericValue(record, fieldName) {
624
+ return this.GetNumericValue(record, fieldName);
625
+ }
626
+ GetBooleanValue(record, fieldName) {
576
627
  const value = record[fieldName];
577
628
  if (value === null || value === undefined)
578
629
  return false;
@@ -584,15 +635,23 @@ export class EntityCardsComponent extends BaseAngularComponent {
584
635
  return value.toLowerCase() === 'true' || value === '1';
585
636
  return Boolean(value);
586
637
  }
587
- getTextValue(record, fieldName, maxLength = 50) {
588
- const value = this.getFieldValue(record, fieldName);
638
+ /** @deprecated Use {@link GetBooleanValue}. */
639
+ getBooleanValue(record, fieldName) {
640
+ return this.GetBooleanValue(record, fieldName);
641
+ }
642
+ GetTextValue(record, fieldName, maxLength = 50) {
643
+ const value = this.GetFieldValue(record, fieldName);
589
644
  if (!value)
590
645
  return '-';
591
646
  if (value.length <= maxLength)
592
647
  return value;
593
648
  return value.substring(0, maxLength) + '...';
594
649
  }
595
- getDateValue(record, fieldName) {
650
+ /** @deprecated Use {@link GetTextValue}. */
651
+ getTextValue(record, fieldName, maxLength = 50) {
652
+ return this.GetTextValue(record, fieldName, maxLength);
653
+ }
654
+ GetDateValue(record, fieldName) {
596
655
  const value = record[fieldName];
597
656
  if (value === null || value === undefined)
598
657
  return '-';
@@ -600,56 +659,89 @@ export class EntityCardsComponent extends BaseAngularComponent {
600
659
  const date = value instanceof Date ? value : new Date(value);
601
660
  if (isNaN(date.getTime()))
602
661
  return String(value);
603
- return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
662
+ const options = { month: 'short', day: 'numeric', year: 'numeric' };
663
+ // A `date` column is a calendar day that arrives as UTC midnight; a local-zone formatter
664
+ // would land on the previous day for every reader west of Greenwich (MJ#4210). A timestamp
665
+ // names an instant and stays in local time.
666
+ if (this.isDateOnlyField(fieldName))
667
+ return FormatDateOnly(date, options);
668
+ return date.toLocaleDateString(undefined, options);
604
669
  }
605
670
  catch {
606
671
  return String(value);
607
672
  }
608
673
  }
674
+ /** @deprecated Use {@link GetDateValue}. */
675
+ getDateValue(record, fieldName) {
676
+ return this.GetDateValue(record, fieldName);
677
+ }
678
+ isDateOnlyField(fieldName) {
679
+ return IsDateOnlySQLType(this.entity?.Fields.find(f => f.Name === fieldName)?.Type);
680
+ }
609
681
  /**
610
682
  * Get display label for a field using EntityFieldInfo's built-in DisplayNameOrName property
611
683
  */
612
- getFieldLabel(field) {
684
+ GetFieldLabel(field) {
613
685
  return field.DisplayNameOrName;
614
686
  }
687
+ /** @deprecated Use {@link GetFieldLabel}. */
688
+ getFieldLabel(field) {
689
+ return this.GetFieldLabel(field);
690
+ }
615
691
  // ========================================
616
692
  // CARD DISPLAY HELPERS
617
693
  // ========================================
618
- getRecordTrackId(record, index) {
694
+ GetRecordTrackId(record, index) {
619
695
  if (!this.entity)
620
696
  return `record_${index}`;
621
697
  try {
622
- const pk = buildPkString(record, this.entity);
698
+ const pk = BuildPkString(record, this.entity);
623
699
  if (pk && pk.trim().length > 0)
624
700
  return pk;
625
701
  }
626
702
  catch { /* ignore */ }
627
703
  return `record_${index}`;
628
704
  }
629
- isSelected(record) {
705
+ /** @deprecated Use {@link GetRecordTrackId}. */
706
+ getRecordTrackId(record, index) {
707
+ return this.GetRecordTrackId(record, index);
708
+ }
709
+ IsSelected(record) {
630
710
  if (!this.entity)
631
711
  return false;
632
- return buildPkString(record, this.entity) === this.selectedRecordId;
712
+ return BuildPkString(record, this.entity) === this.selectedRecordId;
633
713
  }
634
- onCardClick(record) {
714
+ /** @deprecated Use {@link IsSelected}. */
715
+ isSelected(record) {
716
+ return this.IsSelected(record);
717
+ }
718
+ OnCardClick(record) {
635
719
  if (!this.entity)
636
720
  return;
637
721
  this.recordSelected.emit({
638
722
  record,
639
723
  entity: this.entity,
640
- compositeKey: buildCompositeKey(record, this.entity)
724
+ compositeKey: BuildCompositeKey(record, this.entity)
641
725
  });
642
726
  }
643
- onOpenClick(event, record) {
727
+ /** @deprecated Use {@link OnCardClick}. */
728
+ onCardClick(record) {
729
+ return this.OnCardClick(record);
730
+ }
731
+ OnOpenClick(event, record) {
644
732
  event.stopPropagation();
645
733
  if (!this.entity)
646
734
  return;
647
735
  this.recordOpened.emit({
648
736
  record,
649
737
  entity: this.entity,
650
- compositeKey: buildCompositeKey(record, this.entity)
738
+ compositeKey: BuildCompositeKey(record, this.entity)
651
739
  });
652
740
  }
741
+ /** @deprecated Use {@link OnOpenClick}. */
742
+ onOpenClick(event, record) {
743
+ return this.OnOpenClick(event, record);
744
+ }
653
745
  // ========================================
654
746
  // PRECOMPUTED VIEW-MODELS (perf)
655
747
  // ========================================
@@ -660,8 +752,8 @@ export class EntityCardsComponent extends BaseAngularComponent {
660
752
  * template method bindings avoids re-running it on every Angular CD cycle.
661
753
  */
662
754
  buildCardViewModels() {
663
- const records = this.effectiveRecords;
664
- this.cardViewModels = records.map((record, index) => this.buildCardViewModel(record, index));
755
+ const records = this.EffectiveRecords;
756
+ this.CardViewModels = records.map((record, index) => this.buildCardViewModel(record, index));
665
757
  }
666
758
  /**
667
759
  * Cheaply update the isSelected flag on the EXISTING card view-models in place,
@@ -672,7 +764,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
672
764
  * compare per card with zero allocation or regex work.
673
765
  */
674
766
  updateSelectionFlags() {
675
- for (const vm of this.cardViewModels) {
767
+ for (const vm of this.CardViewModels) {
676
768
  vm.isSelected = vm.pkString.length > 0 && vm.pkString === this.selectedRecordId;
677
769
  }
678
770
  }
@@ -683,9 +775,9 @@ export class EntityCardsComponent extends BaseAngularComponent {
683
775
  buildCardViewModel(record, index) {
684
776
  const pkString = this.computePkString(record);
685
777
  const trackId = pkString && pkString.trim().length > 0 ? pkString : `record_${index}`;
686
- const template = this.effectiveTemplate;
778
+ const template = this.EffectiveTemplate;
687
779
  const subtitleField = template?.subtitleField ?? null;
688
- const subtitleValue = subtitleField ? this.getFieldValue(record, subtitleField) : '';
780
+ const subtitleValue = subtitleField ? this.GetFieldValue(record, subtitleField) : '';
689
781
  const descriptionField = template?.descriptionField ?? null;
690
782
  return {
691
783
  record,
@@ -693,17 +785,17 @@ export class EntityCardsComponent extends BaseAngularComponent {
693
785
  trackId,
694
786
  isSelected: pkString.length > 0 && pkString === this.selectedRecordId,
695
787
  color: this.computeRecordColor(pkString),
696
- thumbnailType: this.getThumbnailType(record),
697
- thumbnailUrl: this.getThumbnailUrl(record),
698
- initials: this.getInitials(record),
699
- highlightedTitle: this.highlightMatch(this.getCombinedTitle(record)),
788
+ thumbnailType: this.GetThumbnailType(record),
789
+ thumbnailUrl: this.GetThumbnailUrl(record),
790
+ initials: this.GetInitials(record),
791
+ highlightedTitle: this.HighlightMatch(this.GetCombinedTitle(record)),
700
792
  subtitleValue,
701
- highlightedSubtitle: this.highlightMatch(subtitleValue),
793
+ highlightedSubtitle: this.HighlightMatch(subtitleValue),
702
794
  highlightedDescription: descriptionField
703
- ? this.highlightMatch(this.getTextValue(record, descriptionField, 100))
795
+ ? this.HighlightMatch(this.GetTextValue(record, descriptionField, 100))
704
796
  : '',
705
797
  displayFields: this.buildDisplayFieldVMs(record, template),
706
- badgeValue: template?.badgeField ? this.getFieldValue(record, template.badgeField) : '',
798
+ badgeValue: template?.badgeField ? this.GetFieldValue(record, template.badgeField) : '',
707
799
  hasHiddenFieldMatch: this.computeHasHiddenFieldMatch(pkString),
708
800
  hiddenMatchFieldName: this.computeHiddenMatchFieldName(pkString)
709
801
  };
@@ -716,10 +808,10 @@ export class EntityCardsComponent extends BaseAngularComponent {
716
808
  return [];
717
809
  return template.displayFields.map(field => ({
718
810
  field,
719
- booleanValue: this.getBooleanValue(record, field.name),
720
- numericValue: this.getNumericValue(record, field.name),
721
- dateValue: this.getDateValue(record, field.name),
722
- highlightedText: this.highlightMatch(this.getTextValue(record, field.name, 40))
811
+ booleanValue: this.GetBooleanValue(record, field.name),
812
+ numericValue: this.GetNumericValue(record, field.name),
813
+ dateValue: this.GetDateValue(record, field.name),
814
+ highlightedText: this.HighlightMatch(this.GetTextValue(record, field.name, 40))
723
815
  }));
724
816
  }
725
817
  /**
@@ -730,7 +822,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
730
822
  if (!this.entity)
731
823
  return '';
732
824
  try {
733
- return buildPkString(record, this.entity);
825
+ return BuildPkString(record, this.entity);
734
826
  }
735
827
  catch {
736
828
  return '';
@@ -755,7 +847,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
755
847
  computeHasHiddenFieldMatch(pkString) {
756
848
  if (!this.entity || !pkString)
757
849
  return false;
758
- return this.hiddenFieldMatches.has(pkString);
850
+ return this.HiddenFieldMatches.has(pkString);
759
851
  }
760
852
  /**
761
853
  * Resolve the display name of the hidden field that matched, from a PK string.
@@ -763,7 +855,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
763
855
  computeHiddenMatchFieldName(pkString) {
764
856
  if (!this.entity || !pkString)
765
857
  return '';
766
- const fieldName = this.hiddenFieldMatches.get(pkString);
858
+ const fieldName = this.HiddenFieldMatches.get(pkString);
767
859
  if (!fieldName)
768
860
  return '';
769
861
  const field = this.entity.Fields.find(f => f.Name === fieldName);
@@ -773,17 +865,21 @@ export class EntityCardsComponent extends BaseAngularComponent {
773
865
  * Get the combined title from all title fields for a record.
774
866
  * Joins multiple IsNameField values with spaces (e.g., "Elizabeth Rodriguez").
775
867
  */
776
- getCombinedTitle(record) {
777
- const template = this.effectiveTemplate;
868
+ GetCombinedTitle(record) {
869
+ const template = this.EffectiveTemplate;
778
870
  if (!template?.titleFields || template.titleFields.length === 0)
779
871
  return '';
780
872
  const parts = template.titleFields
781
- .map(f => this.getFieldValue(record, f))
873
+ .map(f => this.GetFieldValue(record, f))
782
874
  .filter(v => v && v.trim().length > 0);
783
875
  return parts.length > 0 ? parts.join(' ') : '';
784
876
  }
785
- getInitials(record) {
786
- const title = this.getCombinedTitle(record);
877
+ /** @deprecated Use {@link GetCombinedTitle}. */
878
+ getCombinedTitle(record) {
879
+ return this.GetCombinedTitle(record);
880
+ }
881
+ GetInitials(record) {
882
+ const title = this.GetCombinedTitle(record);
787
883
  if (!title)
788
884
  return '?';
789
885
  const words = title.split(/\s+/).filter(w => w.length > 0);
@@ -791,10 +887,14 @@ export class EntityCardsComponent extends BaseAngularComponent {
791
887
  return words[0].substring(0, 2).toUpperCase();
792
888
  return (words[0][0] + words[words.length - 1][0]).toUpperCase();
793
889
  }
890
+ /** @deprecated Use {@link GetInitials}. */
891
+ getInitials(record) {
892
+ return this.GetInitials(record);
893
+ }
794
894
  /**
795
895
  * Get the thumbnail type for a record, with per-record fallback through thumbnailFields
796
896
  */
797
- getThumbnailType(record) {
897
+ GetThumbnailType(record) {
798
898
  const fieldInfo = this.getEffectiveThumbnailField(record);
799
899
  if (!fieldInfo)
800
900
  return 'none';
@@ -810,26 +910,34 @@ export class EntityCardsComponent extends BaseAngularComponent {
810
910
  return 'icon';
811
911
  return 'none';
812
912
  }
913
+ /** @deprecated Use {@link GetThumbnailType}. */
914
+ getThumbnailType(record) {
915
+ return this.GetThumbnailType(record);
916
+ }
813
917
  /**
814
918
  * Get the thumbnail URL/value for a record, with per-record fallback
815
919
  */
816
- getThumbnailUrl(record) {
920
+ GetThumbnailUrl(record) {
817
921
  const fieldInfo = this.getEffectiveThumbnailField(record);
818
922
  if (!fieldInfo)
819
923
  return '';
820
924
  return CoerceImageSrc(fieldInfo.value) || fieldInfo.value;
821
925
  }
926
+ /** @deprecated Use {@link GetThumbnailUrl}. */
927
+ getThumbnailUrl(record) {
928
+ return this.GetThumbnailUrl(record);
929
+ }
822
930
  /**
823
931
  * Find the first thumbnail field that has a value for this record
824
932
  * Returns both the field name and value for type determination
825
933
  */
826
934
  getEffectiveThumbnailField(record) {
827
- const template = this.effectiveTemplate;
935
+ const template = this.EffectiveTemplate;
828
936
  if (!template?.thumbnailFields || template.thumbnailFields.length === 0)
829
937
  return null;
830
938
  // Try each field in priority order until we find one with a value
831
939
  for (const fieldName of template.thumbnailFields) {
832
- const value = this.getFieldValue(record, fieldName);
940
+ const value = this.GetFieldValue(record, fieldName);
833
941
  if (value && value.trim() !== '') {
834
942
  return { fieldName, value };
835
943
  }
@@ -853,10 +961,14 @@ export class EntityCardsComponent extends BaseAngularComponent {
853
961
  return true;
854
962
  return false;
855
963
  }
856
- getRecordColor(record) {
964
+ GetRecordColor(record) {
857
965
  return this.computeRecordColor(this.computePkString(record));
858
966
  }
859
- isEnumField(fieldName) {
967
+ /** @deprecated Use {@link GetRecordColor}. */
968
+ getRecordColor(record) {
969
+ return this.GetRecordColor(record);
970
+ }
971
+ IsEnumField(fieldName) {
860
972
  if (!this.entity)
861
973
  return false;
862
974
  const field = this.entity.Fields.find(f => f.Name === fieldName);
@@ -864,45 +976,69 @@ export class EntityCardsComponent extends BaseAngularComponent {
864
976
  return false;
865
977
  return field.ValueListTypeEnum !== EntityFieldValueListType.None && field.EntityFieldValues.length > 0;
866
978
  }
867
- get subtitleIsPill() {
868
- const template = this.effectiveTemplate;
979
+ /** @deprecated Use {@link IsEnumField}. */
980
+ isEnumField(fieldName) {
981
+ return this.IsEnumField(fieldName);
982
+ }
983
+ get SubtitleIsPill() {
984
+ const template = this.EffectiveTemplate;
869
985
  if (!template?.subtitleField || !this.entity)
870
986
  return false;
871
- return this.isEnumField(template.subtitleField);
987
+ return this.IsEnumField(template.subtitleField);
872
988
  }
873
- getPillColorType(value) {
989
+ /** @deprecated Use {@link SubtitleIsPill}. */
990
+ get subtitleIsPill() {
991
+ return this.SubtitleIsPill;
992
+ }
993
+ GetPillColorType(value) {
874
994
  return PillColorUtil.getColorType(value);
875
995
  }
996
+ /** @deprecated Use {@link GetPillColorType}. */
997
+ getPillColorType(value) {
998
+ return this.GetPillColorType(value);
999
+ }
876
1000
  /**
877
1001
  * Check if a record matched on a hidden field
878
1002
  */
879
- hasHiddenFieldMatch(record) {
1003
+ HasHiddenFieldMatch(record) {
880
1004
  if (!this.entity)
881
1005
  return false;
882
- return this.hiddenFieldMatches.has(buildPkString(record, this.entity));
1006
+ return this.HiddenFieldMatches.has(BuildPkString(record, this.entity));
1007
+ }
1008
+ /** @deprecated Use {@link HasHiddenFieldMatch}. */
1009
+ hasHiddenFieldMatch(record) {
1010
+ return this.HasHiddenFieldMatch(record);
883
1011
  }
884
1012
  /**
885
1013
  * Get the display name of the hidden field that matched
886
1014
  */
887
- getHiddenMatchFieldName(record) {
1015
+ GetHiddenMatchFieldName(record) {
888
1016
  if (!this.entity)
889
1017
  return '';
890
- const fieldName = this.hiddenFieldMatches.get(buildPkString(record, this.entity));
1018
+ const fieldName = this.HiddenFieldMatches.get(BuildPkString(record, this.entity));
891
1019
  if (!fieldName || !this.entity)
892
1020
  return '';
893
1021
  // Look up the field in entity metadata and use DisplayNameOrName
894
1022
  const field = this.entity.Fields.find(f => f.Name === fieldName);
895
1023
  return field ? field.DisplayNameOrName : fieldName;
896
1024
  }
1025
+ /** @deprecated Use {@link GetHiddenMatchFieldName}. */
1026
+ getHiddenMatchFieldName(record) {
1027
+ return this.GetHiddenMatchFieldName(record);
1028
+ }
897
1029
  /**
898
1030
  * Highlight matching text in a string based on the filter text
899
1031
  * Uses HighlightUtil which only highlights if the text actually matches the pattern
900
1032
  */
901
- highlightMatch(text) {
1033
+ HighlightMatch(text) {
902
1034
  return HighlightUtil.highlight(text, this.filterText, false);
903
1035
  }
1036
+ /** @deprecated Use {@link HighlightMatch}. */
1037
+ highlightMatch(text) {
1038
+ return this.HighlightMatch(text);
1039
+ }
904
1040
  static ɵfac = function EntityCardsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EntityCardsComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
905
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EntityCardsComponent, selectors: [["mj-entity-cards"]], inputs: { entity: "entity", records: "records", selectedRecordId: "selectedRecordId", cardTemplate: "cardTemplate", hiddenFieldMatches: "hiddenFieldMatches", filterText: "filterText", pageSize: "pageSize" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 3, vars: 1, consts: [[1, "cards-view-wrapper"], [1, "loading-container"], [1, "cards-container"], ["text", "Loading records..."], [1, "data-card", 3, "selected"], ["Variant", "no-results", "Title", "No records to display", 1, "no-results"], [1, "data-card", 3, "click"], [1, "card-header"], [1, "card-thumbnail"], [1, "card-avatar", 3, "background-color"], [1, "card-header-content"], [1, "card-title", 3, "innerHTML"], ["title", "Open Record", 1, "card-open-btn", 3, "click"], [1, "fa-solid", "fa-external-link-alt"], [1, "card-body"], [1, "card-description", 3, "innerHTML"], [1, "card-fields"], [1, "card-footer"], [1, "hidden-match-indicator", 3, "title"], ["alt", "", 3, "src"], [1, "card-avatar"], [3, "value"], [1, "card-subtitle", 3, "innerHTML"], [1, "field-item", 3, "field-boolean", "field-text"], [1, "field-item"], [1, "field-icon"], [1, "field-label"], [1, "field-value"], [1, "field-value", "field-date"], [1, "field-text-value", 3, "innerHTML"], [1, "fa-solid", "fa-magnifying-glass"]], template: function EntityCardsComponent_Template(rf, ctx) { if (rf & 1) {
1041
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EntityCardsComponent, selectors: [["mj-entity-cards"]], inputs: { entity: "entity", records: "records", selectedRecordId: "selectedRecordId", CardTemplate: "CardTemplate", cardTemplate: "cardTemplate", HiddenFieldMatches: "HiddenFieldMatches", hiddenFieldMatches: "hiddenFieldMatches", filterText: "filterText", pageSize: "pageSize" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 3, vars: 1, consts: [[1, "cards-view-wrapper"], [1, "loading-container"], [1, "cards-container"], ["text", "Loading records..."], [1, "data-card", 3, "selected"], ["Variant", "no-results", 1, "no-results", 3, "Title"], [1, "data-card", 3, "click"], [1, "card-header"], [1, "card-thumbnail"], [1, "card-avatar", 3, "background-color"], [1, "card-header-content"], [1, "card-title", 3, "innerHTML"], ["title", "Open Record", 1, "card-open-btn", 3, "click"], [1, "fa-solid", "fa-external-link-alt"], [1, "card-body"], [1, "card-description", 3, "innerHTML"], [1, "card-fields"], [1, "card-footer"], [1, "hidden-match-indicator", 3, "title"], ["alt", "", 3, "src"], [1, "card-avatar"], [3, "value"], [1, "card-subtitle", 3, "innerHTML"], [1, "field-item", 3, "field-boolean", "field-text"], [1, "field-item"], [1, "field-icon"], [1, "field-label"], [1, "field-value"], [1, "field-value", "field-date"], [1, "field-text-value", 3, "innerHTML"], [1, "fa-solid", "fa-magnifying-glass"]], template: function EntityCardsComponent_Template(rf, ctx) { if (rf & 1) {
906
1042
  i0.ɵɵelementStart(0, "div", 0);
907
1043
  i0.ɵɵconditionalCreate(1, EntityCardsComponent_Conditional_1_Template, 2, 0, "div", 1)(2, EntityCardsComponent_Conditional_2_Template, 4, 1, "div", 2);
908
1044
  i0.ɵɵelementEnd();
@@ -913,15 +1049,19 @@ export class EntityCardsComponent extends BaseAngularComponent {
913
1049
  }
914
1050
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityCardsComponent, [{
915
1051
  type: Component,
916
- 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 <mj-empty-state\n class=\"no-results\"\n Variant=\"no-results\"\n Title=\"No records to display\" />\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: 50%;\n overflow: hidden;\n flex-shrink: 0;\n background: var(--mj-bg-surface-sunken);\n}\nmj-entity-cards .card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center;\n display: block;\n}\n\nmj-entity-cards .card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\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 \u2014 span the full grid width */\nmj-entity-cards .no-results {\n grid-column: 1 / -1;\n}\n"] }]
1052
+ 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 <mj-empty-state\n class=\"no-results\"\n Variant=\"no-results\"\n [Title]=\"entity ? 'No ' + entity.DisplayNamePlural + ' to display' : 'No records to display'\" />\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: 50%;\n overflow: hidden;\n flex-shrink: 0;\n background: var(--mj-bg-surface-sunken);\n}\nmj-entity-cards .card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center;\n display: block;\n}\n\nmj-entity-cards .card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\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 \u2014 span the full grid width */\nmj-entity-cards .no-results {\n grid-column: 1 / -1;\n}\n"] }]
917
1053
  }], () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], { entity: [{
918
1054
  type: Input
919
1055
  }], records: [{
920
1056
  type: Input
921
1057
  }], selectedRecordId: [{
922
1058
  type: Input
1059
+ }], CardTemplate: [{
1060
+ type: Input
923
1061
  }], cardTemplate: [{
924
1062
  type: Input
1063
+ }], HiddenFieldMatches: [{
1064
+ type: Input
925
1065
  }], hiddenFieldMatches: [{
926
1066
  type: Input
927
1067
  }], filterText: [{