@masterteam/customization 0.0.14 → 0.0.15

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, computed, input, signal, effect, Component } from '@angular/core';
2
+ import { inject, Injectable, computed, input, signal, effect, Component, output } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
4
  import { FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
5
5
  import { TranslocoDirective } from '@jsverse/transloco';
@@ -494,6 +494,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
494
494
  args: [{ selector: 'mt-property-config-drawer', standalone: true, imports: [TranslocoDirective, ReactiveFormsModule, Button, ToggleField], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-2 h-full overflow-y-auto pb-10 flex flex-col gap-5\">\r\n <!-- Hide Name Toggle (all entity types) -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('hide-name')\"\r\n icon=\"general.eye-off\"\r\n [formControl]=\"hideNameControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('label-on-top')\"\r\n icon=\"layout.align-right-02\"\r\n [formControl]=\"labelOnTopControl\"\r\n ></mt-toggle-field>\r\n\r\n <!-- Align End Toggle (all entity types) -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('align-end')\"\r\n [icon]=\"alignEndIcon()\"\r\n [formControl]=\"alignEndControl\"\r\n ></mt-toggle-field>\r\n\r\n <!-- Show Border Toggle (Text, Currency, Date, DateTime) -->\r\n @if (isBorderViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-border')\"\r\n icon=\"layout.layout-grid-01\"\r\n [formControl]=\"showBorderControl\"\r\n ></mt-toggle-field>\r\n }\r\n\r\n <!-- User-specific Toggles -->\r\n @if (isUserViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-display-name')\"\r\n icon=\"user.user-circle\"\r\n [formControl]=\"showDisplayNameControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-phone-number')\"\r\n icon=\"communication.phone\"\r\n [formControl]=\"showPhoneNumberControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-email')\"\r\n icon=\"communication.mail-01\"\r\n [formControl]=\"showEmailControl\"\r\n ></mt-toggle-field>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onCancel()\"\r\n ></mt-button>\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
495
495
  }], ctorParameters: () => [], propDecorators: { propertyKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertyKey", required: false }] }], propertyName: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertyName", required: false }] }], viewType: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewType", required: false }] }], areaKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "areaKey", required: false }] }], currentOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentOrder", required: false }] }], initialConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialConfig", required: false }] }] } });
496
496
 
497
+ class LeafDetailsSelector {
498
+ property = input.required(...(ngDevMode ? [{ debugName: "property" }] : /* istanbul ignore next */ []));
499
+ leafLevels = input({}, ...(ngDevMode ? [{ debugName: "leafLevels" }] : /* istanbul ignore next */ []));
500
+ configChange = output();
501
+ levels = computed(() => this.property().configuration ?? [], ...(ngDevMode ? [{ debugName: "levels" }] : /* istanbul ignore next */ []));
502
+ isLevelEnabled(levelId) {
503
+ return this.leafLevels()[String(levelId)] != null;
504
+ }
505
+ isStatusSelected(levelId, statusKey) {
506
+ return this.leafLevels()[String(levelId)]?.selectedStatuses.includes(statusKey) ?? false;
507
+ }
508
+ /** Section toggle — show or hide the entire level in the preview */
509
+ toggleLevel(levelId, enabled) {
510
+ const current = { ...this.leafLevels() };
511
+ if (enabled) {
512
+ const level = this.levels().find((l) => l.levelId === levelId);
513
+ current[String(levelId)] = {
514
+ order: Object.keys(current).length + 1,
515
+ selectedStatuses: level?.statuses.map((s) => s.key) ?? [],
516
+ };
517
+ }
518
+ else {
519
+ delete current[String(levelId)];
520
+ }
521
+ this.configChange.emit(current);
522
+ }
523
+ /** Status toggle — show or hide a single status badge in the level preview */
524
+ toggleStatus(levelId, statusKey) {
525
+ const current = { ...this.leafLevels() };
526
+ const saved = current[String(levelId)];
527
+ if (!saved)
528
+ return;
529
+ const idx = saved.selectedStatuses.indexOf(statusKey);
530
+ current[String(levelId)] = {
531
+ ...saved,
532
+ selectedStatuses: idx >= 0
533
+ ? saved.selectedStatuses.filter((k) => k !== statusKey)
534
+ : [...saved.selectedStatuses, statusKey],
535
+ };
536
+ this.configChange.emit(current);
537
+ }
538
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
539
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: LeafDetailsSelector, isStandalone: true, selector: "mt-leaf-details-selector", inputs: { property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, leafLevels: { classPropertyName: "leafLevels", publicName: "leafLevels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { configChange: "configChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\n <div class=\"space-y-2 pt-1\">\n\n <p class=\"text-sm font-semibold text-color px-1\">\n {{ t('leaf-details-target-statuses') }}\n </p>\n\n @for (level of levels(); track $index) {\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\n\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\n <mt-icon [name]=\"level.levelIcon\" size=\"xs\" class=\"text-color-secondary shrink-0\" />\n <span class=\"flex-1 text-sm font-medium truncate\">{{ level.levelName }}</span>\n <mt-toggle-field\n [ngModel]=\"isLevelEnabled(level.levelId)\"\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\n @if (isLevelEnabled(level.levelId)) {\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\n @for (status of level.statuses; track $index) {\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\n <span\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\n ></span>\n <span class=\"text-sm flex-1 truncate\">{{ status.display }}</span>\n <mt-toggle-field\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n }\n </div>\n }\n\n </div>\n }\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }] });
540
+ }
541
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, decorators: [{
542
+ type: Component,
543
+ args: [{ selector: 'mt-leaf-details-selector', standalone: true, imports: [FormsModule, TranslocoDirective, Icon, ToggleField], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\n <div class=\"space-y-2 pt-1\">\n\n <p class=\"text-sm font-semibold text-color px-1\">\n {{ t('leaf-details-target-statuses') }}\n </p>\n\n @for (level of levels(); track $index) {\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\n\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\n <mt-icon [name]=\"level.levelIcon\" size=\"xs\" class=\"text-color-secondary shrink-0\" />\n <span class=\"flex-1 text-sm font-medium truncate\">{{ level.levelName }}</span>\n <mt-toggle-field\n [ngModel]=\"isLevelEnabled(level.levelId)\"\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\n @if (isLevelEnabled(level.levelId)) {\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\n @for (status of level.statuses; track $index) {\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\n <span\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\n ></span>\n <span class=\"text-sm flex-1 truncate\">{{ status.display }}</span>\n <mt-toggle-field\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n }\n </div>\n }\n\n </div>\n }\n </div>\n</ng-container>\n" }]
544
+ }], propDecorators: { property: [{ type: i0.Input, args: [{ isSignal: true, alias: "property", required: true }] }], leafLevels: [{ type: i0.Input, args: [{ isSignal: true, alias: "leafLevels", required: false }] }], configChange: [{ type: i0.Output, args: ["configChange"] }] } });
545
+
546
+ /** Separator used to build synthetic entity keys for LeafDetails sub-entities */
547
+ const LEAF_DETAILS_SEP = '::leaflevel::';
497
548
  class Customization {
498
549
  facade = inject(CustomizationFacade);
499
550
  modalService = inject(ModalService);
@@ -534,15 +585,39 @@ class Customization {
534
585
  filteredProperties = computed(() => {
535
586
  const query = this.searchQuery().toLowerCase().trim();
536
587
  const list = this.propertiesWithEnabled();
588
+ // LeafDetails always first in the list regardless of active state
589
+ const leaf = list.filter((p) => p.viewType === 'LeafDetails');
590
+ const regular = list.filter((p) => p.viewType !== 'LeafDetails');
591
+ const ordered = [...leaf, ...regular];
537
592
  if (!query)
538
- return list;
539
- return list.filter((prop) => {
593
+ return ordered;
594
+ return ordered.filter((prop) => {
540
595
  const name = typeof prop.name === 'string'
541
596
  ? prop.name
542
597
  : Object.values(prop.name).join(' ');
543
598
  return name.toLowerCase().includes(query);
544
599
  });
545
600
  }, ...(ngDevMode ? [{ debugName: "filteredProperties" }] : /* istanbul ignore next */ []));
601
+ /**
602
+ * LeafDetails saved configs indexed by propertyKey for the active area.
603
+ * Used by the inline selector in the property list.
604
+ */
605
+ leafConfigsByProp = computed(() => {
606
+ const configs = this.validConfigurations();
607
+ const area = this.activeArea();
608
+ if (!area)
609
+ return {};
610
+ const result = {};
611
+ for (const config of configs) {
612
+ if (config.areaKey !== area)
613
+ continue;
614
+ const leafLevels = config.configuration?.['leafLevels'];
615
+ if (leafLevels && typeof leafLevels === 'object' && !Array.isArray(leafLevels)) {
616
+ result[config.propertyKey] = leafLevels;
617
+ }
618
+ }
619
+ return result;
620
+ }, ...(ngDevMode ? [{ debugName: "leafConfigsByProp" }] : /* istanbul ignore next */ []));
546
621
  /** Preview entities built from configurations + properties for the active area */
547
622
  previewEntities = signal([], ...(ngDevMode ? [{ debugName: "previewEntities" }] : /* istanbul ignore next */ []));
548
623
  constructor() {
@@ -563,25 +638,35 @@ class Customization {
563
638
  return;
564
639
  }
565
640
  const areaConfigs = configs.filter((c) => c.areaKey === area);
566
- const entities = areaConfigs
567
- .map((config) => this.buildEntityFromConfig(config, props))
568
- .filter((e) => e !== null)
641
+ const all = areaConfigs.flatMap((config) => this.buildEntitiesFromConfig(config, props));
642
+ // Regular entities come first, LeafDetails always last.
643
+ // Orders are reassigned sequentially so EntitiesManage's internal sort stays consistent.
644
+ const regular = all
645
+ .filter((e) => !e.key?.includes(LEAF_DETAILS_SEP))
646
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
647
+ const leafEntities = all
648
+ .filter((e) => e.key?.includes(LEAF_DETAILS_SEP))
569
649
  .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
650
+ const entities = [
651
+ ...regular.map((e, i) => ({ ...e, order: i + 1 })),
652
+ ...leafEntities.map((e, i) => ({ ...e, order: regular.length + i + 1 })),
653
+ ];
570
654
  this.previewEntities.set(entities);
571
655
  });
572
656
  }
573
657
  ngOnInit() {
574
- // Load display areas
575
658
  this.facade.loadAreas();
576
659
  this.facade.loadProperties();
577
660
  this.facade.loadConfigurations();
578
661
  }
579
662
  onPropertyToggle(prop, enabled) {
663
+ // LeafDetails is managed entirely via the inline status selector — skip
664
+ if (prop.viewType === 'LeafDetails')
665
+ return;
580
666
  if (enabled) {
581
667
  this.activateProperty(prop, this.editableViewTypes.has(prop.viewType));
582
668
  return;
583
669
  }
584
- // Remove this property and bulk-replace with remaining checked props
585
670
  this.bulkSaveWithout(prop.key);
586
671
  }
587
672
  onEditProperty(prop) {
@@ -596,6 +681,9 @@ class Customization {
596
681
  });
597
682
  }
598
683
  onEntityClicked(entity) {
684
+ // LeafDetails sub-entities are configured inline — not via drawer
685
+ if (entity.key?.includes(LEAF_DETAILS_SEP))
686
+ return;
599
687
  const area = this.activeArea();
600
688
  if (!area || !entity.key)
601
689
  return;
@@ -616,44 +704,174 @@ class Customization {
616
704
  if (!area)
617
705
  return;
618
706
  const configs = this.validConfigurations();
619
- // Update the configuration for the resized entity in the current area
707
+ // ── LeafDetails sub-entity resize ──
708
+ if (event.entity.key?.includes(LEAF_DETAILS_SEP)) {
709
+ const [propKey, levelId] = event.entity.key.split(LEAF_DETAILS_SEP);
710
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
711
+ const leafLevels = {
712
+ ...(existingConfig?.configuration?.['leafLevels'] ?? {}),
713
+ };
714
+ if (leafLevels[levelId]) {
715
+ leafLevels[levelId] = { ...leafLevels[levelId], size: event.newSize };
716
+ }
717
+ const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === propKey
718
+ ? { ...c, configuration: { ...c.configuration, leafLevels } }
719
+ : c);
720
+ this.facade.bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs)).subscribe();
721
+ return;
722
+ }
723
+ // ── Regular entity resize ──
620
724
  const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === event.entity.key
621
725
  ? { ...c, configuration: { ...c.configuration, size: event.newSize } }
622
726
  : c);
623
- // Build bulk-replace items from updated configs
624
- const items = this.groupConfigsByProperty(updatedConfigs);
625
- this.facade.bulkReplaceConfigurations(items).subscribe();
727
+ this.facade.bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs)).subscribe();
626
728
  }
627
729
  onEntitiesReordered(entities) {
628
730
  const area = this.activeArea();
629
731
  if (!area)
630
732
  return;
631
733
  const configs = this.validConfigurations();
632
- const items = entities.map((entity, index) => {
734
+ // Separate regular entities from LeafDetails sub-entities
735
+ const regularEntities = entities.filter((e) => !e.key?.includes(LEAF_DETAILS_SEP));
736
+ const leafEntities = entities.filter((e) => e.key?.includes(LEAF_DETAILS_SEP));
737
+ // Regular entities → BulkReplaceItems
738
+ const regularItems = regularEntities.map((entity) => {
633
739
  const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === entity.key);
634
740
  return {
635
741
  propertyKey: entity.key,
636
742
  displayAreas: [
637
743
  {
638
744
  areaKey: area,
639
- order: index + 1,
745
+ order: entity.order,
640
746
  configuration: existingConfig?.configuration ?? {},
641
747
  },
642
748
  ],
643
749
  };
644
750
  });
645
- // Include configs from other areas that are not being reordered
646
- const otherAreaConfigs = configs.filter((c) => c.areaKey !== area);
647
- const otherAreaItems = this.groupConfigsByProperty(otherAreaConfigs);
648
- // Merge: for properties that exist in both, combine their displayAreas
649
- const mergedItems = this.mergeItems(items, otherAreaItems);
650
- this.facade.bulkReplaceConfigurations(mergedItems).subscribe();
751
+ // Leaf entities group by parent propertyKey, update per-level order
752
+ const leafByProp = new Map();
753
+ for (const entity of leafEntities) {
754
+ const [propKey, levelId] = entity.key.split(LEAF_DETAILS_SEP);
755
+ if (!leafByProp.has(propKey))
756
+ leafByProp.set(propKey, []);
757
+ leafByProp.get(propKey).push({ levelId, order: entity.order });
758
+ }
759
+ const leafItems = [];
760
+ for (const [propKey, levelUpdates] of leafByProp) {
761
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
762
+ const leafLevels = {
763
+ ...(existingConfig?.configuration?.['leafLevels'] ?? {}),
764
+ };
765
+ for (const { levelId, order } of levelUpdates) {
766
+ if (leafLevels[levelId]) {
767
+ leafLevels[levelId] = { ...leafLevels[levelId], order };
768
+ }
769
+ }
770
+ const minOrder = Math.min(...levelUpdates.map((l) => l.order));
771
+ leafItems.push({
772
+ propertyKey: propKey,
773
+ displayAreas: [
774
+ {
775
+ areaKey: area,
776
+ order: existingConfig?.order ?? minOrder,
777
+ configuration: { ...existingConfig?.configuration, leafLevels },
778
+ },
779
+ ],
780
+ });
781
+ }
782
+ // Configs from other areas (preserve as-is)
783
+ const otherAreaItems = this.groupConfigsByProperty(configs.filter((c) => c.areaKey !== area));
784
+ this.facade
785
+ .bulkReplaceConfigurations(this.mergeItems([...regularItems, ...leafItems], otherAreaItems))
786
+ .subscribe();
787
+ }
788
+ /**
789
+ * Called by the inline LeafDetailsSelector when the user changes status selections.
790
+ * Immediately persists the updated leafLevels configuration.
791
+ */
792
+ onLeafConfigChange(prop, leafLevels) {
793
+ const area = this.activeArea();
794
+ if (!area)
795
+ return;
796
+ const configs = this.validConfigurations();
797
+ const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === prop.key);
798
+ // Ensure every level has an order assigned
799
+ let nextOrder = configs.filter((c) => c.areaKey === area && c.propertyKey !== prop.key).length + 1;
800
+ const normalizedLevels = {};
801
+ for (const [levelId, levelCfg] of Object.entries(leafLevels)) {
802
+ normalizedLevels[levelId] = levelCfg.order
803
+ ? levelCfg
804
+ : { ...levelCfg, order: nextOrder++ };
805
+ }
806
+ // Other configs excluding this leaf property in the current area
807
+ const otherItems = this.groupConfigsByProperty(configs.filter((c) => !(c.areaKey === area && c.propertyKey === prop.key)));
808
+ if (Object.keys(normalizedLevels).length === 0) {
809
+ // All levels deselected — remove property from display config
810
+ this.facade.bulkReplaceConfigurations(otherItems).subscribe();
811
+ return;
812
+ }
813
+ const minOrder = Math.min(...Object.values(normalizedLevels).map((l) => l.order));
814
+ const newItem = {
815
+ propertyKey: prop.key,
816
+ displayAreas: [
817
+ {
818
+ areaKey: area,
819
+ order: existingConfig?.order ?? minOrder,
820
+ configuration: { leafLevels: normalizedLevels },
821
+ },
822
+ ],
823
+ };
824
+ this.facade.bulkReplaceConfigurations(this.mergeItems([newItem], otherItems)).subscribe();
651
825
  }
652
826
  // ── Private helpers ──
653
827
  /**
654
- * Add a property to the active area immediately, then optionally open its configuration drawer.
655
- * This keeps the property active even if the user closes the drawer without saving extra options.
828
+ * Builds zero or more EntityData entries from a single DisplayConfiguration.
829
+ * Regular properties one entity. LeafDetails one entity per enabled level.
656
830
  */
831
+ buildEntitiesFromConfig(config, props) {
832
+ const prop = props.find((p) => p.key === config.propertyKey);
833
+ if (!prop)
834
+ return [];
835
+ if (prop.viewType === 'LeafDetails') {
836
+ return this.buildLeafDetailsEntities(config, prop);
837
+ }
838
+ const entity = this.buildEntityFromConfig(config, props);
839
+ return entity ? [entity] : [];
840
+ }
841
+ /** Builds one EntityData per enabled level from a LeafDetails display config. */
842
+ buildLeafDetailsEntities(config, prop) {
843
+ const levels = prop.configuration ?? [];
844
+ const leafLevels = config.configuration?.['leafLevels'];
845
+ if (!leafLevels)
846
+ return [];
847
+ const result = [];
848
+ for (const level of levels) {
849
+ const levelCfg = leafLevels[String(level.levelId)];
850
+ if (!levelCfg)
851
+ continue;
852
+ const selectedKeys = new Set(levelCfg.selectedStatuses);
853
+ const value = {
854
+ levelId: level.levelId,
855
+ levelName: level.levelName,
856
+ levelIcon: level.levelIcon,
857
+ statuses: level.statuses
858
+ .filter((s) => selectedKeys.has(s.key))
859
+ .map((s) => ({ key: s.key, display: s.display, color: s.color, count: 0 })),
860
+ };
861
+ result.push({
862
+ id: prop.id,
863
+ propertyId: prop.id,
864
+ key: `${prop.key}${LEAF_DETAILS_SEP}${level.levelId}`,
865
+ name: level.levelName,
866
+ // Cast needed until entities package is rebuilt with LeafDetailsEntityValue in the union
867
+ value: value,
868
+ viewType: 'LeafDetails',
869
+ order: levelCfg.order,
870
+ configuration: levelCfg.size ? { size: levelCfg.size } : undefined,
871
+ });
872
+ }
873
+ return result;
874
+ }
657
875
  activateProperty(prop, openConfigAfterSave = false) {
658
876
  const area = this.activeArea();
659
877
  if (!area)
@@ -672,60 +890,38 @@ class Customization {
672
890
  const areaConfigs = configs.filter((c) => c.areaKey === area);
673
891
  const nextOrder = areaConfigs.length + 1;
674
892
  const configuration = {};
675
- const newDisplayArea = {
676
- areaKey: area,
677
- order: nextOrder,
678
- configuration,
679
- };
680
- // Build bulk items from existing configs + new property
681
893
  const itemsMap = new Map();
682
894
  for (const c of configs) {
683
- if (!itemsMap.has(c.propertyKey)) {
895
+ if (!itemsMap.has(c.propertyKey))
684
896
  itemsMap.set(c.propertyKey, []);
685
- }
686
897
  itemsMap.get(c.propertyKey).push({
687
898
  areaKey: c.areaKey,
688
899
  order: c.order,
689
900
  configuration: c.configuration,
690
901
  });
691
902
  }
692
- // Add the new property
693
- if (!itemsMap.has(prop.key)) {
903
+ if (!itemsMap.has(prop.key))
694
904
  itemsMap.set(prop.key, []);
695
- }
696
- itemsMap.get(prop.key).push(newDisplayArea);
905
+ itemsMap.get(prop.key).push({ areaKey: area, order: nextOrder, configuration });
697
906
  const items = Array.from(itemsMap.entries()).map(([propertyKey, displayAreas]) => ({ propertyKey, displayAreas }));
698
907
  this.facade.bulkReplaceConfigurations(items).subscribe({
699
908
  next: () => {
700
- if (!openConfigAfterSave) {
701
- return;
909
+ if (openConfigAfterSave) {
910
+ this.openDrawer(prop, { currentOrder: nextOrder, configuration });
702
911
  }
703
- this.openDrawer(prop, {
704
- currentOrder: nextOrder,
705
- configuration,
706
- });
707
912
  },
708
913
  });
709
914
  }
710
- /**
711
- * Build bulk items from all current configurations, excluding a specific property.
712
- * Then call bulk-replace so the backend replaces the entire context atomically.
713
- */
714
915
  bulkSaveWithout(excludePropertyKey) {
715
916
  const configs = this.validConfigurations();
716
- const remaining = configs.filter((c) => c.propertyKey !== excludePropertyKey);
717
- const items = this.groupConfigsByProperty(remaining);
917
+ const items = this.groupConfigsByProperty(configs.filter((c) => c.propertyKey !== excludePropertyKey));
718
918
  this.facade.bulkReplaceConfigurations(items).subscribe();
719
919
  }
720
- /**
721
- * Group flat DisplayConfiguration[] into bulk-replace items grouped by propertyKey.
722
- */
723
920
  groupConfigsByProperty(configs) {
724
921
  const map = new Map();
725
922
  for (const c of configs) {
726
- if (!map.has(c.propertyKey)) {
923
+ if (!map.has(c.propertyKey))
727
924
  map.set(c.propertyKey, []);
728
- }
729
925
  map.get(c.propertyKey).push({
730
926
  areaKey: c.areaKey,
731
927
  order: c.order,
@@ -737,23 +933,16 @@ class Customization {
737
933
  displayAreas,
738
934
  }));
739
935
  }
740
- /**
741
- * Merge two sets of bulk items. If a propertyKey exists in both,
742
- * combine their displayAreas arrays.
743
- */
744
936
  mergeItems(primary, secondary) {
745
937
  const map = new Map();
746
- for (const item of primary) {
938
+ for (const item of primary)
747
939
  map.set(item.propertyKey, [...item.displayAreas]);
748
- }
749
940
  for (const item of secondary) {
750
941
  const existing = map.get(item.propertyKey);
751
- if (existing) {
942
+ if (existing)
752
943
  existing.push(...item.displayAreas);
753
- }
754
- else {
944
+ else
755
945
  map.set(item.propertyKey, [...item.displayAreas]);
756
- }
757
946
  }
758
947
  return Array.from(map.entries()).map(([propertyKey, displayAreas]) => ({
759
948
  propertyKey,
@@ -807,7 +996,7 @@ class Customization {
807
996
  return null;
808
997
  const displayName = this.getPropertyDisplayName(prop);
809
998
  const viewType = prop.viewType;
810
- const entity = {
999
+ return {
811
1000
  id: prop.id,
812
1001
  propertyId: prop.id,
813
1002
  key: prop.key,
@@ -818,7 +1007,6 @@ class Customization {
818
1007
  order: config.order,
819
1008
  configuration: config.configuration,
820
1009
  };
821
- return entity;
822
1010
  }
823
1011
  getPropertyDisplayName(prop) {
824
1012
  if (typeof prop.name === 'string')
@@ -877,7 +1065,7 @@ class Customization {
877
1065
  }
878
1066
  }
879
1067
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Customization, deps: [], target: i0.ɵɵFactoryTarget.Component });
880
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: Customization, isStandalone: true, selector: "mt-customization", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n size=\"small\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-130 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n (entityClicked)=\"onEntityClicked($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-full text-muted-color gap-3 text-center px-4\"\r\n >\r\n <mt-icon\r\n name=\"general.eye-off\"\r\n size=\"xl\"\r\n class=\"opacity-40\"\r\n />\r\n <p class=\"text-sm font-medium\">{{ t(\"no-preview-items\") }}</p>\r\n <p class=\"text-xs\">{{ t(\"no-preview-items-hint\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "size", "fluid", "disabled"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: EntitiesManage, selector: "mt-entities-manage", inputs: ["entities"], outputs: ["entitiesChange", "entitiesReordered", "entityClicked"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }] });
1068
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: Customization, isStandalone: true, selector: "mt-customization", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n @if (prop.viewType === 'LeafDetails') {\r\n <!-- \u2500\u2500 LeafDetails: always first, title is \"Target Statuses\" inside the selector \u2500\u2500 -->\r\n <div class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\">\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\r\n />\r\n </div>\r\n } @else {\r\n <!-- \u2500\u2500 Normal property toggle row \u2500\u2500 -->\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n size=\"small\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-130 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n (entityClicked)=\"onEntityClicked($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-full text-muted-color gap-3 text-center px-4\"\r\n >\r\n <mt-icon\r\n name=\"general.eye-off\"\r\n size=\"xl\"\r\n class=\"opacity-40\"\r\n />\r\n <p class=\"text-sm font-medium\">{{ t(\"no-preview-items\") }}</p>\r\n <p class=\"text-xs\">{{ t(\"no-preview-items-hint\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "size", "fluid", "disabled"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: EntitiesManage, selector: "mt-entities-manage", inputs: ["entities"], outputs: ["entitiesChange", "entitiesReordered", "entityClicked"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: LeafDetailsSelector, selector: "mt-leaf-details-selector", inputs: ["property", "leafLevels"], outputs: ["configChange"] }] });
881
1069
  }
882
1070
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Customization, decorators: [{
883
1071
  type: Component,
@@ -892,7 +1080,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
892
1080
  Skeleton,
893
1081
  EntitiesManage,
894
1082
  Icon,
895
- ], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n size=\"small\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-130 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n (entityClicked)=\"onEntityClicked($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-full text-muted-color gap-3 text-center px-4\"\r\n >\r\n <mt-icon\r\n name=\"general.eye-off\"\r\n size=\"xl\"\r\n class=\"opacity-40\"\r\n />\r\n <p class=\"text-sm font-medium\">{{ t(\"no-preview-items\") }}</p>\r\n <p class=\"text-xs\">{{ t(\"no-preview-items-hint\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"] }]
1083
+ LeafDetailsSelector,
1084
+ ], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n @if (prop.viewType === 'LeafDetails') {\r\n <!-- \u2500\u2500 LeafDetails: always first, title is \"Target Statuses\" inside the selector \u2500\u2500 -->\r\n <div class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\">\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\r\n />\r\n </div>\r\n } @else {\r\n <!-- \u2500\u2500 Normal property toggle row \u2500\u2500 -->\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n size=\"small\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-130 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n (entityClicked)=\"onEntityClicked($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-full text-muted-color gap-3 text-center px-4\"\r\n >\r\n <mt-icon\r\n name=\"general.eye-off\"\r\n size=\"xl\"\r\n class=\"opacity-40\"\r\n />\r\n <p class=\"text-sm font-medium\">{{ t(\"no-preview-items\") }}</p>\r\n <p class=\"text-xs\">{{ t(\"no-preview-items-hint\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"] }]
896
1085
  }], ctorParameters: () => [] });
897
1086
 
898
1087
  /**